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