From 1306f2a33966bc7b664d21b51a4980e25234f9f2 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Wed, 19 Oct 2022 17:15:56 +0200 Subject: [PATCH] export xrechnung 2.1 ok + test --- edocument.py | 69 +++++++++++++++++- locale/de.po | 12 +++ message.xml | 9 +++ template/XRechnung-2.2/XRechnung.xml | 105 +++++++++++++-------------- tests/test_edocument.py | 15 ++++ tryton.cfg | 2 +- 6 files changed, 152 insertions(+), 60 deletions(-) diff --git a/edocument.py b/edocument.py index a61ea94..44b4123 100644 --- a/edocument.py +++ b/edocument.py @@ -15,25 +15,88 @@ class Invoice(Invoice): 'EDocument XRechnung' __name__ = 'edocument.xrechnung.invoice' + def sales_order_nums(self): + """ get string of sale-numbers + """ + if getattr(self.invoice, 'sales', None) is not None: + return ', '.join([x.number for x in self.invoice.sales]) + def prepaid_amount(self, invoice): """ compute already paid amount """ return invoice.total_amount - invoice.amount_to_pay + def invoice_note(self): + """ get 'description' + 'comment' + """ + notes = [] + if self.invoice.description: + notes.append(self.invoice.description) + + if self.invoice.comment: + notes.extend(self.invoice.comment.split('\n')) + if len(notes) > 0: + return '; '.join(notes) + + def invoice_line_tax(self, line): + """ get tax of invoice-line, + fire exception if no/multiple taxes exists + """ + if len(line.invoice_taxes) != 1: + raise UserError(gettext( + 'edocument_xrechnung.msg_linetax_invalid_number', + linename = line.rec_name, + numtax = len(line.invoice_taxes), + )) + + allowed_cat = ['AE', 'L', 'M', 'E', 'S', 'Z', 'G', 'O', 'K', 'B'] + if not line.invoice_taxes[0].tax.unece_category_code in allowed_cat: + raise UserError(gettext( + 'edocument_xrechnung.msg_linetax_invalid_catcode', + taxname = line.invoice_taxes[0].tax.rec_name, + allowed = ', '.join(allowed_cat), + )) + + return line.invoice_taxes[0].tax + + def taxident_data(self, tax_identifier): + """ get tax-scheme-id and codes + """ + result = { + 'code': None, + 'id': None, + } + + if tax_identifier: + if tax_identifier.type == 'de_vat': + result['code'] = 'DE%s' % tax_identifier.code + result['id'] = 'VAT' + return result + def tax_rate(self, tax): """ get tax-rate in procent """ - return (tax.tax.rate * Decimal('100.0')).quantize(Decimal('0.01')) + return (tax.rate * Decimal('100.0')).quantize(Decimal('0.01')) + + def uom_unece_code(self, line): + """ 'line': invoice.line + """ + if len(line.unit.unece_code or '') == 0: + raise UserError(gettext( + 'edocument_xrechnung.msg_uom_code_missing', + uomname = line.unit.rec_name, + )) + return line.unit.unece_code def tax_category_code(self, tax): """ read tax-category, fire exception if missing """ - if len(tax.tax.unece_category_code or '') == 0: + if len(tax.unece_category_code or '') == 0: raise UserError(gettext( 'edocument_xrechnung.mds_tax_category_missing', taxname = tax.rec_name, )) - return tax.tax.unece_category_code + return tax.unece_category_code def quote_text(self, text): """ replace critical chars diff --git a/locale/de.po b/locale/de.po index 691c523..8eed6b4 100644 --- a/locale/de.po +++ b/locale/de.po @@ -14,6 +14,18 @@ msgctxt "model:ir.message,text:mds_tax_category_missing" msgid "The UNECE tax category is not configured for tax '%(taxname)s'." msgstr "Für die Steuer '%(taxname)s' ist die UNECE-Steuerkategorie nicht konfiguriert." +msgctxt "model:ir.message,text:msg_uom_code_missing" +msgid "The UNECE uom code is not configured for unit '%(uomname)s'." +msgstr "Für die Einheit '%(uomname)s' ist der UNECE-Einheitencode nicht konfiguriert." + +msgctxt "model:ir.message,text:msg_linetax_invalid_number" +msgid "The invoice line '%(linename)s' must have exactly one tax (number of taxes currently: %(numtax)d)." +msgstr "Die Rechnungszeile '%(linename)s' muß genau eine Steuer haben (Anzahl Steuern derzeit: %(numtax)d)." + +msgctxt "model:ir.message,text:msg_linetax_invalid_catcode" +msgid "Invalid category code at tax '%(taxname)s' (allowed: %(allowed)s)." +msgstr "Ungültiger Kategoriecode an der Steuer '%(taxname)s' (erlaubt: %(allowed)s)." + ####################### # party.configuration # diff --git a/message.xml b/message.xml index ec96f4c..4105e7c 100644 --- a/message.xml +++ b/message.xml @@ -11,6 +11,15 @@ full copyright notices and license terms. --> The UNECE tax category is not configured for tax '%(taxname)s'. + + The UNECE uom code is not configured for unit '%(uomname)s'. + + + The invoice line '%(linename)s' must have exactly one tax (number of taxes currently: %(numtax)d). + + + Invalid category code at tax '%(taxname)s' (allowed: %(allowed)s). + diff --git a/template/XRechnung-2.2/XRechnung.xml b/template/XRechnung-2.2/XRechnung.xml index 6c6d5f8..b07ad43 100644 --- a/template/XRechnung-2.2/XRechnung.xml +++ b/template/XRechnung-2.2/XRechnung.xml @@ -17,56 +17,88 @@ ${getattr(getattr(value, 'country', None), 'code', None)} + - ${getattr(value, 'code', None)} + ${this.taxident_data(value)['code']} - VAT + ${this.taxident_data(value)['id']} + ${value.name} ${value.phone} ${value.email} + ${value.name} - ${getattr(this.seller_trade_tax_identifier, 'code', None)} + ${this.taxident_data(this.seller_trade_tax_identifier)['code']} + ${value.bank_accounts[0].numbers[0].number_compact} ${value.name} + + + ${this.tax_category_code(value)} + ${this.tax_rate(value)} + ${value.legal_notice or '-'} + + VAT + + + ${value.base} ${value.amount} - ${this.tax_category_code(value)} - ${this.tax_rate(value)} - - VAT - + ${TaxCategory(value.tax, True)} + + + ${value.id} + ${value.quantity} + ${value.amount} + + ${value.description or getattr(value.product, 'name', None)} + + ${value.product.code} + + + ${TaxCategory(this.invoice_line_tax(value))} + + + + ${value.unit_price} + + + + urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_2.1 ${this.invoice.number} ${this.invoice.invoice_date.isoformat()} ${(getattr(this.invoice, 'payment_term_date', None) or this.invoice.invoice_date).isoformat()} ${this.type_code} + ${this.invoice_note()} ${this.invoice.currency.code} ${this.invoice.party.get_xrechnung_route_id()} - - bestell-nr - auftrags-nr + + ${this.invoice.reference} + ${this.sales_order_nums()} - + vertrags-nr - + proj-referenz @@ -137,50 +169,11 @@ ${this.invoice.untaxed_amount} ${this.invoice.total_amount} 0.00 - 0.00 + 0.00 ${this.prepaid_amount(this.invoice)} ${this.invoice.amount_to_pay} - - 1 - 0.5 - 35.00 - - Reparaturdienstleistung in Stunden - - REP-012 - - - S - 19.00 - - VAT - - - - - 70.00 - - - - 2 - 3 - 5.25 - - Material - - MAT-987 - - - S - 19.00 - - VAT - - - - - 1.75 - - + + ${InvoiceLine(line)} + diff --git a/tests/test_edocument.py b/tests/test_edocument.py index fcae41f..c2cdad8 100644 --- a/tests/test_edocument.py +++ b/tests/test_edocument.py @@ -7,6 +7,7 @@ from trytond.tests.test_tryton import ModuleTestCase, with_transaction from trytond.pool import Pool from trytond.modules.edocument_uncefact.tests.test_edocument_uncefact import get_invoice from unittest.mock import Mock, MagicMock +from decimal import Decimal class EdocTestCase(ModuleTestCase): @@ -21,9 +22,23 @@ class EdocTestCase(ModuleTestCase): Template = pool.get('edocument.xrechnung.invoice') Address = pool.get('party.address') Identifier = pool.get('party.identifier') + Party = pool.get('party.party') + Bank = pool.get('bank') + BankAccount = pool.get('bank.account') + BankNumber = pool.get('bank.account.number') invoice = get_invoice() invoice.party.get_xrechnung_route_id = Mock(return_value='xrechn-route-id-123') + invoice.company.party.bank_accounts = [ + Mock(spec=BankAccount, + currency=invoice.currency, + bank=Mock(spec=Bank, party=Mock(spec=Party, name='Bank')), + owners = [invoice.company.party], + numbers = [Mock(spec=BankNumber, type='other', number='123456')], + )] + invoice.description = 'description of invoice' + invoice.comment = 'note line 1\nnote line 2' + invoice.taxes[0].tax.rate = Decimal('0.1') invoice.identifiers = [ Mock(spec=Identifier, type='edoc_route_id', diff --git a/tryton.cfg b/tryton.cfg index fec7d8f..5fc2311 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -3,7 +3,7 @@ version=6.0.0 depends: edocument_uncefact party -#extras_depend: + bank account_invoice xml: message.xml