From 31f813af54ee483f2c6ea0858d9a69663f78f29e Mon Sep 17 00:00:00 2001 From: Matt Taylor Date: Thu, 17 Oct 2024 15:11:30 -0600 Subject: [PATCH] [FIX] mrp_multi_level: check for variant bom fixes #1366 Using the _bom_find() method, we get the BOM with lowest sequence, whether it's a variant BOM or a template BOM (no product_id). --- mrp_multi_level/models/product_mrp_area.py | 14 ++++-- mrp_multi_level/tests/common.py | 48 ++++++++++++++++++- mrp_multi_level/tests/test_mrp_multi_level.py | 21 +++++--- mrp_multi_level/wizards/mrp_multi_level.py | 9 +--- 4 files changed, 72 insertions(+), 20 deletions(-) diff --git a/mrp_multi_level/models/product_mrp_area.py b/mrp_multi_level/models/product_mrp_area.py index 2df8c77c11..ec1fbe124f 100644 --- a/mrp_multi_level/models/product_mrp_area.py +++ b/mrp_multi_level/models/product_mrp_area.py @@ -85,6 +85,11 @@ class ProductMRPArea(models.Model): ], compute="_compute_supply_method", ) + supply_bom_id = fields.Many2one( + comodel_name="mrp.bom", + string="Supply BoM", + compute="_compute_supply_method", + ) qty_available = fields.Float( string="Quantity Available", compute="_compute_qty_available" ) @@ -198,20 +203,21 @@ def _get_rule(self): return rule def _compute_supply_method(self): + boms_by_product = self.env["mrp.bom"]._bom_find(self.mapped("product_id")) for rec in self: rule = rec._get_rule() if not rule: rec.supply_method = "none" + rec.supply_bom_id = False continue # Determine the supply method based on the final rule. - boms = rec.product_id.product_tmpl_id.bom_ids.filtered( - lambda x: x.type in ["normal", "phantom"] - ) + bom = boms_by_product.get(rec.product_id, self.env["mrp.bom"]) rec.supply_method = ( "phantom" - if rule.action == "manufacture" and boms and boms[0].type == "phantom" + if rule.action == "manufacture" and bom.type == "phantom" else rule.action ) + rec.supply_bom_id = bom @api.depends( "mrp_area_id", "supply_method", "product_id.route_ids", "product_id.seller_ids" diff --git a/mrp_multi_level/tests/common.py b/mrp_multi_level/tests/common.py index b5e68763bd..91a0f9ece3 100644 --- a/mrp_multi_level/tests/common.py +++ b/mrp_multi_level/tests/common.py @@ -27,6 +27,7 @@ def setUpClass(cls): cls.mrp_move_obj = cls.env["mrp.move"] cls.planned_order_obj = cls.env["mrp.planned.order"] cls.lot_obj = cls.env["stock.lot"] + cls.mrp_bom_obj = cls.env["mrp.bom"] cls.fp_1 = cls.env.ref("mrp_multi_level.product_product_fp_1") cls.fp_2 = cls.env.ref("mrp_multi_level.product_product_fp_2") @@ -40,6 +41,7 @@ def setUpClass(cls): cls.pp_3 = cls.env.ref("mrp_multi_level.product_product_pp_3") cls.pp_4 = cls.env.ref("mrp_multi_level.product_product_pp_4") cls.product_4b = cls.env.ref("product.product_product_4b") + cls.product_4c = cls.env.ref("product.product_product_4c") cls.av_11 = cls.env.ref("mrp_multi_level.product_product_av_11") cls.av_12 = cls.env.ref("mrp_multi_level.product_product_av_12") cls.av_21 = cls.env.ref("mrp_multi_level.product_product_av_21") @@ -302,7 +304,38 @@ def setUpClass(cls): cls.product_scenario_1, 18, dt_next_group, location=cls.cases_loc ) - # Create test picking for FP-1, FP-2 and Desk(steel, black): + # product_4b will use the template bom (sequence 5) + # (11, 22) = ("steel", "black") + # create variant bom for product_4c (sequence 1) + # (12, 21) = ("aluminum", "white") + cls.mrp_bom_obj.create( + { + "product_tmpl_id": cls.product_4c.product_tmpl_id.id, + "product_id": cls.product_4c.id, + "type": "normal", + "sequence": 1, + "bom_line_ids": [ + ( + 0, + 0, + { + "product_id": cls.av_12.id, + "product_qty": 1.0, + }, + ), + ( + 0, + 0, + { + "product_id": cls.av_21.id, + "product_qty": 1.0, + }, + ), + ], + } + ) + + # Create test picking for FP-1, FP-2, Desk(steel, black), Desk(aluminum, white) res = cls.calendar.plan_days(7 + 1, datetime.today().replace(hour=0)) date_move = res.date() cls.picking_1 = cls.stock_picking_obj.create( @@ -364,6 +397,19 @@ def setUpClass(cls): "location_dest_id": cls.customer_location.id, }, ), + ( + 0, + 0, + { + "name": "Test move product-4c", + "product_id": cls.product_4c.id, + "date": date_move, + "product_uom": cls.product_4c.uom_id.id, + "product_uom_qty": 56, + "location_id": cls.stock_location.id, + "location_dest_id": cls.customer_location.id, + }, + ), ], } ) diff --git a/mrp_multi_level/tests/test_mrp_multi_level.py b/mrp_multi_level/tests/test_mrp_multi_level.py index a7eee08a3f..8db2b70466 100644 --- a/mrp_multi_level/tests/test_mrp_multi_level.py +++ b/mrp_multi_level/tests/test_mrp_multi_level.py @@ -321,25 +321,32 @@ def test_12_bom_line_attribute_value_skip(self): [("product_mrp_area_id.product_id", "=", self.product_4b.id)] ) self.assertTrue(product_4b_demand) - self.assertTrue(product_4b_demand.to_procure) - # No demand or supply for AV-12 or AV-21 + self.assertEqual(product_4b_demand.to_procure, 100) + product_4c_demand = self.mrp_inventory_obj.search( + [("product_mrp_area_id.product_id", "=", self.product_4c.id)] + ) + self.assertTrue(product_4c_demand) + self.assertEqual(product_4c_demand.to_procure, 1) + # Testing variant BoM + # Supply of one unit for AV-12 or AV-21 av_12_supply = self.mrp_inventory_obj.search( [("product_mrp_area_id.product_id", "=", self.av_12.id)] ) - self.assertFalse(av_12_supply) + self.assertEqual(av_12_supply.to_procure, 1.0) av_21_supply = self.mrp_inventory_obj.search( [("product_mrp_area_id.product_id", "=", self.av_21.id)] ) - self.assertFalse(av_21_supply) - # Supply for AV-11 and AV-22 + self.assertEqual(av_21_supply.to_procure, 1.0) + # Testing template BoM + # Supply of 150 units for AV-11 and AV-22 av_11_supply = self.mrp_inventory_obj.search( [("product_mrp_area_id.product_id", "=", self.av_11.id)] ) - self.assertTrue(av_11_supply) + self.assertEqual(av_11_supply.to_procure, 100.0) av_22_supply = self.mrp_inventory_obj.search( [("product_mrp_area_id.product_id", "=", self.av_22.id)] ) - self.assertTrue(av_22_supply) + self.assertTrue(av_22_supply.to_procure, 100.0) def test_13_timezone_handling(self): self.calendar.tz = "Australia/Sydney" # Oct-Apr/Apr-Oct: UTC+11/UTC+10 diff --git a/mrp_multi_level/wizards/mrp_multi_level.py b/mrp_multi_level/wizards/mrp_multi_level.py index 06aaf2fd9a..8f1eb279a8 100644 --- a/mrp_multi_level/wizards/mrp_multi_level.py +++ b/mrp_multi_level/wizards/mrp_multi_level.py @@ -179,14 +179,7 @@ def _get_action_and_supply_dates(self, product_mrp_area, mrp_date): @api.model def _get_bom_to_explode(self, product_mrp_area_id): - boms = self.env["mrp.bom"] - if product_mrp_area_id.supply_method in ["manufacture", "phantom"]: - boms = product_mrp_area_id.product_id.bom_ids.filtered( - lambda x: x.type in ["normal", "phantom"] - ) - if not boms: - return False - return boms[0] + return product_mrp_area_id.supply_bom_id @api.model def explode_action(