details:   https://code.tryton.org/tryton/commit/f76f971c8b48
branch:    default
user:      Cédric Krier <[email protected]>
date:      Sat Mar 14 00:46:13 2026 +0100
description:
        Fill invoice source amounts instead of checking amounts from UBL

        Closes #14624
diffstat:

 modules/edocument_ubl/CHANGELOG                                      |    1 +
 modules/edocument_ubl/edocument.py                                   |  148 
++++-----
 modules/edocument_ubl/message.xml                                    |    6 -
 modules/edocument_ubl/tests/scenario_ubl_2_credit_note_parse.rst     |    5 +
 modules/edocument_ubl/tests/scenario_ubl_2_invoice_parse.rst         |    5 +
 modules/edocument_ubl/tests/scenario_ubl_2_invoice_parse_trivial.rst |    4 +
 6 files changed, 85 insertions(+), 84 deletions(-)

diffs (260 lines):

diff -r 5d5cf986829c -r f76f971c8b48 modules/edocument_ubl/CHANGELOG
--- a/modules/edocument_ubl/CHANGELOG   Fri Mar 13 23:53:22 2026 +0100
+++ b/modules/edocument_ubl/CHANGELOG   Sat Mar 14 00:46:13 2026 +0100
@@ -1,3 +1,4 @@
+* Fill invoice source amounts instead of checking amounts
 * Filter the MIME type of the additional documents allowed by the specification
 * Use VATEX as tax exemption reason code
 * Add support for Python 3.14
diff -r 5d5cf986829c -r f76f971c8b48 modules/edocument_ubl/edocument.py
--- a/modules/edocument_ubl/edocument.py        Fri Mar 13 23:53:22 2026 +0100
+++ b/modules/edocument_ubl/edocument.py        Sat Mar 14 00:46:13 2026 +0100
@@ -282,7 +282,6 @@
         invoice, attachments = cls.parser(namespace)(root)
         invoice.save()
         invoice.update_taxes()
-        cls.checker(namespace)(root, invoice)
         attachments = list(attachments)
         for attachment in attachments:
             attachment.resource = invoice
@@ -299,15 +298,6 @@
             }.get(namespace)
 
     @classmethod
-    def checker(cls, namespace):
-        return {
-            'urn:oasis:names:specification:ubl:schema:xsd:Invoice-2': (
-                cls._check_invoice_2),
-            'urn:oasis:names:specification:ubl:schema:xsd:CreditNote-2': (
-                cls._check_credit_note_2),
-            }.get(namespace)
-
-    @classmethod
     def _parse_invoice_2(cls, root):
         pool = Pool()
         Invoice = pool.get('account.invoice')
@@ -406,6 +396,41 @@
                     './{*}LegalMonetaryTotal/{*}PayableRoundingAmount')
                     is not None)):
             invoice.cash_rounding = True
+
+        if tax_exlusive_amount := root.findtext(
+                './{*}LegalMonetaryTotal/{*}TaxExclusiveAmount'):
+            untaxed_amount = Decimal(tax_exlusive_amount)
+
+            if allowance_total_amount := root.findtext(
+                    './{*}LegalMonetaryTotal/{*}AllowanceTotalAmount'):
+                allowance_total_amount = Decimal(allowance_total_amount)
+                untaxed_amount -= allowance_total_amount
+
+            if charge_total_amount := root.findtext(
+                    './{*}LegalMonetaryTotal/{*}ChargeTotalAmount'):
+                charge_total_amount = Decimal(charge_total_amount)
+                untaxed_amount += charge_total_amount
+
+            invoice.source_untaxed_amount = untaxed_amount
+
+        tax_amount = sum(Decimal(amount.text) for amount in root.iterfind(
+                './{*}TaxTotal/{*}TaxAmount'))
+        invoice.source_tax_amount = tax_amount
+
+        payable_amount = Decimal(
+            root.findtext('./{*}LegalMonetaryTotal/{*}PayableAmount'))
+        prepaid_amount = Decimal(
+            root.findtext('./{*}LegalMonetaryTotal/{*}PrepaidAmount')
+            or 0)
+        total_amount = payable_amount + prepaid_amount
+        if not getattr(invoice, 'cash_rounding', False):
+            payable_rounding_amount = Decimal(
+                root.findtext(
+                    './{*}LegalMonetaryTotal/{*}PayableRoundingAmount')
+                or 0)
+            total_amount -= payable_rounding_amount
+        invoice.source_total_amount = total_amount
+
         return invoice, cls._parse_2_attachments(root)
 
     @classmethod
@@ -682,6 +707,41 @@
                     './{*}LegalMonetaryTotal/{*}PayableRoundingAmount')
                 is not None):
             invoice.cash_rounding = True
