From fe3700b38f2e7457e0c1ca0e2c93e218632c1b7c Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Tue, 4 Apr 2023 12:58:46 +0200 Subject: [PATCH 1/9] Version 6.6.0 --- README.rst | 6 +++--- tryton.cfg | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index e2fa676..22e8aab 100644 --- a/README.rst +++ b/README.rst @@ -9,11 +9,11 @@ pip install mds-edocument-xrechnung Requires ======== -- Tryton 6.0 +- Tryton 6.6 Changes ======= -*6.0.0 - 17.10.2022* +*6.6.0 - 04.04.2023* -- init +- compatibility to Tryton 6.6 diff --git a/tryton.cfg b/tryton.cfg index 5fc2311..9adad88 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=6.0.0 +version=6.6.0 depends: edocument_uncefact party From a801810874c7ca068e78cccca5e8b8096c4c81ec Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Tue, 4 Apr 2023 12:59:37 +0200 Subject: [PATCH 2/9] Version 6.6.1 --- README.rst | 2 +- tryton.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 22e8aab..75f2e28 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,6 @@ Requires Changes ======= -*6.6.0 - 04.04.2023* +*6.6.1 - 04.04.2023* - compatibility to Tryton 6.6 diff --git a/tryton.cfg b/tryton.cfg index 9adad88..265446c 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=6.6.0 +version=6.6.1 depends: edocument_uncefact party From 117a247281fc381797fcad3a3666ef257b245603 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Fri, 30 Jun 2023 11:36:17 +0200 Subject: [PATCH 4/9] formatting --- __init__.py | 2 +- edocument.py | 23 +++++++-------- party.py | 5 ++-- setup.py | 65 +++++++++++++++++++++-------------------- tests/__init__.py | 19 +++++------- tests/test_edocument.py | 19 +++++++----- 6 files changed, 65 insertions(+), 68 deletions(-) diff --git a/__init__.py b/__init__.py index 84ce866..fe1a4a0 100644 --- a/__init__.py +++ b/__init__.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# This file is part of the cashbook-module from m-ds for Tryton. +# This file is part of the edocument-module for Tryton from m-ds.de. # The COPYRIGHT file at the top level of this repository contains the # full copyright notices and license terms. diff --git a/edocument.py b/edocument.py index 44b4123..ef0ae91 100644 --- a/edocument.py +++ b/edocument.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- -# This file is part of the edcoment-module for Tryton. +# This file is part of the edocument-module for Tryton from m-ds.de. # The COPYRIGHT file at the top level of this repository contains the # full copyright notices and license terms. import genshi.template -import os, html +import os +import html from trytond.exceptions import UserError from trytond.i18n import gettext from trytond.modules.edocument_uncefact.edocument import Invoice @@ -45,17 +46,15 @@ class Invoice(Invoice): if len(line.invoice_taxes) != 1: raise UserError(gettext( 'edocument_xrechnung.msg_linetax_invalid_number', - linename = line.rec_name, - numtax = len(line.invoice_taxes), - )) + 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), - )) + taxname=line.invoice_taxes[0].tax.rec_name, + allowed=', '.join(allowed_cat))) return line.invoice_taxes[0].tax @@ -84,8 +83,7 @@ class Invoice(Invoice): if len(line.unit.unece_code or '') == 0: raise UserError(gettext( 'edocument_xrechnung.msg_uom_code_missing', - uomname = line.unit.rec_name, - )) + uomname=line.unit.rec_name)) return line.unit.unece_code def tax_category_code(self, tax): @@ -94,8 +92,7 @@ class Invoice(Invoice): if len(tax.unece_category_code or '') == 0: raise UserError(gettext( 'edocument_xrechnung.mds_tax_category_missing', - taxname = tax.rec_name, - )) + taxname=tax.rec_name)) return tax.unece_category_code def quote_text(self, text): @@ -112,7 +109,7 @@ class Invoice(Invoice): os.path.join(os.path.dirname(__file__), 'template'), auto_reload=True) return loader.load(os.path.join(version, 'XRechnung.xml')) - else : + else: return super(Invoice, self)._get_template(version) # end Invoice diff --git a/party.py b/party.py index 38cc321..037bae9 100644 --- a/party.py +++ b/party.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# This file is part of the edcoment-module for Tryton. +# This file is part of the edocument-module for Tryton from m-ds.de. # The COPYRIGHT file at the top level of this repository contains the # full copyright notices and license terms. @@ -19,8 +19,7 @@ class Party(metaclass=PoolMeta): return ident.code raise UserError(gettext( 'edocument_xrechnung.msg_missing_xrechnung_route_id', - partyname = self.rec_name, - )) + partyname=self.rec_name)) # end Party diff --git a/setup.py b/setup.py index b0df333..6458cf7 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ """ # Always prefer setuptools over distutils -from setuptools import setup, find_packages +from setuptools import setup # To use a consistent encoding from codecs import open from os import path @@ -36,7 +36,7 @@ with open(path.join(here, 'versiondep.txt'), encoding='utf-8') as f: l2 = i.strip().split(';') if len(l2) < 4: continue - modversion[l2[0]] = {'min':l2[1], 'max':l2[2], 'prefix':l2[3]} + modversion[l2[0]] = {'min': l2[1], 'max': l2[2], 'prefix': l2[3]} # tryton-version major_version = 6 @@ -51,19 +51,21 @@ for dep in info.get('depends', []): prefix = modversion[dep]['prefix'] if len(modversion[dep]['max']) > 0: - requires.append('%s_%s >= %s, <= %s' % - (prefix, dep, modversion[dep]['min'], modversion[dep]['max'])) - else : - requires.append('%s_%s >= %s' % - (prefix, dep, modversion[dep]['min'])) - else : - requires.append('%s_%s >= %s.%s, < %s.%s' % - ('trytond', dep, major_version, minor_version, + requires.append('%s_%s >= %s, <= %s' % ( + prefix, dep, modversion[dep]['min'], + modversion[dep]['max'])) + else: + requires.append('%s_%s >= %s' % ( + prefix, dep, modversion[dep]['min'])) + else: + requires.append('%s_%s >= %s.%s, < %s.%s' % ( + 'trytond', dep, major_version, minor_version, major_version, minor_version + 1)) -requires.append('trytond >= %s.%s, < %s.%s' % - (major_version, minor_version, major_version, minor_version + 1)) +requires.append('trytond >= %s.%s, < %s.%s' % ( + major_version, minor_version, major_version, minor_version + 1)) -setup(name='%s_%s' % (PREFIX, MODULE), +setup( + name='%s_%s' % (PREFIX, MODULE), version=info.get('version', '0.0.1'), description='Tryton module to XRechnung to edocument.', long_description=long_description, @@ -72,22 +74,22 @@ setup(name='%s_%s' % (PREFIX, MODULE), author_email='service@m-ds.de', license='GPL-3', classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Plugins', - 'Framework :: Tryton', - 'Intended Audience :: Developers', - 'Intended Audience :: Customer Service', - 'Intended Audience :: Information Technology', - 'Intended Audience :: Financial and Insurance Industry', - 'Topic :: Office/Business', - 'Topic :: Office/Business :: Financial :: Accounting', - 'Natural Language :: German', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'License :: OSI Approved :: GNU General Public License (GPL)', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', + 'Development Status :: 5 - Production/Stable', + 'Environment :: Plugins', + 'Framework :: Tryton', + 'Intended Audience :: Developers', + 'Intended Audience :: Customer Service', + 'Intended Audience :: Information Technology', + 'Intended Audience :: Financial and Insurance Industry', + 'Topic :: Office/Business', + 'Topic :: Office/Business :: Financial :: Accounting', + 'Natural Language :: German', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'License :: OSI Approved :: GNU General Public License (GPL)', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', ], keywords='tryton xrechnung edcoument', @@ -96,9 +98,10 @@ setup(name='%s_%s' % (PREFIX, MODULE), 'trytond.modules.%s' % MODULE, ], package_data={ - 'trytond.modules.%s' % MODULE: (info.get('xml', []) + 'trytond.modules.%s' % MODULE: ( + info.get('xml', []) + ['tryton.cfg', 'locale/*.po', 'tests/*.py', - 'template/*/*.xml','versiondep.txt', 'README.rst']), + 'template/*/*.xml', 'versiondep.txt', 'README.rst']), }, install_requires=requires, diff --git a/tests/__init__.py b/tests/__init__.py index 173e725..bd1a230 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,23 +1,18 @@ -# This file is part of Tryton. The COPYRIGHT file at the top level of -# this repository contains the full copyright notices and license terms. +# -*- coding: utf-8 -*- +# This file is part of the edocument-module for Tryton from m-ds.de. +# The COPYRIGHT file at the top level of this repository contains the +# full copyright notices and license terms. import trytond.tests.test_tryton import unittest -from trytond.modules.edocument_xrechnung.tests.test_edocument import EdocTestCase +from .test_edocument import EdocTestCase __all__ = ['suite'] -class EDocumentTestCase(\ - EdocTestCase,\ - ): - 'Test edocument module' - module = 'edocument_xrechnung' - -# end EDocumentTestCase - def suite(): suite = trytond.tests.test_tryton.suite() - suite.addTests(unittest.TestLoader().loadTestsFromTestCase(EDocumentTestCase)) + suite.addTests(unittest.TestLoader().loadTestsFromTestCase( + EdocTestCase)) return suite diff --git a/tests/test_edocument.py b/tests/test_edocument.py index aa6c8ad..5323c9c 100644 --- a/tests/test_edocument.py +++ b/tests/test_edocument.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- -# This file is part of the cashbook-module from m-ds for Tryton. +# This file is part of the edocument-module for Tryton from m-ds.de. # The COPYRIGHT file at the top level of this repository contains the # full copyright notices and license terms. from trytond.tests.test_tryton import ModuleTestCase, with_transaction from trytond.pool import Pool from trytond.modules.edocument_uncefact.tests.test_module import get_invoice -from unittest.mock import Mock, MagicMock +from unittest.mock import Mock from decimal import Decimal @@ -20,7 +20,6 @@ class EdocTestCase(ModuleTestCase): """ pool = Pool() 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') @@ -28,19 +27,22 @@ class EdocTestCase(ModuleTestCase): BankNumber = pool.get('bank.account.number') invoice = get_invoice() - invoice.party.get_xrechnung_route_id = Mock(return_value='xrechn-route-id-123') + invoice.party.get_xrechnung_route_id = Mock( + return_value='xrechn-route-id-123') invoice.company.party.bank_accounts = [ - Mock(spec=BankAccount, + 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')], + 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, + Mock( + spec=Identifier, type='edoc_route_id', code='xrechn-route-id-123') ] @@ -52,4 +54,5 @@ class EdocTestCase(ModuleTestCase): # end EdocTestCase + del ModuleTestCase From 7a6055280f9662a467300020af0bc174bc9f54d3 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Fri, 30 Jun 2023 15:29:51 +0200 Subject: [PATCH 5/9] 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 From 1a059b8aee5a6d1a23c02026d52d454ea9892c2a Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Fri, 30 Jun 2023 15:31:47 +0200 Subject: [PATCH 6/9] formatting --- edocument.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/edocument.py b/edocument.py index 077398b..c71c002 100644 --- a/edocument.py +++ b/edocument.py @@ -32,7 +32,7 @@ class Invoice(Invoice): return -1.0 * amount elif isinstance(amount, int): return -1 * amount - else : + else: return amount def prepaid_amount(self, invoice): @@ -122,10 +122,12 @@ class Invoice(Invoice): os.path.join(os.path.dirname(__file__), 'template'), auto_reload=True) if self.type_code in ['380', '389']: - return loader.load(os.path.join(version, 'XRechnung_invoice.xml')) + 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 : + 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) From c6c0c08609f95cd448fec97f184dcc780e397a59 Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Fri, 30 Jun 2023 15:47:23 +0200 Subject: [PATCH 7/9] Version 6.6.2 --- README.rst | 4 ++++ tryton.cfg | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 75f2e28..ec3c4db 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,10 @@ Requires Changes ======= +*6.6.2 - 30.06.2023* + +- add: credit-note + *6.6.1 - 04.04.2023* - compatibility to Tryton 6.6 diff --git a/tryton.cfg b/tryton.cfg index 265446c..4a6a354 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,5 +1,5 @@ [tryton] -version=6.6.1 +version=6.6.2 depends: edocument_uncefact party