[FIX] sale_mrp: BOM selection in anglosaxon price calculation

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

closes odoo/odoo#150506

X-original-commit: 855727eccb4ba9a2af72350b6ab9cc40ba7e50d9
Signed-off-by: William Henrotin (whe) <whe@odoo.com>
This commit is contained in:
AaronHForgeFlow
2024-01-24 14:02:17 +00:00
committed by William Henrotin
parent 80fa6d1416
commit 4cbeb0dade
2 changed files with 145 additions and 2 deletions
+6 -2
View File
@@ -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)
+139
View File
@@ -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,