Compare commits

..

40 commits

Author SHA1 Message Date
Frederik Jaeckel
0607d4c079 Version 6.8.13 2025-01-28 13:00:50 +01:00
Frederik Jaeckel
7f092befca Merge branch 'main' into 6.8 2025-01-28 12:55:05 +01:00
Frederik Jaeckel
135ad4a9be Version 6.8.12 2025-01-09 12:09:14 +01:00
Frederik Jaeckel
d327b999cf Merge branch 'main' into 6.8 2025-01-09 12:07:30 +01:00
Frederik Jaeckel
e0eec130e5 Version 6.8.11 2024-12-19 11:57:59 +01:00
Frederik Jaeckel
e97cd71d11 add docs 2024-12-19 11:55:01 +01:00
Mathias Behrle
bbe56c578d Improve the help text of xrechnung_routeid. 2024-12-19 11:54:57 +01:00
Mathias Behrle
454087226e Lookup parent taxes for unece tax codes.
In the same way as for categories the unece tax codes must be searched
on parents.
2024-12-19 11:54:53 +01:00
Mathias Behrle
188074e025 Correct a translation. 2024-12-19 11:54:43 +01:00
Frederik Jaeckel
0af2c94942 Version 6.8.10 2024-12-18 12:04:34 +01:00
Frederik Jaeckel
78cb4dfbf2 add folder 'view' to setup.py 2024-12-18 11:55:43 +01:00
Mathias Behrle
a01aa12cc0 Remove arguments in super() calls.
They are no more needed in Python3 and usually result in unexpected
behavior when wrongly used (like e.g. the one introduced in
764cacc091 and solved meanwhile by
refactorization).
2024-12-18 11:55:32 +01:00
Frederik Jaeckel
93a60873f6 Version 6.8.9 2024-12-11 17:09:45 +01:00
Frederik Jaeckel
7dd47e6b2a fix name of party in error message 2024-12-11 17:08:38 +01:00
Frederik Jaeckel
679d4b0b67 Version 6.8.7 2024-12-10 14:16:43 +01:00
Frederik Jaeckel
aa5f3e6814 fix code 2024-12-10 14:09:49 +01:00
Frederik Jaeckel
6f6de9ffb4 add bank account number to xml-export 2024-12-10 14:09:45 +01:00
Frederik Jaeckel
32ef5159d2 fix typo 2024-12-10 14:09:40 +01:00
Frederik Jaeckel
5d0032880c configuration: add setting for used bank nuumbers 2024-12-10 14:09:35 +01:00
Frederik Jaeckel
0165d56115 bank accont number: add field 'company_owned' 2024-12-10 14:09:31 +01:00
Frederik Jaeckel
9bff1f142b doks 2024-12-10 14:09:26 +01:00
Frederik Jaeckel
b3e6133dac check for valid address data 2024-12-10 14:09:21 +01:00
Frederik Jaeckel
bdd0c36483 facturx: check unece codes at tax 2024-12-10 14:09:15 +01:00
Frederik Jaeckel
70f079a0dc update setup.py 2024-12-10 14:09:08 +01:00
Frederik Jaeckel
bdc98f25fb export: add factur-x 1.07.2 2024-12-10 14:09:03 +01:00
Frederik Jaeckel
fc29a63969 add xsd for factur-x 2024-12-10 14:08:57 +01:00
Frederik Jaeckel
e3335655c9 allow route-id to be optional 2024-12-10 14:08:42 +01:00
Frederik Jaeckel
39da264fbe update docs 2024-12-10 14:07:49 +01:00
Frederik Jaeckel
5b72056598 export: add xrechnung 2.3 + 3.0 + tests against xsd 2024-12-10 14:07:44 +01:00
Frederik Jaeckel
82c70c68d5 add xsd UBL-2.1 2024-12-10 14:07:37 +01:00
Frederik Jaeckel
52d9b14da8 add docs 2024-12-10 14:07:27 +01:00
Frederik Jaeckel
45fae1ed1c formatting 2024-12-10 14:07:22 +01:00
Frederik Jaeckel
b1364cf7a2 file info 2024-12-10 14:07:09 +01:00
Frederik Jaeckel
1d99114b2e update gitignore 2024-12-10 14:06:54 +01:00
Frederik Jaeckel
bd04215c76 update gitignore 2024-12-10 14:06:49 +01:00
Mathias Behrle
1acd251ed9 Lookup parent taxes for unece tax codes.
Child taxes do not respect the setting of the parent 'Override
tmeplate', thus can not be configured for unece codes. It is anyway
better to allow the code definition on the parent tax.
2024-12-10 14:06:35 +01:00
Frederik Jaeckel
580f74a84b remove code from tests 2024-12-10 14:05:34 +01:00
Frederik Jaeckel
ebd925f5e8 add license, fix fileinfo 2024-12-10 14:04:06 +01:00
Frederik Jaeckel
7c2e56072a Etikett ver 6.8.2 zum Änderungssatz e242c1388074 hinzugefügt 2023-06-30 15:44:01 +02:00
Frederik Jaeckel
69b7678f02 Version 6.8.2 2023-06-30 15:43:49 +02:00
8 changed files with 46 additions and 207 deletions

View file

@ -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

View file

