diff --git a/COPYRIGHT b/COPYRIGHT index 27458f9..0fbd54d 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -1,5 +1,7 @@ -Copyright (C) 2021-2025 martin-data services. -Copyright (C) 2024-2025 Mathias Behrle +Copyright (C) 2015-2023 Cédric Krier. +Copyright (C) 2015-2023 B2CK SPRL. +Copyright (C) 2021-2024 martin-data services. +Copyright (C) 2024 Mathias Behrle This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/README.rst b/README.rst index 9eabda9..c27d2e4 100644 --- a/README.rst +++ b/README.rst @@ -14,6 +14,16 @@ Requires Changes ======= +*7.0.12 - 09.01.2025* + +- handle tax childs (Jan Grasnick ) + +*7.0.11 - 19.12.2024* + +- Lookup parent taxes for unece tax codes. +- Improve the help text of xrechnung_routeid. Correct a translation. + (Mathias Behrle ) + *7.0.10 - 12.12.2024* - fix missing views diff --git a/docs/xrechnung.txt b/docs/xrechnung.txt index dab13fa..f8fbbbd 100644 --- a/docs/xrechnung.txt +++ b/docs/xrechnung.txt @@ -12,5 +12,3 @@ https://portal3.gefeg.com/projectdata/invoice/deliverables/installed/publishingp https://erechnungsvalidator.service-bw.de/ https://ecosio.com/de/peppol-und-xml-dokumente-online-validieren/ - -https://www.e-rechnungs-checker.de/ diff --git a/mixin.py b/mixin.py index dc235ac..8a75aca 100644 --- a/mixin.py +++ b/mixin.py @@ -10,8 +10,6 @@ from trytond.exceptions import UserError from trytond.i18n import gettext from trytond.tools import cached_property from trytond.pool import Pool -from trytond.transaction import Transaction -from trytond.modules.product import round_price class EdocumentMixin(object): @@ -161,44 +159,6 @@ class EdocumentMixin(object): taxname=tax.rec_name)) return tax.unece_code - def get_line_amount(self, line): - """ get amount of current invoice-line, - depends on modegross of invoice, set used-modegross to 'net' - - Args: - line (record): model account.invoice.line - """ - if not hasattr(line, 'modegross'): - return line.amount - - if line.modegross == 'net': - return line.amount - elif line.modegross == 'gross': - # get net-amount - # copy from account_invoice/invoice.py:2416-2434 - currency = ( - line.invoice.currency - if line.invoice else line.currency) - - amount = (Decimal(str(line.quantity or 0)) * ( - line.unit_price or Decimal(0))) - invoice_type = ( - line.invoice.type - if line.invoice else line.invoice_type) - - if (invoice_type == 'in' - and line.taxes_deductible_rate is not None - and line.taxes_deductible_rate != 1): - with Transaction().set_context(_deductible_rate=1): - tax_amount = sum( - t['amount'] for t in line._get_taxes().values()) - non_deductible_amount = ( - tax_amount * (1 - line.taxes_deductible_rate)) - amount += non_deductible_amount - if currency: - return currency.round(amount) - return amount - def get_tax_unece_code(self, tax): while tax: if tax.unece_code: @@ -223,49 +183,10 @@ class EdocumentMixin(object): taxname=tax.rec_name)) return unece_category_code - def round_unitprice(self, value): - """ round value by digits in unit_price of account.invoice.line - - Args: - value (Decimal): unit-price - - Returns: - Decimal: rounded value - """ - if value is not None: - return round_price(value) - return value - def quote_text(self, text): """ replace critical chars """ if text: return html.escape(text) - def _party_legal_types(self): - """ get list of identifier-types to be used as - legal-ids - """ - return ['de_handelsregisternummer'] - - def party_legal_ids(self, party, address): - """ get list of legal-ids of party - - Args: - party (record): model party.party - address (record): model party.address - """ - result = super().party_legal_ids(party, address) - - legal_types = self._party_legal_types() - if party and party.identifiers: - for x in party.identifiers: - if x.type in legal_types: - if x.address: - if x.address == address: - result.append((x.rec_name, {'schemeID': '0002'})) - else: - result.append((x.rec_name, {'schemeID': '0002'})) - return result - # end EdocumentMixin diff --git a/setup.py b/setup.py index c82d777..efa68a9 100644 --- a/setup.py +++ b/setup.py @@ -13,9 +13,11 @@ here = path.abspath(path.dirname(__file__)) MODULE = 'edocument_xrechnung' PREFIX = 'mds' +# Get the long description from the README file with open(path.join(here, 'README.rst'), encoding='utf-8') as f: long_description = f.read() +# tryton.cfg einlesen config = ConfigParser() config.readfp(open('tryton.cfg')) info = dict(config.items('tryton')) @@ -85,8 +87,6 @@ setup( 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', ], keywords='tryton xrechnung edcoument', diff --git a/template/Factur-X-1.07.2-extended/invoice.xml b/template/Factur-X-1.07.2-extended/invoice.xml index a4620c5..f23354e 100644 --- a/template/Factur-X-1.07.2-extended/invoice.xml +++ b/template/Factur-X-1.07.2-extended/invoice.xml @@ -19,7 +19,7 @@ this repository contains the full copyright notices and license terms. --> ${TradeAddress(address)} - + ${tax_identifier.code} @@ -38,7 +38,7 @@ this repository contains the full copyright notices and license terms. --> ${amount * this.type_sign} ${this.tax_unece_code(tax)} - ${tax.legal_notice} + ${tax.legal_notice} ${base * this.type_sign} ${this.tax_category_code(tax)} ${tax.rate * 100} @@ -71,12 +71,12 @@ this repository contains the full copyright notices and license terms. --> ${line.product.code} - ${this.quote_text(line.product.name if line.product else line.description if line.description else 'name not set')} - ${this.quote_text(line.description if line.product else '')} + ${this.quote_text(line.product.name if line.product else '')} + ${this.quote_text(line.description)} - ${this.round_unitprice(line.unit_price)} + ${this.invoice.currency.round(line.unit_price)} @@ -85,7 +85,7 @@ this repository contains the full copyright notices and license terms. --> ${TradeTax(this.invoice_line_tax(line))} - ${this.get_line_amount(line)} + ${line.amount} diff --git a/tests/test_edocument.py b/tests/test_edocument.py index 179957d..5ba67ac 100644 --- a/tests/test_edocument.py +++ b/tests/test_edocument.py @@ -7,8 +7,7 @@ from lxml import etree import os from decimal import Decimal from datetime import date -from trytond.tests.test_tryton import ( - ModuleTestCase, with_transaction, activate_module) +from trytond.tests.test_tryton import ModuleTestCase, with_transaction from trytond.pool import Pool from trytond.modules.company.tests import create_company, set_company from trytond.modules.account.tests import create_chart, get_fiscalyear @@ -43,14 +42,6 @@ class EdocTestCase(ModuleTestCase): 'Test e-rechnung module' module = 'edocument_xrechnung' - @classmethod - def setUpClass(cls): - super().setUpClass() - activate_module([ - 'edocument_uncefact', 'party', 'bank', - 'account_invoice', 'sale_point_invoice', - 'product_grossprice'], 'en') - def prep_fiscalyear(self, company1): """ prepare fiscal year, sequences... """ @@ -78,11 +69,6 @@ class EdocTestCase(ModuleTestCase): company = create_company('m-ds') Party.write(*[[company.party], { - 'identifiers': [('create', [ - # post.de - {'type': 'de_handelsregisternummer', 'code': 'Bonn HRB 6792'}, - {'type': 'de_vat', 'code': 'DE 169838187'}, - ])], 'addresses': [('write', [company.party.addresses[0]], { 'country': country_de.id})]}]) @@ -98,7 +84,7 @@ class EdocTestCase(ModuleTestCase): 'number': 'DE02300209000106531065'}])]}]) return company - def prep_invoice(self, credit_note=False, modegross='net'): + def prep_invoice(self, credit_note=False): """ add invoice """ pool = Pool() @@ -126,12 +112,11 @@ class EdocTestCase(ModuleTestCase): }]) currency1, = Currency.search([('code', '=', 'usd')]) - Currency.write(*[[currency1], {'code': 'USD'}]) tax, = Taxes.search([('name', '=', '20% VAT')]) Taxes.write(*[ [tax], - {'unece_code': 'VAT', 'unece_category_code': 'S', + {'unece_code': 'GST', 'unece_category_code': 'S', 'legal_notice': 'Legal Notice'}]) account_lst = Account.search([ @@ -145,7 +130,6 @@ class EdocTestCase(ModuleTestCase): to_create_invoice = [{ 'type': 'out', - 'modegross': modegross, 'description': 'description of invoice', 'comment': 'note line 1\nnote line 2', 'invoice_date': date(2024, 7, 1), @@ -165,17 +149,12 @@ class EdocTestCase(ModuleTestCase): 'currency': currency1.id, }])], }] - - if modegross == 'gross': - to_create_invoice[0]['lines'][0][1][0]['unit_gross_price'] = ( - Decimal('50.0') * Decimal('1.2')) - inv_lst, = Invoice.create(to_create_invoice) inv_lst.on_change_lines() inv_lst.save() Invoice.validate_invoice([inv_lst]) Invoice.post([inv_lst]) - self.assertEqual(inv_lst.currency.code, 'USD') + self.assertEqual(inv_lst.currency.code, 'usd') self.assertEqual(len(inv_lst.move.lines), 3) return inv_lst @@ -254,69 +233,6 @@ class EdocTestCase(ModuleTestCase): {'identifiers': [('delete', [party.identifiers[0].id])]}] ) - @with_transaction() - def test_xrechn_export_facturx_gross(self): - """ run export - factur-x, modegross='gross' - """ - pool = Pool() - Template = pool.get('edocument.facturxext.invoice') - - company = self.prep_company() - with set_company(company): - create_chart(company=company, tax=True) - self.prep_fiscalyear(company) - invoice = self.prep_invoice(modegross='gross') - - template = Template(invoice) - - schema_file = os.path.join( - os.path.dirname(__file__), - 'Factur-X_1.07.2_EXTENDED', - 'Factur-X_1.07.2_EXTENDED.xsd') - - invoice_string = template.render('Factur-X-1.07.2-extended') - with open('gross_invoice_string.xml', 'wb') as fhdl: - fhdl.write(invoice_string) - - invoice_xml = etree.fromstring(invoice_string) - - # check values in xml - nodes = invoice_xml.xpath(self._readxml_xpath([ - 'rsm:CrossIndustryInvoice', 'rsm:SupplyChainTradeTransaction', - 'ram:ApplicableHeaderTradeAgreement', 'ram:SellerTradeParty', - 'ram:SpecifiedLegalOrganization', 'ram:ID']), - namespaces=invoice_xml.nsmap) - self.assertEqual(nodes[0].text, 'Bonn HRB 6792') - - nodes = invoice_xml.xpath(self._readxml_xpath([ - 'rsm:CrossIndustryInvoice', 'rsm:SupplyChainTradeTransaction', - 'ram:IncludedSupplyChainTradeLineItem', - 'ram:SpecifiedLineTradeSettlement', - 'ram:SpecifiedTradeSettlementLineMonetarySummation', - 'ram:LineTotalAmount']), - namespaces=invoice_xml.nsmap) - self.assertEqual(nodes[0].text, '100.00') - - schema = etree.XMLSchema(etree.parse(schema_file)) - schema.assertValid(invoice_xml) - - def _readxml_xpath(self, tags): - """ generate xpath - - Args: - tags (list): list of string or integer to build path - """ - parts = [] - for x in tags: - if isinstance(x, str): - parts.append(x) - elif isinstance(x, int): - if parts[-1].endswith(']'): - raise ValueError('multiple list selector') - parts[-1] += '[%d]' % x - result = '/' + '/'.join(parts) - return result - @with_transaction() def test_xrechn_export_facturx(self): """ run export - factur-x @@ -332,10 +248,6 @@ class EdocTestCase(ModuleTestCase): template = Template(invoice) - self.assertEqual( - template.party_legal_ids(invoice.company_party, None), - [('Bonn HRB 6792', {'schemeID': '0002'})]) - schema_file = os.path.join( os.path.dirname(__file__), 'Factur-X_1.07.2_EXTENDED', @@ -343,15 +255,6 @@ class EdocTestCase(ModuleTestCase): invoice_string = template.render('Factur-X-1.07.2-extended') invoice_xml = etree.fromstring(invoice_string) - - # check values in xml - nodes = invoice_xml.xpath(self._readxml_xpath([ - 'rsm:CrossIndustryInvoice', 'rsm:SupplyChainTradeTransaction', - 'ram:ApplicableHeaderTradeAgreement', 'ram:SellerTradeParty', - 'ram:SpecifiedLegalOrganization', 'ram:ID']), - namespaces=invoice_xml.nsmap) - self.assertEqual(nodes[0].text, 'Bonn HRB 6792') - schema = etree.XMLSchema(etree.parse(schema_file)) schema.assertValid(invoice_xml) diff --git a/tryton.cfg b/tryton.cfg index ecd8916..62b98bc 100644 --- a/tryton.cfg +++ b/tryton.cfg @@ -1,13 +1,10 @@ [tryton] -version=7.0.10 +version=7.0.12 depends: edocument_uncefact party bank account_invoice -extras_depend: - sale_point_invoice - product_grossprice xml: message.xml configuration.xml