From e0c5834bfb4c83cb22c281f9ba22a4cc68aac09c Mon Sep 17 00:00:00 2001 From: "Touati Djamel (otd)" Date: Wed, 14 Sep 2022 08:31:01 +0000 Subject: [PATCH] [FIX] mrp_account, sale_mrp: wrong kit cost after so confirmation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Steps to reproduce the bug: - Create a storable product “B1”: - Costing Method: average - With BOM: - BOM Type: Kit - Components: - 3 units of “C1” (cost c1 = $2) - Go to the “B1” product form: - Click on “Compute Price from BOM” - Cost = 3 * $2 = $6 - Create a storable product A1: - Costing Method: average - BOM - BOM Type: Kit - Components: - 2 units of B1 - Go to the “A1” product form: - Click on “Compute Price from BOM” So: 1 unit of A1 → 2 units of B1 → 3 units of C1 - Cost = (2 * 3 * $2) = $12 - Create a SO: - Add 1 unit of “A1” - save - cost(`purchase_price`) = $12 - Confirm the SO - The cost is recomputed and becomes = $6 Problem: When the SO is created and the product “A1” is added, the cost is retrieved from the product: https://github.com/odoo/odoo/blob/14.0/addons/sale_margin/models/sale_order.py#L27 But when the SO is confirmed, a picking is created, therefore the cost is recomputed: https://github.com/odoo/odoo/blob/14.0/addons/sale_stock_margin/models/sale_order_line.py#L18 So The `_compute_average_price` function is called, in which a loop is made for each move related to the `sale.order.line`, but the `bom_line.product_qty` is used for each component (in this case the qty necessary of the product `C1 ` to make a single unit of `B1`, but this is not multiplied by the number of units needed to be used in the parent's bom_line opw-2971248 closes odoo/odoo#101012 X-original-commit: 9ec7aa9b89bc929af15473d0cc29084fd831cf94 Signed-off-by: Arnold Moyaux (arm) Signed-off-by: Djamel Touati (otd) --- addons/mrp_account/models/product.py | 2 +- addons/sale_mrp/tests/test_sale_mrp_flow.py | 43 +++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/addons/mrp_account/models/product.py b/addons/mrp_account/models/product.py index ab44b72b50c..98f0ee2bfba 100644 --- a/addons/mrp_account/models/product.py +++ b/addons/mrp_account/models/product.py @@ -60,7 +60,7 @@ class ProductProduct(models.Model): for move in moves_list: value += move.product_qty * move.product_id._compute_average_price(qty_invoiced * move.product_qty, qty_to_invoice * move.product_qty, move, is_returned=is_returned) continue - line_qty = bom_line.product_uom_id._compute_quantity(bom_line.product_qty, bom_line.product_id.uom_id) + line_qty = bom_line.product_uom_id._compute_quantity(bom_lines[bom_line]['qty'], bom_line.product_id.uom_id) moves = self.env['stock.move'].concat(*moves_list) value += line_qty * bom_line.product_id._compute_average_price(qty_invoiced * line_qty, qty_to_invoice * line_qty, moves, is_returned=is_returned) return value diff --git a/addons/sale_mrp/tests/test_sale_mrp_flow.py b/addons/sale_mrp/tests/test_sale_mrp_flow.py index e1b596a7fc0..f19378839fc 100644 --- a/addons/sale_mrp/tests/test_sale_mrp_flow.py +++ b/addons/sale_mrp/tests/test_sale_mrp_flow.py @@ -1928,3 +1928,46 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon): {'product_id': self.component_f.id, 'location_dest_id': stock_location.id, 'quantity_done': 20, 'state': 'done'}, {'product_id': self.component_g.id, 'location_dest_id': stock_location.id, 'quantity_done': 40, 'state': 'done'}, ]) + + def test_kit_cost_calculation(self): + """ Check that the average cost price is computed correctly after SO confirmation: + BOM 1: + - 1 unit of “super kit”: + - 2 units of “component a” + BOM 2: + - 1 unit of “component a”: + - 3 units of "component b" + 1 unit of "component b" = $10 + 1 unit of "super kit" = 2 * 3 * $10 = *$60 + """ + super_kit = self._cls_create_product('Super Kit', self.uom_unit) + (super_kit + self.component_a + self.component_b).categ_id.property_cost_method = 'average' + self.env['mrp.bom'].create({ + 'product_tmpl_id': self.component_a.product_tmpl_id.id, + 'product_qty': 1.0, + 'type': 'phantom', + 'bom_line_ids': [(0, 0, { + 'product_id': self.component_b.id, + 'product_qty': 3.0, + })] + }) + self.env['mrp.bom'].create({ + 'product_tmpl_id': super_kit.product_tmpl_id.id, + 'product_qty': 1.0, + 'type': 'phantom', + 'bom_line_ids': [(0, 0, { + 'product_id': self.component_a.id, + 'product_qty': 2.0, + })] + }) + self.component_b.standard_price = 10 + self.component_a.button_bom_cost() + super_kit.button_bom_cost() + so_form = Form(self.env['sale.order']) + so_form.partner_id = self.partner_a + with so_form.order_line.new() as line: + line.product_id = super_kit + so = so_form.save() + self.assertEqual(so.order_line.purchase_price, 60) + so.action_confirm() + self.assertEqual(so.order_line.purchase_price, 60)