diff --git a/modules/edocument_ubl/CHANGELOG b/modules/edocument_ubl/CHANGELOG
index 2d549b8429..63d5151e31 100644
--- a/modules/edocument_ubl/CHANGELOG
+++ b/modules/edocument_ubl/CHANGELOG
@@ -1,3 +1,4 @@
+* Render allowance and charge
* Fill buyer's item identification
Version 7.8.0 - 2025-12-15
diff --git a/modules/edocument_ubl/edocument.py b/modules/edocument_ubl/edocument.py
index 45dfe1af78..637dd125ba 100644
--- a/modules/edocument_ubl/edocument.py
+++ b/modules/edocument_ubl/edocument.py
@@ -30,6 +30,9 @@ from .exceptions import InvoiceError
from .party import ISO6523_TYPES
ISO6523 = {v: k for k, v in ISO6523_TYPES.items()}
+PEPPOL_ALLOWANCES = {
+ '41', '42', '60', '62', '63', '64', '65', '66', '67', '68', '70', '71',
+ '88', '95', '100', '102', '103', '104', '105'}
if not hasattr(ASTCodeGenerator, 'visit_NameConstant'):
def visit_NameConstant(self, node):
@@ -217,7 +220,30 @@ class Invoice(Model):
@cached_property
def lines(self):
- return [l for l in self.invoice.lines if l.type == 'line']
+ return [
+ l for l in self.invoice.lines
+ if l.type == 'line' and not self.allowance_charge_code(l)]
+
+ @cached_property
+ def allowance_charge(self):
+ return [
+ l for l in self.invoice.lines
+ if l.type == 'line' and self.allowance_charge_code(l)]
+
+ @classmethod
+ def allowance_charge_code(cls, line, specification=None):
+ if product := line.product:
+ sequence_type = line.invoice.sequence_type
+ if ((line.quantity > 0 and sequence_type == 'invoice')
+ or (line.quantity < 0 and sequence_type == 'credit_note')):
+ return product.unece_special_service_code
+ elif ((line.quantity < 0 and sequence_type == 'invoice')
+ or (line.quantity < 0 and sequence_type == 'credit_note')):
+ if (specification or '').startswith('peppol'):
+ if (product.unece_allowance_charge_code
+ not in PEPPOL_ALLOWANCES):
+ return
+ return product.unece_allowance_charge_code
@classmethod
def parse(cls, document):
diff --git a/modules/edocument_ubl/template/2/CreditNote.xml b/modules/edocument_ubl/template/2/CreditNote.xml
index e26639b73a..5d3158962e 100644
--- a/modules/edocument_ubl/template/2/CreditNote.xml
+++ b/modules/edocument_ubl/template/2/CreditNote.xml
@@ -65,6 +65,17 @@ this repository contains the full copyright notices and license terms. -->
+
+ ${'false' if allowance_charge.quantity > 0 else 'true'}
+
+ ${reason_code}
+
+ ${allowance_charge.product.name}
+ ${abs(allowance_charge.amount)}
+
+ ${TaxCategory(tax)}
+
+
${-amount}
@@ -72,11 +83,7 @@ this repository contains the full copyright notices and license terms. -->
${-line.amount}
${format((line.tax.rate * 100).normalize(), 'f')}
- ${line.tax.unece_category_code}
- ${format((line.tax.rate * 100).normalize(), 'f')}
-
- ${line.tax.unece_code}
-
+ ${TaxCategory(line.tax)}
@@ -110,11 +117,7 @@ this repository contains the full copyright notices and license terms. -->
- ${tax.unece_category_code}
- ${format((tax.rate * 100).normalize(), 'f')}
-
- ${tax.unece_code}
-
+ ${TaxCategory(tax)}
diff --git a/modules/edocument_ubl/template/2/Invoice.xml b/modules/edocument_ubl/template/2/Invoice.xml
index 260328241e..a7e9fe8cd1 100644
--- a/modules/edocument_ubl/template/2/Invoice.xml
+++ b/modules/edocument_ubl/template/2/Invoice.xml
@@ -65,6 +65,17 @@ this repository contains the full copyright notices and license terms. -->
+
+ ${'true' if allowance_charge.quantity > 0 else 'false'}
+
+ ${reason_code}
+
+ ${allowance_charge.product.name}
+ ${abs(allowance_charge.amount)}
+
+ ${TaxCategory(tax)}
+
+
${amount}
@@ -72,11 +83,7 @@ this repository contains the full copyright notices and license terms. -->
${line.amount}
${format((line.tax.rate * 100).normalize(), 'f')}
- ${line.tax.unece_category_code}
- ${format((line.tax.rate * 100).normalize(), 'f')}
-
- ${line.tax.unece_code}
-
+ ${TaxCategory(line.tax)}
@@ -110,11 +117,7 @@ this repository contains the full copyright notices and license terms. -->
- ${tax.unece_category_code}
- ${format((tax.rate * 100).normalize(), 'f')}
-
- ${tax.unece_code}
-
+ ${TaxCategory(tax)}
diff --git a/modules/edocument_ubl/template/2/base.xml b/modules/edocument_ubl/template/2/base.xml
index d9a3d328ab..4b2d4502b5 100644
--- a/modules/edocument_ubl/template/2/base.xml
+++ b/modules/edocument_ubl/template/2/base.xml
@@ -77,4 +77,11 @@ this repository contains the full copyright notices and license terms. -->
${address.country.name}
+
+ ${tax.unece_category_code}
+ ${format((tax.rate * 100).normalize(), 'f')}
+
+ ${tax.unece_code}
+
+
diff --git a/modules/edocument_ubl/tests/test_module.py b/modules/edocument_ubl/tests/test_module.py
index d0cef47636..53139cbbcf 100644
--- a/modules/edocument_ubl/tests/test_module.py
+++ b/modules/edocument_ubl/tests/test_module.py
@@ -79,16 +79,22 @@ def get_invoice():
type='percentage',
rate=Decimal('.1'),
),
- base=Decimal('100.00'),
- amount=Decimal('10.00'),
+ base=Decimal('105.00'),
+ amount=Decimal('10.50'),
legal_notice="Legal Notice",
)]
product = Mock(spec=Product,
code="12345",
type='service',
+ unece_special_service_code=None,
)
product.name = "Product"
product.identifier_get.return_value = '98412345678908'
+ charge = Mock(spec=Product,
+ type='service',
+ unece_special_service_code='ZZZ',
+ )
+ charge.name = "Charge"
lines = [Mock(spec=InvoiceLine,
type='line',
product=product,
@@ -99,6 +105,12 @@ def get_invoice():
amount=Decimal('100.00'),
description="Description",
taxes=[t.tax for t in taxes],
+ ), Mock(spec=Invoice,
+ type='line',
+ product=charge,
+ quantity=1,
+ amount=Decimal('5.00'),
+ taxes=[t.tax for t in taxes],
)]
invoice = MagicMock(spec=Invoice,
id=-1,
@@ -120,17 +132,19 @@ def get_invoice():
lines=lines,
line_lines=lines,
taxes=taxes,
- untaxed_amount=Decimal('100.00'),
- tax_amount=Decimal('10.00'),
- total_amount=Decimal('110.00'),
- amount_to_pay=Decimal('110.00'),
+ untaxed_amount=Decimal('105.00'),
+ tax_amount=Decimal('10.50'),
+ total_amount=Decimal('110.50'),
+ amount_to_pay=Decimal('110.50'),
lines_to_pay=[Mock(spec=MoveLine,
maturity_date=datetime.date.today(),
- amount=Decimal('110.00'),
+ amount=Decimal('110.50'),
)],
sales=[],
state='posted',
)
+ for line in invoice.lines:
+ line.invoice = invoice
return invoice