From f48056fd3297733dee8e000482e44aefbf312a60 Mon Sep 17 00:00:00 2001 From: "Adrien Widart (awt)" Date: Tue, 29 Nov 2022 09:53:58 +0000 Subject: [PATCH] [FIX] purchase_stock,stock_account: convert UoM for price diff When confirming a bill with a price diff, if the UoM of the bill line is not the default UoM of the product, the values will be incorrect To reproduce the issue: (Need account_accountant) 1. In Settings, enable 'Units of Measure' 2. Create a product category PC: - Costing Method: FIFO - Inventory Valuation: Automated 3. Create a product P - Type: Storable - Category: PC 4. Create and confirm a PO with - 1 Dozen x P at $50 5. Process the receipt R01 6. Create and process the return RT01 of R01 7. Create and process the return RT02 of RT01 8. Create the bill related to PO 9. Set the bill line price to $60 10. Confirm 11. Open the valuation of P Error: a stock valuation layer has been created because of the price diff, which is correct, however the value is not the good one: $670 instead of $10 ($60 - $50) When confirming the bill, we check if there is a price difference. To do so, we compare the price unit of the bill line with the one of the incoming layer: https://github.com/odoo/odoo/blob/d3d41c7679f9068a7b986925b15d0d7670233bfc/addons/stock_account/models/account_move.py#L304 But, currently, the price unit of the bill line is based on the UoM of the line while the price unit of the layer is based on the default UoM of the product. We need to convert the PU of the bill line OPW-3081449 OPW-3062433 OPW-3075917 OPW-3048587 OPW-3078429 OPW-3075627 closes odoo/odoo#107145 X-original-commit: a1906f0b5e22f03937389d47e6ce5a650f9269e6 Signed-off-by: William Henrotin (whe) Signed-off-by: Adrien Widart --- .../tests/test_stockvaluation.py | 76 ++++++++++++------- addons/stock_account/models/account_move.py | 1 + 2 files changed, 49 insertions(+), 28 deletions(-) diff --git a/addons/purchase_stock/tests/test_stockvaluation.py b/addons/purchase_stock/tests/test_stockvaluation.py index db65cafc170..64f9591ae69 100644 --- a/addons/purchase_stock/tests/test_stockvaluation.py +++ b/addons/purchase_stock/tests/test_stockvaluation.py @@ -1285,24 +1285,26 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon): def test_price_diff_with_return_backorder_and_partial_bill(self): """ Fifo + Real time. + Default UoM of the product is Unit. Company in USD. 1 USD = 2 EUR. - Receive 10 @ $50: - Receive 10 (R1) - Return 10 (RET) - Receive 7 (R2) - Receive 3 (R3) - Deliver 5 + Receive 10 Hundred @ $50: + Receive 10 Hundred (R1) + Return 10 Hundred (RET) + Receive 7 Hundred (R2) + Receive 3 Hundred (R3) + Deliver 5 Hundred Bill - 1 @ 120€ -> already out, should not generate any SVL - 3 @ 120€ -> already out, should not generate any SVL - 2 @ 120€ -> only one out, should generate an SVL for the other one and + 1 Hundred @ 120€ -> already out, should not generate any SVL + 3 Hundred @ 120€ -> already out, should not generate any SVL + 2 Hundred @ 120€ -> only one out, should generate an SVL for the other one and this SVL should be linked to SVL_R1 - 4 @ 120€ -> should generate two SVL: one for one product and attached to + 4 Hundred @ 120€ -> should generate two SVL: one for one product and attached to SVL_R1 and another one for the three last products of SVL_R2 - Deliver 2 + Deliver 2 Hundred The SVL should include: - - 2 x 50 (the product cost) - - 2 x 10 (the price diff from step "Bill 2@60" and "Bill 4@60") + - 2 x 50 (cost by hundred) + - 2 x 10 (the price diff from step "Bill 2 Hundred @ 60" and "Bill + 4 Hundred @ 60") """ expected_svl_values = [] # USD warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1) @@ -1317,6 +1319,21 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon): 'rate': 2, }) + grp_uom = self.env.ref('uom.group_uom') + self.env.user.write({'groups_id': [(4, grp_uom.id)]}) + uom_unit = self.env.ref('uom.product_uom_unit') + uom_hundred = self.env['uom.uom'].create({ + 'name': '100 x U', + 'category_id': uom_unit.category_id.id, + 'ratio': 100.0, + 'uom_type': 'bigger', + 'rounding': uom_unit.rounding, + }) + self.product1.write({ + 'uom_id': uom_unit.id, + 'uom_po_id': uom_unit.id, + }) + self.product1.categ_id.property_cost_method = 'fifo' self.product1.categ_id.property_valuation = 'real_time' @@ -1325,35 +1342,36 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon): with po_form.order_line.new() as po_line: po_line.product_id = self.product1 po_line.product_qty = 10 + po_line.product_uom = uom_hundred po_line.price_unit = 50.0 po = po_form.save() po.button_confirm() - # Receive 10 + # Receive 10 Hundred receipt01 = po.picking_ids[0] - receipt01.move_ids.quantity_done = 10 + receipt01.move_ids._set_quantities_to_reservation() receipt01.button_validate() expected_svl_values += [10 * 50] self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values) - # Return 10 + # Return 10 Hundred return_form = Form(self.env['stock.return.picking'].with_context(active_id=receipt01.id, active_model='stock.picking')) return_wizard = return_form.save() return_picking_id, _pick_type_id = return_wizard._create_returns() return_picking = self.env['stock.picking'].browse(return_picking_id) - return_picking.move_ids.quantity_done = 10 + return_picking.move_ids._set_quantities_to_reservation() return_picking.button_validate() expected_svl_values += [-10 * 50] self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values) - # Receive 7 with backorder + # Receive 7 Hundred with backorder return_form = Form(self.env['stock.return.picking'].with_context(active_id=return_picking_id, active_model='stock.picking')) return_wizard = return_form.save() receipt_id, _pick_type_id = return_wizard._create_returns() receipt02 = self.env['stock.picking'].browse(receipt_id) - receipt02.move_ids.quantity_done = 7 + receipt02.move_ids.move_line_ids.qty_done = 700 action = receipt02.button_validate() backorder_wizard = Form(self.env['stock.backorder.confirmation'].with_context(action['context'])).save() backorder_wizard.process() @@ -1361,15 +1379,15 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon): expected_svl_values += [7 * 50] self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values) - # Receive 3 + # Receive 3 Hundred receipt03 = receipt02.backorder_ids - receipt03.move_ids.quantity_done = 3 + receipt03.move_ids._set_quantities_to_reservation() receipt03.button_validate() expected_svl_values += [3 * 50] self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values) - # Delivery 5 + # Delivery 5 Hundred delivery01 = self.env['stock.picking'].create({ 'location_id': stock_location.id, 'location_dest_id': customer_location.id, @@ -1378,33 +1396,35 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon): 'name': self.product1.name, 'product_id': self.product1.id, 'product_uom_qty': 5, - 'product_uom': self.product1.uom_id.id, + 'product_uom': uom_hundred.id, 'location_id': stock_location.id, 'location_dest_id': customer_location.id, })], }) delivery01.action_confirm() - delivery01.move_ids.quantity_done = 5 + delivery01.move_ids._set_quantities_to_reservation() delivery01.button_validate() expected_svl_values += [-5 * 50] self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values) # We will create a price diff SVL only for the remaining quantities not yet billed + # On the bill, price unit is 120€, i.e. $60 -> price diff equal to $10 for qty, new_svl_expected in [(1, []), (3, []), (2, [1 * 10.0]), (4, [1 * 10.0, 3 * 10.0])]: bill_form = Form(self.env['account.move'].with_context(default_move_type='in_invoice')) bill_form.invoice_date = bill_form.date bill_form.purchase_vendor_bill_id = self.env['purchase.bill.union'].browse(-po.id) bill = bill_form.save() bill.invoice_line_ids.quantity = qty - bill.invoice_line_ids.price_unit = 120.0 # i.e. $60 -> price diff of $10 + bill.invoice_line_ids.price_unit = 120.0 + bill.invoice_line_ids.product_uom_id = uom_hundred bill.currency_id = eur_curr bill.action_post() expected_svl_values += new_svl_expected self.assertEqual(self.product1.stock_valuation_layer_ids.mapped('value'), expected_svl_values) - # Delivery 2 + # Delivery 2 Hundred delivery02 = self.env['stock.picking'].create({ 'location_id': stock_location.id, 'location_dest_id': customer_location.id, @@ -1413,13 +1433,13 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon): 'name': self.product1.name, 'product_id': self.product1.id, 'product_uom_qty': 2, - 'product_uom': self.product1.uom_id.id, + 'product_uom': uom_hundred.id, 'location_id': stock_location.id, 'location_dest_id': customer_location.id, })], }) delivery02.action_confirm() - delivery02.move_ids.quantity_done = 2 + delivery02.move_ids._set_quantities_to_reservation() delivery02.button_validate() expected_svl_values += [-2 * 50 + -2 * 10] diff --git a/addons/stock_account/models/account_move.py b/addons/stock_account/models/account_move.py index aed1a345344..3d01a0adce6 100644 --- a/addons/stock_account/models/account_move.py +++ b/addons/stock_account/models/account_move.py @@ -301,6 +301,7 @@ class AccountMoveLine(models.Model): )['total_excluded'] price_unit /= prec price_unit = line.currency_id._convert(price_unit, line.company_id.currency_id, line.company_id, line.date, round=False) + price_unit = line.product_uom_id._compute_price(price_unit, line.product_id.uom_id) layers_price_unit = line._get_stock_valuation_layers_price_unit(layers) layers_to_correct = line._get_stock_layer_price_difference(layers, layers_price_unit, price_unit) svl_vals_list += line._prepare_in_invoice_svl_vals(layers_to_correct)