details:   https://code.tryton.org/tryton/commit/8cf722f70544
branch:    7.8
user:      Cédric Krier <[email protected]>
date:      Mon Jan 26 12:08:41 2026 +0100
description:
        Import UBL allowance/charge as invoice lines

        Closes #14524
        (grafted from c8c9393ee2491202b82cf6108d397305d89b7bdb)
diffstat:

 modules/edocument_ubl/edocument.py                               |  114 
+++++++++-
 modules/edocument_ubl/tests/scenario_ubl_2_credit_note_parse.rst |    2 +-
 modules/edocument_ubl/tests/scenario_ubl_2_invoice_parse.rst     |    2 +-
 3 files changed, 113 insertions(+), 5 deletions(-)

diffs (176 lines):

diff -r c867f00cd0a0 -r 8cf722f70544 modules/edocument_ubl/edocument.py
--- a/modules/edocument_ubl/edocument.py        Fri Jan 23 18:50:42 2026 +0100
+++ b/modules/edocument_ubl/edocument.py        Mon Jan 26 12:08:41 2026 +0100
@@ -323,11 +323,21 @@
 
         invoice.payment_term_date = cls._parse_2_payment_term_date(
             root.findall('./{*}PaymentTerms'))
-        invoice.lines = [
+        lines = [
             cls._parse_invoice_2_line(
-                line, company=invoice.company, currency=invoice.currency,
+                line,
+                company=invoice.company,
+                currency=invoice.currency,
                 supplier=supplier)
             for line in root.iterfind('./{*}InvoiceLine')]
+        lines += [
+            cls._parse_invoice_2_allowance_charge(
+                allowance_charge,
+                company=invoice.company,
+                currency=invoice.currency,
+                supplier=supplier)
+            for allowance_charge in root.iterfind('./{*}AllowanceCharge')]
+        invoice.lines = lines
         invoice.taxes = [
             cls._parse_2_tax(
                 tax, company=invoice.company)
@@ -458,6 +468,51 @@
         return line
 
     @classmethod
+    def _parse_invoice_2_allowance_charge(
+            cls, allowance_charge, company, currency, supplier=None):
+        pool = Pool()
+        AccountConfiguration = pool.get('account.configuration')
+        Line = pool.get('account.invoice.line')
+        Tax = pool.get('account.tax')
+
+        account_configuration = AccountConfiguration(1)
+
+        line = Line(
+            type='line', company=company, currency=currency, invoice_type='in')
+
+        indicator = allowance_charge.findtext('./{*}ChargeIndicator')
+        line.quantity = {'true': 1, 'false': -1}[indicator]
+        line.account = account_configuration.get_multivalue(
+            'default_category_account_expense',
+            company=company.id)
+        line.description = allowance_charge.findtext(
+            './{*}AllowanceChargeReason')
+        line.unit_price = round_price(Decimal(allowance_charge.findtext(
+                    './{*}Amount')))
+
+        taxes = []
+        tax_categories = allowance_charge.iterfind('./{*}TaxCategory')
+        for tax_category in tax_categories:
+            domain = cls._parse_2_tax_category(tax_category)
+            domain.extend([
+                    ['OR',
+                        ('group', '=', None),
+                        ('group.kind', 'in', ['purchase', 'both']),
+                        ],
+                    ('company', '=', company.id),
+                    ])
+            try:
+                tax, = Tax.search(domain, limit=1)
+            except ValueError:
+                raise InvoiceError(gettext(
+                        'edocument_ubl.msg_tax_not_found',
+                        tax_category=etree.tostring(
+                            tax_category, pretty_print=True).decode()))
+            taxes.append(tax)
+        line.taxes = taxes
+        return line
+
+    @classmethod
     def _parse_credit_note_2(cls, root):
         pool = Pool()
         Invoice = pool.get('account.invoice')
@@ -504,11 +559,19 @@
                         code=currency_code))
         invoice.payment_term_date = cls._parse_2_payment_term_date(
             root.findall('./{*}PaymentTerms'))
