[REF] stock: refactored the removal and putaway strategies + added FEFO removal in product_expiry
bzr revid: qdp-launchpad@openerp.com-20140411171901-ibjelg7wldld167y
This commit is contained in:
parent
d31e02fb3f
commit
2fd72600fa
|
@ -35,8 +35,9 @@ Following dates can be tracked:
|
|||
- removal date
|
||||
- alert date
|
||||
|
||||
Used, for example, in food industries.""",
|
||||
'data' : ['product_expiry_view.xml'],
|
||||
Also implements the removal strategy First Expiry First Out (FEFO) widely used, for example, in food industries.
|
||||
""",
|
||||
'data' : ['product_expiry_view.xml', 'product_expiry_data.xml'],
|
||||
'auto_install': False,
|
||||
'installable': True,
|
||||
'images': ['images/production_lots_dates.jpeg','images/products_dates.jpeg'],
|
||||
|
|
|
@ -75,6 +75,20 @@ class stock_production_lot(osv.osv):
|
|||
'alert_date': _get_date('alert_time'),
|
||||
}
|
||||
|
||||
|
||||
class stock_quant(osv.osv):
|
||||
_inherit = 'stock.quant'
|
||||
_column = {
|
||||
'removal_date': fields.related('lot_id', 'removal_date', type='date', string='Removal Date', store=True),
|
||||
}
|
||||
|
||||
def apply_removal_strategy(self, cr, uid, location, product, qty, domain, removal_strategy, context=None):
|
||||
if removal_strategy == 'fefo':
|
||||
order = 'removal_date, id'
|
||||
return self._quants_get_order(cr, uid, location, product, qty, domain, order, context=context)
|
||||
return super(stock_quant, self).apply_removal_strategy(cr, uid, location, product, qty, domain, removal_strategy, context=context)
|
||||
|
||||
|
||||
class product_product(osv.osv):
|
||||
_inherit = 'product.product'
|
||||
_columns = {
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" ?>
|
||||
<openerp>
|
||||
<data>
|
||||
<record id="removal_fefo" model="product.removal">
|
||||
<field name="name">First Expiry First Out (FEFO)</field>
|
||||
<field name="method">fefo</field>
|
||||
</record>
|
||||
</data>
|
||||
</openerp>
|
||||
|
|
@ -328,23 +328,50 @@ class product_template(osv.osv):
|
|||
class product_removal_strategy(osv.osv):
|
||||
_name = 'product.removal'
|
||||
_description = 'Removal Strategy'
|
||||
_order = 'sequence'
|
||||
|
||||
_columns = {
|
||||
'product_categ_id': fields.many2one('product.category', 'Category', required=True),
|
||||
'sequence': fields.integer('Sequence'),
|
||||
'method': fields.selection([('fifo', 'FIFO'), ('lifo', 'LIFO')], "Method", required = True),
|
||||
'location_id': fields.many2one('stock.location', 'Locations', required=True),
|
||||
'name': fields.char('Name', required=True),
|
||||
'method': fields.char("Method", required=True, help="FIFO, LIFO..."),
|
||||
}
|
||||
|
||||
|
||||
class product_putaway_strategy(osv.osv):
|
||||
_name = 'product.putaway'
|
||||
_description = 'Put Away Strategy'
|
||||
|
||||
def _get_putaway_options(self, cr, uid, context=None):
|
||||
return [('fixed', 'Fixed Location')]
|
||||
|
||||
_columns = {
|
||||
'product_categ_id':fields.many2one('product.category', 'Product Category', required=True),
|
||||
'location_id': fields.many2one('stock.location','Parent Location', help="Parent Destination Location from which a child bin location needs to be chosen", required=True), #domain=[('type', '=', 'parent')],
|
||||
'method': fields.selection([('fixed', 'Fixed Location')], "Method", required = True),
|
||||
'location_spec_id': fields.many2one('stock.location','Specific Location', help="When the location is specific, it will be put over there"), #domain=[('type', '=', 'parent')],
|
||||
'name': fields.char('Name', required=True),
|
||||
'method': fields.selection(_get_putaway_options, "Method", required=True),
|
||||
'fixed_location_ids': fields.one2many('stock.fixed.putaway.strat', 'putaway_id', 'Fixed Locations Per Product Category', help="When the method is fixed, this location will be used to store the products"),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'method': 'fixed',
|
||||
}
|
||||
|
||||
def putaway_apply(self, cr, uid, putaway_strat, product, context=None):
|
||||
if putaway_strat.method == 'fixed':
|
||||
all_parent_categs = []
|
||||
categ = product.categ_id
|
||||
while categ:
|
||||
all_parent_categs.append(categ.id)
|
||||
categ = categ.parent_id
|
||||
for strat in putaway_strat.fixed_location_ids:
|
||||
if strat.category_id.id in all_parent_categs:
|
||||
return strat.fixed_location_id.id
|
||||
|
||||
|
||||
class fixed_putaway_strat(osv.osv):
|
||||
_name = 'stock.fixed.putaway.strat'
|
||||
_order = 'sequence'
|
||||
_columns = {
|
||||
'putaway_id': fields.many2one('product.putaway', 'Put Away Method', required=True),
|
||||
'category_id': fields.many2one('product.category', 'Product Category', required=True),
|
||||
'fixed_location_id': fields.many2one('stock.location', 'Location', required=True),
|
||||
'sequence': fields.integer('Priority', help="Give to the more specialized category, a higher priority to have them in top of the list."),
|
||||
}
|
||||
|
||||
|
||||
|
@ -353,7 +380,6 @@ class product_category(osv.osv):
|
|||
|
||||
def calculate_total_routes(self, cr, uid, ids, name, args, context=None):
|
||||
res = {}
|
||||
route_obj = self.pool.get("stock.location.route")
|
||||
for categ in self.browse(cr, uid, ids, context=context):
|
||||
categ2 = categ
|
||||
routes = [x.id for x in categ.route_ids]
|
||||
|
@ -365,8 +391,7 @@ class product_category(osv.osv):
|
|||
|
||||
_columns = {
|
||||
'route_ids': fields.many2many('stock.location.route', 'stock_location_route_categ', 'categ_id', 'route_id', 'Routes', domain="[('product_categ_selectable', '=', True)]"),
|
||||
'removal_strategy_ids': fields.one2many('product.removal', 'product_categ_id', 'Removal Strategies'),
|
||||
'putaway_strategy_ids': fields.one2many('product.putaway', 'product_categ_id', 'Put Away Strategies'),
|
||||
'removal_strategy_id': fields.many2one('product.removal', 'Force Removal Strategy', help="Set a specific removal strategy that will be used regardless of the source location for this product category"),
|
||||
'total_route_ids': fields.function(calculate_total_routes, relation='stock.location.route', type='many2many', string='Total routes', readonly=True),
|
||||
}
|
||||
|
||||
|
|
|
@ -129,9 +129,9 @@ class stock_location(osv.osv):
|
|||
|
||||
'company_id': fields.many2one('res.company', 'Company', select=1, help='Let this field empty if this location is shared between all companies'),
|
||||
'scrap_location': fields.boolean('Is a Scrap Location?', help='Check this box to allow using this location to put scrapped/damaged goods.'),
|
||||
'removal_strategy_ids': fields.one2many('product.removal', 'location_id', 'Removal Strategies'),
|
||||
'putaway_strategy_ids': fields.one2many('product.putaway', 'location_id', 'Put Away Strategies'),
|
||||
'loc_barcode': fields.char('Location barcode'),
|
||||
'removal_strategy_id': fields.many2one('product.removal', 'Removal Strategy', help="Defines the default method used for suggesting the exact location (shelf) where to take the products from, which lot etc. for this location. This method can be enforced at the product category level, and a fallback is made on the parent locations if none is set here."),
|
||||
'putaway_strategy_id': fields.many2one('product.putaway', 'Put Away Strategy', help="Defines the default method used for suggesting the exact location (shelf) where to store the products. This method can be enforced at the product category level, and a fallback is made on the parent locations if none is set here."),
|
||||
'loc_barcode': fields.char('Location Barcode'),
|
||||
}
|
||||
_defaults = {
|
||||
'active': True,
|
||||
|
@ -147,31 +147,36 @@ class stock_location(osv.osv):
|
|||
def create(self, cr, uid, default, context=None):
|
||||
if not default.get('loc_barcode', False):
|
||||
default.update({'loc_barcode': default.get('complete_name', False)})
|
||||
return super(stock_location,self).create(cr, uid, default, context=context)
|
||||
return super(stock_location, self).create(cr, uid, default, context=context)
|
||||
|
||||
def get_putaway_strategy(self, cr, uid, location, product, context=None):
|
||||
pa = self.pool.get('product.putaway')
|
||||
categ = product.categ_id
|
||||
categs = [categ.id, False]
|
||||
while categ.parent_id:
|
||||
categ = categ.parent_id
|
||||
categs.append(categ.id)
|
||||
''' Returns the location where the product has to be put, if any compliant putaway strategy is found. Otherwise returns None.'''
|
||||
putaway_obj = self.pool.get('product.putaway')
|
||||
loc = location
|
||||
while loc:
|
||||
if loc.putaway_strategy_id:
|
||||
res = putaway_obj.putaway_strat_apply(cr, uid, loc.putaway_strategy_id, product, context=context)
|
||||
if res:
|
||||
return res
|
||||
loc = loc.location_id
|
||||
|
||||
result = pa.search(cr, uid, [('location_id', '=', location.id), ('product_categ_id', 'in', categs)], context=context)
|
||||
if result:
|
||||
return pa.browse(cr, uid, result[0], context=context)
|
||||
def _default_removal_strategy(self, cr, uid, context=None):
|
||||
return 'fifo'
|
||||
|
||||
def get_removal_strategy(self, cr, uid, location, product, context=None):
|
||||
pr = self.pool.get('product.removal')
|
||||
categ = product.categ_id
|
||||
categs = [categ.id, False]
|
||||
while categ.parent_id:
|
||||
categ = categ.parent_id
|
||||
categs.append(categ.id)
|
||||
|
||||
result = pr.search(cr, uid, [('location_id', '=', location.id), ('product_categ_id', 'in', categs)], context=context)
|
||||
if result:
|
||||
return pr.browse(cr, uid, result[0], context=context).method
|
||||
''' Returns the removal strategy to consider for the given product and location.
|
||||
:param location: browse record (stock.location)
|
||||
:param product: browse record (product.product)
|
||||
:rtype: char
|
||||
'''
|
||||
if product.categ_id.removal_strategy_id:
|
||||
return product.categ_id.removal_strategy_id.method
|
||||
loc = location
|
||||
while loc:
|
||||
if loc.removal_strategy_id:
|
||||
return loc.removal_strategy_id.method
|
||||
loc = loc.location_id
|
||||
return self._default_removal_strategy(cr, uid, context=context)
|
||||
|
||||
|
||||
#----------------------------------------------------------
|
||||
|
@ -419,15 +424,19 @@ class stock_quant(osv.osv):
|
|||
if restrict_lot_id:
|
||||
domain += [('lot_id', '=', restrict_lot_id)]
|
||||
if location:
|
||||
removal_strategy = self.pool.get('stock.location').get_removal_strategy(cr, uid, location, product, context=context) or 'fifo'
|
||||
if removal_strategy == 'fifo':
|
||||
result += self._quants_get_fifo(cr, uid, location, product, qty, domain, context=context)
|
||||
elif removal_strategy == 'lifo':
|
||||
result += self._quants_get_lifo(cr, uid, location, product, qty, domain, context=context)
|
||||
else:
|
||||
raise osv.except_osv(_('Error!'), _('Removal strategy %s not implemented.' % (removal_strategy,)))
|
||||
removal_strategy = self.pool.get('stock.location').get_removal_strategy(cr, uid, location, product, context=context)
|
||||
result += self.apply_removal_strategy(cr, uid, location, product, qty, domain, removal_strategy, context=context)
|
||||
return result
|
||||
|
||||
def apply_removal_strategy(self, cr, uid, location, product, quantity, domain, removal_strategy, context=None):
|
||||
if removal_strategy == 'fifo':
|
||||
order = 'in_date, id'
|
||||
return self._quants_get_order(cr, uid, location, product, quantity, domain, order, context=context)
|
||||
elif removal_strategy == 'lifo':
|
||||
order = 'in_date desc, id desc'
|
||||
return self._quants_get_order(cr, uid, location, product, quantity, domain, order, context=context)
|
||||
raise osv.except_osv(_('Error!'), _('Removal strategy %s not implemented.' % (removal_strategy,)))
|
||||
|
||||
def _quant_create(self, cr, uid, qty, move, lot_id=False, owner_id=False, src_package_id=False, dest_package_id=False, force_location=False, context=None):
|
||||
'''Create a quant in the destination location and create a negative quant in the source location if it's an internal location.
|
||||
'''
|
||||
|
@ -574,14 +583,6 @@ class stock_quant(osv.osv):
|
|||
offset += 10
|
||||
return res
|
||||
|
||||
def _quants_get_fifo(self, cr, uid, location, product, quantity, domain=[], context=None):
|
||||
order = 'in_date, id'
|
||||
return self._quants_get_order(cr, uid, location, product, quantity, domain, order, context=context)
|
||||
|
||||
def _quants_get_lifo(self, cr, uid, location, product, quantity, domain=[], context=None):
|
||||
order = 'in_date desc, id desc'
|
||||
return self._quants_get_order(cr, uid, location, product, quantity, domain, order, context=context)
|
||||
|
||||
def _check_location(self, cr, uid, location, context=None):
|
||||
if location.usage == 'view':
|
||||
raise osv.except_osv(_('Error'), _('You cannot move to a location of type view %s.') % (location.name))
|
||||
|
@ -922,8 +923,10 @@ class stock_picking(osv.osv):
|
|||
self.do_prepare_partial(cr, uid, picking_ids, context=context)
|
||||
|
||||
def _picking_putaway_resolution(self, cr, uid, picking, product, putaway, context=None):
|
||||
if putaway.method == 'fixed' and putaway.location_spec_id:
|
||||
return putaway.location_spec_id.id
|
||||
if putaway.method == 'fixed':
|
||||
for strat in putaway.fixed_location_ids:
|
||||
if product.categ_id.id == strat.category_id.id:
|
||||
return strat.fixed_location_id.id
|
||||
return False
|
||||
|
||||
def _get_top_level_packages(self, cr, uid, quants_suggested_locations, context=None):
|
||||
|
@ -981,11 +984,10 @@ class stock_picking(osv.osv):
|
|||
location = False
|
||||
# Search putaway strategy
|
||||
if product_putaway_strats.get(product.id):
|
||||
putaway_strat = product_putaway_strats[product.id]
|
||||
location = product_putaway_strats[product.id]
|
||||
else:
|
||||
putaway_strat = self.pool.get('stock.location').get_putaway_strategy(cr, uid, picking.location_dest_id, product, context=context)
|
||||
product_putaway_strats[product.id] = putaway_strat
|
||||
if putaway_strat:
|
||||
location = self.pool.get('stock.location').get_putaway_strategy(cr, uid, picking.location_dest_id, product, context=context)
|
||||
product_putaway_strats[product.id] = location
|
||||
location = self._picking_putaway_resolution(cr, uid, picking, product, putaway_strat, context=context)
|
||||
return location or picking.picking_type_id.default_location_dest_id.id or picking.location_dest_id.id
|
||||
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="removal_fifo" model="product.removal">
|
||||
<field name="name">First In First Out (FIFO)</field>
|
||||
<field name="method">fifo</field>
|
||||
</record>
|
||||
<record id="removal_lifo" model="product.removal">
|
||||
<field name="name">Last In First Out (LIFO)</field>
|
||||
<field name="method">lifo</field>
|
||||
</record>
|
||||
|
||||
<!--
|
||||
Resource: stock.location
|
||||
|
|
|
@ -383,37 +383,10 @@
|
|||
<field name="posz"/>
|
||||
<field name="loc_barcode"/>
|
||||
</group>
|
||||
</group>
|
||||
<separator string="Removal Strategies" groups="stock.group_adv_location"/>
|
||||
<group groups="stock.group_adv_location">
|
||||
<div class="oe_inline">
|
||||
<p class="oe_grey">
|
||||
Removal strategies define the method used for suggesting the
|
||||
location to take the products from
|
||||
</p>
|
||||
<field name="removal_strategy_ids" class ="oe_inline">
|
||||
<tree editable="bottom" string="removal">
|
||||
<field name="product_categ_id"/>
|
||||
<field name="method"/>
|
||||
</tree>
|
||||
</field>
|
||||
</div>
|
||||
<newline/>
|
||||
<separator string="Putaway Strategies"/>
|
||||
<newline/>
|
||||
<div class="oe_inline">
|
||||
<p class="oe_grey">
|
||||
Putaway strategies define the method used for suggesting the
|
||||
location to put the products
|
||||
</p>
|
||||
<field name="putaway_strategy_ids" class="oe_inline">
|
||||
<tree string="Put Away" editable="bottom">
|
||||
<field name="product_categ_id"/>
|
||||
<field name="method"/>
|
||||
<field name="location_spec_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</div>
|
||||
<group string="Logistics" groups="stock.group_adv_location">
|
||||
<field name="removal_strategy_id" options="{'no_create': True}"/>
|
||||
<field name="putaway_strategy_id"/>
|
||||
</group>
|
||||
</group>
|
||||
<separator string="Additional Information"/>
|
||||
<field name="comment"/>
|
||||
|
@ -452,11 +425,21 @@
|
|||
<field name="name">product.putaway.form</field>
|
||||
<field name="model">product.putaway</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Putaway">
|
||||
<field name="product_categ_id"/>
|
||||
<field name="location_id"/>
|
||||
<field name="method"/>
|
||||
<field name="location_spec_id"/>
|
||||
<form string="Putaway" version="7.0">
|
||||
<group colspan="4">
|
||||
<field name="name"/>
|
||||
<field name="method"/>
|
||||
</group>
|
||||
<div attrs="{'invisible': [('method', '!=', 'fixed')]}">
|
||||
<separator string="Fixed Locations Per Categories"/>
|
||||
<field name="fixed_location_ids" colspan="4" nolabel="1">
|
||||
<tree editable="top">
|
||||
<field name="sequence" widget='handle'/>
|
||||
<field name="category_id"/>
|
||||
<field name="fixed_location_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -466,8 +449,7 @@
|
|||
<field name="model">product.removal</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Removal">
|
||||
<field name="product_categ_id"/>
|
||||
<field name="location_id"/>
|
||||
<field name="name"/>
|
||||
<field name="method"/>
|
||||
</form>
|
||||
</field>
|
||||
|
@ -515,48 +497,17 @@
|
|||
<field name="model">product.category</field>
|
||||
<field name="inherit_id" ref="product.product_category_form_view" />
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//sheet" position="inside">
|
||||
<group string="Routes" colspan="4">
|
||||
<div class="oe_inline">
|
||||
<p attrs="{'invisible':[('route_ids','=',False)]}">
|
||||
<field name="route_ids" nolabel="1" widget="many2many_tags" class="oe_inline"/>
|
||||
</p>
|
||||
<xpath expr="//group[@name='parent']" position="inside">
|
||||
<group string="Logistics" colspan="2">
|
||||
<field name="route_ids" widget="many2many_tags"/>
|
||||
<div class="oe_inline" colspan="2">
|
||||
<p attrs="{'invisible':[('parent_id','=',False)]}">
|
||||
The following routes will apply to the products in this category taking into account parent categories:
|
||||
<field name="total_route_ids" nolabel="1" widget="many2many_tags"/>
|
||||
</p>
|
||||
</div>
|
||||
<field name="removal_strategy_id" options="{'no_create': True}"/>
|
||||
</group>
|
||||
<separator string="Removal Strategies"/>
|
||||
<div class="oe_inline">
|
||||
<p class="oe_grey">
|
||||
Removal strategies define the method used for suggesting the
|
||||
location to take the products from
|
||||
</p>
|
||||
<field name="removal_strategy_ids">
|
||||
<tree editable="bottom" string="removal">
|
||||
<field name="location_id"/>
|
||||
<field name="method"/>
|
||||
</tree>
|
||||
</field>
|
||||
|
||||
</div>
|
||||
<newline/>
|
||||
<separator string="Putaway Strategies"/>
|
||||
<newline/>
|
||||
<div class="oe_inline">
|
||||
<p class="oe_grey">
|
||||
Putaway strategies define the method used for suggesting the
|
||||
location to put the products
|
||||
</p>
|
||||
<field name="putaway_strategy_ids">
|
||||
<tree string="Put Away" editable="bottom">
|
||||
<field name="location_id"/>
|
||||
<field name="method"/>
|
||||
<field name="location_spec_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
|
Loading…
Reference in New Issue