From fff6ffc5a1431bbb219f64fe3410cd84522f5ed3 Mon Sep 17 00:00:00 2001 From: "Achraf (abz)" Date: Mon, 13 Sep 2021 10:16:06 +0000 Subject: [PATCH] [FIX] sale_stock: Don't compute UoM twice The amount on generated COGS lines can have the UoM factor applied two times. For example, if we have a product costing 1$ per item and we sell it by pack of 12 ("Pack" UoM) we will have 1$*12*12=144$ instead of 1$*12=12$ 0/ Go to a V14 runbot 1/ Create a company which use COGS (eg. Mexican accounting) 2/ Have a storable product priced at 1.5$ from a product category with Costing Method as "First In First Out (FIFO)" and Inventory Valuation as "Automated" 3/ Create a UoM 12 times bigger than the default Unit (eg. 12 Pack) 4/ Create a purchase order costing 50$ for 50 Unit of the product => cost of the product should be 1$ 5/ Create a new purchase order for 50 Unit but costing 0$ (like if got free products from supplier) 6/ Create sale order for 50 units of the product, validate the transfert and create/confirm the invoice => computed COGS amount is 50$ on the invoice => ok 7/ Create a sale order using the "12 Pack" UoM, validate transfert and create/confirm invoice => computed COGS is 144$ => nok opw-2503368 closes odoo/odoo#77007 X-original-commit: d0d9c0a8f8e35fbd9624d934a8ca6152677fbd3e Signed-off-by: William Henrotin Signed-off-by: Achraf --- addons/sale_stock/models/account_move.py | 4 +- .../tests/test_anglo_saxon_valuation.py | 121 ++++++++++++++++++ 2 files changed, 123 insertions(+), 2 deletions(-) diff --git a/addons/sale_stock/models/account_move.py b/addons/sale_stock/models/account_move.py index bd95e155ce2..7cc1dc11266 100644 --- a/addons/sale_stock/models/account_move.py +++ b/addons/sale_stock/models/account_move.py @@ -120,8 +120,8 @@ class AccountMoveLine(models.Model): qty_to_invoice = self.product_uom_id._compute_quantity(self.quantity, self.product_id.uom_id) qty_invoiced = sum([x.product_uom_id._compute_quantity(x.quantity, x.product_id.uom_id) for x in so_line.invoice_lines if x.move_id.state == 'posted']) average_price_unit = self.product_id._compute_average_price(qty_invoiced, qty_to_invoice, so_line.move_ids) + if average_price_unit: + price_unit = self.product_id.uom_id._compute_price(average_price_unit, self.product_uom_id) - price_unit = average_price_unit or price_unit - price_unit = self.product_id.uom_id._compute_price(price_unit, self.product_uom_id) return price_unit diff --git a/addons/sale_stock/tests/test_anglo_saxon_valuation.py b/addons/sale_stock/tests/test_anglo_saxon_valuation.py index 90c2dc5142e..ffb94563494 100644 --- a/addons/sale_stock/tests/test_anglo_saxon_valuation.py +++ b/addons/sale_stock/tests/test_anglo_saxon_valuation.py @@ -1114,3 +1114,124 @@ class TestAngloSaxonValuation(ValuationReconciliationTestCommon): income_aml_2 = amls_2.filtered(lambda aml: aml.account_id == self.company_data['default_account_revenue']) self.assertEqual(income_aml_2.debit, 0) self.assertEqual(income_aml_2.credit, 12) + + def test_fifo_uom_computation(self): + self.env.company.anglo_saxon_accounting = True + self.product.categ_id.property_cost_method = 'fifo' + self.product.categ_id.property_valuation = 'real_time' + quantity = 50.0 + self.product.list_price = 1.5 + self.product.standard_price = 2.0 + unit_12 = self.env['uom.uom'].create({ + 'name': 'Pack of 12 units', + 'category_id': self.product.uom_id.category_id.id, + 'uom_type': 'bigger', + 'factor_inv': 12, + 'rounding': 1, + }) + + # Create, confirm and deliver a sale order for 12@1.5 without reception with std_price = 2.0 (SO1) + so_1 = self.env['sale.order'].create({ + 'partner_id': self.partner_a.id, + 'order_line': [ + (0, 0, { + 'name': self.product.name, + 'product_id': self.product.id, + 'product_uom_qty': 1, + 'product_uom': unit_12.id, + 'price_unit': 18, + 'tax_id': False, # no love taxes amls + })], + }) + so_1.action_confirm() + so_1.picking_ids.move_lines.quantity_done = 12 + so_1.picking_ids.button_validate() + + # Invoice the sale order. + invoice_1 = so_1._create_invoices() + invoice_1.action_post() + + # Invoice 1 + + # Correct Journal Items + + # Name Debit Credit + + # Product Sales 0.00$ 18.00$ + # Account Receivable 18.00$ 0.00$ + # Default Account Stock Out 0.00$ 24.00$ + # Expenses 24.00$ 0.00$ + + aml = invoice_1.line_ids + # Product Sales + self.assertEqual(aml[0].debit, 0.0) + self.assertEqual(aml[0].credit, 18.0) + # Account Receivable + self.assertEqual(aml[1].debit, 18.0) + self.assertEqual(aml[1].credit, 0.0) + # Default Account Stock Out + self.assertEqual(aml[2].debit, 0.0) + self.assertEqual(aml[2].credit, 24.0) + # Expenses + self.assertEqual(aml[3].debit, 24.0) + self.assertEqual(aml[3].credit, 0.0) + + # Create stock move 1 + in_move_1 = self.env['stock.move'].create({ + 'name': 'a', + 'product_id': self.product.id, + 'location_id': self.env.ref('stock.stock_location_suppliers').id, + 'location_dest_id': self.company_data['default_warehouse'].lot_stock_id.id, + 'product_uom': self.product.uom_id.id, + 'product_uom_qty': quantity, + 'price_unit': 1.0, + }) + in_move_1._action_confirm() + in_move_1.quantity_done = quantity + in_move_1._action_done() + + # Create, confirm and deliver a sale order for 12@1.5 with reception (50 * 1.0, 50 * 0.0)(SO2) + so_2 = self.env['sale.order'].create({ + 'partner_id': self.partner_a.id, + 'order_line': [ + (0, 0, { + 'name': self.product.name, + 'product_id': self.product.id, + 'product_uom_qty': 1, + 'product_uom': unit_12.id, + 'price_unit': 18, + 'tax_id': False, # no love taxes amls + })], + }) + so_2.action_confirm() + so_2.picking_ids.move_lines.quantity_done = 12 + so_2.picking_ids.button_validate() + + # Invoice the sale order. + invoice_2 = so_2._create_invoices() + invoice_2.action_post() + + # Invoice 2 + + # Correct Journal Items + + # Name Debit Credit + + # Product Sales 0.00$ 18.0$ + # Account Receivable 18.00$ 0.0$ + # Default Account Stock Out 0.00$ 12.0$ + # Expenses 12.00$ 0.0$ + + aml = invoice_2.line_ids + # Product Sales + self.assertEqual(aml[0].debit, 0.0) + self.assertEqual(aml[0].credit, 18.0) + # Account Receivable + self.assertEqual(aml[1].debit, 18.0) + self.assertEqual(aml[1].credit, 0.0) + # Default Account Stock Out + self.assertEqual(aml[2].debit, 0.0) + self.assertEqual(aml[2].credit, 12.0) + # Expenses + self.assertEqual(aml[3].debit, 12.0) + self.assertEqual(aml[3].credit, 0.0)