diff options
| author | Adrien Widart <awt@odoo.com> | 2021-04-15 08:08:07 +0000 |
|---|---|---|
| committer | Adrien Widart <awt@odoo.com> | 2021-04-30 08:13:11 +0000 |
| commit | 77b7a23c3c759b33e26bf91faf14a10f02449a91 (patch) | |
| tree | 5278e80f0b6ea64ad27f73075004dc4c16e2ad85 /addons/purchase_stock | |
| parent | 89e419036e33ebdb2628895f88019da41c87da79 (diff) | |
[FIX] purchase_stock: consider product unit price precision
When changing the product price precision, this can lead to incorrect
stock valuations.
To reproduce the error:
(Enable debug mode)
1. Go to Settings > Technical > Database Strucutre > Decimal Accuracy
2. Edit Product Price:
- Digits: 4
3. Create a Product Category PC:
- Costing Method: FIFO
4. Create a Product P:
- Product Type: Storable Product
- Product Category: PC
5. Create a RfQ with product P:
- Quantity: 1000
- Unit Price: 0.035
6. Confirm Order, Receive Products, Validate
7. Click on Valuation
Error: The total value is equal to $40 instead of $35. The calculation
was done after rounding the unit price: $0.035 becomes $0.04, then
1000*0.04=$40.
When confirming the RfQ, a stock move is created. To do so, the method
`_get_stock_move_price_unit` is called. When validating the delivery, it
recomputes the unit price thanks to method `_get_price_unit`. In both
situation, and if the line has taxes, the method `compute_all` is called
like this:
```python
price_unit = line.taxes_id.with_context(round=False).compute_all(price_unit,
currency=line.order_id.currency_id, quantity=1.0)['total_void']
```
But here is the problem. In this method, total amount is computed with
this line:
```python
base = currency.round(price_unit * quantity)
```
However, `quantity` is equal to 1 and the multiplication is rounded
using the currency precision. As a result, `base` is equal to $0.04.
Then, all computations will use this value and will be incorrect.
This fix applies the real quantity so `base` will have the correct
value:
```
base = currency.round(price_unit * quantity)
= currency.round(0.035 * 1000)
= 35
```
OPW-2472192
closes odoo/odoo#70080
X-original-commit: a57dc07189c15296055d6a07ca6d0bd350c4f11a
Signed-off-by: William Henrotin <Whenrow@users.noreply.github.com>
Diffstat (limited to 'addons/purchase_stock')
| -rw-r--r-- | addons/purchase_stock/models/purchase.py | 7 | ||||
| -rw-r--r-- | addons/purchase_stock/models/stock.py | 6 | ||||
| -rw-r--r-- | addons/purchase_stock/tests/test_fifo_price.py | 45 |
3 files changed, 55 insertions, 3 deletions
diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py index b5e923d2b50..1a3c4987b0c 100644 --- a/addons/purchase_stock/models/purchase.py +++ b/addons/purchase_stock/models/purchase.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, SUPERUSER_ID, _ -from odoo.tools.float_utils import float_compare +from odoo.tools.float_utils import float_compare, float_round from datetime import datetime from dateutil.relativedelta import relativedelta from odoo.exceptions import UserError @@ -382,10 +382,13 @@ class PurchaseOrderLine(models.Model): line = self[0] order = line.order_id price_unit = line.price_unit + price_unit_prec = self.env['decimal.precision'].precision_get('Product Price') if line.taxes_id: + qty = line.product_qty or 1 price_unit = line.taxes_id.with_context(round=False).compute_all( - price_unit, currency=line.order_id.currency_id, quantity=1.0, product=line.product_id, partner=line.order_id.partner_id + price_unit, currency=line.order_id.currency_id, quantity=qty, product=line.product_id, partner=line.order_id.partner_id )['total_void'] + price_unit = float_round(price_unit / qty, precision_digits=price_unit_prec) if line.product_uom.id != line.product_id.uom_id.id: price_unit *= line.product_uom.factor / line.product_id.uom_id.factor if order.currency_id != order.company_id.currency_id: diff --git a/addons/purchase_stock/models/stock.py b/addons/purchase_stock/models/stock.py index 2aadedd4638..867026e986b 100644 --- a/addons/purchase_stock/models/stock.py +++ b/addons/purchase_stock/models/stock.py @@ -2,6 +2,7 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import api, fields, models, _ +from odoo.tools.float_utils import float_round class StockPicking(models.Model): @@ -36,11 +37,14 @@ class StockMove(models.Model): """ Returns the unit price for the move""" self.ensure_one() if self.purchase_line_id and self.product_id.id == self.purchase_line_id.product_id.id: + price_unit_prec = self.env['decimal.precision'].precision_get('Product Price') line = self.purchase_line_id order = line.order_id price_unit = line.price_unit if line.taxes_id: - price_unit = line.taxes_id.with_context(round=False).compute_all(price_unit, currency=line.order_id.currency_id, quantity=1.0)['total_void'] + qty = line.product_qty or 1 + price_unit = line.taxes_id.with_context(round=False).compute_all(price_unit, currency=line.order_id.currency_id, quantity=qty)['total_void'] + price_unit = float_round(price_unit / qty, precision_digits=price_unit_prec) if line.product_uom.id != line.product_id.uom_id.id: price_unit *= line.product_uom.factor / line.product_id.uom_id.factor if order.currency_id != order.company_id.currency_id: diff --git a/addons/purchase_stock/tests/test_fifo_price.py b/addons/purchase_stock/tests/test_fifo_price.py index 8125bf65715..848d967c7bf 100644 --- a/addons/purchase_stock/tests/test_fifo_price.py +++ b/addons/purchase_stock/tests/test_fifo_price.py @@ -319,3 +319,48 @@ class TestFifoPrice(ValuationReconciliationTestCommon): original_out_move = outgoing_shipment_neg.move_lines[0] self.assertEqual(original_out_move.product_id.value_svl, 12000.0, 'Value of the move should be 12000') self.assertEqual(original_out_move.product_id.qty_available, 150.0, 'Qty available should be 150') + + def test_01_test_fifo(self): + """" This test ensures that unit price keeps its decimal precision """ + + unit_price_precision = self.env['ir.model.data'].xmlid_to_object('product.decimal_price') + unit_price_precision.digits = 3 + + tax = self.env["account.tax"].create({ + "name": "Dummy Tax", + "amount": "0.00", + "type_tax_use": "purchase", + }) + + super_product = self.env['product.product'].create({ + 'name': 'Super Product', + 'type': 'product', + 'categ_id': self.stock_account_product_categ.id, + 'standard_price': 0.035, + }) + self.assertEqual(super_product.cost_method, 'fifo') + self.assertEqual(super_product.valuation, 'real_time') + + purchase_order = self.env['purchase.order'].create({ + 'partner_id': self.env.ref('base.res_partner_3').id, + 'order_line': [(0, 0, { + 'name': super_product.name, + 'product_id': super_product.id, + 'product_qty': 1000, + 'product_uom': super_product.uom_id.id, + 'price_unit': super_product.standard_price, + 'date_planned': time.strftime('%Y-%m-%d'), + 'taxes_id': [(4, tax.id)], + })], + }) + + purchase_order.button_confirm() + self.assertEqual(purchase_order.state, 'purchase') + + picking = purchase_order.picking_ids[0] + res = picking.button_validate() + Form(self.env[res['res_model']].with_context(res['context'])).save().process() + + self.assertEqual(super_product.standard_price, 0.035) + self.assertEqual(super_product.value_svl, 35.0) + self.assertEqual(picking.move_lines.price_unit, 0.035) |
