Merge pull request #2771 from odoo-dev/8.0-wmsstaging11-jco

8.0 wmsstaging11 jco
This commit is contained in:
Josse Colpaert 2014-09-30 11:06:50 +02:00
commit a64af728b2
22 changed files with 223 additions and 225 deletions

View File

@ -155,42 +155,6 @@ class mrp_bom(osv.osv):
_description = 'Bill of Material'
_inherit = ['mail.thread']
def _child_compute(self, cr, uid, ids, name, arg, context=None):
""" Gets child bom.
@param self: The object pointer
@param cr: The current row, from the database cursor,
@param uid: The current user ID for security checks
@param ids: List of selected IDs
@param name: Name of the field
@param arg: User defined argument
@param context: A standard dictionary for contextual values
@return: Dictionary of values
"""
result = {}
if context is None:
context = {}
bom_obj = self.pool.get('mrp.bom')
bom_id = context and context.get('active_id', False) or False
cr.execute('select id from mrp_bom')
if all(bom_id != r[0] for r in cr.fetchall()):
ids.sort()
bom_id = ids[0]
bom_parent = bom_obj.browse(cr, uid, bom_id, context=context)
for bom in self.browse(cr, uid, ids, context=context):
if (bom_parent) or (bom.id == bom_id):
result[bom.id] = map(lambda x: x.id, bom.bom_line_ids)
else:
result[bom.id] = []
if bom.bom_line_ids:
continue
ok = ((name=='child_complete_ids'))
if (bom.type=='phantom' or ok):
sids = bom_obj.search(cr, uid, [('product_tmpl_id','=',bom.product_tmpl_id.id)])
if sids:
bom2 = bom_obj.browse(cr, uid, sids[0], context=context)
result[bom.id] += map(lambda x: x.id, bom2.bom_line_ids)
return result
_columns = {
'name': fields.char('Name'),
'code': fields.char('Reference', size=16),
@ -213,7 +177,6 @@ class mrp_bom(osv.osv):
'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity."),
'product_efficiency': fields.float('Manufacturing Efficiency', required=True, help="A factor of 0.9 means a loss of 10% during the production process."),
'property_ids': fields.many2many('mrp.property', string='Properties'),
'child_complete_ids': fields.function(_child_compute, relation='mrp.bom', string="BoM Hierarchy", type='many2many'),
'company_id': fields.many2one('res.company', 'Company', required=True),
}
@ -230,7 +193,7 @@ class mrp_bom(osv.osv):
}
_order = "sequence"
def _bom_find(self, cr, uid, product_uom, product_tmpl_id=None, product_id=None, properties=None, context=None):
def _bom_find(self, cr, uid, product_tmpl_id=None, product_id=None, properties=None, context=None):
""" Finds BoM for particular product and product uom.
@param product_tmpl_id: Selected product.
@param product_uom: Unit of measure of a product.
@ -254,16 +217,20 @@ class mrp_bom(osv.osv):
else:
# neither product nor template, makes no sense to search
return False
if product_uom:
domain += [('product_uom','=',product_uom)]
domain = domain + [ '|', ('date_start', '=', False), ('date_start', '<=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT)),
'|', ('date_stop', '=', False), ('date_stop', '>=', time.strftime(DEFAULT_SERVER_DATETIME_FORMAT))]
# order to prioritize bom with product_id over the one without
ids = self.search(cr, uid, domain, order='product_id')
for bom in self.pool.get('mrp.bom').browse(cr, uid, ids):
ids = self.search(cr, uid, domain, order='product_id', context=context)
# Search a BoM which has all properties specified, or if you can not find one, you could
# pass a BoM without any properties
bom_empty_prop = False
for bom in self.pool.get('mrp.bom').browse(cr, uid, ids, context=context):
if not set(map(int, bom.property_ids or [])) - set(properties or []):
return bom.id
return False
if properties and not bom.property_ids:
bom_empty_prop = bom.id
else:
return bom.id
return bom_empty_prop
def _bom_explode(self, cr, uid, bom, product, factor, properties=None, level=0, routing_id=False, previous_products=None, master_bom=None, context=None):
""" Finds Products and Work Centers for related BoM for manufacturing order.
@ -279,9 +246,9 @@ class mrp_bom(osv.osv):
"""
uom_obj = self.pool.get("product.uom")
routing_obj = self.pool.get('mrp.routing')
all_prod = [] + (previous_products or [])
master_bom = master_bom or bom
def _factor(factor, product_efficiency, product_rounding):
factor = factor / (product_efficiency or 1.0)
factor = _common.ceiling(factor, product_rounding)
@ -318,11 +285,11 @@ class mrp_bom(osv.osv):
if not product or (set(map(int,bom_line_id.attribute_value_ids or [])) - set(map(int,product.attribute_value_ids))):
continue
if bom_line_id.product_id.id in all_prod:
if previous_products and bom_line_id.product_id.product_tmpl_id.id in previous_products:
raise osv.except_osv(_('Invalid Action!'), _('BoM "%s" contains a BoM line with a product recursion: "%s".') % (master_bom.name,bom_line_id.product_id.name_get()[0][1]))
quantity = _factor(bom_line_id.product_qty * factor, bom_line_id.product_efficiency, bom_line_id.product_rounding)
bom_id = self._bom_find(cr, uid, bom_line_id.product_uom.id, product_id=bom_line_id.product_id.id, properties=properties, context=context)
bom_id = self._bom_find(cr, uid, product_id=bom_line_id.product_id.id, properties=properties, context=context)
#If BoM should not behave like PhantoM, just add the product, otherwise explode further
if bom_line_id.type != "phantom" and (not bom_id or self.browse(cr, uid, bom_id, context=context).type != "phantom"):
@ -335,7 +302,7 @@ class mrp_bom(osv.osv):
'product_uos': bom_line_id.product_uos and bom_line_id.product_uos.id or False,
})
elif bom_id:
all_prod.append(bom_line_id.product_id.id)
all_prod = [bom.product_tmpl_id.id] + (previous_products or [])
bom2 = self.browse(cr, uid, bom_id, context=context)
# We need to convert to units/UoM of chosen BoM
factor2 = uom_obj._compute_qty(cr, uid, bom_line_id.product_uom.id, quantity, bom2.product_uom.id)
@ -345,7 +312,7 @@ class mrp_bom(osv.osv):
result = result + res[0]
result2 = result2 + res[1]
else:
raise osv.except_osv(_('Invalid Action!'), _('BoM "%s" contains a phantom BoM line but the product "%s" don\'t have any BoM defined.') % (master_bom.name,bom_line_id.product_id.name_get()[0][1]))
raise osv.except_osv(_('Invalid Action!'), _('BoM "%s" contains a phantom BoM line but the product "%s" does not have any BoM defined.') % (master_bom.name,bom_line_id.product_id.name_get()[0][1]))
return result, result2
@ -385,6 +352,21 @@ class mrp_bom_line(osv.osv):
_name = 'mrp.bom.line'
_order = "sequence"
def _get_child_bom_lines(self, cr, uid, ids, field_name, arg, context=None):
"""If the BOM line refers to a BOM, return the ids of the child BOM lines"""
bom_obj = self.pool['mrp.bom']
res = {}
for bom_line in self.browse(cr, uid, ids, context=context):
bom_id = bom_obj._bom_find(cr, uid,
product_tmpl_id=bom_line.product_id.product_tmpl_id.id,
product_id=bom_line.product_id.id, context=context)
if bom_id:
child_bom = bom_obj.browse(cr, uid, bom_id, context=context)
res[bom_line.id] = [x.id for x in child_bom.bom_line_ids]
else:
res[bom_line.id] = False
return res
_columns = {
'type': fields.selection([('normal', 'Normal'), ('phantom', 'Phantom')], 'BoM Line Type', required=True,
help="Phantom: this product line will not appear in the raw materials of manufacturing orders,"
@ -403,10 +385,11 @@ class mrp_bom_line(osv.osv):
'routing_id': fields.many2one('mrp.routing', 'Routing', help="The list of operations (list of work centers) to produce the finished product. The routing is mainly used to compute work center costs during operations and to plan future loads on work centers based on production planning."),
'product_rounding': fields.float('Product Rounding', help="Rounding applied on the product quantity."),
'product_efficiency': fields.float('Manufacturing Efficiency', required=True, help="A factor of 0.9 means a loss of 10% within the production process."),
'property_ids': fields.many2many('mrp.property', string='Properties'),
'property_ids': fields.many2many('mrp.property', string='Properties'), #Not used
'bom_id': fields.many2one('mrp.bom', 'Parent BoM', ondelete='cascade', select=True, required=True),
'attribute_value_ids': fields.many2many('product.attribute.value', string='Variants', help="BOM Product Variants needed form apply this line."),
'child_line_ids': fields.function(_get_child_bom_lines, relation="mrp.bom.line", string="BOM lines of the referred bom", type="one2many")
}
def _get_uom_id(self, cr, uid, *args):
@ -645,7 +628,7 @@ class mrp_production(osv.osv):
}}
bom_obj = self.pool.get('mrp.bom')
product = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
bom_id = bom_obj._bom_find(cr, uid, product.uom_id and product.uom_id.id, product_id=product.id, properties=[], context=context)
bom_id = bom_obj._bom_find(cr, uid, product_id=product.id, properties=[], context=context)
routing_id = False
if bom_id:
bom_point = bom_obj.browse(cr, uid, bom_id, context=context)
@ -694,7 +677,7 @@ class mrp_production(osv.osv):
bom_point = production.bom_id
bom_id = production.bom_id.id
if not bom_point:
bom_id = bom_obj._bom_find(cr, uid, production.product_uom.id, product_id=production.product_id.id, properties=properties, context=context)
bom_id = bom_obj._bom_find(cr, uid, product_id=production.product_id.id, properties=properties, context=context)
if bom_id:
bom_point = bom_obj.browse(cr, uid, bom_id)
routing_id = bom_point.routing_id.id or False
@ -707,6 +690,7 @@ class mrp_production(osv.osv):
factor = uom_obj._compute_qty(cr, uid, production.product_uom.id, production.product_qty, bom_point.product_uom.id)
# product_lines, workcenter_lines
results, results2 = bom_obj._bom_explode(cr, uid, bom_point, production.product_id, factor / bom_point.product_qty, properties, routing_id=production.routing_id.id, context=context)
# reset product_lines in production order
for line in results:
line['production_id'] = production.id
@ -823,26 +807,36 @@ class mrp_production(osv.osv):
def _calculate_qty(self, cr, uid, production, product_qty=0.0, context=None):
"""
Calculates the quantity still needed to produce an extra number of products
product_qty is in the uom of the product
"""
quant_obj = self.pool.get("stock.quant")
uom_obj = self.pool.get("product.uom")
produced_qty = self._get_produced_qty(cr, uid, production, context=context)
consumed_data = self._get_consumed_data(cr, uid, production, context=context)
#In case no product_qty is given, take the remaining qty to produce for the given production
if not product_qty:
product_qty = production.product_qty - produced_qty
product_qty = uom_obj._compute_qty(cr, uid, production.product_uom.id, production.product_qty, production.product_id.uom_id.id) - produced_qty
production_qty = uom_obj._compute_qty(cr, uid, production.product_uom.id, production.product_qty, production.product_id.uom_id.id)
dicts = {}
# Find product qty to be consumed and consume it
scheduled_qty = {}
for scheduled in production.product_lines:
if scheduled.product_id.type == 'service':
continue
product_id = scheduled.product_id.id
qty = uom_obj._compute_qty(cr, uid, scheduled.product_uom.id, scheduled.product_qty, scheduled.product_id.uom_id.id)
if scheduled_qty.get(scheduled.product_id.id):
scheduled_qty[scheduled.product_id.id] += qty
else:
scheduled_qty[scheduled.product_id.id] = qty
dicts = {}
# Find product qty to be consumed and consume it
for product_id in scheduled_qty.keys():
consumed_qty = consumed_data.get(product_id, 0.0)
# qty available for consume and produce
qty_avail = scheduled.product_qty - consumed_qty
sched_product_qty = scheduled_qty[product_id]
qty_avail = sched_product_qty - consumed_qty
if qty_avail <= 0.0:
# there will be nothing to consume for this raw material
continue
@ -851,10 +845,10 @@ class mrp_production(osv.osv):
dicts[product_id] = {}
# total qty of consumed product we need after this consumption
if product_qty + produced_qty <= production.product_qty:
total_consume = ((product_qty + produced_qty) * scheduled.product_qty / production.product_qty)
if product_qty + produced_qty <= production_qty:
total_consume = ((product_qty + produced_qty) * sched_product_qty / production_qty)
else:
total_consume = (production.product_qty * scheduled.product_qty / production.product_qty)
total_consume = sched_product_qty
qty = total_consume - consumed_qty
# Search for quants related to this related move
@ -865,8 +859,8 @@ class mrp_production(osv.osv):
continue
q = min(move.product_qty, qty)
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, scheduled.product_id, q, domain=[('qty', '>', 0.0)],
prefered_domain_list=[[('reservation_id', '=', move.id)], [('reservation_id', '=', False)]], context=context)
quants = quant_obj.quants_get_prefered_domain(cr, uid, move.location_id, move.product_id, q, domain=[('qty', '>', 0.0)],
prefered_domain_list=[[('reservation_id', '=', move.id)]], context=context)
for quant, quant_qty in quants:
if quant:
lot_id = quant.lot_id.id
@ -895,18 +889,15 @@ class mrp_production(osv.osv):
If Production mode is consume & produce, all stock move lines of raw materials will be done/consumed
and stock move lines of final product will be also done/produced.
@param production_id: the ID of mrp.production object
@param production_qty: specify qty to produce
@param production_qty: specify qty to produce in the uom of the production order
@param production_mode: specify production mode (consume/consume&produce).
@param wiz: the mrp produce product wizard, which will tell the amount of consumed products needed
@return: True
"""
stock_mov_obj = self.pool.get('stock.move')
uom_obj = self.pool.get("product.uom")
production = self.browse(cr, uid, production_id, context=context)
if not production.move_lines and production.state == 'ready':
# trigger workflow if not products to consume (eg: services)
self.signal_workflow(cr, uid, [production_id], 'button_produce')
produced_qty = self._get_produced_qty(cr, uid, production, context=context)
production_qty_uom = uom_obj._compute_qty(cr, uid, production.product_uom.id, production_qty, production.product_id.uom_id.id)
main_production_move = False
if production_mode == 'consume_produce':
@ -920,13 +911,12 @@ class mrp_production(osv.osv):
produced_products[produced_product.product_id.id] += produced_product.product_qty
for produce_product in production.move_created_ids:
produced_qty = produced_products.get(produce_product.product_id.id, 0)
subproduct_factor = self._get_subproduct_factor(cr, uid, production.id, produce_product.id, context=context)
rest_qty = (subproduct_factor * production.product_qty) - produced_qty
lot_id = False
if wiz:
lot_id = wiz.lot_id.id
new_moves = stock_mov_obj.action_consume(cr, uid, [produce_product.id], (subproduct_factor * production_qty), location_id=produce_product.location_id.id, restrict_lot_id=lot_id, context=context)
new_moves = stock_mov_obj.action_consume(cr, uid, [produce_product.id], (subproduct_factor * production_qty_uom),
location_id=produce_product.location_id.id, restrict_lot_id=lot_id, context=context)
stock_mov_obj.write(cr, uid, new_moves, {'production_id': production_id}, context=context)
if produce_product.product_id.id == production.product_id.id and new_moves:
main_production_move = new_moves[0]
@ -937,7 +927,7 @@ class mrp_production(osv.osv):
for cons in wiz.consume_lines:
consume_lines.append({'product_id': cons.product_id.id, 'lot_id': cons.lot_id.id, 'product_qty': cons.product_qty})
else:
consume_lines = self._calculate_qty(cr, uid, production, production_qty, context=context)
consume_lines = self._calculate_qty(cr, uid, production, production_qty_uom, context=context)
for consume in consume_lines:
remaining_qty = consume['product_qty']
for raw_material_line in production.move_lines:
@ -1053,8 +1043,9 @@ class mrp_production(osv.osv):
def _get_raw_material_procure_method(self, cr, uid, product, context=None):
'''This method returns the procure_method to use when creating the stock move for the production raw materials'''
warehouse_obj = self.pool['stock.warehouse']
try:
mto_route = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'stock', 'route_warehouse0_mto')[1]
mto_route = warehouse_obj._get_mto_route(cr, uid, context=context)
except:
return "make_to_stock"
routes = product.route_ids + product.categ_id.total_route_ids

