[FIX] sale: round untaxed amount to invoice after computations

Suppose such a SO line:
- Unit Price: 541.26
- Qty: 38
- Discount: 2%

The line amount is computed thanks to application of the discount on the
price, then the multiplication of the result with the quantity. The
rounding eventually happens after these steps:
https://github.com/odoo/odoo/blob/254b2a0840d3e817cc6062c483b5213c10088af5/addons/sale/models/sale.py#L1013-L1024
https://github.com/odoo/odoo/blob/b76e9ef658bde0178fa1660b6ad27b880e91632a/addons/account/models/account.py#L1138
Therefore, in the above case, we have:
```
Total = round(541.26 * 0.98 * 38)
      = round(530.4348 * 38)
      = round(20156.5224)
      = 20156.5224
```

However, when confirming the SO and setting the delivered quantity, the
Untaxed Amount To Invoice is computed using the unit price with both the
discount and the rounding already applied:
```
Total = round(541.26 * 0.98) * 38
      = round(530.4348) * 38
      = 530.43 * 38
      = 20156.34
```
As a result, the amount is not the same than the first one, this is
incorrect.

OPW-2525975

closes odoo/odoo#74709

X-original-commit: 014a40448ca7854646d929ab369f92c6dede1eb4
Signed-off-by: Adrien Widart <adwid@users.noreply.github.com>
This commit is contained in:
Adrien Widart
2021-08-04 13:31:09 +00:00
parent db6f32246c
commit 1628ec5a77
2 changed files with 44 additions and 4 deletions
+5 -4
View File
@@ -479,16 +479,17 @@ class SaleOrderLine(models.Model):
# amount and not zero. Since we compute untaxed amount, we can use directly the price
# reduce (to include discount) without using `compute_all()` method on taxes.
price_subtotal = 0.0
umo_qty_to_consider = line.qty_delivered if line.product_id.invoice_policy == 'delivery' else line.product_uom_qty
price_subtotal = line.price_reduce * umo_qty_to_consider
uom_qty_to_consider = line.qty_delivered if line.product_id.invoice_policy == 'delivery' else line.product_uom_qty
price_reduce = line.price_unit * (1 - (line.discount or 0.0) / 100.0)
price_subtotal = price_reduce * uom_qty_to_consider
if len(line.tax_id.filtered(lambda tax: tax.price_include)) > 0:
# As included taxes are not excluded from the computed subtotal, `compute_all()` method
# has to be called to retrieve the subtotal without them.
# `price_reduce_taxexcl` cannot be used as it is computed from `price_subtotal` field. (see upper Note)
price_subtotal = line.tax_id.compute_all(
line.price_reduce,
price_reduce,
currency=line.order_id.currency_id,
quantity=umo_qty_to_consider,
quantity=uom_qty_to_consider,
product=line.product_id,
partner=line.order_id.partner_shipping_id)['total_excluded']
+39
View File
@@ -697,3 +697,42 @@ class TestSaleOrder(TestSaleCommon):
self.sale_order.action_unlock()
self.assertEqual(self.sale_order.state, 'sale')
def test_discount_and_untaxed_subtotal(self):
"""When adding a discount on a SO line, this test ensures that the untaxed amount to invoice is
equal to the untaxed subtotal"""
self.product_a.invoice_policy = 'delivery'
sale_order = self.env['sale.order'].create({
'partner_id': self.partner_a.id,
'order_line': [(0, 0, {
'product_id': self.product_a.id,
'product_uom_qty': 38,
'price_unit': 541.26,
'discount': 2.00,
})]
})
sale_order.action_confirm()
line = sale_order.order_line
self.assertEqual(line.untaxed_amount_to_invoice, 0)
line.qty_delivered = 38
# (541.26 - 0.02 * 541.26) * 38 = 20156.5224 ~= 20156.52
self.assertEqual(line.price_subtotal, 20156.52)
self.assertEqual(line.untaxed_amount_to_invoice, line.price_subtotal)
# Same with an included-in-price tax
sale_order = sale_order.copy()
line = sale_order.order_line
line.tax_id = [(0, 0, {
'name': 'Super Tax',
'amount_type': 'percent',
'amount': 15.0,
'price_include': True,
})]
sale_order.action_confirm()
self.assertEqual(line.untaxed_amount_to_invoice, 0)
line.qty_delivered = 38
# (541,26 / 1,15) * ,98 * 38 = 17527,410782609 ~= 17527.41
self.assertEqual(line.price_subtotal, 17527.41)
self.assertEqual(line.untaxed_amount_to_invoice, line.price_subtotal)