diff --git a/addons/sale_mrp/models/account_move.py b/addons/sale_mrp/models/account_move.py index f1de1900838..c3f2a9ae229 100644 --- a/addons/sale_mrp/models/account_move.py +++ b/addons/sale_mrp/models/account_move.py @@ -11,10 +11,8 @@ class AccountMoveLine(models.Model): so_line = self.sale_line_ids and self.sale_line_ids[-1] or False if so_line: - bom = (so_line.product_id.variant_bom_ids or so_line.product_id.product_tmpl_id.bom_ids).filtered( - lambda b: not b.company_id or b.company_id == so_line.company_id - )[:1] - if bom and bom.type == 'phantom': + 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: is_line_reversing = bool(self.move_id.reversed_entry_id) qty_to_invoice = self.product_uom_id._compute_quantity(self.quantity, self.product_id.uom_id) posted_invoice_lines = so_line.invoice_lines.filtered(lambda l: l.move_id.state == 'posted' and bool(l.move_id.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 f19378839fc..c24bc6b0fa3 100644 --- a/addons/sale_mrp/tests/test_sale_mrp_flow.py +++ b/addons/sale_mrp/tests/test_sale_mrp_flow.py @@ -1777,6 +1777,152 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon): invoice.action_post() self.assertEqual(invoice.state, 'posted') + def test_15_anglo_saxon_variant_price_unit(self): + """ + Test the price unit of a variant from which template has another variant with kit bom. + Products: + Template A + variant NOKIT + variant KIT: + Component A + Business Flow: + create products and kit + create SO selling both variants + validate the delivery + create the invoice + post the invoice + """ + + # Create environment + self.env.company.currency_id = self.env.ref('base.USD') + self.env.company.anglo_saxon_accounting = True + self.partner = self.env.ref('base.res_partner_1') + 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_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_receiv + self.category.property_account_expense_categ_id = account_expense + self.category.property_stock_account_input_categ_id = account_receiv + 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 = self.env['product.attribute.value'].create({'name': 'KIT', 'attribute_id': self.prod_att_test.id, 'sequence': 1}) + self.prod_attr_NOKIT = self.env['product.attribute.value'].create({'name': 'NOKIT', 'attribute_id': self.prod_att_test.id, 'sequence': 2}) + + # 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.id, self.prod_attr_NOKIT.id])] + })] + }) + + # Create the variants + self.pt_attr_KIT = self.product_template.attribute_line_ids[0].product_template_value_ids[0] + self.pt_attr_NOKIT = self.product_template.attribute_line_ids[0].product_template_value_ids[1] + self.variant_KIT = self.product_template._get_variant_for_combination(self.pt_attr_KIT) + self.variant_NOKIT = self.product_template._get_variant_for_combination(self.pt_attr_NOKIT) + # Assign a cost to the NOKIT variant + self.variant_NOKIT.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.id, + 'product_qty': 1.0, + 'type': 'phantom' + }) + self.env['mrp.bom.line'].create({ + 'product_id': self.comp_kit_a.id, + 'product_qty': 2.0, + 'bom_id': bom.id + }) + self.env['mrp.bom.line'].create({ + 'product_id': self.comp_kit_b.id, + 'product_qty': 1.0, + 'bom_id': bom.id + }) + + # Create the quants + self.env['stock.quant']._update_available_quantity(self.variant_KIT, self.stock_location, 1) + 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) + self.env['stock.quant']._update_available_quantity(self.variant_NOKIT, 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.name, + 'product_id': self.variant_KIT.id, + 'product_uom_qty': 1, + 'product_uom': self.uom_unit.id, + 'price_unit': 100, + }), (0, 0, { + 'name': self.variant_NOKIT.name, + 'product_id': self.variant_NOKIT.id, + 'product_uom_qty': 1, + 'product_uom': self.uom_unit.id, + 'price_unit': 50 + })], + 'pricelist_id': self.env.ref('product.list0').id, + '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 + wiz_act = pick.button_validate() + Form(self.env[wiz_act['res_model']].with_context(wiz_act['context'])).save().process() + # Create the invoice + so._create_invoices() + # Validate the invoice + invoice = so.invoice_ids + invoice.action_post() + + amls = invoice.line_ids + aml_kit_expense = amls.filtered(lambda l: l.display_type == 'cogs' and l.debit > 0 and l.product_id == self.variant_KIT) + aml_kit_output = amls.filtered(lambda l: l.display_type == 'cogs' and l.credit > 0 and l.product_id == self.variant_KIT) + aml_nokit_expense = amls.filtered(lambda l: l.display_type == 'cogs' and l.debit > 0 and l.product_id == self.variant_NOKIT) + aml_nokit_output = amls.filtered(lambda l: l.display_type == 'cogs' and l.credit > 0 and l.product_id == self.variant_NOKIT) + + # Check that the Cost of Goods Sold for variant KIT is equal to (2*20)+10 = 50 + self.assertEqual(aml_kit_expense.debit, 50, "Cost of Good Sold entry missing or mismatching for variant with kit") + self.assertEqual(aml_kit_output.credit, 50, "Cost of Good Sold entry missing or mismatching for variant with kit") + # Check that the Cost of Goods Sold for variant NOKIT is equal to its standard_price = 25 + 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_reconfirm_cancelled_kit(self): so = self.env['sale.order'].create({ 'partner_id': self.env.ref('base.res_partner_1').id,