diff --git a/addons/mrp_account/models/product.py b/addons/mrp_account/models/product.py index b2a8a4ee082..8aa27a0fae4 100644 --- a/addons/mrp_account/models/product.py +++ b/addons/mrp_account/models/product.py @@ -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): diff --git a/addons/sale_mrp/tests/test_sale_mrp_flow.py b/addons/sale_mrp/tests/test_sale_mrp_flow.py index 17781849af9..ce7e7407981 100644 --- a/addons/sale_mrp/tests/test_sale_mrp_flow.py +++ b/addons/sale_mrp/tests/test_sale_mrp_flow.py @@ -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)