[IMP] Change documentation + new action_consume/qty/origin corrections

Simplify the action_consume of the consumption lines after the corrections
by Kevin Wang.  Also the UoMs are revised as the action_consume uses the default UoM
of the product.

We have to avoid circular boms where a child bom should not contain the product that
represents the parent bom, but it is possible for example to use another product of the parent bom in
the child bom.

As the consume line move has no procurement rule, its origin will have no description.  So, when there is
none it will also check the description of the previous move (when passed to procurement for example) This way
the chained moves or purchase order for example will have the MO-number as origin and not nothing.

[IMP] Change assignation

[IMP] UoM changes continuation

[IMP] Make sure we can use 2 times the same product in a BoM

[IMP] Source document for consume lines to procurement
This commit is contained in:
Josse Colpaert 2014-09-22 22:17:15 +02:00
parent 817e459d43
commit 3dcb020373
7 changed files with 101 additions and 75 deletions

View File

@ -279,9 +279,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,7 +318,7 @@ 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)
@ -335,7 +335,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 +345,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
@ -824,6 +824,7 @@ 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")
@ -832,19 +833,27 @@ class mrp_production(osv.osv):
#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.uom_id.id, production.product_qty, production.product_id.uom_id.id) - produced_qty
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
quants_taken = []
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
@ -854,14 +863,11 @@ class mrp_production(osv.osv):
# 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)
total_consume = ((product_qty + produced_qty) * sched_product_qty / production.product_qty)
else:
total_consume = (production.product_qty * scheduled.product_qty / production.product_qty)
total_consume = sched_product_qty
qty = total_consume - consumed_qty
# Convert to UoM of product itself
qty = uom_obj._compute_qty(cr, uid, scheduled.product_uom.id, qty, scheduled.product_id.uom_id.id, round=False)
# Search for quants related to this related move
for move in production.move_lines:
if qty <= 0.0:
@ -870,8 +876,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
@ -900,14 +906,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)
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':
@ -927,7 +934,8 @@ class mrp_production(osv.osv):
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]
@ -938,7 +946,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:

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"/>

View File

@ -152,7 +152,7 @@ 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
@ -176,28 +176,29 @@ class StockMove(osv.osv):
ids2.append(move.id)
toassign_move_ids = []
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
if move_qty <= 0:
raise osv.except_osv(_('Error!'), _('Cannot consume a move with negative or zero quantity.'))
quantity_rest = move_qty - product_qty
if quantity_rest > 0:
ctx = context.copy()
if location_id:
ctx['source_location_id'] = location_id
new_mov = self.split(cr, uid, move, 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)
toassign_move_ids.append(new_mov)
new_mov = self.split(cr, uid, move, quantity_rest, context=context)
if move.location_id.usage == 'internal':
toassign_move_ids.append(new_mov)
res.append(move.id)
vals = {'restrict_lot_id': restrict_lot_id,
'restrict_partner_id': restrict_partner_id,
'consumed_for': consumed_for}
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)
if toassign_move_ids:
self.action_assign(cr, uid, toassign_move_ids, context=context)
production_ids = production_obj.search(cr, uid, [('move_lines', 'in', [move.id])])
production_obj.signal_workflow(cr, uid, production_ids, 'button_produce')
vals.update({'location_id': location_id})
self.write(cr, uid, [move.id], vals, context=context)
self.action_done(cr, uid, res, context=context)
if toassign_move_ids:
self.action_assign(cr, uid, toassign_move_ids, 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

@ -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, round=False)
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

@ -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,14 +223,14 @@ 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.
@ -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:
@ -2155,6 +2155,7 @@ class stock_move(osv.osv):
move = record.move_id
if move.id in main_domain:
domain = main_domain[move.id] + self.pool.get('stock.move.operation.link').get_specific_domain(cr, uid, record, context=context)
qty = record.qty
if qty:
quants = quant_obj.quants_get_prefered_domain(cr, uid, ops.location_id, move.product_id, qty, domain=domain, prefered_domain_list=[], restrict_lot_id=move.restrict_lot_id.id, restrict_partner_id=move.restrict_partner_id.id, context=context)
quant_obj.quants_reserve(cr, uid, quants, move, record, context=context)