@ -9,37 +9,39 @@ pip install mds-edocument-xrechnung
Requires
========
- Tryton 7.0
- Tryton 6.8
Changes
=======
*7.0.10 - 12.12.2024*
*6.8.13 - 28.01.2025*
- fix: rounding of unit_price at invoice-line
*6.8.12 - 09.01.2025*
- handle tax childs (Jan Grasnick <jan@mittelwind.de>)
*6.8.11 - 19.12.2024*
- Lookup parent taxes for unece tax codes.
- Improve the help text of xrechnung_routeid. Correct a translation.
(Mathias Behrle <mathiasb@m9s.biz>)
*6.8.10 - 18.12.2024*
- fix missing views
- Remove arguments in super() calls. (Mathias Behrle)
*7.0.9 - 11.12.2024*
*6.8.9 - 11.12.2024*
- fix name of party in exceptions
*7.0.7 - 10.12.2024*
*6.8.7 - 10.12.2024*
- add iban to xml-export
- add: export of factur-x 1.07.2, XRechnung 2.3 + 3.0
*7.0.6 - 09.12.2024*
*6.8.2 - 30.06.2023*
- add: check for valid data to generate xml
*7.0.5 - 05.12.2024*
- add: export Factur-X 1.07.2
*7.0.4 - 05.12.2024*
- add: export XRechnung 2.3 + 3.0
- updt: xrechnung-route-id optional
*7.0.3 - 22.12.2023*
- compatibility to Tryton 7.0
- add credit-note
- compatibility to Tryton 6.8

View file

@ -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/

View file

@ -4,14 +4,13 @@
# full copyright notices and license terms.
from decimal import Decimal
from decimal import Decimal, ROUND_HALF_EVEN
import html
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
from trytond.modules.product import price_digits
class EdocumentMixin(object):
@ -161,44 +160,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:
@ -232,8 +193,10 @@ class EdocumentMixin(object):
Returns:
Decimal: rounded value
"""
if value is not None:
return round_price(value)
if isinstance(value, Decimal):
return value.quantize(
Decimal(str(1/10 ** price_digits[1])),
ROUND_HALF_EVEN)
return value
def quote_text(self, text):
@ -242,30 +205,4 @@ class EdocumentMixin(object):
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

View file

@ -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'))
@ -34,8 +36,8 @@ with open(path.join(here, 'versiondep.txt'), encoding='utf-8') as f:
modversion[l2[0]] = {'min': l2[1], 'max': l2[2], 'prefix': l2[3]}
# tryton-version
major_version = 7
minor_version = 0
major_version = 6
minor_version = 8
requires = []
for dep in info.get('depends', []):
@ -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',

View file

@ -19,7 +19,7 @@ this repository contains the full copyright notices and license terms. -->
</py:for>
</ram:SpecifiedLegalOrganization>
<ram:PostalTradeAddress py:if="address">${TradeAddress(address)}</ram:PostalTradeAddress>
<ram:SpecifiedTaxRegistration py:if="tax_identifier and tax_identifier.type.endswith('_vat')">
<ram:SpecifiedTaxRegistration py:if="tax_identifier and tax_identifier.type == 'eu_vat'">
<ram:ID schemeID='VA'>${tax_identifier.code}</ram:ID>
</ram:SpecifiedTaxRegistration>
</py:def>
@ -38,7 +38,7 @@ this repository contains the full copyright notices and license terms. -->
<ram:ApplicableTradeTax>
<ram:CalculatedAmount py:if="amount" py:attrs="{'currencyID': this.invoice.currency.code}">${amount * this.type_sign}</ram:CalculatedAmount>
<ram:TypeCode>${this.tax_unece_code(tax)}</ram:TypeCode>
<ram:ExemptionReason py:if="tax.legal_notice and not this.tax_category_code(tax)">${tax.legal_notice}</ram:ExemptionReason>
<ram:ExemptionReason py:if="tax.legal_notice">${tax.legal_notice}</ram:ExemptionReason>
<ram:BasisAmount py:if="base">${base * this.type_sign}</ram:BasisAmount>
<ram:CategoryCode>${this.tax_category_code(tax)}</ram:CategoryCode>
<ram:RateApplicablePercent py:if="tax.type == 'percentage'">${tax.rate * 100}</ram:RateApplicablePercent>
@ -71,8 +71,8 @@ this repository contains the full copyright notices and license terms. -->
</ram:AssociatedDocumentLineDocument>
<ram:SpecifiedTradeProduct>
<ram:ID py:if="line.product and line.product.code">${line.product.code}</ram:ID>
<ram:Name>${this.quote_text(line.product.name if line.product else line.description if line.description else 'name not set')}</ram:Name>
<ram:Description py:if="line.description">${this.quote_text(line.description if line.product else '')}</ram:Description>
<ram:Name>${this.quote_text(line.product.name if line.product else '')}</ram:Name>
<ram:Description py:if="line.description">${this.quote_text(line.description)}</ram:Description>
</ram:SpecifiedTradeProduct>
<ram:SpecifiedLineTradeAgreement>
<ram:NetPriceProductTradePrice>
@ -85,7 +85,7 @@ this repository contains the full copyright notices and license terms. -->
<ram:SpecifiedLineTradeSettlement>
${TradeTax(this.invoice_line_tax(line))}
<ram:SpecifiedTradeSettlementLineMonetarySummation>
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${this.get_line_amount(line)}</ram:LineTotalAmount>
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${line.amount}</ram:LineTotalAmount>
</ram:SpecifiedTradeSettlementLineMonetarySummation>
</ram:SpecifiedLineTradeSettlement>
</ram:IncludedSupplyChainTradeLineItem>

View file

@ -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)

View file

@ -1,13 +1,10 @@
[tryton]
version=7.0.10
version=6.8.13
depends:
edocument_uncefact
party
bank
account_invoice
extras_depend:
sale_point_invoice
product_grossprice
xml:
message.xml
configuration.xml