From cfeffc3c7427c6fd4e545936b77c6f25d8b08119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nshimiyimana=20S=C3=A9na?= Date: Fri, 31 Mar 2023 07:09:40 +0000 Subject: [PATCH] [FIX] account: invoice totals using Cash Discount and price-included taxes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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) Signed-off-by: Brice Bartoletti (bib) Co-authored-by: Laurent Smet --- addons/account/models/account_tax.py | 13 +-- .../tests/test_early_payment_discount.py | 85 +++++++++++++++---- .../sale/wizard/sale_make_invoice_advance.py | 13 +-- 3 files changed, 79 insertions(+), 32 deletions(-) diff --git a/addons/account/models/account_tax.py b/addons/account/models/account_tax.py index 2d6f81a70d3..ddfb87cb674 100644 --- a/addons/account/models/account_tax.py +++ b/addons/account/models/account_tax.py @@ -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)) diff --git a/addons/account/tests/test_early_payment_discount.py b/addons/account/tests/test_early_payment_discount.py index 2dba3ffab4c..00fe85a6826 100644 --- a/addons/account/tests/test_early_payment_discount.py +++ b/addons/account/tests/test_early_payment_discount.py @@ -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, + } + ], + }) diff --git a/addons/sale/wizard/sale_make_invoice_advance.py b/addons/sale/wizard/sale_make_invoice_advance.py index a061788b79c..ec1801b7162 100644 --- a/addons/sale/wizard/sale_make_invoice_advance.py +++ b/addons/sale/wizard/sale_make_invoice_advance.py @@ -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()