From 74ba8039c7e300bb2adeda8cd071df4272b3635a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Voet=20=28ryv=29?= <ryv@odoo.com> Date: Thu, 4 Feb 2021 16:15:41 +0000 Subject: [PATCH] [FIX] stock_account: fix rounding adjustment This fix (odoo/odoo#46850) was too permissive and fix too much stock valuation issues (other than rounding error) under the hood. The problem of the "too permissive" is will hide some errors without get any feedback about them. Also, it changes the SVL value without any explanation. To avoid the first issue, apply a threshold on the rounding adjustment. This threshold is '(quantity_out * smallest_value_of_the_currency) / 2' (`smallest_value_of_the_currency` = `rounding` field of the related currency). Also to improve debugging, add a line in the description about the rounding adjustment done in a SVL task-2452786 closes odoo/odoo#65567 Signed-off-by: William Henrotin <Whenrow@users.noreply.github.com> --- addons/stock_account/models/product.py | 24 +++++++++++++------ addons/stock_account/models/stock_move.py | 1 + .../tests/test_stockvaluationlayer.py | 21 ++++++++++++++-- 3 files changed, 37 insertions(+), 9 deletions(-) diff --git a/addons/stock_account/models/product.py b/addons/stock_account/models/product.py index 86f563b00abe..cc1dd966ec50 100644 --- a/addons/stock_account/models/product.py +++ b/addons/stock_account/models/product.py @@ -3,7 +3,7 @@ from odoo import api, fields, models, tools, _ from odoo.exceptions import UserError -from odoo.tools import float_is_zero +from odoo.tools import float_is_zero, float_repr from odoo.exceptions import ValidationError @@ -161,12 +161,22 @@ class ProductProduct(models.Model): if self.cost_method in ('average', 'fifo'): fifo_vals = self._run_fifo(abs(quantity), company) vals['remaining_qty'] = fifo_vals.get('remaining_qty') - # in case of AVCO, fix rounding issue of standard price when needed. + # In case of AVCO, fix rounding issue of standard price when needed. if self.cost_method == 'average': - rounding_error = self.standard_price * self.quantity_svl - self.value_svl - vals['value'] += self.env.company.currency_id.round(rounding_error) - if self.quantity_svl: - vals['unit_cost'] = self.value_svl / self.quantity_svl + currency = self.env.company.currency_id + rounding_error = currency.round(self.standard_price * self.quantity_svl - self.value_svl) + if rounding_error: + # If it is bigger than the (smallest number of the currency * quantity) / 2, + # then it isn't a rounding error but a stock valuation error, we shouldn't fix it under the hood ... + if abs(rounding_error) <= (abs(quantity) * currency.rounding) / 2: + vals['value'] += rounding_error + vals['rounding_adjustment'] = '\nRounding Adjustment: %s%s %s' % ( + '+' if rounding_error > 0 else '', + float_repr(rounding_error, precision_digits=currency.decimal_places), + currency.symbol + ) + if self.quantity_svl: + vals['unit_cost'] = self.value_svl / self.quantity_svl if self.cost_method == 'fifo': vals.update(fifo_vals) return vals @@ -438,7 +448,7 @@ class ProductProduct(models.Model): # FIXME: create an empty layer to track the change? continue svsl_vals = product._prepare_out_svl_vals(product.quantity_svl, self.env.company) - svsl_vals['description'] = description + svsl_vals['description'] = description + svsl_vals.pop('rounding_adjustment', '') svsl_vals['company_id'] = self.env.company.id empty_stock_svl_list.append(svsl_vals) return empty_stock_svl_list, products_orig_quantity_svl, impacted_products diff --git a/addons/stock_account/models/stock_move.py b/addons/stock_account/models/stock_move.py index 558769556f5d..d83e5d570904 100644 --- a/addons/stock_account/models/stock_move.py +++ b/addons/stock_account/models/stock_move.py @@ -182,6 +182,7 @@ class StockMove(models.Model): svl_vals.update(move._prepare_common_svl_vals()) if forced_quantity: svl_vals['description'] = 'Correction of %s (modification of past move)' % move.picking_id.name or move.name + svl_vals['description'] += svl_vals.pop('rounding_adjustment', '') svl_vals_list.append(svl_vals) return self.env['stock.valuation.layer'].sudo().create(svl_vals_list) diff --git a/addons/stock_account/tests/test_stockvaluationlayer.py b/addons/stock_account/tests/test_stockvaluationlayer.py index 652be87fc2b8..6a5c52607130 100644 --- a/addons/stock_account/tests/test_stockvaluationlayer.py +++ b/addons/stock_account/tests/test_stockvaluationlayer.py @@ -485,19 +485,36 @@ class TestStockValuationAVCO(TestStockValuationCommon): self.assertEqual(self.product1.quantity_svl, 2) self.assertEqual(self.product1.standard_price, 15) - def test_rounding_1(self): + def test_rounding_slv_1(self): move1 = self._make_in_move(self.product1, 1, unit_cost=1.00) move2 = self._make_in_move(self.product1, 1, unit_cost=1.00) move3 = self._make_in_move(self.product1, 1, unit_cost=1.01) self.assertAlmostEqual(self.product1.value_svl, 3.01) - move4 = self._make_out_move(self.product1, 3, create_picking=True) + move_out = self._make_out_move(self.product1, 3, create_picking=True) + + self.assertIn('Rounding Adjustment: -0.01', move_out.stock_valuation_layer_ids.description) self.assertEqual(self.product1.value_svl, 0) self.assertEqual(self.product1.quantity_svl, 0) self.assertEqual(self.product1.standard_price, 1.00) + def test_rounding_slv_2(self): + self._make_in_move(self.product1, 1, unit_cost=1.02) + self._make_in_move(self.product1, 1, unit_cost=1.00) + self._make_in_move(self.product1, 1, unit_cost=1.00) + + self.assertAlmostEqual(self.product1.value_svl, 3.02) + + move_out = self._make_out_move(self.product1, 3, create_picking=True) + + self.assertIn('Rounding Adjustment: +0.01', move_out.stock_valuation_layer_ids.description) + + self.assertEqual(self.product1.value_svl, 0) + self.assertEqual(self.product1.quantity_svl, 0) + self.assertEqual(self.product1.standard_price, 1.01) + class TestStockValuationFIFO(TestStockValuationCommon): def setUp(self): -- GitLab