View File

@ -451,7 +451,7 @@
<field name="product_tmpl_id" ref="product.product_product_3_product_template"/>
<field name="product_uom" ref="product.product_uom_unit"/>
<field name="sequence">5</field>
<field name="type">phantom</field>
<field name="type">normal</field>
</record>
<record id="mrp_bom_line_17" model="mrp.bom.line">

View File

@ -378,9 +378,9 @@
<field name="product_id" on_change="onchange_product_id(product_id, product_qty)"/>
<field name="type"/>
<field name="product_qty"/>
<field name="product_uom" on_change="onchange_uom(product_id, product_uom)" groups="product.group_uom"/>
<field name="product_rounding"/>
<field name="product_efficiency"/>
<field name="product_uom" on_change="onchange_uom(product_id, product_uom)" groups="product.group_uom"/>
<field name="date_start"/>
<field name="date_stop"/>
<field name="attribute_value_ids" widget="many2many_tags"/>
@ -436,16 +436,13 @@
<record id="mrp_bom_tree_view" model="ir.ui.view">
<field name="name">mrp.bom.tree</field>
<field name="model">mrp.bom</field>
<!--field name="field_parent">child_complete_ids</field-->
<field name="model">mrp.bom.line</field>
<field name="field_parent">child_line_ids</field>
<field name="arch" type="xml">
<tree string="Bill of Materials">
<field name="sequence" invisible="1"/>
<field name="name" invisible="1"/>
<field name="product_tmpl_id"/>
<field name="product_id"/>
<field name="product_uom" groups="product.group_uom"/>
<field name="code"/>
<field name="type"/>
<field name="routing_id" groups="mrp.group_mrp_routings"/>
<field name="date_start"/>
@ -592,8 +589,8 @@
<record id="action2" model="ir.actions.act_window">
<field name="name">Bill of Materials Structure</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.bom</field>
<field name="domain">[('id', 'in', active_ids)]</field>
<field name="res_model">mrp.bom.line</field>
<field name="domain">[('bom_id', 'in', active_ids)]</field>
<field name="view_type">tree</field>
<field name="view_id" ref="mrp_bom_tree_view"/>
<field name="view_type">tree</field>

