From 7ed1c1a0743d70f576e519df540f97def5753f9e Mon Sep 17 00:00:00 2001 From: "Adrien Widart (awt)" Date: Thu, 7 Sep 2023 09:11:21 +0000 Subject: [PATCH] [FIX] mrp: prevent from merging other products' SM To reproduce the issue: 1. In Settings, enable: - Multi-Routes 2. Unarchive the route MTO 3. Create three storable product P1, P2, P3: - P2: - With route MTO 4. Create and confirm a MO: - Product: P1 - Components: - 1 x P2 - 1 x P3 - 1 x P3 5. Set the produced/consumed quantities: - For P2, set 1.5 6. Mark the MO as done Error: an error message is displayed: "Record does not exist or has been deleted." In `SM._action_done`, we create some extra moves: https://github.com/odoo/odoo/blob/e029abe649573350e633999e42ab040c57b8fe4e/addons/stock/models/stock_move.py#L1705-L1710 Because of the exceed quantity on the first components line, we create a new SM (qty 0.5). There is a difference between both SM: the `procure_method` (MTO for the initial SM, MTS for the new one). Because of that difference, when confirming the new SM, we don't provide any `merge_into` (the `else` block): https://github.com/odoo/odoo/blob/e029abe649573350e633999e42ab040c57b8fe4e/addons/stock/models/stock_move.py#L1684-L1690 Confirming the new SM leads to the `_merge_moves` method. In this method, because we didn't provide any `merge_into`, we first try to get some candidates: https://github.com/odoo/odoo/blob/e029abe649573350e633999e42ab040c57b8fe4e/addons/stock/models/stock_move.py#L866-L868 And at that point, we will provide with all components SMs: https://github.com/odoo/odoo/blob/e029abe649573350e633999e42ab040c57b8fe4e/addons/mrp/models/stock_move.py#L494-L497 So, we will also provide the two SM of C02. Therefore, the method will merge these SMs and unlink the second one. Then, back to the extra moves creation in `SM._action_done`, the for loop will iterate on the deleted record, hence the error. OPW-3454899 closes odoo/odoo#135305 X-original-commit: 2674851f66253efea19b86eb9d428dd6e85fa06d Signed-off-by: William Henrotin (whe) Signed-off-by: Adrien Widart (awt) --- addons/mrp/models/stock_move.py | 4 +-- addons/mrp/tests/test_order.py | 44 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/addons/mrp/models/stock_move.py b/addons/mrp/models/stock_move.py index 68d590e9a67..3bd6a83c9b6 100644 --- a/addons/mrp/models/stock_move.py +++ b/addons/mrp/models/stock_move.py @@ -551,9 +551,9 @@ class StockMove(models.Model): def _update_candidate_moves_list(self, candidate_moves_set): super()._update_candidate_moves_list(candidate_moves_set) for production in self.mapped('raw_material_production_id'): - candidate_moves_set.add(production.move_raw_ids) + candidate_moves_set.add(production.move_raw_ids.filtered(lambda m: m.product_id in self.product_id)) for production in self.mapped('production_id'): - candidate_moves_set.add(production.move_finished_ids) + candidate_moves_set.add(production.move_finished_ids.filtered(lambda m: m.product_id in self.product_id)) # this will include sibling pickings as a result of merging MOs for picking in self.move_dest_ids.raw_material_production_id.picking_ids: candidate_moves_set.add(picking.move_ids) diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py index aebf2758c5f..ef0f71a0f51 100644 --- a/addons/mrp/tests/test_order.py +++ b/addons/mrp/tests/test_order.py @@ -3303,3 +3303,47 @@ class TestMrpOrder(TestMrpCommon): consumption.action_set_qty() self.assertEqual(mo.move_raw_ids[0].product_uom_qty, 30) self.assertEqual(mo.move_raw_ids[0].quantity_done, 30) + + def test_exceeded_consumed_qty_and_duplicated_lines(self): + """ + Two components C01, C02. C01 has the MTO route. + MO with 1 x C01, 1 x C02, 1 x C02. + Process the MO and set a high consumed qty for C01. + Ensure that the MO can still be processed and that the consumed quantities + are correct. + """ + warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1) + mto_route = warehouse.mto_pull_id.route_id + manufacture_route = warehouse.manufacture_pull_id.route_id + mto_route.active = True + + product01, product02, product03 = self.env['product.product'].create([{ + 'name': 'Product %s' % (i + 1), + 'type': 'product', + } for i in range(3)]) + + product02.route_ids = [(6, 0, (mto_route | manufacture_route).ids)] + + mo_form = Form(self.env['mrp.production']) + mo_form.product_id = product01 + mo_form.product_qty = 1 + for component in (product02, product03, product03): + with mo_form.move_raw_ids.new() as line: + line.product_id = component + line.product_uom_qty = 1 + mo = mo_form.save() + mo.action_confirm() + + mo_form = Form(mo) + mo_form.qty_producing = 1.0 + mo = mo_form.save() + + mo.move_raw_ids[0].move_line_ids.qty_done = 1.5 + mo.button_mark_done() + + self.assertEqual(mo.state, 'done') + + p02_raws = mo.move_raw_ids.filtered(lambda m: m.product_id == product02) + p03_raws = mo.move_raw_ids.filtered(lambda m: m.product_id == product03) + self.assertEqual(sum(p02_raws.mapped('quantity_done')), 1.5) + self.assertEqual(sum(p03_raws.mapped('quantity_done')), 2)