diff --git a/__init__.py b/__init__.py
index fe1a4a0..cb9e647 100644
--- a/__init__.py
+++ b/__init__.py
@@ -4,13 +4,14 @@
# full copyright notices and license terms.
from trytond.pool import Pool
-from .edocument import Invoice
+from .edocument import Invoice, FacturX
from .party import PartyConfiguration, Party
def register():
Pool.register(
Invoice,
+ FacturX,
Party,
PartyConfiguration,
module='edocument_xrechnung', type_='model')
diff --git a/edocument.py b/edocument.py
index 436d40e..b5e5a4c 100644
--- a/edocument.py
+++ b/edocument.py
@@ -12,6 +12,42 @@ from trytond.modules.edocument_uncefact.edocument import Invoice
from decimal import Decimal
+class FacturX(Invoice):
+ 'Factur-X'
+ __name__ = 'edocument.facturxext.invoice'
+
+ def get_list_of_comments(self):
+ """ comment, to export in
+
+ Returns:
+ _type_: _description_
+ """
+ result = []
+ if self.invoice.comment:
+ result.append({
+ 'content': self.invoice.comment,
+ 'subject_code': '',
+ 'content_code': ''})
+ return result
+
+ def _get_template(self, version):
+ """ load our own template if 'version' is ours
+ """
+ loader = genshi.template.TemplateLoader(
+ os.path.join(os.path.dirname(__file__), 'template'),
+ auto_reload=True)
+
+ if version == 'Factur-X-1.07.2-extended':
+ if self.type_code in ['380', '389', '381', '261']:
+ return loader.load(os.path.join(version, 'invoice.xml'))
+ else:
+ raise ValueError('invalid type-code "%s"' % self.type_code)
+ else:
+ return super(Invoice, self)._get_template(version)
+
+# end FacturX
+
+
class Invoice(Invoice):
'EDocument XRechnung'
__name__ = 'edocument.xrechnung.invoice'
@@ -123,16 +159,20 @@ class Invoice(Invoice):
def _get_template(self, version):
""" load our own template if 'version' is ours
"""
+ loader = genshi.template.TemplateLoader(
+ os.path.join(os.path.dirname(__file__), 'template'),
+ auto_reload=True)
+
if version in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']:
- loader = genshi.template.TemplateLoader(
- 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'))
- elif self.type_code in ['381', '261']:
- return loader.load(os.path.join(
- version, 'XRechnung_credit.xml'))
+ file_name = {
+ '380': 'XRechnung_invoice.xml',
+ '389': 'XRechnung_invoice.xml',
+ '381': 'XRechnung_credit.xml',
+ '261': 'XRechnung_credit.xml',
+ }.get(self.type_code)
+
+ if file_name:
+ return loader.load(os.path.join(version, file_name))
else:
raise ValueError('invalid type-code "%s"' % self.type_code)
else:
diff --git a/template/Factur-X-1.07.2-extended/invoice.xml b/template/Factur-X-1.07.2-extended/invoice.xml
new file mode 100644
index 0000000..c10307f
--- /dev/null
+++ b/template/Factur-X-1.07.2-extended/invoice.xml
@@ -0,0 +1,141 @@
+
+
+
+
+ ${value.strftime('%Y%m%d')}
+
+
+ ${party.name}
+
+
+ ${id}
+
+
+ ${TradeAddress(address)}
+
+ ${tax_identifier.code}
+
+
+
+ ${address.postal_code}
+
+ ${lines[0]}
+ ${lines[1]}
+ ${lines[2]}
+
+ ${address.city}
+ ${address.country.code}
+ ${address.subdivision.name}
+
+
+
+ ${amount * this.type_sign}
+ ${tax.unece_code}
+ ${tax.legal_notice}
+ ${base * this.type_sign}
+ ${tax.unece_category_code}
+ ${tax.rate * 100}
+
+
+
+
+ urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended
+
+
+
+ ${this.invoice.number}
+ ${this.invoice.description}
+ ${this.type_code}
+
+ ${DateTime(this.invoice.invoice_date)}
+
+
+
+ ${comment_line['content_code']}
+ ${comment_line['content']}
+ ${comment_line['subject_content']}
+
+
+
+
+
+
+ ${line_id}
+
+
+ ${line.product.code}
+ ${line.product.name}
+ ${line.description}
+
+
+
+ ${this.invoice.currency.round(line.unit_price)}
+
+
+
+ ${line.quantity * this.type_sign}
+
+
+
+ ${TradeTax(tax.tax)}
+
+
+ ${line.amount}
+
+
+
+
+
+ ${TradeParty(this.seller_trade_party, this.seller_trade_address, this.seller_trade_tax_identifier)}
+
+
+ ${TradeParty(this.buyer_trade_party, this.buyer_trade_address, this.buyer_trade_tax_identifier)}
+
+
+ ${this.invoice.reference}
+
+
+ ${this.invoice.reference}
+
+
+
+
+ ${TradeParty(this.ship_to_trade_party, this.ship_to_trade_address)}
+
+
+ ${TradeParty(this.ship_from_trade_party, this.ship_from_trade_address)}
+
+
+
+ ${this.payment_reference}
+ ${this.invoice.currency.code}
+
+ 1
+
+
+ ${TradeTax(tax.tax, tax.amount, tax.base)}
+
+
+ ${this.invoice.payment_term.description}
+
+ ${DateTime(line.maturity_date)}
+
+ ${(line.amount_second_currency or (line.debit - line.credit)) * this.type_sign}
+
+
+ ${this.invoice.untaxed_amount * this.type_sign}
+ ${this.invoice.untaxed_amount * this.type_sign}
+ ${this.invoice.tax_amount * this.type_sign}
+ ${this.invoice.total_amount * this.type_sign}
+ ${this.invoice.amount_to_pay * this.type_sign}
+
+
+
+
diff --git a/tests/test_edocument.py b/tests/test_edocument.py
index 73b4695..eaa75df 100644
--- a/tests/test_edocument.py
+++ b/tests/test_edocument.py
@@ -46,6 +46,52 @@ class EdocTestCase(ModuleTestCase):
{'identifiers': [('delete', [party.identifiers[0].id])]}]
)
+ @with_transaction()
+ def test_xrechn_export_facturx(self):
+ """ run export - factur-x
+ """
+ pool = Pool()
+ Template = pool.get('edocument.facturxext.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()
+ invoice.payment_term_date = date.today()
+ 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)
+
+ 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')
+ invoice_xml = etree.fromstring(invoice_string)
+ schema = etree.XMLSchema(etree.parse(schema_file))
+ schema.assertValid(invoice_xml)
+
@with_transaction()
def test_xrechn_export_xml_invoice(self):
""" run export - invoice
diff --git a/tests/validate_xml.py b/tests/validate_xml.py
new file mode 100644
index 0000000..f99603c
--- /dev/null
+++ b/tests/validate_xml.py
@@ -0,0 +1,21 @@
+# -*- 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.
+
+from lxml import etree
+import os
+
+file_name = 'file-to-check.xml'
+
+schema_file = os.path.join(
+ os.path.dirname(__file__),
+ 'Factur-X_1.07.2_EXTENDED',
+ 'Factur-X_1.07.2_EXTENDED.xsd')
+
+with open(file_name, 'r') as fhdl:
+ f_content = fhdl.read()
+ f_content = f_content.encode('utf8')
+ invoice_xml = etree.fromstring(f_content)
+ schema = etree.XMLSchema(etree.parse(schema_file))
+ print('Result:', schema.assertValid(invoice_xml))