+
+        if tax_exlusive_amount := root.findtext(
+                './{*}LegalMonetaryTotal/{*}TaxExclusiveAmount'):
+            untaxed_amount = Decimal(tax_exlusive_amount)
+
+            if allowance_total_amount := root.findtext(
+                    './{*}LegalMonetaryTotal/{*}AllowanceTotalAmount'):
+                allowance_total_amount = Decimal(allowance_total_amount)
+                untaxed_amount -= allowance_total_amount
+
+            if charge_total_amount := root.findtext(
+                    './{*}LegalMonetaryTotal/{*}ChargeTotalAmount'):
+                charge_total_amount = Decimal(charge_total_amount)
+                untaxed_amount += charge_total_amount
+
+            invoice.source_untaxed_amount = -untaxed_amount
+
+        tax_amount = sum(Decimal(amount.text) for amount in root.iterfind(
+                './{*}TaxTotal/{*}TaxAmount'))
+        invoice.source_tax_amount = -tax_amount
+
+        payable_amount = Decimal(
+            root.findtext('./{*}LegalMonetaryTotal/{*}PayableAmount'))
+        prepaid_amount = Decimal(
+            root.findtext('./{*}LegalMonetaryTotal/{*}PrepaidAmount')
+            or 0)
+        total_amount = payable_amount + prepaid_amount
+        if not getattr(invoice, 'cash_rounding', False):
+            payable_rounding_amount = Decimal(
+                root.findtext(
+                    './{*}LegalMonetaryTotal/{*}PayableRoundingAmount')
+                or 0)
+            total_amount -= payable_rounding_amount
+        invoice.source_total_amount = -total_amount
+
         return invoice, cls._parse_2_attachments(root)
 
     @classmethod
@@ -1259,74 +1319,6 @@
                 attachment.name = name
                 yield attachment
 
-    @classmethod
-    def _check_invoice_2(cls, root, invoice):
-        pool = Pool()
-        Lang = pool.get('ir.lang')
-        lang = Lang.get()
-
-        payable_amount = Decimal(
-            root.findtext('./{*}LegalMonetaryTotal/{*}PayableAmount'))
-        prepaid_amount = Decimal(
-            root.findtext('./{*}LegalMonetaryTotal/{*}PrepaidAmount')
-            or 0)
-        amount = payable_amount + prepaid_amount
-        if not getattr(invoice, 'cash_rounding', False):
-            payable_rounding_amount = Decimal(
-                root.findtext(
-                    './{*}LegalMonetaryTotal/{*}PayableRoundingAmount')
-                or 0)
-            amount -= payable_rounding_amount
-        if invoice.total_amount != amount:
-            raise InvoiceError(gettext(
-                    'edocument_ubl.msg_invoice_total_amount_different',
-                    invoice=invoice.rec_name,
-                    total_amount=lang.format_number(invoice.total_amount),
-                    amount=lang.format_number(amount)))
-
-        tax_total = sum(Decimal(amount.text) for amount in root.iterfind(
-                './{*}TaxTotal/{*}TaxAmount'))
-        if invoice.tax_amount != tax_total:
-            raise InvoiceError(gettext(
-                    'edocument_ubl.msg_invoice_tax_amount_different',
-                    invoice=invoice.rec_name,
-                    tax_amount=lang.format_number(invoice.tax_amount),
-                    tax_total=lang.format_number(tax_total)))
-
-    @classmethod
-    def _check_credit_note_2(cls, root, invoice):
-        pool = Pool()
-        Lang = pool.get('ir.lang')
-        lang = Lang.get()
-
-        payable_amount = Decimal(
-            root.findtext('./{*}LegalMonetaryTotal/{*}PayableAmount'))
-        prepaid_amount = Decimal(
-            root.findtext('./{*}LegalMonetaryTotal/{*}PrepaidAmount')
-            or 0)
-        amount = payable_amount + prepaid_amount
-        if not getattr(invoice, 'cash_rounding', False):
-            payable_rounding_amount = Decimal(
-                root.findtext(
-                    './{*}LegalMonetaryTotal/{*}PayableRoundingAmount')
-                or 0)
-            amount -= payable_rounding_amount
-        if -invoice.total_amount != amount:
-            raise InvoiceError(gettext(
-                    'edocument_ubl.msg_invoice_total_amount_different',
-                    invoice=invoice.rec_name,
-                    total_amount=lang.format_number(-invoice.total_amount),
-                    amount=lang.format_number(amount)))
-
-        tax_total = sum(Decimal(amount.text) for amount in root.iterfind(
-                './{*}TaxTotal/{*}TaxAmount'))
-        if -invoice.tax_amount != tax_total:
-            raise InvoiceError(gettext(
-                    'edocument_ubl.msg_invoice_tax_amount_different',
-                    invoice=invoice.rec_name,
-                    tax_amount=lang.format_number(-invoice.tax_amount),
-                    tax_total=lang.format_number(tax_total)))
-
 
 class Invoice_Bank(metaclass=PoolMeta):
     __name__ = 'edocument.ubl.invoice'
