diff --git a/addons/mrp/report/mrp_report_mo_overview.py b/addons/mrp/report/mrp_report_mo_overview.py index 5d2c4bc745baf..00bcfd67da90b 100644 --- a/addons/mrp/report/mrp_report_mo_overview.py +++ b/addons/mrp/report/mrp_report_mo_overview.py @@ -82,7 +82,11 @@ def _get_report_data(self, production_id): if production.bom_id: currency = (production.company_id or self.env.company).currency_id - missing_components = (bom_line for bom_line in production.bom_id.bom_line_ids if bom_line not in (production.move_raw_ids.bom_line_id + self._get_kit_bom_lines(production.bom_id))) + current_bom_lines = production.move_raw_ids.bom_line_id | self._get_kit_bom_lines(production.bom_id) + missing_components = production.bom_id.bom_line_ids.filtered( + lambda bom_line: bom_line not in current_bom_lines and + not bom_line._skip_bom_line(production.product_id) + ) missing_operations = (bom_line for bom_line in production.bom_id.operation_ids if bom_line not in production.workorder_ids.operation_id) for line in missing_components: line_cost = line.product_id.uom_id._compute_price(line.product_id.standard_price, line.product_uom_id) * line.product_qty diff --git a/addons/mrp/tests/test_stock_report.py b/addons/mrp/tests/test_stock_report.py index 0fb73bd111d9d..483e0b40c7973 100644 --- a/addons/mrp/tests/test_stock_report.py +++ b/addons/mrp/tests/test_stock_report.py @@ -513,3 +513,125 @@ def test_mo_overview_same_component(self): self.assertEqual(len(repl1), 1) self.assertEqual(repl0[0]['summary']['quantity'], 3) self.assertEqual(repl1[0]['summary']['quantity'], 5) + + def test_report_price_variants(self): + """ + This tests the MO's report price when a variant is involved. It makes sure + that the BoM price takes only the current variant and not all of them. It also + tests that the lines that were removed from the MO but are still in the bom are + used in the BoM cost computing. Lastly, it makes sure that Kits are also accounted + for and used in the BoM cost as they should. + """ + # Create a color variant, which will be used to create a Product + attribute_color = self.env['product.attribute'].create({'name': 'Color'}) + value_black, value_white = self.env['product.attribute.value'].create({ + 'name': name, + 'attribute_id': attribute_color.id, + } for name in ['Black', 'White']) + # Create 3 product templates, one for the variants, to check to not all variants are used + # to compute the cost of the MO, another one to make sure the 'missing components' are still + # taken into account (they are the product that were removed from the MO but are still present + # in the BoM), and the last one to make sure that kits are still taken in as well. + product_variants, missing_product, kit_product = self.env['product.template'].create([ + { + 'name': 'Variant Product', + 'type': 'consu', + 'attribute_line_ids': [Command.create( + { + 'attribute_id': attribute_color.id, + 'value_ids': [ + Command.link(value_black.id), + Command.link(value_white.id), + ], + }, + )], + }, + { + 'name': 'Missing Component', + 'type': 'consu', + 'standard_price': 40, + }, + { + 'name': 'Kit Product', + 'type': 'consu', + 'standard_price': 60, + }, + ]) + + variant_black = product_variants.product_variant_ids[0] + variant_white = product_variants.product_variant_ids[1] + variant_black.standard_price = 50 + variant_white.standard_price = 30 + bom_normal, _ = self.env['mrp.bom'].create([ + { + 'product_tmpl_id': product_variants.id, + 'type': 'normal', + 'bom_line_ids': [Command.create({ + 'product_id': missing_product.product_variant_id.id, + 'product_qty': 1, + }), + Command.create({ + 'product_id': kit_product.product_variant_id.id, + 'product_qty': 1, + })], + }, + { + 'product_tmpl_id': kit_product.id, + 'type': 'phantom', + 'bom_line_ids': [Command.create({ + 'product_id': missing_product.product_variant_id.id, + 'product_qty': 1, + })] + }, + ]) + white_tmpl = bom_normal.product_tmpl_id.product_variant_ids.product_template_attribute_value_ids.filtered(lambda tmpl: tmpl.product_attribute_value_id == value_white) + black_tmpl = bom_normal.product_tmpl_id.product_variant_ids.product_template_attribute_value_ids.filtered(lambda tmpl: tmpl.product_attribute_value_id == value_black) + black_white_product, black_product, white_product = self.env['product.product'].create([{ + 'name': 'Black White', + 'type': 'consu', + 'standard_price': 25, + }, + { + 'name': 'Black Only', + 'type': 'consu', + 'standard_price': 15, + }, + { + 'name': 'White Only', + 'type': 'consu', + 'standard_price': 10, + }, + ]) + self.env['mrp.bom.line'].create([{ + 'bom_id': bom_normal.id, + 'product_id': black_white_product.id, + 'product_qty': 1, + 'bom_product_template_attribute_value_ids': [Command.set([black_tmpl.id, white_tmpl.id])], + }, + { + 'bom_id': bom_normal.id, + 'product_id': black_product.id, + 'product_qty': 1, + 'bom_product_template_attribute_value_ids': [Command.set([black_tmpl.id])], + }, + { + 'bom_id': bom_normal.id, + 'product_id': white_product.id, + 'product_qty': 1, + 'bom_product_template_attribute_value_ids': [Command.set([white_tmpl.id])], + } + ]) + mo = self.env['mrp.production'].create({ + 'product_id': variant_black.id, + 'product_qty': 1, + 'bom_id': bom_normal.id, + }) + mo_report = self.env['report.mrp.report_mo_overview'].get_report_values(mo.id) + self.assertEqual(mo_report['data']['extras']['unit_bom_cost'], mo_report['data']['extras']['unit_mo_cost'], 'The BoM unit cost should be equal to the sum of the products of said BoM') + # Check that the missing components (the components that were removed from the MO but are still in the BoM) + # are taken into account when computing the BoM cost + mo.move_raw_ids.filtered(lambda m: m.product_id == missing_product.product_variant_id and m.bom_line_id.bom_id.type != 'phantom').unlink() + # When a product has two possible variants, and then is deleted, it should be taken in the missing components + mo.move_raw_ids.filtered(lambda m: m.product_id == black_white_product and m.bom_line_id.bom_id.type != 'phantom').unlink() + mo_report = self.env['report.mrp.report_mo_overview'].get_report_values(mo.id) + self.assertEqual(mo_report['data']['extras']['unit_bom_cost'], mo_report['data']['extras']['unit_mo_cost'] + missing_product.standard_price + black_white_product.standard_price, 'The BoM unit cost should take the missing components into account, which are the deleted MO lines')