From b7fd44bee1a5d7228650f9733c5372a09de0fdb8 Mon Sep 17 00:00:00 2001 From: Sven Fuehr Date: Tue, 10 Oct 2023 20:45:55 +0000 Subject: [PATCH] [FIX] account: respect modified invoice tax amount in EDI XML MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There is the following problem when editing the tax amounts on an invoice (in quick-edit mode or via the journal item): The changed amounts are displayed correctly on the PDF but not in the EDI XMLs (e.g. the embedded factur-x). This commit corrects this. Reproduce 1. Create a new invoice 2. In tab "Invoice Lines" add 2 lines with taxes from different tax group 3. Go to tab "Journal Items" and change the tax amounts (or use quick-edit mode; needs to be enabled in the settings) 4. Go back to tab "Invoice Lines" and notice the tax amounts of the groups were changed. 5. Generate a PDF: Here the tax amounts are correct (the changed amounts) 6. Look at the embedded XML: Here the tax amounts are wrong (initial / unchanged amounts). The same issue applies to multiple other EDI exports. To activate the EDIs go to: Accounting > Configuration > Journals > Customer Invoices > Advanced Settings tab > Electronic Data Interchange The EDIs can then be found as attachment in the chatter after confirming and/or printing an invoice. task-3535411 closes odoo/odoo#151090 X-original-commit: 5d456ce13970980efa8b1efab92323e995eaa848 Signed-off-by: Laurent Smet (las) Signed-off-by: Sven Führ (svfu) --- addons/account/models/account_move.py | 67 ++++++ .../bis3_export_with_changed_taxes.xml | 209 ++++++++++++++++++ .../tests/test_xml_ubl_be.py | 59 +++++ 3 files changed, 335 insertions(+) create mode 100644 addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_export_with_changed_taxes.xml diff --git a/addons/account/models/account_move.py b/addons/account/models/account_move.py index d7615838a11..723bc2d604a 100644 --- a/addons/account/models/account_move.py +++ b/addons/account/models/account_move.py @@ -8,6 +8,7 @@ from json import dumps import logging from markupsafe import Markup from psycopg2 import OperationalError +import math import re from textwrap import shorten from unittest.mock import patch @@ -3185,6 +3186,72 @@ class AccountMove(models.Model): to_update_vals, tax_values_list = self.env['account.tax']._compute_taxes_for_single_line(base_line) to_process.append((base_line, to_update_vals, tax_values_list)) + # Handle manually changed tax amounts (via quick-edit or journal entry manipulation): + # For each tax repartition line we compute the difference between the following 2 amounts + # * Manual tax amount: + # The sum of the amounts on the tax lines belonging to the tax repartition line. + # These amounts may have been manually changed. + # * Computed tax amount: + # The sum of the amounts on the items in 'tax_values_list' in 'to_process' belonging to the tax repartition line. + # This difference is then distributed evenly across the 'tax_values_list' in 'to_process' + # such that the manual and computed tax amounts match. + # The updated tax information is later used by '_aggregate_taxes' to compute the right tax amounts (consistently on all levels). + tax_lines = self.line_ids.filtered(lambda x: x.display_type == 'tax') + sign = -1 if self.is_inbound(include_receipts=True) else 1 + + # Collect the tax_amount_currency/balance from tax lines. + current_tax_amount_per_rep_line = {} + for tax_line in tax_lines: + tax_rep_amounts = current_tax_amount_per_rep_line.setdefault(tax_line.tax_repartition_line_id.id, { + 'tax_amount_currency': 0.0, + 'tax_amount': 0.0, + }) + tax_rep_amounts['tax_amount_currency'] += sign * tax_line.amount_currency + tax_rep_amounts['tax_amount'] += sign * tax_line.balance + + # Collect the computed tax_amount_currency/tax_amount from the taxes computation. + tax_details_per_rep_line = {} + for _base_line, _to_update_vals, tax_values_list in to_process: + for tax_values in tax_values_list: + tax_rep_id = tax_values['tax_repartition_line_id'] + tax_rep_amounts = tax_details_per_rep_line.setdefault(tax_rep_id, { + 'tax_amount_currency': 0.0, + 'tax_amount': 0.0, + 'distribute_on': [], + }) + tax_rep_amounts['tax_amount_currency'] += tax_values['tax_amount_currency'] + tax_rep_amounts['tax_amount'] += tax_values['tax_amount'] + tax_rep_amounts['distribute_on'].append(tax_values) + + # Dispatch the delta on tax_values. + for key, currency in (('tax_amount_currency', self.currency_id), ('tax_amount', self.company_currency_id)): + for tax_rep_id, computed_tax_rep_amounts in tax_details_per_rep_line.items(): + current_tax_rep_amounts = current_tax_amount_per_rep_line.get(tax_rep_id, computed_tax_rep_amounts) + diff = current_tax_rep_amounts[key] - computed_tax_rep_amounts[key] + abs_diff = abs(diff) + + if currency.is_zero(abs_diff): + continue + + diff_sign = -1 if diff < 0 else 1 + nb_error = math.ceil(abs_diff / currency.rounding) + nb_cents_per_tax_values = math.floor(nb_error / len(computed_tax_rep_amounts['distribute_on'])) + nb_extra_cent = nb_error % len(computed_tax_rep_amounts['distribute_on']) + for tax_values in computed_tax_rep_amounts['distribute_on']: + + if currency.is_zero(abs_diff): + break + + nb_amount_curr_cent = nb_cents_per_tax_values + if nb_extra_cent: + nb_amount_curr_cent += 1 + nb_extra_cent -= 1 + + # We can have more than one cent to distribute on a single tax_values. + abs_delta_to_add = min(abs_diff, currency.rounding * nb_amount_curr_cent) + tax_values[key] += diff_sign * abs_delta_to_add + abs_diff -= abs_delta_to_add + return self.env['account.tax']._aggregate_taxes( to_process, filter_tax_values_to_apply=filter_tax_values_to_apply, diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_export_with_changed_taxes.xml b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_export_with_changed_taxes.xml new file mode 100644 index 00000000000..282898442b8 --- /dev/null +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_files/from_odoo/bis3_export_with_changed_taxes.xml @@ -0,0 +1,209 @@ + + + urn:cen.eu:en16931:2017#compliant#urn:fdc:peppol.eu:2017:poacc:billing:3.0 + urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 + ___ignore___ + 2017-01-01 + 2017-02-28 + 380 + test narration + USD + ref_partner_2 + + ref_move + + + INV_2017_00001.pdf + + ___ignore___ + + + + + 0202239951 + + partner_1 + + + Chaussée de Namur 40 + Ramillies + 1367 + + BE + + + + BE0202239951 + + VAT + + + + partner_1 + BE0202239951 + + + partner_1 + + + + + + 0477472701 + + partner_2 + + + Rue des Bourlottes 9 + Ramillies + 1367 + + BE + + + + BE0477472701 + + VAT + + + + partner_2 + BE0477472701 + + + partner_2 + + + + + + + Rue des Bourlottes 9 + Ramillies + 1367 + + BE + + + + + + 30 + ___ignore___ + + BE15001559627230 + + + + Payment terms: 30% Advance End of Following Month + + + 108.02 + + 400.00 + 84.03 + + S + 21.0 + + VAT + + + + + 200.00 + 23.99 + + S + 12.0 + + VAT + + + + + + 600.00 + 600.00 + 708.02 + 0.00 + 708.02 + + + 1 + 1.0 + 200.00 + + product_a + product_a + + S + 21.0 + + VAT + + + + + 200.0 + + + + 2 + 1.0 + 200.00 + + product_a + product_a + + S + 21.0 + + VAT + + + + + 200.0 + + + + 3 + 1.0 + 100.00 + + product_b + product_b + + S + 12.0 + + VAT + + + + + 100.0 + + + + 4 + 1.0 + 100.00 + + product_b + product_b + + S + 12.0 + + VAT + + + + + 100.0 + + + diff --git a/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py b/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py index 8607f879f50..401ae2cb90c 100644 --- a/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py +++ b/addons/l10n_account_edi_ubl_cii_tests/tests/test_xml_ubl_be.py @@ -425,6 +425,65 @@ class TestUBLBE(TestUBLCommon, TestAccountMoveSendCommon): ) self._assert_invoice_attachment(invoice.ubl_cii_xml_id, None, 'from_odoo/bis3_pay_term_ecotax.xml') + def test_export_with_changed_taxes(self): + invoice = self._generate_move( + self.partner_1, + self.partner_2, + send=False, + move_type='out_invoice', + invoice_line_ids=[ + { + 'product_id': self.product_a.id, + 'quantity': 1, + 'price_unit': 200, + 'tax_ids': [Command.set([self.tax_21.id])], + }, + { + 'product_id': self.product_a.id, + 'quantity': 1, + 'price_unit': 200, + 'tax_ids': [Command.set([self.tax_21.id])], + }, + { + 'product_id': self.product_b.id, + 'quantity': 1, + 'price_unit': 100, + 'tax_ids': [Command.set([self.tax_12.id])], + }, + { + 'product_id': self.product_b.id, + 'quantity': 1, + 'price_unit': 100, + 'tax_ids': [Command.set([self.tax_12.id])], + }, + ], + ) + self.assertRecordValues(invoice, [{ + 'amount_untaxed': 600.00, + 'amount_tax': 108.00, # tax_12: 24.00 ; tax_21: 84.00 + 'amount_total': 708.00 + }]) + + invoice.button_draft() + tax_lines = invoice.line_ids.filtered(lambda line: line.display_type == 'tax') + tax_line_21 = next((line for line in tax_lines if line.name == 'tax_21')) + tax_line_12 = next((line for line in tax_lines if line.name == 'tax_12')) + invoice.line_ids = [ + Command.update(tax_line_21.id, {'amount_currency': -84.03}), # distribute 3 cents over 2 lines + Command.update(tax_line_12.id, {'amount_currency': -23.99}), # distribute -1 cent over 2 lines + ] + + invoice.action_post() + invoice._generate_pdf_and_send_invoice(self.move_template) + + self.assertRecordValues(invoice, [{ + 'amount_untaxed': 600.00, + 'amount_tax': 108.02, # tax_12: 23.99 ; tax_21: 84.03 + 'amount_total': 708.02 + }]) + + self._assert_invoice_attachment(invoice.ubl_cii_xml_id, None, 'from_odoo/bis3_export_with_changed_taxes.xml') + #################################################### # Test import ####################################################