From f512f8a6506e615aea2c4c5574c6b9b1d38b4f1b Mon Sep 17 00:00:00 2001 From: "Adrien Widart (awt)" Date: Fri, 1 Dec 2023 10:57:25 +0100 Subject: [PATCH] [FIX] purchase_stock: compute pdiff based on bill date Multi-currency setup. On a bill, if the bill date and the accounting date are not the same, there will be some errors in the stock valuation. To reproduce the issue: (Company in USD) 1. Enable EUR, with specific rates: - Today: 100 EUR = 150 USD - One day ago: 100 EUR = 130 USD - Two days ago: 100 EUR = 125 USD 2. Create an auto-avco storable product P 3. Confirm a PO with 1 x P at 100 EUR 4. Receive the product 5. Bill it: - Bill date: two days ago - Accounting date: one day ago 6. Open the inventory valuation Error: a price diff layer has been created, which makes sense, but its value is incorrect: -20 USD. It should be -25 USD. When computing the price difference, we use the bill value at accounting date (130 USD). This is incorrect, it should be based on the bill date (125 USD). OPW-3544318 closes odoo/odoo#144649 X-original-commit: 2d3d01594e09297f74d657c0ab3a0181e3754dc5 Signed-off-by: William Henrotin (whe) Signed-off-by: Adrien Widart (awt) --- .../models/account_move_line.py | 6 +- .../tests/test_stockvaluation.py | 81 ++++++++++++++++++- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/addons/purchase_stock/models/account_move_line.py b/addons/purchase_stock/models/account_move_line.py index c8e18da5210..7d8a2deded1 100644 --- a/addons/purchase_stock/models/account_move_line.py +++ b/addons/purchase_stock/models/account_move_line.py @@ -203,20 +203,20 @@ class AccountMoveLine(models.Model): aml = self aml_gross_price_unit = aml._get_gross_unit_price() - aml_price_unit = aml.currency_id._convert(aml_gross_price_unit, aml.company_id.currency_id, aml.company_id, aml.date, round=False) + aml_price_unit = aml.currency_id._convert(aml_gross_price_unit, aml.company_id.currency_id, aml.company_id, aml.move_id.invoice_date, round=False) aml_price_unit = aml.product_uom_id._compute_price(aml_price_unit, product_uom) unit_valuation_difference = aml_price_unit - layer_price_unit # Generate the AML values for the already out quantities - unit_valuation_difference_curr = self.company_id.currency_id._convert(unit_valuation_difference, self.currency_id, self.company_id, self.date, round=False) + unit_valuation_difference_curr = self.company_id.currency_id._convert(unit_valuation_difference, self.currency_id, self.company_id, self.move_id.invoice_date, round=False) unit_valuation_difference_curr = product_uom._compute_price(unit_valuation_difference_curr, self.product_uom_id) out_qty_to_invoice = product_uom._compute_quantity(out_qty_to_invoice, self.product_uom_id) if not float_is_zero(unit_valuation_difference_curr * out_qty_to_invoice, precision_rounding=self.currency_id.rounding): aml_vals_list += self._prepare_pdiff_aml_vals(out_qty_to_invoice, unit_valuation_difference_curr) # Generate the SVL values for the on hand quantities (and impact the parent layer) - po_pu_curr = po_line.currency_id._convert(po_line.price_unit, self.currency_id, self.company_id, self.date, round=False) + po_pu_curr = po_line.currency_id._convert(po_line.price_unit, self.currency_id, self.company_id, self.move_id.invoice_date, round=False) price_difference_curr = po_pu_curr - aml_gross_price_unit if not float_is_zero(unit_valuation_difference * qty_to_correct, precision_rounding=self.company_id.currency_id.rounding): svl_vals = self._prepare_pdiff_svl_vals(layer, sign * qty_to_correct, unit_valuation_difference, price_difference_curr) diff --git a/addons/purchase_stock/tests/test_stockvaluation.py b/addons/purchase_stock/tests/test_stockvaluation.py index 768df660140..8f9862a8e18 100644 --- a/addons/purchase_stock/tests/test_stockvaluation.py +++ b/addons/purchase_stock/tests/test_stockvaluation.py @@ -2,7 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. import time -from datetime import datetime +from datetime import datetime, timedelta from freezegun import freeze_time from unittest.mock import patch @@ -2624,6 +2624,85 @@ class TestStockValuationWithCOA(AccountTestInvoicingCommon): self.assertEqual(svls.mapped('remaining_value'), [13.0, 12.0, 0.0, 0.0, 0.0, 0.0]) self.assertEqual(svls.mapped('value'), [10.0, 10.0, 1.0, 2.0, -1.0, 3.0]) + def test_pdiff_multi_curr_and_rates(self): + """ + Company in USD. + Today: 100 EUR = 150 USD + One day ago: 100 EUR = 130 USD + Two days ago: 100 EUR = 125 USD + Buy and receive one auto-AVCO product at 100 EUR. Bill it with: + - Bill date: two days ago (125 USD) + - Accounting date: one day ago (130 USD) + The value at bill date should be used for both bill value and price + diff value. + """ + usd_currency = self.env.ref('base.USD') + eur_currency = self.env.ref('base.EUR') + + today = fields.Date.today() + one_day_ago = today - timedelta(days=1) + two_days_ago = today - timedelta(days=2) + + self.env.company.currency_id = usd_currency.id + + self.env['res.currency.rate'].search([]).unlink() + self.env['res.currency.rate'].create([{ + 'name': day.strftime('%Y-%m-%d'), + 'rate': 1 / rate, + 'currency_id': eur_currency.id, + 'company_id': self.env.company.id, + } for (day, rate) in [ + (today, 1.5), + (one_day_ago, 1.3), + (two_days_ago, 1.25), + ]]) + + self.product1.product_tmpl_id.categ_id.property_cost_method = 'average' + + po = self.env['purchase.order'].create({ + 'partner_id': self.partner_id.id, + 'currency_id': eur_currency.id, + 'order_line': [ + (0, 0, { + 'name': self.product1.name, + 'product_id': self.product1.id, + 'product_qty': 1.0, + 'product_uom': self.product1.uom_po_id.id, + 'price_unit': 100.0, + 'taxes_id': False, + }), + ], + }) + po.button_confirm() + + receipt = po.picking_ids + receipt.move_ids.move_line_ids.quantity = 1.0 + receipt.button_validate() + + layer = receipt.move_ids.stock_valuation_layer_ids + self.assertEqual(layer.value, 150) + + action = po.action_create_invoice() + bill = self.env["account.move"].browse(action["res_id"]) + bill.invoice_date = two_days_ago + bill.date = one_day_ago + bill.action_post() + + pdiff_layer = layer.stock_valuation_layer_ids + self.assertEqual(pdiff_layer.value, -25) + self.assertEqual(layer.remaining_value, 125) + + in_stock_amls = self.env['account.move.line'].search([ + ('product_id', '=', self.product1.id), + ('account_id', '=', self.stock_input_account.id), + ], order='id') + self.assertRecordValues(in_stock_amls, [ + # pylint: disable=bad-whitespace + {'date': today, 'debit': 0, 'credit': 150, 'reconciled': True}, + {'date': one_day_ago, 'debit': 125, 'credit': 0, 'reconciled': True}, + {'date': one_day_ago, 'debit': 25, 'credit': 0, 'reconciled': True}, + ]) + def test_invoice_on_ordered_qty_with_backorder_and_different_currency_automated(self): """Create a PO with currency different from the company currency. Set the product to be invoiced on ordered quantities. Receive partially the products