[FIX] stock,mrp: redirect byproducts with capacity constraints

When redirecting the SML of byproducts, the capacity constraints of
the locations are not considered.

To reproduce the issue:
1. In Settings, enable:
   - By-Products
   - Storage Locations
   - Storage Categories
2. Create three storable products P01, P02, P_comp
3. Create a storage category SG:
   - Max Weight: 1000
   - Capacity by Products:
     - 2 x P02
4. Create two locations L01, L02:
   - Parent Location: WH/Stock
   - Storage Category: SG
5. Create a putaway rule:
   - From: WH/Stock
   - Product: P02
   - To: WH/Stock
   - Having Category: SG
6. Create a BoM:
   - Product: P01
   - Components: 1 x P_compo
   - By-products: 1 x P02
7. Update on hand qty of P02:
   - 1 x P02 at L01
8. Create and confirm a MO with 2 x P01
9. Set the producing qty to 2
10. Open the detailed operations of P02

Error: The destination location is L01, this would break the
capacity constraint. The location should be L02

When setting the producing qty, we also set the done quantities of
the finished moves and we redirect them thanks to the putaway rules:
https://github.com/odoo/odoo/blob/cc6e31efe6fdd9dee16de1565afe9e1b502de45d/addons/stock/models/stock_move.py#L1885-L1888
However, the SML created only has the `qty_done` defined and, when
applying the putaway rules, we consider the `product_uom_qty` which
is equal to 0 in such a case.

So, when trying to redirect the SML of P02, we check if the capacity
constraint of L01 would be exceeded with the quantity of the SML.
But, as explained above, that quantity will be 0, so we conclude
that the capacity constraint is ok and we return L01 as best solution.

This is the reason why this commit suggests the use of the maximum
between done quantity and reserved quantity. This logic is already
used in the putaway application process, when we check the forecasted
quantity by location:
https://github.com/odoo/odoo/blob/a217ba27600c64c9104f8c1f003afd74ad06bd05/addons/stock/models/stock_location.py#L297-L301

OPW-3100322

closes odoo/odoo#109669

X-original-commit: 4ee8f974f993d2bbea7d9fc00779ebb1c1fa047d
Signed-off-by: William Henrotin (whe) <whe@odoo.com>
Signed-off-by: Adrien Widart <awt@odoo.com>
This commit is contained in:
Adrien Widart (awt)
2023-01-12 08:02:48 +01:00
parent 0149a26c62
commit 09f71c0809
2 changed files with 23 additions and 4 deletions
+21 -3
View File
@@ -271,23 +271,39 @@ class TestMrpByProduct(common.TransactionCase):
)
def test_byproduct_putaway(self):
""" Test the byproducts are dispatched correctly with putaway rules"""
"""
Test the byproducts are dispatched correctly with putaway rules. We have
a byproduct P and two sublocations L01, L02 with a capacity constraint:
max 2 x P by location. There is already 1 x P at L01. Process a MO with
2 x P as byproducts. They should be redirected to L02
"""
self.stock_location = self.env.ref('stock.stock_location_stock')
stor_category = self.env['stock.storage.category'].create({
'name': 'Super Storage Category',
'max_weight': 1000,
'product_capacity_ids': [(0, 0, {
'product_id': self.product_b.id,
'quantity': 2,
})]
})
shelf1_location = self.env['stock.location'].create({
'name': 'shelf1',
'usage': 'internal',
'location_id': self.stock_location.id,
'storage_category_id': stor_category.id,
})
shelf2_location = self.env['stock.location'].create({
'name': 'shelf2',
'usage': 'internal',
'location_id': self.stock_location.id,
'storage_category_id': stor_category.id,
})
self.env['stock.putaway.rule'].create({
'product_id': self.product_b.id,
'location_in_id': self.stock_location.id,
'location_out_id': shelf1_location.id,
'location_out_id': self.stock_location.id,
'storage_category_id': stor_category.id,
})
self.env['stock.putaway.rule'].create({
'product_id': self.product_a.id,
@@ -295,6 +311,8 @@ class TestMrpByProduct(common.TransactionCase):
'location_out_id': shelf2_location.id,
})
self.env['stock.quant']._update_available_quantity(self.product_b, shelf1_location, 1)
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = self.product_a
mo_form.bom_id = self.bom_byproduct
@@ -308,5 +326,5 @@ class TestMrpByProduct(common.TransactionCase):
mo._post_inventory()
byproduct_move_line = mo.move_byproduct_ids.move_line_ids
finished_move_line = mo.move_finished_ids.filtered(lambda m: m.product_id == self.product_a).move_line_ids
self.assertEqual(byproduct_move_line.location_dest_id, shelf1_location)
self.assertEqual(byproduct_move_line.location_dest_id, shelf2_location)
self.assertEqual(finished_move_line.location_dest_id, shelf2_location)
+2 -1
View File
@@ -250,8 +250,9 @@ class StockMoveLine(models.Model):
smls.package_level_id.location_dest_id = smls.location_dest_id
else:
for sml in smls:
qty = max(sml.reserved_uom_qty, sml.qty_done)
sml.location_dest_id = sml.move_id.location_dest_id.with_context(exclude_sml_ids=excluded_smls.ids)._get_putaway_strategy(
sml.product_id, quantity=sml.reserved_uom_qty, packaging=sml.move_id.product_packaging_id,
sml.product_id, quantity=qty, packaging=sml.move_id.product_packaging_id,
)
excluded_smls -= sml