From 09f71c0809c689218b23dbfaa8ffd68f6ec2760c Mon Sep 17 00:00:00 2001 From: "Adrien Widart (awt)" Date: Thu, 5 Jan 2023 12:22:30 +0000 Subject: [PATCH] [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) Signed-off-by: Adrien Widart --- addons/mrp/tests/test_byproduct.py | 24 +++++++++++++++++++++--- addons/stock/models/stock_move_line.py | 3 ++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/addons/mrp/tests/test_byproduct.py b/addons/mrp/tests/test_byproduct.py index 8fce120b8f9..5564bb8c6bb 100644 --- a/addons/mrp/tests/test_byproduct.py +++ b/addons/mrp/tests/test_byproduct.py @@ -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) diff --git a/addons/stock/models/stock_move_line.py b/addons/stock/models/stock_move_line.py index 3f1b9d1ec41..7be9f3ccf34 100644 --- a/addons/stock/models/stock_move_line.py +++ b/addons/stock/models/stock_move_line.py @@ -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