Skip to content
Snippets Groups Projects
Commit a76fb7ff authored by David (dafr)'s avatar David (dafr)
Browse files

[FIX] stock_account: Compensate rounding error on small quantities


To reproduce the issue:
1. Create a product:
   - Type: Storable
   - Category:
     - Costing Method: AVCO
2. IN 2 @ 4.63/u
3. IN 5 @ 3.04/u
4. OUT 0.1
5. Repeat step 4 70 time in total, so that the final quantity is 0
6. Open the inventory valuation of the product

Error: the total value is $-0.04 instead of $0.
The compensation of rounding issue is stuck by a check that ensure we don't erase a valuation error.

However, this check can never succeed with small quantities, here is the mathematical proof:
°Current check: rounding_error <= qty * curr_rounding / 2
With:
1) rounding_error >= curr_rounding
2) 0 < qty < 2
3) v1 = rounding_error / curr_rounding
4) v2 = qty / 2
We can be sure that:
5) v1 >= 1 because of 1)
6) v2 < 1 because of 2)
7) v1 > v2 because of 5) and 6)

° rounding_error <= qty * curr_rounding / 2
° rounding_error * curr_rounding <= qty * curr_rounding / 2 * curr_rounding
° rounding_error / curr_rounding <= qty / 2
° v1 <= v2
==>> This contradict 7), hence this check can never be True for qty < 2

To fix this issue, we change the check to:
 ° rounding_error <= qty * curr_rounding / 2 OR rounding_error <= curr_rounding
 Where the 1st part doesn't change, but the second one is true when rounding_error == curr_rounding

closes odoo/odoo#115516

Signed-off-by: default avatarWilliam Henrotin (whe) <whe@odoo.com>
parent c5305de9
No related branches found
No related tags found
No related merge requests found
......@@ -199,7 +199,7 @@ class ProductProduct(models.Model):
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:
if abs(rounding_error) <= max((abs(quantity) * currency.rounding) / 2, currency.rounding):
vals['value'] += rounding_error
vals['rounding_adjustment'] = '\nRounding Adjustment: %s%s %s' % (
'+' if rounding_error > 0 else '',
......
......@@ -554,6 +554,22 @@ class TestStockValuationAVCO(TestStockValuationCommon):
self.assertEqual(self.product1.value_svl, 0)
def test_rounding_svl_4(self):
"""
The first 2 In moves result in a rounded standard_price at 3.4943, which is rounded at 3.49.
This test ensures that no rounding error is generated with small out quantities.
"""
self.product1.categ_id.property_cost_method = 'average'
self._make_in_move(self.product1, 2, unit_cost=4.63)
self._make_in_move(self.product1, 5, unit_cost=3.04)
self.assertEqual(self.product1.standard_price, 3.49)
for _ in range(70):
self._make_out_move(self.product1, 0.1)
self.assertEqual(self.product1.quantity_svl, 0)
self.assertEqual(self.product1.value_svl, 0)
def test_return_delivery_2(self):
self.product1.write({"standard_price": 1})
move1 = self._make_out_move(self.product1, 10, create_picking=True, force_assign=True)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment