From 92a049ecff41c036f61fbbd6d28a56a34a6f6301 Mon Sep 17 00:00:00 2001 From: Adrien Widart Date: Tue, 22 Feb 2022 09:37:29 +0000 Subject: [PATCH] [FIX] mrp: find child/source MO When a MO is generated in order to supply another one, the smart buttons "Source MO"/"Child MO" are still invisible. To reproduce the issue: 1. In Settings, enable "Multi-Step Routes" 2. Enable MTO route 3. Edit the warehouse: 3-steps manufacturing 4. Create 3 stored products Super, Sub and Compo: - Sub has two routes: MTO and Manufacture 5. Create two BoMs: - For 1 x Super: - 1 x Sub - For 1 x Sub: - 1 x Compo 6. Create and confirm a MO with 1 x Super Error: On confirmation, a second MO is created to produce one Sub, which is correct. However, neither the first MO nor the generated one has a link with the other one (through the smart button "Source MO"/"Child MO") When confirming the MO, a SM is created to bring one Sub to the pre-production location. Thanks to MTO route, this will generate another SM that brings one Sub from post-production to stock location (this will lead to the creation of the second MO). However, the other SM (1 x Sub from Post to Stock) doesn't have the same procurement group. This is the reason why it is not found by the `_compute` methods. OPW-2730830 closes odoo/odoo#94334 X-original-commit: 81e5ce05f2f79eb19489f00fb867143189fb38df Signed-off-by: Arnold Moyaux (arm) Signed-off-by: Adrien Widart --- addons/mrp/models/mrp_production.py | 26 ++++++++++--- addons/mrp/tests/test_order.py | 57 +++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py index d7b21c7f843..eea3e743663 100644 --- a/addons/mrp/models/mrp_production.py +++ b/addons/mrp/models/mrp_production.py @@ -247,15 +247,17 @@ class MrpProduction(models.Model): ('qty_positive', 'check (product_qty > 0)', 'The quantity to produce must be positive!'), ] - @api.depends('procurement_group_id.stock_move_ids.created_production_id.procurement_group_id.mrp_production_ids') + @api.depends('procurement_group_id.stock_move_ids.created_production_id.procurement_group_id.mrp_production_ids', + 'procurement_group_id.stock_move_ids.move_orig_ids.created_production_id.procurement_group_id.mrp_production_ids') def _compute_mrp_production_child_count(self): for production in self: - production.mrp_production_child_count = len(production.procurement_group_id.stock_move_ids.created_production_id.procurement_group_id.mrp_production_ids - production) + production.mrp_production_child_count = len(production._get_children()) - @api.depends('move_dest_ids.group_id.mrp_production_ids') + @api.depends('procurement_group_id.mrp_production_ids.move_dest_ids.group_id.mrp_production_ids', + 'procurement_group_id.stock_move_ids.move_dest_ids.group_id.mrp_production_ids') def _compute_mrp_production_source_count(self): for production in self: - production.mrp_production_source_count = len(production.procurement_group_id.mrp_production_ids.move_dest_ids.group_id.mrp_production_ids - production) + production.mrp_production_source_count = len(production._get_sources()) @api.depends('procurement_group_id.mrp_production_ids') def _compute_mrp_production_backorder(self): @@ -1125,9 +1127,21 @@ class MrpProduction(models.Model): self.workorder_ids.filtered(lambda w: w.state not in ['done', 'cancel'])._action_confirm() + def _get_children(self): + self.ensure_one() + procurement_moves = self.procurement_group_id.stock_move_ids + child_moves = procurement_moves.move_orig_ids + return (procurement_moves | child_moves).created_production_id.procurement_group_id.mrp_production_ids - self + + def _get_sources(self): + self.ensure_one() + dest_moves = self.procurement_group_id.mrp_production_ids.move_dest_ids + parent_moves = self.procurement_group_id.stock_move_ids.move_dest_ids + return (dest_moves | parent_moves).group_id.mrp_production_ids - self + def action_view_mrp_production_childs(self): self.ensure_one() - mrp_production_ids = self.procurement_group_id.stock_move_ids.created_production_id.procurement_group_id.mrp_production_ids.ids + mrp_production_ids = self._get_children().ids action = { 'res_model': 'mrp.production', 'type': 'ir.actions.act_window', @@ -1147,7 +1161,7 @@ class MrpProduction(models.Model): def action_view_mrp_production_sources(self): self.ensure_one() - mrp_production_ids = self.procurement_group_id.mrp_production_ids.move_dest_ids.group_id.mrp_production_ids.ids + mrp_production_ids = self._get_sources().ids action = { 'res_model': 'mrp.production', 'type': 'ir.actions.act_window', diff --git a/addons/mrp/tests/test_order.py b/addons/mrp/tests/test_order.py index c9f9160b658..686cb799a56 100644 --- a/addons/mrp/tests/test_order.py +++ b/addons/mrp/tests/test_order.py @@ -2802,3 +2802,60 @@ class TestMrpOrder(TestMrpCommon): wizard.change_prod_qty() self.assertFalse(mo.move_raw_ids.move_line_ids) + + def test_source_and_child_mo(self): + """ + Suppose three manufactured products A, B and C. C is a component of B + and B is a component of A. If B and C have the routes MTO + Manufacture, + when producing one A, it should generate a MO for B and C. Moreover, + starting from one of the MOs, we should be able to find the source/child + MO. + (The test checks the flow in 1-step, 2-steps and 3-steps manufacturing) + """ + 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 + + grandparent, parent, child = self.env['product.product'].create([{ + 'name': n, + 'type': 'product', + 'route_ids': [(6, 0, mto_route.ids + manufacture_route.ids)], + } for n in ['grandparent', 'parent', 'child']]) + component = self.env['product.product'].create({ + 'name': 'component', + 'type': 'consu', + }) + + self.env['mrp.bom'].create([{ + 'product_tmpl_id': finished_product.product_tmpl_id.id, + 'product_qty': 1, + 'type': 'normal', + 'bom_line_ids': [ + (0, 0, {'product_id': compo.id, 'product_qty': 1}), + ], + } for finished_product, compo in [(grandparent, parent), (parent, child), (child, component)]]) + + none_production = self.env['mrp.production'] + for steps, case_description, in [('mrp_one_step', '1-step Manufacturing'), ('pbm', '2-steps Manufacturing'), ('pbm_sam', '3-steps Manufacturing')]: + warehouse.manufacture_steps = steps + + grandparent_production_form = Form(self.env['mrp.production']) + grandparent_production_form.product_id = grandparent + grandparent_production = grandparent_production_form.save() + grandparent_production.action_confirm() + + child_production, parent_production = self.env['mrp.production'].search([('product_id', 'in', (parent + child).ids)], order='id desc', limit=2) + + for source_mo, mo, product, child_mo in [(none_production, grandparent_production, grandparent, parent_production), + (grandparent_production, parent_production, parent, child_production), + (parent_production, child_production, child, none_production)]: + + self.assertEqual(mo.product_id, product, '[%s] There should be a MO for product %s' % (case_description, product.display_name)) + self.assertEqual(mo.mrp_production_source_count, len(source_mo), '[%s] Incorrect value for product %s' % (case_description, product.display_name)) + self.assertEqual(mo.mrp_production_child_count, len(child_mo), '[%s] Incorrect value for product %s' % (case_description, product.display_name)) + + source_action = mo.action_view_mrp_production_sources() + child_action = mo.action_view_mrp_production_childs() + self.assertEqual(source_action.get('res_id', False), source_mo.id, '[%s] Incorrect value for product %s' % (case_description, product.display_name)) + self.assertEqual(child_action.get('res_id', False), child_mo.id, '[%s] Incorrect value for product %s' % (case_description, product.display_name))