diff -r 5d5cf986829c -r f76f971c8b48 modules/edocument_ubl/message.xml
--- a/modules/edocument_ubl/message.xml Fri Mar 13 23:53:22 2026 +0100
+++ b/modules/edocument_ubl/message.xml Sat Mar 14 00:46:13 2026 +0100
@@ -24,11 +24,5 @@
             <field name="text">Could not find tax for:
 %(tax_category)s</field>
         </record>
-        <record model="ir.message" id="msg_invoice_total_amount_different">
-            <field name="text">The total amount %(total_amount)s of the 
invoice "%(invoice)s" is different from the amount %(amount)s.</field>
-        </record>
-        <record model="ir.message" id="msg_invoice_tax_amount_different">
-            <field name="text">The tax amount %(tax_amount)s of the invoice 
"%(invoice)s" is different from the tax total %(tax_total)s.</field>
-        </record>
     </data>
 </tryton>
diff -r 5d5cf986829c -r f76f971c8b48 
modules/edocument_ubl/tests/scenario_ubl_2_credit_note_parse.rst
--- a/modules/edocument_ubl/tests/scenario_ubl_2_credit_note_parse.rst  Fri Mar 
13 23:53:22 2026 +0100
+++ b/modules/edocument_ubl/tests/scenario_ubl_2_credit_note_parse.rst  Sat Mar 
14 00:46:13 2026 +0100
@@ -79,6 +79,11 @@
     ...     Decimal('-1729.00') if cash_rounding else Decimal('-1728.70'))
     >>> invoice.tax_amount
     Decimal('-292.20')
+    >>> assertEqual(invoice.source_untaxed_amount, Decimal('-1436.50'))
+    >>> assertEqual(invoice.source_tax_amount, Decimal('-292.20'))
+    >>> assertEqual(
+    ...     invoice.source_total_amount,
+    ...     Decimal('-1729.00') if cash_rounding else Decimal('-1728.70'))
     >>> len(invoice.lines)
     7
 
diff -r 5d5cf986829c -r f76f971c8b48 
modules/edocument_ubl/tests/scenario_ubl_2_invoice_parse.rst
--- a/modules/edocument_ubl/tests/scenario_ubl_2_invoice_parse.rst      Fri Mar 
13 23:53:22 2026 +0100
+++ b/modules/edocument_ubl/tests/scenario_ubl_2_invoice_parse.rst      Sat Mar 
14 00:46:13 2026 +0100
@@ -79,6 +79,11 @@
     ...     Decimal('1729.00') if cash_rounding else Decimal('1728.70'))
     >>> invoice.tax_amount
     Decimal('292.20')
+    >>> assertEqual(invoice.source_untaxed_amount, Decimal('1436.50'))
+    >>> assertEqual(invoice.source_tax_amount, Decimal('292.20'))
+    >>> assertEqual(
+    ...     invoice.source_total_amount,
+    ...     Decimal('1729.00') if cash_rounding else Decimal('1728.70'))
     >>> len(invoice.lines)
     7
 
diff -r 5d5cf986829c -r f76f971c8b48 
modules/edocument_ubl/tests/scenario_ubl_2_invoice_parse_trivial.rst
--- a/modules/edocument_ubl/tests/scenario_ubl_2_invoice_parse_trivial.rst      
Fri Mar 13 23:53:22 2026 +0100
+++ b/modules/edocument_ubl/tests/scenario_ubl_2_invoice_parse_trivial.rst      
Sat Mar 14 00:46:13 2026 +0100
@@ -5,6 +5,7 @@
 Imports::
 
     >>> import datetime as dt
+    >>> from decimal import Decimal
 
     >>> from proteus import Model
     >>> from trytond.modules.account.tests.tools import create_chart
@@ -46,6 +47,9 @@
     >>> assertEqual(invoice.company, company)
     >>> invoice.total_amount
     Decimal('100.00')
+    >>> invoice.source_untaxed_amount
+    >>> assertEqual(invoice.source_tax_amount, Decimal('0.00'))
+    >>> assertEqual(invoice.source_total_amount, Decimal('100.00'))
     >>> line, = invoice.lines
     >>> line.description
     'Cotter pin, MIL-SPEC'

Reply via email to