From 1628ec5a77cb3ef03e54952cf7c3c38b6199ff17 Mon Sep 17 00:00:00 2001 From: Adrien Widart Date: Wed, 28 Jul 2021 09:31:18 +0000 Subject: [PATCH] [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 --- addons/sale/models/sale_order_line.py | 9 ++++--- addons/sale/tests/test_sale_order.py | 39 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/addons/sale/models/sale_order_line.py b/addons/sale/models/sale_order_line.py index 9de364a3a99..e214b31b8a7 100644 --- a/addons/sale/models/sale_order_line.py +++ b/addons/sale/models/sale_order_line.py @@ -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'] diff --git a/addons/sale/tests/test_sale_order.py b/addons/sale/tests/test_sale_order.py index 05009fd9f94..803c2f7a720 100644 --- a/addons/sale/tests/test_sale_order.py +++ b/addons/sale/tests/test_sale_order.py @@ -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)