diff --git a/addons/sale_mrp/models/account_move.py b/addons/sale_mrp/models/account_move.py index 220fa523634..7969143c066 100644 --- a/addons/sale_mrp/models/account_move.py +++ b/addons/sale_mrp/models/account_move.py @@ -11,8 +11,12 @@ class AccountMoveLine(models.Model): so_line = self.sale_line_ids and self.sale_line_ids[-1] or False if so_line: - bom = self.env['mrp.bom']._bom_find(products=so_line.product_id, company_id=so_line.company_id.id, bom_type='phantom')[so_line.product_id] - if bom: + # We give preference to the bom in the stock moves for the sale order lines + # If there are changes in BOMs between the stock moves creation and the + # invoice validation a wrong price will be taken + boms = so_line.move_ids.filtered(lambda m: m.state != 'cancel').mapped('bom_line_id.bom_id').filtered(lambda b: b.type == 'phantom') + if boms: + bom = boms[0] is_line_reversing = self.move_id.move_type == 'out_refund' qty_to_invoice = self.product_uom_id._compute_quantity(self.quantity, self.product_id.uom_id) account_moves = so_line.invoice_lines.move_id.filtered(lambda m: m.state == 'posted' and bool(m.reversed_entry_id) == is_line_reversing) diff --git a/addons/sale_mrp/tests/test_sale_mrp_flow.py b/addons/sale_mrp/tests/test_sale_mrp_flow.py index c6499f4b1a9..97877a7331e 100644 --- a/addons/sale_mrp/tests/test_sale_mrp_flow.py +++ b/addons/sale_mrp/tests/test_sale_mrp_flow.py @@ -1922,6 +1922,145 @@ class TestSaleMrpFlow(TestSaleMrpFlowCommon): self.assertEqual(aml_nokit_expense.debit, 25, "Cost of Good Sold entry missing or mismatching for variant without kit") self.assertEqual(aml_nokit_output.credit, 25, "Cost of Good Sold entry missing or mismatching for variant without kit") + def test_16_anglo_saxon_variant_price_unit_multi_company(self): + """ + Test the price unit of the BOM of the stock move is taken + Products: + Template A + variant KIT 1 + variant KIT 2 + Business Flow: + create SO + validate the delivery + archive the BOM and create a new one + create the invoice + post the invoice + """ + + # Create environment + self.partner = self.env['res.partner'].create({'name': 'Test Partner'}) + self.category = self.env.ref('product.product_category_1').copy({'name': 'Test category', 'property_valuation': 'real_time', 'property_cost_method': 'fifo'}) + account_receiv = self.env['account.account'].create({'name': 'Receivable', 'code': 'RCV00', 'account_type': 'asset_receivable', 'reconcile': True}) + account_income = self.env['account.account'].create({'name': 'Income', 'code': 'INC00', 'account_type': 'asset_current', 'reconcile': True}) + account_expense = self.env['account.account'].create({'name': 'Expense', 'code': 'EXP00', 'account_type': 'liability_current', 'reconcile': True}) + account_output = self.env['account.account'].create({'name': 'Output', 'code': 'OUT00', 'account_type': 'liability_current', 'reconcile': True}) + account_valuation = self.env['account.account'].create({'name': 'Valuation', 'code': 'STV00', 'account_type': 'asset_receivable', 'reconcile': True}) + self.stock_location = self.company_data['default_warehouse'].lot_stock_id + self.partner.property_account_receivable_id = account_receiv + self.category.property_account_income_categ_id = account_income + self.category.property_account_expense_categ_id = account_expense + self.category.property_stock_account_input_categ_id = account_income + self.category.property_stock_account_output_categ_id = account_output + self.category.property_stock_valuation_account_id = account_valuation + + # Create variant attributes + self.prod_att_test = self.env['product.attribute'].create({'name': 'test'}) + self.prod_attr_KIT_A = self.env['product.attribute.value'].create({'name': 'KIT A', 'attribute_id': self.prod_att_test.id, 'sequence': 1}) + + # Create the template + self.product_template = self.env['product.template'].create({ + 'name': 'Template A', + 'type': 'product', + 'uom_id': self.uom_unit.id, + 'invoice_policy': 'delivery', + 'categ_id': self.category.id, + 'attribute_line_ids': [(0, 0, { + 'attribute_id': self.prod_att_test.id, + 'value_ids': [(6, 0, [self.prod_attr_KIT_A.id])] + })] + }) + + # Create another variant + self.pt_attr_KIT_A = self.product_template.attribute_line_ids[0].product_template_value_ids[0] + self.variant_KIT_A = self.product_template._get_variant_for_combination(self.pt_attr_KIT_A) + # Assign a cost to the NOKIT variant + self.variant_KIT_A.write({'standard_price': 25}) + + # Create the components + self.comp_kit_a = self.env['product.product'].create({ + 'name': 'Component Kit A', + 'type': 'product', + 'uom_id': self.uom_unit.id, + 'categ_id': self.category.id, + 'standard_price': 20 + }) + self.comp_kit_b = self.env['product.product'].create({ + 'name': 'Component Kit B', + 'type': 'product', + 'uom_id': self.uom_unit.id, + 'categ_id': self.category.id, + 'standard_price': 10 + }) + + # Create the bom + bom = self.env['mrp.bom'].create({ + 'product_tmpl_id': self.product_template.id, + 'product_id': self.variant_KIT_A.id, + 'product_qty': 1.0, + 'type': 'phantom', + 'company_id': self.env.company.id, + }) + self.env['mrp.bom.line'].create({ + 'product_id': self.comp_kit_a.id, + 'product_qty': 1.0, + 'company_id': self.env.company.id, + 'bom_id': bom.id + }) + + # Create the quants + self.env['stock.quant']._update_available_quantity(self.comp_kit_a, self.stock_location, 2) + self.env['stock.quant']._update_available_quantity(self.comp_kit_b, self.stock_location, 1) + + # Create the sale order + so_vals = { + 'partner_id': self.partner.id, + 'partner_invoice_id': self.partner.id, + 'partner_shipping_id': self.partner.id, + 'order_line': [(0, 0, { + 'name': self.variant_KIT_A.name, + 'product_id': self.variant_KIT_A.id, + 'product_uom_qty': 1, + 'product_uom': self.uom_unit.id, + 'price_unit': 50 + })], + 'company_id': self.env.company.id, + } + so = self.env['sale.order'].create(so_vals) + # Validate the sale order + so.action_confirm() + # Deliver the products + pick = so.picking_ids + pick.button_validate() + # archive bOM and update it + bom.active = False + bom_updated = self.env['mrp.bom'].create({ + 'product_tmpl_id': self.product_template.id, + 'product_id': self.variant_KIT_A.id, + 'product_qty': 1.0, + 'type': 'phantom', + 'company_id': self.env.company.id, + }) + self.env['mrp.bom.line'].create({ + 'product_id': self.comp_kit_b.id, + 'product_qty': 1.0, + 'company_id': self.env.company.id, + 'bom_id': bom_updated.id + }) + + # Create the invoice + so._create_invoices() + # Validate the invoice + invoice = so.invoice_ids + invoice.action_post() + + amls = invoice.line_ids + aml_nokit_expense = amls.filtered(lambda l: l.display_type == 'cogs' and l.debit > 0 and l.product_id == self.variant_KIT_A) + aml_nokit_output = amls.filtered(lambda l: l.display_type == 'cogs' and l.credit > 0 and l.product_id == self.variant_KIT_A) + + # Check that the Cost of Goods Sold for variant NOKIT is equal to the cost of the first BOM + self.assertEqual(aml_nokit_expense.debit, 20, "Cost of Good Sold entry missing or mismatching for variant without kit") + self.assertEqual(aml_nokit_output.credit, 20, "Cost of Good Sold entry missing or mismatching for variant without kit") + def test_reconfirm_cancelled_kit(self): so = self.env['sale.order'].create({ 'partner_id': self.env['res.partner'].create({'name': 'Test Partner'}).id,