-        invoice.lines = [
+        lines = [
             cls._parse_credit_note_2_line(
                 line, company=invoice.company, currency=invoice.currency,
                 supplier=supplier)
             for line in root.iterfind('./{*}CreditNoteLine')]
+        lines += [
+            cls._parse_credit_note_2_allowance_charge(
+                allowance_charge,
+                company=invoice.company,
+                currency=invoice.currency,
+                supplier=supplier)
+            for allowance_charge in root.iterfind('./{*}AllowanceCharge')]
+        invoice.lines = lines
         invoice.taxes = [
             cls._parse_2_tax(
                 tax, company=invoice.company)
@@ -641,6 +704,51 @@
         return line
 
     @classmethod
+    def _parse_credit_note_2_allowance_charge(
+            cls, allowance_charge, company, currency, supplier=None):
+        pool = Pool()
+        AccountConfiguration = pool.get('account.configuration')
+        Line = pool.get('account.invoice.line')
+        Tax = pool.get('account.tax')
+
+        account_configuration = AccountConfiguration(1)
+
+        line = Line(
+            type='line', company=company, currency=currency, invoice_type='in')
+
+        indicator = allowance_charge.findtext('./{*}ChargeIndicator')
+        line.quantity = {'true': -1, 'false': 1}[indicator]
+        line.account = account_configuration.get_multivalue(
+            'default_category_account_expense',
+            company=company.id)
+        line.description = allowance_charge.findtext(
+            './{*}AllowanceChargeReason')
+        line.unit_price = round_price(Decimal(allowance_charge.findtext(
+                    './{*}Amount')))
+
+        taxes = []
+        tax_categories = allowance_charge.iterfind('./{*}TaxCategory')
+        for tax_category in tax_categories:
+            domain = cls._parse_2_tax_category(tax_category)
+            domain.extend([
+                    ['OR',
+                        ('group', '=', None),
+                        ('group.kind', 'in', ['purchase', 'both']),
+                        ],
+                    ('company', '=', company.id),
+                    ])
+            try:
+                tax, = Tax.search(domain, limit=1)
+            except ValueError:
+                raise InvoiceError(gettext(
+                        'edocument_ubl.msg_tax_not_found',
+                        tax_category=etree.tostring(
+                            tax_category, pretty_print=True).decode()))
+            taxes.append(tax)
+        line.taxes = taxes
+        return line
+
+    @classmethod
     def _parse_2_supplier(cls, supplier_party, create=False):
         pool = Pool()
         Party = pool.get('party.party')
diff -r c867f00cd0a0 -r 8cf722f70544 
modules/edocument_ubl/tests/scenario_ubl_2_credit_note_parse.rst
--- a/modules/edocument_ubl/tests/scenario_ubl_2_credit_note_parse.rst  Fri Jan 
23 18:50:42 2026 +0100
+++ b/modules/edocument_ubl/tests/scenario_ubl_2_credit_note_parse.rst  Mon Jan 
26 12:08:41 2026 +0100
@@ -80,7 +80,7 @@
     >>> invoice.tax_amount
     Decimal('-292.20')
     >>> len(invoice.lines)
-    5
+    7
 
     >>> attachments = Attachment.find([])
     >>> len(attachments)
diff -r c867f00cd0a0 -r 8cf722f70544 
modules/edocument_ubl/tests/scenario_ubl_2_invoice_parse.rst
--- a/modules/edocument_ubl/tests/scenario_ubl_2_invoice_parse.rst      Fri Jan 
23 18:50:42 2026 +0100
+++ b/modules/edocument_ubl/tests/scenario_ubl_2_invoice_parse.rst      Mon Jan 
26 12:08:41 2026 +0100
@@ -80,7 +80,7 @@
     >>> invoice.tax_amount
     Decimal('292.20')
     >>> len(invoice.lines)
-    5
+    7
 
     >>> attachments = Attachment.find([])
     >>> len(attachments)

Reply via email to