From 5e77d40888c7578a47a194f07b9adef06e4d0ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Tue, 31 Jan 2023 10:32:24 -0300 Subject: [PATCH 1/4] [ADD] mrp_account_bom_attribute_match --- mrp_account_bom_attribute_match/README.rst | 1 + mrp_account_bom_attribute_match/__init__.py | 1 + .../__manifest__.py | 16 +++++++++ .../models/__init__.py | 1 + .../models/product_product.py | 34 +++++++++++++++++++ .../tests/__init__.py | 1 + .../test_mrp_account_bom_attribute_match.py | 23 +++++++++++++ mrp_bom_attribute_match/tests/common.py | 2 +- .../tests/test_mrp_bom_attribute_match.py | 8 ++--- .../addons/mrp_account_bom_attribute_match | 1 + .../mrp_account_bom_attribute_match/setup.py | 6 ++++ 11 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 mrp_account_bom_attribute_match/README.rst create mode 100644 mrp_account_bom_attribute_match/__init__.py create mode 100644 mrp_account_bom_attribute_match/__manifest__.py create mode 100644 mrp_account_bom_attribute_match/models/__init__.py create mode 100644 mrp_account_bom_attribute_match/models/product_product.py create mode 100644 mrp_account_bom_attribute_match/tests/__init__.py create mode 100644 mrp_account_bom_attribute_match/tests/test_mrp_account_bom_attribute_match.py create mode 120000 setup/mrp_account_bom_attribute_match/odoo/addons/mrp_account_bom_attribute_match create mode 100644 setup/mrp_account_bom_attribute_match/setup.py diff --git a/mrp_account_bom_attribute_match/README.rst b/mrp_account_bom_attribute_match/README.rst new file mode 100644 index 000000000..f86ee85e1 --- /dev/null +++ b/mrp_account_bom_attribute_match/README.rst @@ -0,0 +1 @@ +TO BE GENERATED diff --git a/mrp_account_bom_attribute_match/__init__.py b/mrp_account_bom_attribute_match/__init__.py new file mode 100644 index 000000000..0650744f6 --- /dev/null +++ b/mrp_account_bom_attribute_match/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/mrp_account_bom_attribute_match/__manifest__.py b/mrp_account_bom_attribute_match/__manifest__.py new file mode 100644 index 000000000..0171062e6 --- /dev/null +++ b/mrp_account_bom_attribute_match/__manifest__.py @@ -0,0 +1,16 @@ +# Copyright 2023 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +{ + "name": "MRP Account BOM Attribute Match", + "summary": "Glue module between `mrp_account` and `mrp_bom_attribute_match`", + "version": "15.0.1.0.0", + "author": "Camptocamp, Odoo Community Association (OCA)", + "maintainers": ["ivantodorovich"], + "website": "https://github.com/OCA/manufacture", + "license": "AGPL-3", + "category": "Manufacturing", + "depends": ["mrp_account", "mrp_bom_attribute_match"], + "auto_install": True, +} diff --git a/mrp_account_bom_attribute_match/models/__init__.py b/mrp_account_bom_attribute_match/models/__init__.py new file mode 100644 index 000000000..5c74c8c30 --- /dev/null +++ b/mrp_account_bom_attribute_match/models/__init__.py @@ -0,0 +1 @@ +from . import product_product diff --git a/mrp_account_bom_attribute_match/models/product_product.py b/mrp_account_bom_attribute_match/models/product_product.py new file mode 100644 index 000000000..7cb219164 --- /dev/null +++ b/mrp_account_bom_attribute_match/models/product_product.py @@ -0,0 +1,34 @@ +# Copyright 2023 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import Command, models + + +class ProductProduct(models.Model): + _inherit = "product.product" + + def _compute_bom_price(self, bom, boms_to_recompute=False, byproduct_bom=False): + # OVERRIDE to fill in the `line.product_id` if a component template is used. + # To avoid a complete override, we HACK the bom by replacing it with a virtual + # record, and modifying it's lines on-the-fly. + has_template_lines = bom and any( + line.component_template_id for line in bom.bom_line_ids + ) + if has_template_lines: + bom = bom.new(origin=bom) + to_ignore_line_ids = [] + for line in bom.bom_line_ids: + if line._skip_bom_line(self) or not line.component_template_id: + continue + line_product = bom._get_component_template_product( + line, self, line.product_id + ) + if not line_product: + to_ignore_line_ids.append(line.id) + continue + else: + line.product_id = line_product + if to_ignore_line_ids: + bom.bom_line_ids = [Command.unlink(id) for id in to_ignore_line_ids] + return super()._compute_bom_price(bom, boms_to_recompute, byproduct_bom) diff --git a/mrp_account_bom_attribute_match/tests/__init__.py b/mrp_account_bom_attribute_match/tests/__init__.py new file mode 100644 index 000000000..50455c9c1 --- /dev/null +++ b/mrp_account_bom_attribute_match/tests/__init__.py @@ -0,0 +1 @@ +from . import test_mrp_account_bom_attribute_match diff --git a/mrp_account_bom_attribute_match/tests/test_mrp_account_bom_attribute_match.py b/mrp_account_bom_attribute_match/tests/test_mrp_account_bom_attribute_match.py new file mode 100644 index 000000000..1aa9f7ffb --- /dev/null +++ b/mrp_account_bom_attribute_match/tests/test_mrp_account_bom_attribute_match.py @@ -0,0 +1,23 @@ +# Copyright 2023 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo.addons.mrp_bom_attribute_match.tests.common import ( + TestMrpBomAttributeMatchBase, +) + + +class TestMrpAccount(TestMrpBomAttributeMatchBase): + @classmethod + def setUpClass(cls): + super().setUpClass() + + def test_bom_cost(self): + sword_cyan, sword_magenta = self.product_sword.product_variant_ids + plastic_cyan, plastic_magenta = self.product_plastic.product_variant_ids + plastic_cyan.standard_price = 1.00 + plastic_magenta.standard_price = 2.00 + sword_cyan.button_bom_cost() + sword_magenta.button_bom_cost() + self.assertEqual(sword_cyan.standard_price, 1.00) + self.assertEqual(sword_magenta.standard_price, 2.00) diff --git a/mrp_bom_attribute_match/tests/common.py b/mrp_bom_attribute_match/tests/common.py index 82f747818..65e082930 100644 --- a/mrp_bom_attribute_match/tests/common.py +++ b/mrp_bom_attribute_match/tests/common.py @@ -1,7 +1,7 @@ from odoo.tests import Form, TransactionCase -class TestMrpAttachmentMgmtBase(TransactionCase): +class TestMrpBomAttributeMatchBase(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() diff --git a/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py b/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py index e6b5b5083..0673f3565 100644 --- a/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py +++ b/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py @@ -1,14 +1,10 @@ from odoo.exceptions import UserError, ValidationError from odoo.tests import Form -from .common import TestMrpAttachmentMgmtBase +from .common import TestMrpBomAttributeMatchBase -class TestMrpAttachmentMgmt(TestMrpAttachmentMgmtBase): - @classmethod - def setUpClass(cls): - super().setUpClass() - +class TestMrpAttachmentMgmt(TestMrpBomAttributeMatchBase): def test_bom_1(self): mrp_bom_form = Form(self.env["mrp.bom"]) mrp_bom_form.product_tmpl_id = self.product_sword diff --git a/setup/mrp_account_bom_attribute_match/odoo/addons/mrp_account_bom_attribute_match b/setup/mrp_account_bom_attribute_match/odoo/addons/mrp_account_bom_attribute_match new file mode 120000 index 000000000..cb758dc5c --- /dev/null +++ b/setup/mrp_account_bom_attribute_match/odoo/addons/mrp_account_bom_attribute_match @@ -0,0 +1 @@ +../../../../mrp_account_bom_attribute_match \ No newline at end of file diff --git a/setup/mrp_account_bom_attribute_match/setup.py b/setup/mrp_account_bom_attribute_match/setup.py new file mode 100644 index 000000000..28c57bb64 --- /dev/null +++ b/setup/mrp_account_bom_attribute_match/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) From c39e5bbcb476871adf3579e14923c3837f617be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Tue, 31 Jan 2023 11:35:04 -0300 Subject: [PATCH 2/4] [FIX] mrp_bom_attribute_match: structure and cost report --- mrp_bom_attribute_match/__init__.py | 1 + mrp_bom_attribute_match/reports/__init__.py | 1 + .../reports/mrp_report_bom_structure.py | 44 +++++++++++++++++++ .../tests/test_mrp_bom_attribute_match.py | 17 ++++++- 4 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 mrp_bom_attribute_match/reports/__init__.py create mode 100644 mrp_bom_attribute_match/reports/mrp_report_bom_structure.py diff --git a/mrp_bom_attribute_match/__init__.py b/mrp_bom_attribute_match/__init__.py index 0650744f6..55ec7fc9a 100644 --- a/mrp_bom_attribute_match/__init__.py +++ b/mrp_bom_attribute_match/__init__.py @@ -1 +1,2 @@ from . import models +from . import reports diff --git a/mrp_bom_attribute_match/reports/__init__.py b/mrp_bom_attribute_match/reports/__init__.py new file mode 100644 index 000000000..d5f0e0470 --- /dev/null +++ b/mrp_bom_attribute_match/reports/__init__.py @@ -0,0 +1 @@ +from . import mrp_report_bom_structure diff --git a/mrp_bom_attribute_match/reports/mrp_report_bom_structure.py b/mrp_bom_attribute_match/reports/mrp_report_bom_structure.py new file mode 100644 index 000000000..3cb228911 --- /dev/null +++ b/mrp_bom_attribute_match/reports/mrp_report_bom_structure.py @@ -0,0 +1,44 @@ +# Copyright 2023 Camptocamp SA (https://www.camptocamp.com). +# @author Iván Todorovich +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import Command, models + + +class ReportBomStructure(models.AbstractModel): + _inherit = "report.mrp.report_bom_structure" + + def _get_bom_lines(self, bom, bom_quantity, product, line_id, level): + # OVERRIDE to fill in the `line.product_id` if a component template is used. + # To avoid a complete override, we HACK the bom by replacing it with a virtual + # record, and modifying it's lines on-the-fly. + has_template_lines = any( + line.component_template_id for line in bom.bom_line_ids + ) + if has_template_lines: + bom = bom.new(origin=bom) + to_ignore_line_ids = [] + for line in bom.bom_line_ids: + if line._skip_bom_line(product) or not line.component_template_id: + continue + line_product = bom._get_component_template_product( + line, product, line.product_id + ) + if not line_product: + to_ignore_line_ids.append(line.id) + continue + else: + line.product_id = line_product + if to_ignore_line_ids: + bom.bom_line_ids = [Command.unlink(id) for id in to_ignore_line_ids] + components, total = super()._get_bom_lines( + bom, bom_quantity, product, line_id, level + ) + # Replace any NewId value by the real record id + # Otherwise it's evaluated as False in some situations, and it may cause issues + if has_template_lines: + for component in components: + for key, value in component.items(): + if isinstance(value, models.NewId): + component[key] = value.origin + return components, total diff --git a/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py b/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py index 0673f3565..379af7de3 100644 --- a/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py +++ b/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py @@ -4,7 +4,7 @@ from odoo.tests import Form from .common import TestMrpBomAttributeMatchBase -class TestMrpAttachmentMgmt(TestMrpBomAttributeMatchBase): +class TestMrpBomAttributeMatch(TestMrpBomAttributeMatchBase): def test_bom_1(self): mrp_bom_form = Form(self.env["mrp.bom"]) mrp_bom_form.product_tmpl_id = self.product_sword @@ -168,3 +168,18 @@ class TestMrpAttachmentMgmt(TestMrpBomAttributeMatchBase): ) with self.assertRaisesRegex(UserError, r"Recursion error! .+"): test_bom_3.explode(self.product_9, 1) + + def test_mrp_report_bom_structure(self): + sword_cyan = self.product_sword.product_variant_ids[0] + BomStructureReport = self.env["report.mrp.report_bom_structure"] + res = BomStructureReport._get_report_data(self.bom_id.id) + self.assertTrue(res["is_variant_applied"]) + self.assertEqual(res["lines"]["product"], sword_cyan) + self.assertEqual( + res["lines"]["components"][0]["line_id"], + self.bom_id.bom_line_ids.id, + ) + self.assertEqual( + res["lines"]["components"][0]["parent_id"], + self.bom_id.id, + ) From a68706205653108452a9e23f85b22afa11fee66b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Tue, 31 Jan 2023 13:58:47 -0300 Subject: [PATCH 3/4] [IMP] mrp_bom_attribute_match: improve setUpClass --- mrp_bom_attribute_match/tests/common.py | 178 ++++++++++++++---------- 1 file changed, 106 insertions(+), 72 deletions(-) diff --git a/mrp_bom_attribute_match/tests/common.py b/mrp_bom_attribute_match/tests/common.py index 65e082930..eeed6a207 100644 --- a/mrp_bom_attribute_match/tests/common.py +++ b/mrp_bom_attribute_match/tests/common.py @@ -1,3 +1,5 @@ +from odoo import Command +from odoo.models import BaseModel from odoo.tests import Form, TransactionCase @@ -5,130 +7,162 @@ class TestMrpBomAttributeMatchBase(TransactionCase): @classmethod def setUpClass(cls): super().setUpClass() - cls._create_products(cls) - cls._create_boms(cls) - - def _create_products(self): - self.warehouse = self.env.ref("stock.warehouse0") - route_manufacture = self.warehouse.manufacture_pull_id.route_id.id - self.product_sword = self.env["product.template"].create( + cls.env = cls.env(context=dict(cls.env.context, tracking_disable=True)) + cls.warehouse = cls.env.ref("stock.warehouse0") + cls.route_manufacture = cls.warehouse.manufacture_pull_id.route_id + # Create products + cls.product_sword = cls.env["product.template"].create( { "name": "Plastic Sword", "type": "product", } ) - self.product_surf = self.env["product.template"].create( + cls.product_surf = cls.env["product.template"].create( { "name": "Surf", "type": "product", } ) - self.product_fin = self.env["product.template"].create( + cls.product_fin = cls.env["product.template"].create( { "name": "Surf Fin", "type": "product", } ) - self.product_plastic = self.env["product.template"].create( + cls.product_plastic = cls.env["product.template"].create( { "name": "Plastic Component", "type": "product", } ) - self.p1 = self.env["product.template"].create( + cls.p1 = cls.env["product.template"].create( { "name": "P1", "type": "product", - "route_ids": [(6, 0, [route_manufacture])], + "route_ids": [Command.link(cls.route_manufacture.id)], } ) - self.p2 = self.env["product.template"].create( + cls.p2 = cls.env["product.template"].create( { "name": "P2", "type": "product", - "route_ids": [(6, 0, [route_manufacture])], + "route_ids": [Command.link(cls.route_manufacture.id)], } ) - self.p3 = self.env["product.template"].create( + cls.p3 = cls.env["product.template"].create( { "name": "P3", "type": "product", - "route_ids": [(6, 0, [route_manufacture])], + "route_ids": [Command.link(cls.route_manufacture.id)], } ) - self.product_9 = self.env["product.product"].create( + cls.product_9 = cls.env["product.product"].create( { "name": "Paper", } ) - self.product_10 = self.env["product.product"].create( + cls.product_10 = cls.env["product.product"].create( { "name": "Stone", } ) - self.product_attribute = self.env["product.attribute"].create( + cls.product_attribute = cls.env["product.attribute"].create( {"name": "Colour", "display_type": "radio", "create_variant": "always"} ) - self.attribute_value_ids = self.env["product.attribute.value"].create( + cls.attribute_value_ids = cls.env["product.attribute.value"].create( [ - {"name": "Cyan", "attribute_id": self.product_attribute.id}, - {"name": "Magenta", "attribute_id": self.product_attribute.id}, + {"name": "Cyan", "attribute_id": cls.product_attribute.id}, + {"name": "Magenta", "attribute_id": cls.product_attribute.id}, ] ) - self.plastic_attrs = self.env["product.template.attribute.line"].create( + cls.plastic_attrs = cls.env["product.template.attribute.line"].create( { - "attribute_id": self.product_attribute.id, - "product_tmpl_id": self.product_plastic.id, - "value_ids": [(6, 0, self.product_attribute.value_ids.ids)], + "attribute_id": cls.product_attribute.id, + "product_tmpl_id": cls.product_plastic.id, + "value_ids": [Command.set(cls.product_attribute.value_ids.ids)], } ) - self.sword_attrs = self.env["product.template.attribute.line"].create( + cls.sword_attrs = cls.env["product.template.attribute.line"].create( { - "attribute_id": self.product_attribute.id, - "product_tmpl_id": self.product_sword.id, - "value_ids": [(6, 0, self.product_attribute.value_ids.ids)], + "attribute_id": cls.product_attribute.id, + "product_tmpl_id": cls.product_sword.id, + "value_ids": [Command.set(cls.product_attribute.value_ids.ids)], } ) + # Create boms + cls.bom_id = cls._create_bom( + cls.product_sword, + [ + dict( + component_template_id=cls.product_plastic.id, + product_qty=1, + ), + ], + ) + cls.fin_bom_id = cls._create_bom( + cls.product_fin, + [ + dict( + product_id=cls.product_plastic.product_variant_ids[0], + product_qty=1, + ), + ], + ) + cls.surf_bom_id = cls._create_bom( + cls.product_surf, + [ + dict( + product_id=cls.product_fin.product_variant_ids[0], + product_qty=1, + ), + ], + ) + cls.p1_bom_id = cls._create_bom( + cls.p1, + [ + dict( + product_id=cls.p2.product_variant_ids[0], + product_qty=1, + ), + ], + ) + cls.p2_bom_id = cls._create_bom( + cls.p2, + [ + dict( + product_id=cls.p3.product_variant_ids[0], + product_qty=1, + ), + ], + ) + cls.p3_bom_id = cls._create_bom( + cls.p3, + [ + dict( + product_id=cls.p1.product_variant_ids[0], + product_qty=1, + ), + ], + ) - def _create_boms(self): - mrp_bom_form = Form(self.env["mrp.bom"]) - mrp_bom_form.product_tmpl_id = self.product_sword - with mrp_bom_form.bom_line_ids.new() as line_form: - line_form.component_template_id = self.product_plastic - line_form.product_qty = 1 - self.bom_id = mrp_bom_form.save() - - mrp_bom_form = Form(self.env["mrp.bom"]) - mrp_bom_form.product_tmpl_id = self.product_fin - with mrp_bom_form.bom_line_ids.new() as line_form: - line_form.product_id = self.product_plastic.product_variant_ids[0] - line_form.product_qty = 1 - self.fin_bom_id = mrp_bom_form.save() - - mrp_bom_form = Form(self.env["mrp.bom"]) - mrp_bom_form.product_tmpl_id = self.product_surf - with mrp_bom_form.bom_line_ids.new() as line_form: - line_form.product_id = self.product_fin.product_variant_ids[0] - line_form.product_qty = 1 - self.surf_bom_id = mrp_bom_form.save() - - mrp_bom_form = Form(self.env["mrp.bom"]) - mrp_bom_form.product_tmpl_id = self.p1 - with mrp_bom_form.bom_line_ids.new() as line_form: - line_form.product_id = self.p2.product_variant_ids[0] - line_form.product_qty = 1 - self.p1_bom_id = mrp_bom_form.save() - - mrp_bom_form = Form(self.env["mrp.bom"]) - mrp_bom_form.product_tmpl_id = self.p2 - with mrp_bom_form.bom_line_ids.new() as line_form: - line_form.product_id = self.p3.product_variant_ids[0] - line_form.product_qty = 1 - self.p2_bom_id = mrp_bom_form.save() - - mrp_bom_form = Form(self.env["mrp.bom"]) - mrp_bom_form.product_tmpl_id = self.p3 - with mrp_bom_form.bom_line_ids.new() as line_form: - line_form.product_id = self.p1.product_variant_ids[0] - line_form.product_qty = 1 - self.p3_bom_id = mrp_bom_form.save() + @classmethod + def _create_bom(cls, product, line_form_vals): + if product._name == "product.template": + template = product + product = cls.env["product.product"] + else: + template = product.product_tmpl_id + with Form(cls.env["mrp.bom"]) as form: + form.product_tmpl_id = template + form.product_id = product + for vals in line_form_vals: + with form.bom_line_ids.new() as line_form: + for key, value in vals.items(): + field = line_form._model._fields.get(key) + if field and field.relational: # pragma: no cover + if value and not isinstance(value, BaseModel): + value = cls.env[field.comodel_name].browse(value) + elif not value: + value = cls.env[field.comodel_name] + setattr(line_form, key, value) + return form.save() From b6a1e3eaea83cb74f9bd887d7427f7facce9ef46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A0n=20Todorovich?= Date: Tue, 31 Jan 2023 14:06:04 -0300 Subject: [PATCH 4/4] [IMP] mrp_bom_attribute_match: increase test strenght: add another component to bom --- mrp_bom_attribute_match/tests/common.py | 4 ++++ .../tests/test_mrp_bom_attribute_match.py | 16 ++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/mrp_bom_attribute_match/tests/common.py b/mrp_bom_attribute_match/tests/common.py index eeed6a207..12ef736ef 100644 --- a/mrp_bom_attribute_match/tests/common.py +++ b/mrp_bom_attribute_match/tests/common.py @@ -97,6 +97,10 @@ class TestMrpBomAttributeMatchBase(TransactionCase): component_template_id=cls.product_plastic.id, product_qty=1, ), + dict( + product_id=cls.product_9, + product_qty=1, + ), ], ) cls.fin_bom_id = cls._create_bom( diff --git a/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py b/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py index 379af7de3..90cbbca05 100644 --- a/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py +++ b/mrp_bom_attribute_match/tests/test_mrp_bom_attribute_match.py @@ -67,18 +67,18 @@ class TestMrpBomAttributeMatch(TestMrpBomAttributeMatchBase): plastic_smells_like_orchid.unlink() def test_manufacturing_order_1(self): + sword_cyan = self.product_sword.product_variant_ids[0] + plastic_cyan = self.product_plastic.product_variant_ids[0] mo_form = Form(self.env["mrp.production"]) - mo_form.product_id = self.product_sword.product_variant_ids.filtered( - lambda x: x.display_name == "Plastic Sword (Cyan)" - ) + mo_form.product_id = sword_cyan mo_form.bom_id = self.bom_id mo_form.product_qty = 1 self.mo_sword = mo_form.save() self.mo_sword.action_confirm() # Assert correct component variant was selected automatically self.assertEqual( - self.mo_sword.move_raw_ids.product_id.display_name, - "Plastic Component (Cyan)", + self.mo_sword.move_raw_ids.product_id, + plastic_cyan + self.product_9, ) def test_manufacturing_order_2(self): @@ -177,7 +177,11 @@ class TestMrpBomAttributeMatch(TestMrpBomAttributeMatchBase): self.assertEqual(res["lines"]["product"], sword_cyan) self.assertEqual( res["lines"]["components"][0]["line_id"], - self.bom_id.bom_line_ids.id, + self.bom_id.bom_line_ids[0].id, + ) + self.assertEqual( + res["lines"]["components"][1]["line_id"], + self.bom_id.bom_line_ids[1].id, ) self.assertEqual( res["lines"]["components"][0]["parent_id"],