[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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user