[FIX] account: invoice totals using Cash Discount and price-included taxes

## Summary

Currently, when using both an early payment discount (upon invoice) and
price-included taxes, the invoice totals are not calculated correctly.

## Steps to Reproduce

1. Set `Cash Discount Tax Reduction` to `Always (upon invoice)`.
2. Create an invoice with the payment term set to `2/7 Net 30`.
3. Add an invoice line with a price of 100 and a 21% price-included tax.
4. Save the invoice: The `Untaxed Amount` should be 100, but it
   incorrectly shows 100.35 instead.

## Cause

When calculating the totals, the price-included tax is applied to one of
the early payment discount lines. Since the tax is price-included, the
system recalculates a new base price for the EPD line, which ultimately
leads to an incorrect `Untaxed Amount`.

opw-3239904

Enterprise PR: odoo/enterprise#39007

closes odoo/odoo#119892

X-original-commit: 43b78a96a8961b83cf1c18f58bd549dabc90c50f
Related: odoo/enterprise#40404
Signed-off-by: Nshimiyimana Serge Séna (sesn) <sesn@odoo.com>
Signed-off-by: Brice Bartoletti (bib) <bib@odoo.com>
Co-authored-by: Laurent Smet <las@odoo.com>
This commit is contained in:
Nshimiyimana Séna
2023-04-27 08:09:39 +02:00
co-authored by Laurent Smet
parent 1502c6cf52
commit cfeffc3c74
3 changed files with 79 additions and 32 deletions
+3 -10
View File
@@ -784,7 +784,7 @@ class AccountTax(models.Model):
partner=None, currency=None, product=None, taxes=None, price_unit=None, quantity=None,
discount=None, account=None, analytic_distribution=None, price_subtotal=None,
is_refund=False, rate=None,
handle_price_include=None,
handle_price_include=True,
extra_context=None,
):
return {
@@ -881,12 +881,6 @@ class AccountTax(models.Model):
price_unit_after_discount = remaining_part_to_consider * price_unit_after_discount
if taxes:
if handle_price_include is None:
manage_price_include = bool(base_line['handle_price_include'])
else:
manage_price_include = handle_price_include
taxes_res = taxes.with_context(**base_line['extra_context']).compute_all(
price_unit_after_discount,
currency=currency,
@@ -894,7 +888,7 @@ class AccountTax(models.Model):
product=base_line['product'],
partner=base_line['partner'],
is_refund=base_line['is_refund'],
handle_price_include=manage_price_include,
handle_price_include=base_line['handle_price_include'],
include_caba_tags=include_caba_tags,
)
@@ -912,7 +906,7 @@ class AccountTax(models.Model):
product=base_line['product'],
partner=base_line['partner'],
is_refund=base_line['is_refund'],
handle_price_include=manage_price_include,
handle_price_include=base_line['handle_price_include'],
include_caba_tags=include_caba_tags,
)
for tax_res, new_taxes_res in zip(taxes_res['taxes'], new_taxes_res['taxes']):
@@ -1082,7 +1076,6 @@ class AccountTax(models.Model):
for base_line in base_lines:
to_update_vals, tax_values_list = self._compute_taxes_for_single_line(
base_line,
handle_price_include=handle_price_include,
include_caba_tags=include_caba_tags,
)
to_process.append((base_line, to_update_vals, tax_values_list))
@@ -24,6 +24,24 @@ class TestAccountEarlyPaymentDiscount(AccountTestInvoicingCommon):
})]
})
def assert_tax_totals(self, document, expected_values):
main_keys_to_ignore = {
'formatted_amount_total', 'formatted_amount_untaxed', 'display_tax_base', 'subtotals_order'}
group_keys_to_ignore = {'group_key', 'tax_group_id', 'tax_group_name',
'formatted_tax_group_amount', 'formatted_tax_group_base_amount'}
subtotals_keys_to_ignore = {'formatted_amount'}
to_compare = document.copy()
for key in main_keys_to_ignore:
del to_compare[key]
for key in group_keys_to_ignore:
for groups in to_compare['groups_by_subtotal'].values():
for group in groups:
del group[key]
for key in subtotals_keys_to_ignore:
for subtotal in to_compare['subtotals']:
del subtotal[key]
self.assertEqual(to_compare, expected_values)
# ========================== Tests Payment Terms ==========================
def test_early_payment_end_date(self):
inv_1200_10_percents_discount_no_tax = self.env['account.move'].create({
@@ -326,22 +344,6 @@ class TestAccountEarlyPaymentDiscount(AccountTestInvoicingCommon):
])
def test_mixed_epd_with_draft_invoice(self):
def assert_tax_totals(document, expected_values):
main_keys_to_ignore = {'formatted_amount_total', 'formatted_amount_untaxed', 'display_tax_base', 'subtotals_order'}
group_keys_to_ignore = {'group_key', 'tax_group_id', 'tax_group_name', 'formatted_tax_group_amount', 'formatted_tax_group_base_amount'}
subtotals_keys_to_ignore = {'formatted_amount'}
to_compare = document
for key in main_keys_to_ignore:
del to_compare[key]
for key in group_keys_to_ignore:
for groups in to_compare['groups_by_subtotal'].values():
for group in groups:
del group[key]
for key in subtotals_keys_to_ignore:
for subtotal in to_compare['subtotals']:
del subtotal[key]
self.assertEqual(to_compare, expected_values)
self.early_pay_10_percents_10_days.early_pay_discount_computation = 'mixed'
tax = self.env['account.tax'].create({
'name': 'WonderTax',
@@ -357,7 +359,7 @@ class TestAccountEarlyPaymentDiscount(AccountTestInvoicingCommon):
line_form.quantity = 1
line_form.tax_ids.clear()
line_form.tax_ids.add(tax)
assert_tax_totals(invoice._values['tax_totals'], {
self.assert_tax_totals(invoice._values['tax_totals'], {
'amount_untaxed': 1000,
'amount_total': 1090,
'groups_by_subtotal': {
@@ -511,3 +513,52 @@ class TestAccountEarlyPaymentDiscount(AccountTestInvoicingCommon):
{'balance': -200.0},
{'balance': 200.0},
])
def test_mixed_epd_with_tax_included(self):
early_pay_2_percents_10_days = self.env['account.payment.term'].create({
'name': '2% discount if paid within 10 days',
'company_id': self.company_data['company'].id,
'early_discount': True,
'discount_percentage': 2,
'discount_days': 10,
'early_pay_discount_computation': 'mixed',
'line_ids': [Command.create({
'value': 'percent',
'nb_days': 0,
'value_amount': 100,
})]
})
tax = self.env['account.tax'].create({
'name': 'Tax 21% included',
'amount': 21,
'price_include': True,
})
with Form(self.env['account.move'].with_context(default_move_type='out_invoice')) as invoice:
invoice.partner_id = self.partner_a
invoice.invoice_date = fields.Date.from_string('2022-02-21')
invoice.invoice_payment_term_id = early_pay_2_percents_10_days
with invoice.invoice_line_ids.new() as line_form:
line_form.product_id = self.product_a
line_form.price_unit = 121
line_form.quantity = 1
line_form.tax_ids.clear()
line_form.tax_ids.add(tax)
self.assert_tax_totals(invoice._values['tax_totals'], {
'amount_untaxed': 100,
'amount_total': 120.58,
'groups_by_subtotal': {
'Untaxed Amount': [
{
'tax_group_amount': 20.58,
'tax_group_base_amount': 98,
},
],
},
'subtotals': [
{
'name': "Untaxed Amount",
'amount': 100,
}
],
})
@@ -287,12 +287,15 @@ class SaleAdvancePaymentInv(models.TransientModel):
order_lines = order.order_line.filtered(lambda l: not l.display_type)
base_downpayment_lines_values = self._prepare_base_downpayment_line_values(order)
tax_base_line_dicts = [line._convert_to_tax_base_line_dict(
analytic_distribution=line.analytic_distribution) for line in order_lines]
tax_base_line_dicts = [
line._convert_to_tax_base_line_dict(
analytic_distribution=line.analytic_distribution,
handle_price_include=False
)
for line in order_lines
]
computed_taxes = self.env['account.tax']._compute_taxes(
tax_base_line_dicts,
handle_price_include=False
)
tax_base_line_dicts)
down_payment_values = []
for line, tax_repartition in computed_taxes['base_lines_to_update']:
taxes = line['taxes'].flatten_taxes_hierarchy()