View File

@ -62,8 +62,8 @@ class procurement_order(osv.osv):
"""
for procurement in self.browse(cr, uid, ids, context=context):
properties = [x.id for x in procurement.property_ids]
bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, procurement.product_uom.id,
product_id=procurement.product_id.id, properties=properties, context=context)
bom_id = self.pool.get('mrp.bom')._bom_find(cr, uid, product_id=procurement.product_id.id,
properties=properties, context=context)
if not bom_id:
return False
return True
@ -84,8 +84,8 @@ class procurement_order(osv.osv):
routing_id = procurement.bom_id.routing_id.id
else:
properties = [x.id for x in procurement.property_ids]
bom_id = bom_obj._bom_find(cr, uid, procurement.product_uom.id,
product_id=procurement.product_id.id, properties=properties, context=context)
bom_id = bom_obj._bom_find(cr, uid, product_id=procurement.product_id.id,
properties=properties, context=context)
bom = bom_obj.browse(cr, uid, bom_id, context=context)
routing_id = bom.routing_id.id

View File

@ -19,7 +19,6 @@
#
##############################################################################
import time
from openerp.osv import osv
from openerp.report import report_sxw
@ -28,8 +27,7 @@ class bom_structure(report_sxw.rml_parse):
def __init__(self, cr, uid, name, context):
super(bom_structure, self).__init__(cr, uid, name, context=context)
self.localcontext.update({
'time': time,
'get_children':self.get_children,
'get_children': self.get_children,
})
def get_children(self, object, level=0):
@ -38,18 +36,17 @@ class bom_structure(report_sxw.rml_parse):
def _get_rec(object, level):
for l in object:
res = {}
res['name'] = l.name
res['pname'] = l.product_id.name
res['pcode'] = l.product_id.default_code
res['pqty'] = l.product_qty
res['uname'] = l.product_uom.name
res['code'] = l.code
res['level'] = level
res['code'] = l.bom_id.code
result.append(res)
if l.child_complete_ids:
if l.child_line_ids:
if level<6:
level += 1
_get_rec(l.child_complete_ids,level)
_get_rec(l.child_line_ids,level)
if level>0 and level<6:
level -= 1
return result

View File

@ -144,7 +144,7 @@ class report_custom(report_rml):
for product in product_pool.browse(cr, uid, ids, context=context):
product_uom_name = to_xml(product.uom_id.name)
bom_id = bom_pool._bom_find(cr, uid, product.uom_id.id, product_id=product.id, context=context)
bom_id = bom_pool._bom_find(cr, uid, product_id=product.id, context=context)
title = "<title>%s</title>" %(_("Cost Structure"))
title += "<title>%s</title>" % (to_xml(product.name))
xml += "<lines style='header'>" + title + prod_header + "</lines>"

View File

@ -25,7 +25,7 @@ from openerp.osv import fields
from openerp.osv import osv
from openerp.tools.translate import _
from openerp import SUPERUSER_ID
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_compare
class StockMove(osv.osv):
_inherit = 'stock.move'
@ -152,18 +152,17 @@ class StockMove(osv.osv):
def action_consume(self, cr, uid, ids, product_qty, location_id=False, restrict_lot_id=False, restrict_partner_id=False,
consumed_for=False, context=None):
""" Consumed product with specific quantity from specific source location.
@param product_qty: Consumed product quantity
@param product_qty: Consumed/produced product quantity (= in quantity of UoM of product)
@param location_id: Source location
@param restrict_lot_id: optionnal parameter that allows to restrict the choice of quants on this specific lot
@param restrict_partner_id: optionnal parameter that allows to restrict the choice of quants to this specific partner
@param consumed_for: optionnal parameter given to this function to make the link between raw material consumed and produced product, for a better traceability
@return: Consumed lines
@return: New lines created if not everything was consumed for this line
"""
if context is None:
context = {}
res = []
production_obj = self.pool.get('mrp.production')
uom_obj = self.pool.get('product.uom')
if product_qty <= 0:
raise osv.except_osv(_('Warning!'), _('Please provide proper quantity.'))
@ -175,32 +174,30 @@ class StockMove(osv.osv):
else:
ids2.append(move.id)
prod_orders = set()
for move in self.browse(cr, uid, ids2, context=context):
prod_orders.add(move.raw_material_production_id.id or move.production_id.id)
move_qty = move.product_qty
uom_qty = uom_obj._compute_qty(cr, uid, move.product_id.uom_id.id, product_qty, move.product_uom.id)
if move_qty <= 0:
raise osv.except_osv(_('Error!'), _('Cannot consume a move with negative or zero quantity.'))
quantity_rest = move.product_qty - uom_qty
if quantity_rest > 0:
ctx = context.copy()
if location_id:
ctx['source_location_id'] = location_id
new_mov = self.split(cr, uid, move, move_qty - quantity_rest, restrict_lot_id=restrict_lot_id, restrict_partner_id=restrict_partner_id, context=ctx)
self.write(cr, uid, new_mov, {'consumed_for': consumed_for}, context=context)
quantity_rest = move_qty - product_qty
# Compare with numbers of move uom as we want to avoid a split with 0 qty
quantity_rest_uom = move.product_uom_qty - self.pool.get("product.uom")._compute_qty_obj(cr, uid, move.product_id.uom_id, product_qty, move.product_uom)
if float_compare(quantity_rest_uom, 0, precision_rounding=move.product_uom.rounding) != 0:
new_mov = self.split(cr, uid, move, quantity_rest, context=context)
res.append(new_mov)
else:
res.append(move.id)
if location_id:
self.write(cr, uid, [move.id], {'location_id': location_id, 'restrict_lot_id': restrict_lot_id,
'restrict_partner_id': restrict_partner_id,
'consumed_for': consumed_for}, context=context)
self.action_done(cr, uid, res, context=context)
production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
production_obj.signal_workflow(cr, uid, production_ids, 'button_produce')
for new_move in res:
if new_move != move.id:
#This move is not already there in move lines of production order
production_obj.write(cr, uid, production_ids, {'move_lines': [(4, new_move)]})
vals = {'restrict_lot_id': restrict_lot_id,
'restrict_partner_id': restrict_partner_id,
'consumed_for': consumed_for}
if location_id:
vals.update({'location_id': location_id})
self.write(cr, uid, [move.id], vals, context=context)
# Original moves will be the quantities consumed, so they need to be done
self.action_done(cr, uid, ids2, context=context)
if res:
self.action_assign(cr, uid, res, context=context)
if prod_orders:
production_obj.signal_workflow(cr, uid, list(prod_orders), 'button_produce')
return res
def action_scrap(self, cr, uid, ids, product_qty, location_id, restrict_lot_id=False, restrict_partner_id=False, context=None):

View File

@ -6,12 +6,10 @@
<t t-call="report.internal_layout">
<div class="page">
<h2>BOM Structure</h2>
<table class="table table-condensed">
<thead>
<tr>
<th>BOM Name</th>
<th>Product Name</th>
<th>Quantity</th>
<th>BOM Ref</th>
</tr>
@ -19,21 +17,23 @@
<tbody>
<t t-foreach="docs" t-as="o">
<tr style="font-weight: bold;">
<td><span t-field="o.name"/></td>
<td><span t-field="o.product_id.default_code"/><span t-field="o.product_id.name"/></td>
<td>
<span t-field="o.product_id.default_code"/>
<span t-field="o.name"/>
</td>
<td>
<span t-field="o.product_qty"/>
<span groups="product.group_uom" t-field="o.product_uom.name"/>
</td>
<td><span t-field="o.code"/></td>
<td>
<span t-field="o.code"/>
</td>
</tr>
<tr t-foreach="get_children(o.bom_line_ids)" t-as="l">
<td>
<span style="color: white;" t-esc="'... '*(l['level'])"/>
</td>
<td>
[ <span t-esc="l['pcode']"/> ]
<span t-esc="l['pname']"/>
<td style="padding-left: 20px;">
<span style="color: white;" t-esc="'... '*(l['level'])"/>[
<span t-esc="l['pcode']"/>]
<span t-esc="l['pname']"/>
</td>
<td>
<span t-esc="formatLang(l['pqty'])"/>

View File

@ -78,7 +78,7 @@ class change_production_qty(osv.osv_memory):
bom_point = prod.bom_id
bom_id = prod.bom_id.id
if not bom_point:
bom_id = bom_obj._bom_find(cr, uid, prod.product_uom.id, product_id=prod.product_id.id, context=context)
bom_id = bom_obj._bom_find(cr, uid, product_id=prod.product_id.id, context=context)
if not bom_id:
raise osv.except_osv(_('Error!'), _("Cannot find bill of material for this product."))
prod_obj.write(cr, uid, [prod.id], {'bom_id': bom_id})

View File

@ -61,11 +61,13 @@ class mrp_product_produce(osv.osv_memory):
which the user can still adapt
"""
prod_obj = self.pool.get("mrp.production")
uom_obj = self.pool.get("product.uom")
production = prod_obj.browse(cr, uid, context['active_id'], context=context)
consume_lines = []
new_consume_lines = []
if product_qty > 0.0:
consume_lines = prod_obj._calculate_qty(cr, uid, production, product_qty=product_qty, context=context)
product_uom_qty = uom_obj._compute_qty(cr, uid, production.product_uom.id, product_qty, production.product_id.uom_id.id)
consume_lines = prod_obj._calculate_qty(cr, uid, production, product_qty=product_uom_qty, context=context)
for consume in consume_lines:
new_consume_lines.append([0, False, consume])
@ -88,8 +90,8 @@ class mrp_product_produce(osv.osv_memory):
for move in prod.move_created_ids2:
if move.product_id == prod.product_id:
if not move.scrapped:
done += move.product_qty
return (prod.product_qty - done) or prod.product_qty
done += move.product_uom_qty # As uom of produced products and production order should correspond
return prod.product_qty - done
def _get_product_id(self, cr, uid, context=None):
""" To obtain product id

View File

@ -46,7 +46,7 @@ class stock_move_consume(osv.osv_memory):
if 'product_uom' in fields:
res.update({'product_uom': move.product_uom.id})
if 'product_qty' in fields:
res.update({'product_qty': move.product_qty})
res.update({'product_qty': move.product_uom_qty})
if 'location_id' in fields:
res.update({'location_id': move.location_id.id})
return res
@ -57,10 +57,14 @@ class stock_move_consume(osv.osv_memory):
if context is None:
context = {}
move_obj = self.pool.get('stock.move')
uom_obj = self.pool.get('product.uom')
move_ids = context['active_ids']
for data in self.browse(cr, uid, ids, context=context):
if move_ids and move_ids[0]:
move = move_obj.browse(cr, uid, move_ids[0], context=context)
qty = uom_obj._compute_qty(cr, uid, data['product_uom'].id, data.product_qty, data.product_id.uom_id.id)
move_obj.action_consume(cr, uid, move_ids,
data.product_qty, data.location_id.id, restrict_lot_id=data.restrict_lot_id.id,
qty, data.location_id.id, restrict_lot_id=data.restrict_lot_id.id,
context=context)
return {'type': 'ir.actions.act_window_close'}

View File

@ -123,7 +123,6 @@ class mrp_production_workcenter_line(osv.osv):
else:
open_count = self.search_count(cr,uid,[('production_id','=',prod_obj.id), ('state', '!=', 'done')])
flag = not bool(open_count)
if flag:
for production in prod_obj_pool.browse(cr, uid, [prod_obj.id], context= None):
if production.move_lines or production.move_created_ids:

View File

@ -79,7 +79,7 @@
order = self.browse(cr, uid, ref("mrp.mrp_production_1"), context=context)
order.workcenter_lines[0].signal_workflow('button_cancel')
-
I reset first work operation and start after resolving techninal fault of work center.
I reset first work operation and start after resolving technical fault of work center.
-
!python {model: mrp.production}: |
order = self.browse(cr, uid, ref("mrp.mrp_production_1"), context=context)

View File

@ -31,17 +31,22 @@
This is the module for computing Procurements.
==============================================
In the MRP process, procurements orders are created to launch manufacturing
orders, purchase orders, stock allocations. Procurement orders are
generated automatically by the system and unless there is a problem, the
user will not be notified. In case of problems, the system will raise some
procurement exceptions to inform the user about blocking problems that need
to be resolved manually (like, missing BoM structure or missing supplier).
This procurement module only depends on the product module and is not useful
on itself. Procurements represent needs that need to be solved by a procurement
rule. When a procurement is created, it is confirmed. When a rule is found,
it will be put in running state. After, it will check if what needed to be done
for the rule has been executed. Then it will go to the done state. A procurement
can also go into exception, for example when it can not find a rule and it can be cancelled.
The procurement order will schedule a proposal for automatic procurement
for the product which needs replenishment. This procurement will start a
task, either a purchase order form for the supplier, or a production order
depending on the product's configuration.
The mechanism will be extended by several modules. The procurement rule of stock will
create a move and the procurement will be fulfilled when the move is done.
The procurement rule of sale_service will create a task. Those of purchase or
mrp will create a purchase order or a manufacturing order.
The scheduler will check if it can assign a rule to confirmed procurements and if
it can put running procurements to done.
Procurements in exception should be checked manually and can be re-run.
""",
'data': [
'security/ir.model.access.csv',

View File

@ -27,10 +27,8 @@
"category" : "Generic Modules/Inventory Control",
"description": """
Product extension. This module adds:
* Last purchase order for each product supplier
* New functional field: Available stock (real+outgoing stock)
* Computes standard price from the BoM of the product (optional for each product)
* Standard price is shown in the BoM and it can be computed with a wizard
* Computes standard price from the BoM of the product with a button on the product variant based
on the materials in the BoM and the work centers. It can create the necessary accounting entries when necessary.
""",
"init_xml" : [],
"demo_xml" : [],

View File

@ -36,7 +36,7 @@ class product_product(osv.osv):
testdict = {}
for prod_id in ids:
bom_obj = self.pool.get('mrp.bom')
bom_id = bom_obj._bom_find(cr, uid, False, product_id = prod_id, context=context)
bom_id = bom_obj._bom_find(cr, uid, product_id = prod_id, context=context)
if bom_id:
# In recursive mode, it will first compute the prices of child boms
if recursive:

View File

@ -288,6 +288,7 @@ class sale_order_line(osv.osv):
context = context or {}
product_uom_obj = self.pool.get('product.uom')
product_obj = self.pool.get('product.product')
warehouse_obj = self.pool['stock.warehouse']
warning = {}
#UoM False due to hack which makes sure uom changes price, ... in product_id_change
res = self.product_id_change(cr, uid, ids, pricelist, product, qty=qty,
@ -311,14 +312,14 @@ class sale_order_line(osv.osv):
#determine if the product is MTO or not (for a further check)
isMto = False
if warehouse_id:
warehouse = self.pool.get('stock.warehouse').browse(cr, uid, warehouse_id, context=context)
warehouse = warehouse_obj.browse(cr, uid, warehouse_id, context=context)
for product_route in product_obj.route_ids:
if warehouse.mto_pull_id and warehouse.mto_pull_id.route_id and warehouse.mto_pull_id.route_id.id == product_route.id:
isMto = True
break
else:
try:
mto_route_id = self.pool.get('ir.model.data').get_object(cr, uid, 'stock', 'route_warehouse0_mto').id
mto_route_id = warehouse_obj._get_mto_route(cr, uid, context=context)
except:
# if route MTO not found in ir_model_data, we treat the product as in MTS
mto_route_id = False

View File

@ -33,7 +33,7 @@ Virtual locations as counterparts for production are used in manufacturing opera
Inventory locations are counterparts of the stock operations that represent your company's profit and loss in terms of your stocks.
In OpenERP, locations are structured hierarchically. You can structure your locations as a tree, dependent on a parent-child relationship. This gives you more detailed levels of analysis of your stock operations and the organization of your warehouses.
In Odoo, locations are structured hierarchically. You can structure your locations as a tree, dependent on a parent-child relationship. This gives you more detailed levels of analysis of your stock operations and the organization of your warehouses.
@ -45,7 +45,7 @@ A warehouse represents the building where we stock our goods. In case of multip
A warehouse corresponds also to a location. As the locations are hierarchical, OpenERP will create one parent location for the warehouse that contains all the different locations in it.
When you create a warehouse, the system will create the necessary picking types and parent locations in the background.
When you create a warehouse, the system will create the necessary picking types and main child locations for this main location in the background.
===========================================
@ -71,13 +71,13 @@ For example, if the product is MTO and we have a delivery order from Stock to Cu
In these confirmed or waiting states it is possible to do "Check Availability". If it can find the necessary stock, the state goes to Assigned. In this state it is possible to effectively execute the move and transfer the products. Incoming shipments are automatically available. Effectively executing the move, brings it to the done state and makes it adapt the quantity available on hand.
Normally, the picking associated to the move, will have the same state as it moves, but the picking can also have a partially available state. It is possible that some products in the picking are available and some are not. On a sales order or delivery order picking, you can specify if you want your customer to be delivered as soon as possible when only a part of the products is available (partial delivery) or only all at once when everything is available (in order to save on transport costs for example). So, if you can do a partial delivery, the picking state will be partially available when only some of the products are available.
Normally, the picking associated to the move, will have the same state as it moves, but the picking can also have a partially available state. It is possible that some products in the picking are available and some are not. On a sales order or delivery order picking, you can specify if you want your customer to be delivered as soon as possible when only a part of the products is available (partial delivery) or only all at once when everything is available (in order to save on transport costs for example). So, if you can do a partial delivery, the picking state will be partially available when only some of the products are available (even a part of a move).
===================================================
Reordering rules, procurement and procurement group
===================================================
Procurements represent needs that need to be solved. For example, every sales order line will create a procurement in Customers. This will be solved by a move for the delivery, which will, in case of a MTO product in buy configuration, create a new procurement (need) in Stock, which will be solved by a purchase order.
Procurements represent needs that need to be solved by a procurement rule. For example, every sales order line will create a procurement in Customers. This will be solved by a move for the delivery, which will, in case of a MTO product, create a new procurement (need) in Stock, which will be solved by a purchase order if it also has the buy-route.
It is not required however, to have this need in stock created by a move. In case of MTS, the move will not create a procurement (need), but the the procurement will originate from a reordering rule created for this product in stock.
@ -85,7 +85,7 @@ An reordering rule (= minimum stock rule) applies the following rule: if the vir
You can also set multiple quantities in the minimum stock rules. If you set a multiple quantity of 3 the system will propose procurement of 15 pieces, and not the 13 it really needs. In this case, it automatically rounds the quantity upwards.
Pay attention to the fact that the maximum quantity is not the maximum you will have in stock. If we take the following situation: a company has 10 pieces of product with minimum stock rules defined for this product by Min quantity = 10, Max quantity = 30 and Qty multiple = 12. If an order of 2 pieces comes, a purchase of 24 pieces order will be executed. The first 12 pieces will be ordered to reach the minimum quantity and the other 12 to reach the maximum quantity. At the end, the stock of this product will be equal to 32 pieces.
Pay attention to the fact that the maximum quantity is not the maximum you will have in stock. If we take the following situation: a company has 10 pieces of product with minimum stock rules defined for this product by Min quantity = 10, Max quantity = 30 and Qty multiple = 12. If an order of 2 pieces comes, a purchase of 24 pieces order will be executed. The first 22 pieces will be needed to have the correct quantity and the other 2 to have a multiple of 12. In the very end, the stock of this product will be equal to 32 pieces.
Scheduler:
@ -93,7 +93,7 @@ In order for the reordering rule to create the procurement, we need to launch th
Procurement groups:
Even when you have multiple lines in your sales order, you want one delivery order with all the lines of the sales order. In order to do that, we group the different procurements of this sale order into the same procurement group we create for the sales order. That way, the moves of a delivery order stay together by putting moves of the same group in the same picking.
Even when you have multiple lines in your sales order, you want one delivery order with all the lines of the sales order. To accomplish this, Odoo groups the different procurements of this sale order into the same procurement group we create for the sales order. That way, the moves of a delivery order stay together by putting moves of the same group in the same picking.
=================================
Consumables vs stockable products
@ -116,7 +116,6 @@ We will create a reordering rule for every product with minimum stock. These or
<<Show where we configure buy and mto>>
<<Show how to configure orderpoints>>
3 Beyond the magic of stock moves
*********************************
@ -126,9 +125,9 @@ In the following chapters, we go a little deeper into the mechanisms behind the
Assigning stock moves to pickings
=================================
When you want to give an assignment to a warehouse operator manually, you will create a picking and create the moves in it by specifying the different products and quantities. When confirming a sale order however, OpenERP will create the moves automatically. In these cases, it will create the stock moves without picking first. In a second step, they will be attributed to an existing picking or a picking will be created.
When you want to give an assignment to a warehouse operator manually, you will create a picking and create the moves in it by specifying the different products and quantities. When confirming a sale order however, Odoo will create procurements which will be solved bt creating moves. First, these stock moves will be created without picking. In a second step, they will be attributed to an existing picking or a picking will be created.
In order to assign the move to a picking, OpenERP will check if the move was assigned a picking type (e.g. Your Company: Delivery Orders) and if it does, it will search for a picking to assign the move to. This picking should be in the right state, picking type, procurement group (=group of procurements related to e.g. the same sale order) and source and destination locations. If no picking can be found, it will create a new one.
In order to assign the move to a picking, Odoo will check if the move was assigned a picking type (e.g. Your Company: Delivery Orders) and if it does, it will search for a picking to assign the move to. This picking should be in the correct state, picking type, procurement group (=group of procurements related to e.g. the same sale order) and source and destination locations. If no picking can be found, it will create a new one.
This mechanism allows for a lot of flexibility when for example some products have to go through the Packing zone for packing and some don't. That way, the packing order will still group the moves that need packing from the sale order and the direct moves will be grouped in a separate picking also. For the delivery order, everything will be together in one picking again.
@ -166,7 +165,7 @@ Pull rules are not the opposite of push rules! Its very different as push rul
When a stock move is confirmed and its procurement method is 'Advanced: Apply procurement rules', it will create a procurement in the source location for the quantity of the move. To fulfill this procurement, a procurement rule needs to be applied on this procurement. There are several types of procurement rules with different results: move products from another location to the source location, purchase to the source location, produce towards the source location.
A procurement does not need to be created by a stock move however. A user can create a procurement manually and when we confirm a sale order, OpenERP will create a procurement per sale order line in the Customers location. Actually, this system of procurements, stock moves and procurement rules is used consistently throughout OpenERP. Even in the simplest warehouse configuration, when we run the procurements generated from the sale order, these procurement rules will generate the delivery order.
A procurement does not need to be created by a stock move however. A user can create a procurement manually and when we confirm a sale order, Odoo will create a procurement per sale order line in the Customers location. Actually, this system of procurements, stock moves and procurement rules is used consistently throughout Odoo. Even in the simplest warehouse configuration, when we run the procurements generated from the sale order, these procurement rules will generate the delivery order.
Procurements will pass through the following states when everything goes well:
@ -185,22 +184,22 @@ A push rule can not be applied anymore when the rule was created from a pull rul
Procurement method of stock moves and procurement rules
=======================================================
Whether a confirmed stock move created a procurement in the source location and applied the procurement rules, depends on its procurement method. It has to be 'Advanced: apply procurement rules'
Whether a confirmed stock move created a procurement in the source location and applied the procurement rules, depends on its procurement method. It has to be 'Apply procurement rules'
When a user creates a stock move in a picking, the stock move will have its procurement method 'Default: Take from stock'. This means it will not create a procurement in the source location created to the move and will try to find the products in the available stock of the source location. This is also the most logical thing to do when some goods need to be transferred internally for example to move death stock to the back of the warehouse.
If the user chooses however to change the procurement method to 'Advanced: Apply procurement rules', a procurement will be created in the source location. And for example, creating a delivery order could lead in the simplest case (with purchase) to creating a purchase order the delivery order will be waiting for.
If the user chooses however to change the procurement method to 'Apply procurement rules', a procurement will be created in the source location. And for example, creating a delivery order could lead in the simplest case (with purchase) to creating a purchase order the delivery order will be waiting for.
When you have procurement rules in a Pick > Pack > Ship configuration, it might be interesting to apply the procurement rules as it will generate the moves from stock to pack. That way you can send something from the stock manually and still go through the pick/pack steps.
When you have procurement rules in a Pick > Pack > Ship configuration, it might be interesting to apply the procurement rules as it will generate the moves from stock to pack when you create a delivery order. That way you can send something from the stock manually and still go through the pick/pack steps.
The procurement method is also only interesting for internal or outgoing pickings. Incoming shipments do not need to reserve stock, so they are always 'Default: take from stock'.
The procurement method is also only interesting for internal or outgoing pickings. Incoming shipments do not need to reserve stock, so they are always 'Take from stock'.
Maybe you wonder how it is possible to create chains of more than two moves this way. When a procurement rule creates another move, it can determine the procurement method of the new move. In other words, it can determine if the new move will again look for procurement rules or will take from the stock.
This makes it possible to create long chains. For example, an MTS product with pick pack ship, will start with the confirmation of a sales order. This will create a procurement, which will create a move from Output to Customers with procurement method "Advanced: Apply procurement rules". This will create procurement in Output. This will continue like this until the procurement in Pack creates a stock move, which will have "Default: Take from stock" instead.
This makes it possible to create long chains. For example, an MTS product with pick pack ship, will start with the confirmation of a sales order. This will create a procurement, which will create a move from Output to Customers with procurement method "Apply procurement rules". This will create procurement in Output. This will continue like this until the procurement in Pack creates a stock move, which will have "Take from stock" instead.
<< Illustrate one from the chains from the Google Doc>>
<< Illustrate one from the chains from the Google Doc or the presentation of 2014 Open Days (see slideshare.net) shows this (and also how it is configured using routes)
@ -224,52 +223,52 @@ If the second one is split however, the split move, won't have any original move
Applied to MTO and MTS products and sale order and dates
========================================================
The checkbox MTO in the product form is actually a procurement rule that may be applied. This means that the delivery order from stock will be created with procurement method "Advanced: apply procurement rules" instead of "Default: take from stock".
The checkbox MTO in the product form is actually a procurement rule that may be applied. This means that the delivery order from stock will be created with procurement method "Apply procurement rules" instead of "Take from stock".
Lead times
All procurement operations (that is, the requirement for both production orders and purchase orders) are automatically calculated by the scheduler. But more than just creating each order, OpenERP plans the timing of each step. A planned date calculated by the system can be found on each order document.
All procurement operations (that is, the requirement for both production orders and purchase orders) are automatically calculated by the scheduler. But more than just creating each order, Odoo plans the timing of each step. A planned date calculated by the system can be found on each order document.
To organize the whole chain of manufacturing and procurement, OpenERP bases everything on the delivery date promised to the customer. This is given by the date of the confirmation in the order and the lead times shown in each product line of the order. This lead time is itself proposed automatically in the field Customer Lead Time shown in the product form. This Customer Lead Time is the difference between the time on an order and that of the delivery.
To organize the whole chain of manufacturing and procurement, Odoo bases everything on the delivery date promised to the customer. This is given by the date of the confirmation in the order and the lead times shown in each product line of the order. This lead time is itself proposed automatically in the field Customer Lead Time shown in the product form. This Customer Lead Time is the difference between the time on an order and that of the delivery. There is also the sale_order_dates module that can help to promise a date to a customer. Below is a calculation from the OpenERP books.
To see a calculation of the lead times, take the example of the cabinet above. Suppose that the cabinet is assembled in two steps, using the two following bills of materials.
Bill of Materials for 1 SHE100 Unit
+-------------+----------+------------
| Product Code| Quantity | UoM |
+====================================+
|SIDEPAN | 2 | PCE |
+-------------+----------+-----------+
|LIN040 | 1 | M |
+-------------+----------+-----------+
|WOOD010 | 0.249| M |
+-------------+----------+-----------+
|METC000 | 12| PCE |
+-------------+----------+-----------+
+--------------+----------+-----------+
| Product Code | Quantity | UoM |
+==============+==========+===========+
| SIDEPAN | 2 | PCE |
+--------------+----------+-----------+
| LIN040 | 1 | M |
+--------------+----------+-----------+
| WOOD010 | 0.249 | M |
+--------------+----------+-----------+
| METC000 | 12 | PCE |
+--------------+----------+-----------+
Bill of Materials for 2 SIDEPAN Units
+-------------+----------+------------
| Product Code| Quantity | UoM |
+====================================+
| WOOD002 | 0.17| M |
+-------------+----------+------------
+--------------+----------+-----------+
| Product Code | Quantity | UoM |
+==============+==========+===========+
| WOOD002 | 0.17 | M |
+--------------+----------+-----------+
The SIDEPAN is made from an order using the workflow shown. The WOOD002 is purchased on order and the other products are all found in stock. An order for the product SHE100 will then generate two production orders (SHE100 and SIDEPAN) then produce two purchase orders for the product WOOD002. Product WOOD002 is used in the production of both SHE100 and SIDEPAN. Set the lead times on the product forms to the following:
+-------------+-------------------+------------------------+---------------------+
|Product Code |Customer Lead Time |Manufacturing Lead Time |Supplier Lead Time |
+================================================================================+
|SHE100 | 30 days | 5 days | |
+-------------+-------------------+------------------------+---------------------+
|SIDEPAN | | 10 days | |
+-------------+-------------------+------------------------+---------------------+
|WOOD002 | | | 5 days |
+-------------+-------------------+------------------------+---------------------+
+--------------+--------------------+-------------------------+--------------------+
| Product Code | Customer Lead Time | Manufacturing Lead Time | Supplier Lead Time |
+=============+=====================+=========================+====================+
| SHE100 | 30 days | 5 days | |
+--------------+--------------------+-------------------------+--------------------+
| SIDEPAN | | 10 days | |
+--------------+--------------------+-------------------------+--------------------+
| WOOD002 | | | 5 days |
+--------------+--------------------+-------------------------+--------------------+
A customer order placed on the 1st January will set up the following operations and lead times:
@ -305,7 +304,7 @@ It is important to make a difference between production orders and purchase orde
4 Complex logistic flows
************************
<<Check setting needed to activate>>
In order to use the logistic flows to its fullest, you should activate the Advanced routes in Settings > Warehouse.
In the previous chapter, we talked about procurement rules and how they were applied. We have not talked yet about when these procurement rules can be applied and how to configure them.
@ -313,7 +312,7 @@ A lot of Warehouses have input docks and output docks or have a packing zone whe
Using these routes is simple as you just need to select them on e.g. a product or product category, but configuring them correctly is a little more difficult. This is the reason why OpenERP will create the necessary routes automatically when you create a new warehouse. Configuring the warehouse can then be a simple as choosing two step incoming and 3 step delivery, will always be supplied from warehouse B, will be purchased, ...
We will however explain the routes as you might maybe enhance the basic config from OpenERP.
We will however explain the routes as you might maybe enhance the basic config from Odoo.
======
Routes
@ -338,24 +337,26 @@ When a sales order creates a procurement it passes some useful information to it
These routes on the procurement itself can also come in handy when the procurement can not find a suitable rule. By adding a route, you can solve the procurement according to the situation. (e.g. a certain product needs to be manufactured sometimes or bought sometimes)
When OpenERP needs to find a procurement/push rule, it will check the routes that can be applied to the procurement as follows:
When Odoo needs to find a procurement/push rule, it will check the routes that can be applied to the procurement as follows:
* It will try to find a rule from the route(s) on the procurement first
* If it does not find any, it will try to find a rule from the route(s) on the product and product category (+ its parents)
* If it does not find any there, it will try to find a rule from the route(s) on the warehouse
If in any of these cases, multiple rules are found, it will select the rule with the highest priority. This sequence can be changed in Warehouse > Routes (drag/drop the lines). Normally, this will play almost no role.
If in any of these cases, multiple rules are found, it will select the rule with the highest priority. This sequence can be changed in Warehouse > Routes (drag/drop the lines). Normally, this will play almost no role as configuring this way makes it really complex.
Actually, when you select MTO on a product, this is a route that is chosen. As in the basic configuration, it is defined on the product. (it is shown in the product form in a special widget that shows all the possible elements it could have in the one2many and you can select them) As such, this route will be chosen over the standard route and will have a rule that puts procure method "Create Procurement on Source" to stock. In the route MTO all such rules for all warehouses will be put in the standard configuration.
The reason behind such a configuration is that in most situations, the routes followed through the warehouse are the same for almost all products. The exceptions on it can be defined for certain product categories or products. Some things like MTO or buy/manufacture might be better to put on product level. And then it is still possible that you change your mind on the sales order line.
For the inter-warehouse configurations, there is also a possibility to put a warehouse on a procurement rule. These rules will only be applied if the warehouse on the procurement is the same.
================================================
How does the system choose the correct push rule
================================================
Searching for a push rule is quite similar as for the pull rule. It will however just search for the routes in the product and product category, then on those of the warehouse passed to the move or of the picking type of the move and then it will search anywhere.
Searching for a push rule is quite similar as for the pull rule. It will however just search for the routes in the product and product category, then on those of the warehouse passed to the move or of the picking type of the move and then it will search a rule that is not in a route.
=======================
@ -415,7 +416,9 @@ Packages and lots
Products can be put in a package and a package can be put in another package. The same hierarchical system is used as is the case for the locations. When pack A is put in pack B, its full name becomes PACK B / PACK A.
Lots are always linked to a certain product and can be put as being required depending on the incoming/outgoing/full traceability selected on the product. If a warehouse operator selects no lot (which you can only do if traceability is disabled), it can take any lot or without lot. If he selects a lot, he has to take it.
Lots are always linked to a certain product and can be put as being required depending on the incoming/outgoing/full traceability selected on the product. If a warehouse operator selects no lot (which you can only do if traceability is disabled), it can take any lot or without lot. If he selects a lot, he has to take it.
In a picking, lots are defined on the pack operations and not on the moves. This also means there is no virtual quantity of lots. What is possible is reserving some lots and then you could see how much you have left of them. (e.g. by looking in the Quants view which are reserved and which not)
=============================
Packaging and logistic units
@ -441,9 +444,9 @@ This is the model used by the bar code interface. There are actually 2 types of
Preparing pack operations
=========================
If a picking will be processed by the bar code scanner, OpenERP will propose the pack operations that need to be executed. If it is an incoming shipment, it will be based on the moves, otherwise it will use the stock that has been reserved already.
If a picking will be processed by the bar code scanner, Odoo will propose the pack operations that need to be executed. If it is an incoming shipment, it will be based on the moves, otherwise it will use the stock that has been reserved already.
Before creating the actual pack operations, OpenERP will group the moves or reserved stock (quants) by:
Before creating the actual pack operations, Odoo will group the moves or reserved stock (quants) by:
* Lot: lot of the quant or empty if from stock move
* Product: product of the quant or stock move
@ -453,7 +456,7 @@ Before creating the actual pack operations, OpenERP will group the moves or rese
The putway strategies are similar to the removal strategies, but determine for the original destination location a child location where the goods should be deposited (instead as for the source location). By default, there is no putaway strategy defined on the destination location. In that case, the goods will be deposited in the destination location of the move. In the stock module, there is one putaway strategy: fixed location. For each such strategy you can also specify the related location. Of course, based on this, custom developments make it possible to implement the putaway strategy you want (as it is applied on all of the stock being moved at once).
For the reserved stock, OpenERP will try to find as many packages (and as high-level) as possible for which the stock is entirely reserved and the destination location is the same for every piece of stock. That way, the operator knows he can simply move the package to the destination location, instead of having to open the box and split the quantities.
For the reserved stock (which also means it is determined which pieces of stock), Odoo will try to find as many packages (and as high-level) as possible for which the stock is entirely reserved and the destination location is the same for every piece of stock. That way, the operator knows he can simply move the package to the destination location, instead of having to open the box unnecessarily.
An example might illustrate this further:
@ -465,7 +468,7 @@ Unreserving
============
If we want to use a certain piece of stock on another picking instead of the picking selected, we can unreserve this piece of stock by clicking on the Unreserve button of the picking.
It is however possible that during the pack operations, the warehouse operator has chosen the stock from another location. In that case, other quants need to be reserved also. When processing this picking further on, the system will unreserve the stock and do the reserve process again, taking into account the created pack operations from the bar code scanner interface.
It is however possible that during the pack operations, the warehouse operator has chosen the stock from another location. In that case, other quants need to be reserved also. When processing this picking further on, the system will unreserve the stock and do the reserve process again, taking into account the created pack operations from the bar code scanner interface.
===============================================
@ -489,8 +492,8 @@ When using the bar code interface, the pack operations will be prepared as expla
- If everything has been done and the operator took the correct products, it will also finish the picking.
If this is not the case, he can do "Create backorder", and then he needs to check if all the products have been done or not. If only part has been done, OpenERP needs to create a backorder for it. It is however more complicated than that. The operator could have chosen other source/destination location or even create new pack operations with new products.
In order to manage all these possible changes, in the background, OpenERP is going to do a matching between the pack operations executed by the warehouse operator and the moves given as assignment beforehand.
It is also possible that the operator chooses other stock than was reserved at forehand. In that case, OpenERP will need to redo the reservation of the stock.
In order to manage all these possible changes, in the background, Odoo is going to do a matching between the pack operations executed by the warehouse operator and the moves given as assignment beforehand.
It is also possible that the operator chooses other stock than was reserved at forehand. In that case, Odoo will need to redo the reservation of the stock.
The matching of the pack operations and stock moves will determine if extra moves need to be created or if some moves need to go (partially) into backorder.
@ -536,17 +539,26 @@ Cancellation
When you cancel a procurement, it will cancel everything in the backwards direction. When you cancel a move itself, it will cancel in the forward direction.
This will happen only if the move has the attribute 'Propagate Cancel and Split' set to true. Also, when a procurement rule (or a push rule) is applied to create another move, it will copy its 'Propagate Cancel and Split' on the move. On the procurement rules, it is actually true by default.
This will happen only if the move has the attribute 'Propagate Cancel and Split' set to true. Also, when a procurement rule (or a push rule) is applied to create another move, it will copy its 'Propagate Cancel and Split' on the move. On the procurement rules, it is actually true by default. This also works for the purchase orders.
=============================
Procurement group propagation
=============================
A procurement group can be fixed on a rule, can be propagated (default = propagate) or can be none. The advantage of putting a fixed procurement group on the rule is that you could for example put all the orders for your picking in one giant picking. That way, you take all the orders to the picking table and over there you could do the individual pickings for every customer.
A procurement group can be put on a reordering rule also, which will put it on the generated procurement.
This is not something which is propagated to the purchase / manufacturing order.
8 Inventory
***********
When you start using OpenERP, you might have an inventory to start from. (Starting Inventory) You will enter all the products that are in the warehouse and OpenERP will put them in this position. When you validate this inventory, OpenERP will create the necessary stock moves that will go from Inventory Loss to these locations.
When you start using Odoo, you might have an inventory to start from. (Starting Inventory) You will enter all the products that are in the warehouse and Odoo will put them in this position. When you validate this inventory, Odoo will create the necessary stock moves that will go from Inventory Loss to these locations.
It is possible that operations in the warehouse are not well registered and the stock in OpenERP does not correspond exactly to the physical stock in the warehouse. Of course, you do not want this to happen, but errors do happen and a way to solve these mistakes, is to check the inventory once and a while. Most companies will do an entire inventory yearly.
It is possible that operations in the warehouse are not well registered and the stock in Odoo does not correspond exactly to the physical stock in the warehouse. Of course, you do not want this to happen, but errors do happen and a way to solve these mistakes, is to check the inventory once and a while. Most companies will do an entire inventory yearly.
You can decide to do a certain product or a certain location. So, you are not required to do all the inventory at once. In a next step OpenERP will propose all the current stock in the system. When you correct this stock, OpenERP will create the necessary moves in a second tab. The inventory is done, when these moves are all transferred.
You can decide to do a certain product or a certain location. So, you are not required to do all the inventory at once. In a next step Odoo will propose all the current stock in the system. When you correct this stock, Odoo will create the necessary moves in a second tab. The inventory is done, when these moves are all transferred.

View File

@ -303,7 +303,7 @@ class procurement_order(osv.osv):
return {}
def _get_orderpoint_date_planned(self, cr, uid, orderpoint, start_date, context=None):
date_planned = start_date
date_planned = start_date + relativedelta(days=orderpoint.product_id.seller_delay or 0.0)
return date_planned.strftime(DEFAULT_SERVER_DATE_FORMAT)
def _prepare_orderpoint_procurement(self, cr, uid, orderpoint, product_qty, context=None):

View File

@ -1787,7 +1787,7 @@ class stock_move(osv.osv):
self.write(cr, uid, [move.id], {'state': 'confirmed'}, context=context)
def _prepare_procurement_from_move(self, cr, uid, move, context=None):
origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_id.name or "/")
origin = (move.group_id and (move.group_id.name + ":") or "") + (move.rule_id and move.rule_id.name or move.origin or "/")
group_id = move.group_id and move.group_id.id or False
if move.rule_id:
if move.rule_id.group_propagation_option == 'fixed' and move.rule_id.group_id:

View File

@ -18,9 +18,3 @@
name: Gate B
usage: internal
location_id: location_dispatch_zone
-
!record {model: stock.location.path, id: push_pick}:
name: Pick List
location_from_id: stock.stock_location_output
location_dest_id: location_pack_zone
picking_type_id: stock.picking_type_internal

View File

@ -645,7 +645,8 @@
<form string="Transfer">
<header>
<button name="action_confirm" states="draft" string="Mark as Todo" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="action_assign" states="confirmed,partially_available" string="Check Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="action_assign" states="confirmed" string="Check Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="rereserve_pick" states="partially_available" string="Recheck Availability" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="force_assign" states="confirmed,waiting,partially_available" string="Force Availability" type="object" groups="base.group_user"/>
<button name="do_enter_transfer_details" states="assigned,partially_available" string="Transfer" groups="stock.group_stock_user" type="object" class="oe_highlight"/>
<button name="do_print_picking" string="Print Picking List" groups="stock.group_stock_user" type="object" attrs="{'invisible': ['|', ('picking_type_code', '=', 'outgoing'), ('state', '!=', 'assigned')]}"/>
@ -1065,7 +1066,7 @@
<group>
<group>
<field name="product_id" on_change="onchange_product_id(product_id,location_id,location_dest_id, parent.partner_id)"/>
<field name="procure_method" groups="stock.group_adv_location"/>
<field name="procure_method" attrs="{'readonly': [('state', '!=', 'draft')]}" groups="stock.group_adv_location"/>
<field name="picking_type_id" invisible="1"/>
<label for="product_uom_qty"/>
<div>