From 2a5e079ef722052c2cdb5e6e59fe9c2a4604edac Mon Sep 17 00:00:00 2001 From: "Touati Djamel (otd)" Date: Wed, 3 May 2023 14:07:59 +0000 Subject: [PATCH] [FIX] mrp: unbuild a manufacturing order with a tracked product MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Steps to reproduce the bug: - Create a storable product “P1”: - Tracked by SN - BoM: - Component: 1 unit of “C1” - Create a Mo to produce one unit of “P1”: - Confirm the MO - Set a new serial number “SN1” and qty done for “C1” - Mark as done - Click on “unbuild” button Problem: An user error is triggered: “This lot ‘SN1’ is incompatible with this product ‘C1’ When you click on the "Unbuild" button, the `lot_id` of the finished product is added in the context with the key `'default_lot_id'`: https://github.com/odoo/odoo/blob/dd60647ce41547ecd00bbde45ddf564a7ada91c7/addons/mrp/models/mrp_production.py#L1977 Therefore, when creating the `stock.move.line` for the component, even if the “lot_id” is not in the vals, we will get the “lot_id” from the context: https://github.com/odoo/odoo/blob/d04a5b5c8c7dc13e4e911a29d1944e90587e2883/odoo/models.py#L4136 https://github.com/odoo/odoo/blob/d04a5b5c8c7dc13e4e911a29d1944e90587e2883/odoo/models.py#L1961 Then, we do a field validation via a constraint, but as the product "C1" is not compatible with the product in lot "P1", an error is triggered: https://github.com/odoo/odoo/blob/b9334c53b84228c00ab100ffd28620b3c923c4e6/addons/stock/models/stock_move_line.py#L94-L95 opw-3284525 closes odoo/odoo#120981 X-original-commit: 2ee59a523e12b79c1338467bcf7577a1c9b63d11 Signed-off-by: Tiffany Chang --- addons/mrp/models/mrp_unbuild.py | 2 +- addons/mrp/tests/test_unbuild.py | 79 ++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/addons/mrp/models/mrp_unbuild.py b/addons/mrp/models/mrp_unbuild.py index 494c398d27b..85f92ed8a24 100644 --- a/addons/mrp/models/mrp_unbuild.py +++ b/addons/mrp/models/mrp_unbuild.py @@ -143,7 +143,7 @@ class MrpUnbuild(models.Model): consume_moves = self._generate_consume_moves() consume_moves._action_confirm() produce_moves = self._generate_produce_moves() - produce_moves._action_confirm() + produce_moves.with_context(default_lot_id=False)._action_confirm() finished_moves = consume_moves.filtered(lambda m: m.product_id == self.product_id) consume_moves -= finished_moves diff --git a/addons/mrp/tests/test_unbuild.py b/addons/mrp/tests/test_unbuild.py index a8e6aa19047..58e074d784b 100644 --- a/addons/mrp/tests/test_unbuild.py +++ b/addons/mrp/tests/test_unbuild.py @@ -821,3 +821,82 @@ class TestUnbuild(TestMrpCommon): mo2 = mo_form.save() mo2.button_mark_done() self.assertEqual(mo2.state, 'done', "Production order should be in done state.") + + def test_unbuild_mo_with_tracked_product_and_component(self): + """ + Test that the unbuild order is correctly created when the finished product + and the component is tracked by serial number + """ + finished_product = self.env['product.product'].create({ + 'name': 'Product tracked by sn', + 'type': 'product', + 'tracking': 'serial', + }) + finished_product_sn = self.env['stock.lot'].create({ + 'name': 'sn1', + 'product_id': finished_product.id, + 'company_id': self.env.company.id + }) + component = self.env['product.product'].create({ + 'name': 'Product component', + 'type': 'product', + }) + bom_1 = self.env['mrp.bom'].create({ + 'product_id': finished_product.id, + 'product_tmpl_id': finished_product.product_tmpl_id.id, + 'product_uom_id': self.env.ref('uom.product_uom_unit').id, + 'product_qty': 1.0, + 'type': 'normal', + 'bom_line_ids': [ + (0, 0, {'product_id': component.id, 'product_qty': 1}), + ], + }) + # mo_1 + mo_form = Form(self.env['mrp.production']) + mo_form.product_id = finished_product + mo_form.bom_id = bom_1 + mo_form.product_qty = 1.0 + mo = mo_form.save() + mo.action_confirm() + mo.qty_producing = 1.0 + mo.lot_producing_id = finished_product_sn + mo.move_raw_ids.quantity_done = 1 + mo.button_mark_done() + self.assertEqual(mo.state, 'done', "Production order should be in done state.") + # unbuild order mo_1 + action = mo.button_unbuild() + wizard = Form(self.env[action['res_model']].with_context(action['context'])).save() + wizard.action_validate() + self.assertEqual(mo.unbuild_ids.produce_line_ids[0].product_id, finished_product) + self.assertEqual(mo.unbuild_ids.produce_line_ids[0].lot_ids, finished_product_sn) + self.assertEqual(mo.unbuild_ids.produce_line_ids[1].product_id, component) + self.assertEqual(mo.unbuild_ids.produce_line_ids[1].lot_ids.id, False) + + # set the component as tracked + component.tracking = 'serial' + component_sn = self.env['stock.lot'].create({ + 'name': 'component-sn1', + 'product_id': component.id, + 'company_id': self.env.company.id + }) + self.env['stock.quant']._update_available_quantity(component, self.stock_location, 1, lot_id=component_sn) + #mo2 with tracked component + mo_form = Form(self.env['mrp.production']) + mo_form.product_id = finished_product + mo_form.bom_id = bom_1 + mo_form.product_qty = 1.0 + mo_2 = mo_form.save() + mo_2.action_confirm() + mo_2.qty_producing = 1.0 + mo_2.lot_producing_id = finished_product_sn + mo_2.move_raw_ids.quantity_done = 1 + mo_2.button_mark_done() + self.assertEqual(mo_2.state, 'done', "Production order should be in done state.") + # unbuild mo_2 + action = mo_2.button_unbuild() + wizard = Form(self.env[action['res_model']].with_context(action['context'])).save() + wizard.action_validate() + self.assertEqual(mo_2.unbuild_ids.produce_line_ids[0].product_id, finished_product) + self.assertEqual(mo_2.unbuild_ids.produce_line_ids[0].lot_ids, finished_product_sn) + self.assertEqual(mo_2.unbuild_ids.produce_line_ids[1].product_id, component) + self.assertEqual(mo_2.unbuild_ids.produce_line_ids[1].lot_ids, component_sn)