From 28a96bc7c9fe2a5b13c5f35354964552cd2aa737 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Fri, 30 Jun 2023 15:29:51 +0200 Subject: [PATCH] add: creditnote --- edocument.py | 20 +- template/XRechnung-2.2/XRechnung_credit.xml | 182 ++++++++++++++++++ .../{XRechnung.xml => XRechnung_invoice.xml} | 8 +- tests/test_edocument.py | 55 +++++- 4 files changed, 259 insertions(+), 6 deletions(-) create mode 100644 template/XRechnung-2.2/XRechnung_credit.xml rename template/XRechnung-2.2/{XRechnung.xml => XRechnung_invoice.xml} (95%) diff --git a/edocument.py b/edocument.py index ef0ae91..077398b 100644 --- a/edocument.py +++ b/edocument.py @@ -22,6 +22,19 @@ class Invoice(Invoice): if getattr(self.invoice, 'sales', None) is not None: return ', '.join([x.number for x in self.invoice.sales]) + def negate_amount(self, amount): + """ amount * -1.0 + """ + if amount is not None and amount: + if isinstance(amount, Decimal): + return amount.copy_negate() + elif isinstance(amount, float): + return -1.0 * amount + elif isinstance(amount, int): + return -1 * amount + else : + return amount + def prepaid_amount(self, invoice): """ compute already paid amount """ @@ -108,7 +121,12 @@ class Invoice(Invoice): loader = genshi.template.TemplateLoader( os.path.join(os.path.dirname(__file__), 'template'), auto_reload=True) - return loader.load(os.path.join(version, 'XRechnung.xml')) + if self.type_code in ['380', '389']: + return loader.load(os.path.join(version, 'XRechnung_invoice.xml')) + elif self.type_code in ['381', '261']: + return loader.load(os.path.join(version, 'XRechnung_credit.xml')) + else : + raise ValueError('invalid type-code "%s"' % self.type_code) else: return super(Invoice, self)._get_template(version) diff --git a/template/XRechnung-2.2/XRechnung_credit.xml b/template/XRechnung-2.2/XRechnung_credit.xml new file mode 100644 index 0000000..e12ba91 --- /dev/null +++ b/template/XRechnung-2.2/XRechnung_credit.xml @@ -0,0 +1,182 @@ + + + + + ${', '.join((getattr(value, 'street', None) or '').split('\n'))} + ${getattr(value, 'city', None) or ''} + ${getattr(value, 'postal_code', None) or ''} + + ${getattr(getattr(value, 'country', None), 'code', None)} + + + + + ${this.taxident_data(value)['code']} + + ${this.taxident_data(value)['id']} + + + + + ${value.name} + + ${value.phone} + + + ${value.email} + + + + + ${value.name} + ${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 + + + + + + ${this.negate_amount(value.base)} + ${this.negate_amount(value.amount)} + + ${TaxCategory(value.tax, True)} + + + + + + + ${value.id} + ${this.negate_amount(value.quantity)} + ${this.negate_amount(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()} + ${this.type_code} + ${this.invoice_note()} + ${this.invoice.currency.code} + ${this.invoice.party.get_xrechnung_route_id()} + + ${this.invoice.reference} + ${this.sales_order_nums()} + + + vertrags-nr + + + proj-referenz + + + + + ${PostalAddress(this.seller_trade_address)} + + + ${PartyTaxScheme(this.seller_trade_tax_identifier)} + + + ${PartyLegalEntity(this.seller_trade_party)} + + + ${Contact(this.seller_trade_party)} + + + + + + + ${this.buyer_trade_party.code} + + + ${PostalAddress(this.buyer_trade_address)} + + + ${PartyTaxScheme(this.buyer_trade_tax_identifier)} + + + ${PartyLegalEntity(this.buyer_trade_party, False)} + + + ${Contact(this.buyer_trade_party)} + + + + + 30 + ${this.payment_reference} + + ${PayeeFinancialAccount(this.seller_trade_party)} + + + + ${this.invoice.payment_term.rec_name} + + + false + Neukundenrabatt + 5.00 + + S + 19.00 + + VAT + + + + + ${this.negate_amount(this.invoice.tax_amount)} + + ${TaxSubTotal(taxline)} + + + + ${this.negate_amount(this.invoice.untaxed_amount)} + ${this.negate_amount(this.invoice.untaxed_amount)} + ${this.negate_amount(this.invoice.total_amount)} + 0.00 + 0.00 + ${this.negate_amount(this.prepaid_amount(this.invoice))} + ${this.negate_amount(this.invoice.amount_to_pay)} + + + ${CreditNoteLine(line)} + + diff --git a/template/XRechnung-2.2/XRechnung.xml b/template/XRechnung-2.2/XRechnung_invoice.xml similarity index 95% rename from template/XRechnung-2.2/XRechnung.xml rename to template/XRechnung-2.2/XRechnung_invoice.xml index b07ad43..a1de5bf 100644 --- a/template/XRechnung-2.2/XRechnung.xml +++ b/template/XRechnung-2.2/XRechnung_invoice.xml @@ -27,8 +27,12 @@ ${value.name} - ${value.phone} - ${value.email} + + ${value.phone} + + + ${value.email} + diff --git a/tests/test_edocument.py b/tests/test_edocument.py index 5323c9c..2b223e4 100644 --- a/tests/test_edocument.py +++ b/tests/test_edocument.py @@ -15,8 +15,8 @@ class EdocTestCase(ModuleTestCase): module = 'edocument_xrechnung' @with_transaction() - def test_xrechn_export_xml(self): - """ run export + def test_xrechn_export_xml_invoice(self): + """ run export - invoice """ pool = Pool() Template = pool.get('edocument.xrechnung.invoice') @@ -49,7 +49,56 @@ class EdocTestCase(ModuleTestCase): template = Template(invoice) invoice_string = template.render('XRechnung-2.2') - with open('xrechnung-test.xml', 'wt') as fhdl: + with open('xrechnung-test-invoice.xml', 'wt') as fhdl: + fhdl.write(invoice_string.decode('utf8')) + + @with_transaction() + def test_xrechn_export_xml_creditnote(self): + """ run export - creditnote + """ + pool = Pool() + Template = pool.get('edocument.xrechnung.invoice') + 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() + + # credit note + invoice.lines[0].quantity = -1 + invoice.lines[0].amount = Decimal('-100.0') + invoice.taxes[0].base = Decimal('-100.0') + invoice.taxes[0].amount = Decimal('-10.0') + invoice.untaxed_amount = Decimal('-100.0') + invoice.tax_amount = Decimal('-10.0') + invoice.total_amount = Decimal('-110.0') + invoice.lines_to_pay[0].debit = Decimal('-110.0') + + 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', + code='xrechn-route-id-123') + ] + + template = Template(invoice) + invoice_string = template.render('XRechnung-2.2') + with open('xrechnung-test-creditnote.xml', 'wt') as fhdl: fhdl.write(invoice_string.decode('utf8')) # end EdocTestCase