[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) <whe@odoo.com>
Signed-off-by: Adrien Widart (awt) <awt@odoo.com>
This commit is contained in:
Adrien Widart (awt)
2023-12-04 10:21:34 +00:00
parent f744e90fc4
commit f512f8a650
2 changed files with 83 additions and 4 deletions
@@ -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)
@@ -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