From ff81ff233474f8c8ad654834e7a7ca0f68cba50f Mon Sep 17 00:00:00 2001 From: Michael Tietz Date: Thu, 9 Nov 2023 23:21:26 +0100 Subject: [PATCH] [FIX] stock_available_mrp: Use correct bom for computation If a product has siblings and more then one has a bom first(product.bom_ids) could return a bom for a diferent product, because bom_ids is a one2many field of product.template and this contains all boms of variants Therefor switch to the default methods of odoo _bom_find and _get_product2bom --- stock_available_mrp/models/product_product.py | 16 +-- stock_available_mrp/readme/CONTRIBUTORS.rst | 1 + .../tests/test_potential_qty.py | 100 ++++++++++++++++++ 3 files changed, 109 insertions(+), 8 deletions(-) diff --git a/stock_available_mrp/models/product_product.py b/stock_available_mrp/models/product_product.py index d49aea2d8..184b05614 100644 --- a/stock_available_mrp/models/product_product.py +++ b/stock_available_mrp/models/product_product.py @@ -1,4 +1,5 @@ # Copyright 2014 Numérigraphe SARL +# Copyright 2023 Michael Tietz (MT Software) # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). from collections import Counter @@ -50,9 +51,10 @@ class ProductProduct(models.Model): for p in component_products } + boms_by_product = self.env["mrp.bom"]._get_product2bom(self) for product in product_with_bom: # Need by product (same product can be in many BOM lines/levels) - bom_id = first(product.bom_ids) + bom_id = boms_by_product[product] exploded_components = exploded_boms[product.id] component_needs = product._get_components_needs(exploded_components) if not component_needs: @@ -134,13 +136,12 @@ class ProductProduct(models.Model): generate thousands of SELECT for searches. """ result = {} - + BOM = self.env["mrp.bom"] + boms_by_product = BOM._get_product2bom(self) for product in self: + bom = boms_by_product[product] lines_done = [] - bom_lines = [ - (first(product.bom_ids), bom_line, product, 1.0) - for bom_line in first(product.bom_ids).bom_line_ids - ] + bom_lines = [(bom, bom_line, product, 1.0) for bom_line in bom.bom_line_ids] while bom_lines: (current_bom, current_line, current_product, current_qty) = bom_lines[0] @@ -150,8 +151,7 @@ class ProductProduct(models.Model): continue line_quantity = current_qty * current_line.product_qty - - sub_bom = first(current_line.product_id.bom_ids) + sub_bom = BOM._bom_find(product=current_line.product_id) if sub_bom.type == "phantom": product_uom = current_line.product_uom_id converted_line_quantity = product_uom._compute_quantity( diff --git a/stock_available_mrp/readme/CONTRIBUTORS.rst b/stock_available_mrp/readme/CONTRIBUTORS.rst index 50df76d7c..05183925d 100644 --- a/stock_available_mrp/readme/CONTRIBUTORS.rst +++ b/stock_available_mrp/readme/CONTRIBUTORS.rst @@ -9,3 +9,4 @@ * Víctor Martínez * David Vidal +* Michael Tietz (MT Software) diff --git a/stock_available_mrp/tests/test_potential_qty.py b/stock_available_mrp/tests/test_potential_qty.py index 9a6e11ff1..35412f034 100644 --- a/stock_available_mrp/tests/test_potential_qty.py +++ b/stock_available_mrp/tests/test_potential_qty.py @@ -496,3 +496,103 @@ class TestPotentialQty(SavepointCase): product.invalidate_cache() self.assertEqual(product.immediately_usable_qty, 0.0) + + def test_product_siblings(self): + # This test ensures that always the right bom is used + # also for products with siblings that also have a bom + attribute = self.env["product.attribute"].create( + { + "name": "Attribute", + "value_ids": [(0, 0, {"name": "Value1"}), (0, 0, {"name": "Value2"})], + } + ) + product_tmpl = self.env["product.template"].create( + { + "name": "Template", + "attribute_line_ids": [ + ( + 0, + 0, + { + "attribute_id": attribute.id, + "value_ids": [(6, 0, attribute.value_ids.ids)], + }, + ) + ], + } + ) + product1, product2 = ( + product_tmpl.product_variant_ids[0], + product_tmpl.product_variant_ids[1], + ) + child1 = product1.create( + { + "name": "Child1", + "type": "product", + } + ) + child2 = product1.create( + { + "name": "Child2", + "type": "product", + } + ) + child3 = product1.create( + { + "name": "Child3", + "type": "product", + } + ) + self.create_inventory(child2.id, 1) + self.create_inventory(child3.id, 1) + bom1 = self.bom_model.create( + { + "product_tmpl_id": product_tmpl.id, + "product_id": product1.id, + "type": "phantom", + } + ) + self.bom_line_model.create( + { + "bom_id": bom1.id, + "product_id": child1.id, + "product_qty": 1, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + } + ) + self.bom_line_model.create( + { + "bom_id": bom1.id, + "product_id": child2.id, + "product_qty": 1, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + } + ) + + bom2 = self.bom_model.create( + { + "product_tmpl_id": product_tmpl.id, + "product_id": product2.id, + "type": "phantom", + } + ) + self.bom_line_model.create( + { + "bom_id": bom2.id, + "product_id": child2.id, + "product_qty": 1, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + } + ) + self.bom_line_model.create( + { + "bom_id": bom2.id, + "product_id": child3.id, + "product_qty": 1, + "product_uom_id": self.env.ref("uom.product_uom_unit").id, + } + ) + product1.invalidate_cache() + product2.invalidate_cache() + self.assertEqual(product1.immediately_usable_qty, 0) + self.assertEqual(product2.immediately_usable_qty, 1)