[FIX] mrp_account: ignore returned SM in price computations

When returning a kit, the margin of the related SO decreases

To reproduce the issue:
(Need sale_management)
1. In Settings, enable "Margins"
2. Create a Product Category PC:
    - Costing Method: FIFO
3. Create two products P_kit, P_compo:
    - Both:
        - Type: Storable
        - Category: PC
    - P_compo:
        - Cost: 10
4. Update P_compo quantity: 1
5. Create a BoM:
    - Product: P_kit
    - Type: Kit
    - Components: 1 x P_compo
6. Update P_kit's cost
7. Create and confirm a SO with 1 x P_kit, unit price = $100
    - Note that the margin is correct: $90
8. Process the related picking
9. Create a return R
10. Go back to the SO

Error: the margin is now $80

When computing the cost of the kit, the move linked to R are considered.
Therefore, the cost becomes $20 and thus the margin becomes $80

OPW-2679473

closes odoo/odoo#83008

X-original-commit: 2f34669f9dc3c9abfcbb13198f90f37405ffc258
Signed-off-by: Arnold Moyaux <arm@odoo.com>
Signed-off-by: Adrien Widart <awt@odoo.com>
This commit is contained in:
Adrien Widart
2022-01-19 16:03:53 +00:00
parent 716bc63dd2
commit ee66e43133
2 changed files with 55 additions and 12 deletions
+9 -12
View File
@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import models
from odoo.tools import float_round
from odoo.tools import float_round, groupby
class ProductTemplate(models.Model):
@@ -53,20 +53,17 @@ class ProductProduct(models.Model):
bom = self.env['mrp.bom']._bom_find(self, company_id=stock_moves.company_id.id, bom_type='phantom')[self]
if not bom:
return super()._compute_average_price(qty_invoiced, qty_to_invoice, stock_moves, is_returned=is_returned)
value = 0
dummy, bom_lines = bom.explode(self, 1)
bom_lines = {line: data for line, data in bom_lines}
value = 0
for move in stock_moves:
if move.state == 'cancel':
for bom_line, moves_list in groupby(stock_moves.filtered(lambda sm: sm.state != 'cancel'), lambda sm: sm.bom_line_id):
if bom_line not in bom_lines:
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
bom_line = move.bom_line_id
if bom_line in bom_lines:
bom_line_data = bom_lines[bom_line]
line_qty = bom_line.product_uom_id._compute_quantity(bom_line_data['qty'], bom_line.product_id.uom_id)
else:
# bom was altered (i.e. bom line removed) after being used
line_qty = move.product_qty
value += line_qty * move.product_id._compute_average_price(qty_invoiced * line_qty, qty_to_invoice * line_qty, move, is_returned=is_returned)
line_qty = bom_line.product_uom_id._compute_quantity(bom_line.product_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
def _compute_bom_price(self, bom, boms_to_recompute=False, byproduct_bom=False):
@@ -2041,3 +2041,49 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon):
cogs_aml = amls.filtered(lambda aml: aml.account_id == expense_account)
self.assertEqual(cogs_aml.debit, 0)
self.assertEqual(cogs_aml.credit, 20, 'Should be to the value of the returned component')
def test_kit_margin_and_return_picking(self):
""" This test ensure that, when returning the components of a sold kit, the
sale order line cost does not change"""
kit = self._cls_create_product('Super Kit', self.uom_unit)
(kit + self.component_a).categ_id.property_cost_method = 'fifo'
self.env['mrp.bom'].create({
'product_tmpl_id': kit.product_tmpl_id.id,
'product_qty': 1.0,
'type': 'phantom',
'bom_line_ids': [(0, 0, {
'product_id': self.component_a.id,
'product_qty': 1.0,
})]
})
self.component_a.standard_price = 10
kit.button_bom_cost()
stock_location = self.company_data['default_warehouse'].lot_stock_id
self.env['stock.quant']._update_available_quantity(self.component_a, stock_location, 1)
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 = kit
so = so_form.save()
so.action_confirm()
line = so.order_line
price = line.product_id.with_company(line.company_id)._compute_average_price(0, line.product_uom_qty, line.move_ids)
self.assertEqual(price, 10)
picking = so.picking_ids
action = picking.button_validate()
wizard = Form(self.env[action['res_model']].with_context(action['context'])).save()
wizard.process()
ctx = {'active_ids':picking.ids, 'active_id': picking.ids[0], 'active_model': 'stock.picking'}
return_picking_wizard_form = Form(self.env['stock.return.picking'].with_context(ctx))
return_picking_wizard = return_picking_wizard_form.save()
return_picking_wizard.create_returns()
price = line.product_id.with_company(line.company_id)._compute_average_price(0, line.product_uom_qty, line.move_ids)
self.assertEqual(price, 10)