Compare commits

..

38 commits

Author SHA1 Message Date
Frederik Jaeckel
a5bf930e55 update license 2025-05-02 14:16:47 +02:00
Frederik Jaeckel
ea5a83d7c1 use product.round_price() to round unit_price of invoice-line 2025-01-29 09:33:40 +01:00
Frederik Jaeckel
02221601e7 round unit_price of invoice-line by price_digits 2025-01-28 12:50:55 +01:00
Frederik Jaeckel
ceb2a72fed tests: drop usage of Mock, generate invoice in db 2025-01-09 11:50:52 +01:00
Frederik Jaeckel
e47380f930 formatting 2025-01-09 11:50:09 +01:00
Frederik Jaeckel
9a13bc325d calculation of the tax adjusted 2025-01-09 11:50:00 +01:00
e291608373 Merge pull request 'handle tax childs' (#6) from jangras/edocument_xrechnung:wip_jan into main
Reviewed-on: m-ds/edocument_xrechnung#6
2025-01-09 08:33:34 +00:00
dba1965894 fix typo 2025-01-04 21:08:43 +01:00
f3b4849e0c handle tax childs 2025-01-03 22:28:58 +01:00
Frederik Jaeckel
6c47581745 Merge branch 'various_fixes' 2024-12-19 11:35:33 +01:00
Frederik Jaeckel
7ddb94f47e add docs 2024-12-19 11:34:36 +01:00
Mathias Behrle
8eb9c284fb Improve the help text of xrechnung_routeid. 2024-12-19 10:42:41 +01:00
Mathias Behrle
df46388967 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 10:22:12 +01:00
Mathias Behrle
659e78a686 Correct a translation. 2024-12-19 10:20:57 +01:00
Frederik Jaeckel
4177265713 Version 7.0.10 2024-12-12 11:56:33 +01:00
Frederik Jaeckel
d6b5893a8b add folder 'view' to setup.py 2024-12-12 11:52:50 +01:00
Mathias Behrle
79c2227131 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-12 09:53:03 +01:00
Frederik Jaeckel
87f7593340 Version 7.0.9 2024-12-11 17:03:29 +01:00
Frederik Jaeckel
a9bbaebbd9 Merge branch 'main' into 7.0 2024-12-11 17:02:28 +01:00
Frederik Jaeckel
b28ce594d8 fix name of party in error message 2024-12-11 14:34:10 +01:00
Frederik Jaeckel
5a0af0be7c Version 7.0.7 2024-12-10 13:54:56 +01:00
Frederik Jaeckel
d442390154 Merge branch 'main' into 7.0 2024-12-10 13:54:02 +01:00
Frederik Jaeckel
ab064c43ef fix code 2024-12-10 13:16:04 +01:00
Frederik Jaeckel
9f2be4e593 add bank account number to xml-export 2024-12-10 12:59:09 +01:00
Frederik Jaeckel
813f5a4bcf fix typo 2024-12-10 12:58:45 +01:00
Frederik Jaeckel
81ea9d6ab1 configuration: add setting for used bank nuumbers 2024-12-10 12:25:51 +01:00
Frederik Jaeckel
f04d8c907b bank accont number: add field 'company_owned' 2024-12-10 12:24:45 +01:00
Frederik Jaeckel
bff0f296c3 doks 2024-12-10 12:22:33 +01:00
Frederik Jaeckel
52b1be57f6 Version 7.0.6 2024-12-09 15:43:42 +01:00
Frederik Jaeckel
4881504b2e Merge branch 'main' into 7.0 2024-12-09 15:42:33 +01:00
Frederik Jaeckel
994f9b2880 check for valid address data 2024-12-09 15:15:27 +01:00
Frederik Jaeckel
1e7a021eee facturx: check unece codes at tax 2024-12-09 13:24:39 +01:00
Frederik Jaeckel
6659730857 Merge branch 'main' into 7.0 2024-12-05 15:47:24 +01:00
Frederik Jaeckel
45ff5ef39f update setup.py 2024-12-05 15:47:11 +01:00
Frederik Jaeckel
39e5d5a361 Version 7.0.5 2024-12-05 15:37:43 +01:00
Frederik Jaeckel
9e7adb6f5d Merge branch 'main' into 7.0 2024-12-05 15:36:40 +01:00
Frederik Jaeckel
764cacc091 export: add factur-x 1.07.2 2024-12-05 15:36:07 +01:00
Frederik Jaeckel
3d703e300e add xsd for factur-x 2024-12-05 15:35:23 +01:00
27 changed files with 61894 additions and 180 deletions

View file

@ -1,6 +1,5 @@
Copyright (C) 2015-2023 Cédric Krier.
Copyright (C) 2015-2023 B2CK SPRL.
Copyright (C) 2021-2023 martin-data services.
Copyright (C) 2021-2025 martin-data services.
Copyright (C) 2024-2025 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

@ -14,6 +14,27 @@ Requires
Changes
=======
*7.0.10 - 12.12.2024*
- fix missing views
- Remove arguments in super() calls. (Mathias Behrle)
*7.0.9 - 11.12.2024*
- fix name of party in exceptions
*7.0.7 - 10.12.2024*
- add iban to xml-export
*7.0.6 - 09.12.2024*
- 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

View file

@ -4,13 +4,19 @@
# full copyright notices and license terms.
from trytond.pool import Pool
from .edocument import Invoice
from .edocument import XRechnung, FacturX
from .bank import AccountNumber
from .party import PartyConfiguration, Party
from .configuration import Configuration, BankEdocumentRel
def register():
Pool.register(
Invoice,
AccountNumber,
XRechnung,
Configuration,
BankEdocumentRel,
FacturX,
Party,
PartyConfiguration,
module='edocument_xrechnung', type_='model')

97
bank.py Normal file
View file

@ -0,0 +1,97 @@
# -*- 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 sql.conditionals import Case
from trytond.pool import PoolMeta, Pool
from trytond.model import fields
from trytond.transaction import Transaction
DEF_NONE = None
class AccountNumber(metaclass=PoolMeta):
__name__ = 'bank.account.number'
company_owned = fields.Function(fields.Boolean(
string='Number belongs to Company',
readonly=True),
'get_company_owned',
searcher='searcher_company_owned')
@classmethod
def get_company_owned_sql(cls):
""" get sql to search for bank acconts owned by company-party
"""
pool = Pool()
Account = pool.get('bank.account')
Number = pool.get('bank.account.number')
Owners = pool.get('bank.account-party.party')
Company = pool.get('company.company')
context = Transaction().context
tab_acc = Account.__table__()
tab_owner = Owners.__table__()
tab_num = Number.__table__()
company_id = context.get('company', -1)
party_id = -1
if company_id and company_id > 0:
party_id = Company(company_id).party.id
query = tab_num.join(
tab_acc,
condition=tab_num.account == tab_acc.id,
).join(
tab_owner,
condition=tab_owner.account == tab_acc.id,
).select(
tab_num.id.as_('number'),
Case(
(tab_owner.owner == party_id, True),
else_=False,
).as_('owned'))
return (tab_num, query)
@classmethod
def searcher_company_owned(cls, name, clause):
""" search in owned by party
Args:
name (str): field name
clause (list): search domain
Returns:
list: updated search domain
"""
Operator = fields.SQL_OPERATORS[clause[1]]
(tab_num, query) = cls.get_company_owned_sql()
query = query.select(
query.number,
where=Operator(query.owned, clause[2]))
return [('id', 'in', query)]
@classmethod
def get_company_owned(cls, records, names):
""" get list of bank account numbers owned by company
"""
cursor = Transaction().connection.cursor()
result = {x: {y.id: False for y in records} for x in names}
(tab_num, query) = cls.get_company_owned_sql()
query.where = tab_num.id.in_([x.id for x in records])
cursor.execute(*query)
lines = cursor.fetchall()
for line in lines:
values = {'company_owned': line[1]}
for name in names:
result[name][line[0]] = values.get(name)
return result
# end AccountNumber

35
configuration.py Normal file
View file

@ -0,0 +1,35 @@
# -*- 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 trytond.pool import PoolMeta
from trytond.model import ModelSQL, fields
class Configuration(metaclass=PoolMeta):
__name__ = 'account.configuration'
edocument_bank = fields.Many2Many(
string='Bank accounts',
relation_name='edocument_xrechnung.bank_rel',
origin='config', target='bankaccount',
filter=[('company_owned', '=', True)],
help='The bank accounts listed here are output in the invoice XML.')
# end Configuration
class BankEdocumentRel(ModelSQL):
'Bank - eDocument - Relation'
__name__ = 'edocument_xrechnung.bank_rel'
bankaccount = fields.Many2One(
string='Account', model_name='bank.account.number',
required=True, ondelete='CASCADE')
config = fields.Many2One(
string='Configuration', model_name='account.configuration',
required=True, ondelete='CASCADE')
# end BankEdocumentRel

15
configuration.xml Normal file
View file

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<!-- 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. -->
<tryton>
<data>
<record model="ir.ui.view" id="configuration_view_form">
<field name="model">account.configuration</field>
<field name="inherit" ref="account.configuration_view_form"/>
<field name="name">configuration_form</field>
</record>
</data>
</tryton>

View file

@ -6,3 +6,9 @@ https://github.com/itplr-kosit
validator:
- https://erechnungsvalidator.service-bw.de/
überweisungsdaten
https://portal3.gefeg.com/projectdata/invoice/deliverables/installed/publishingproject/zugferd%202.0.1%20-%20facturx%201.03/en%2016931%20%E2%80%93%20facturx%201.03%20%E2%80%93%20zugferd%202.0.1%20-%20basic.scm/html/de/021.htm?https://portal3.gefeg.com/projectdata/invoice/deliverables/installed/publishingproject/zugferd%202.0.1%20-%20facturx%201.03/en%2016931%20%E2%80%93%20facturx%201.03%20%E2%80%93%20zugferd%202.0.1%20-%20basic.scm/html/de/02134.htm
https://erechnungsvalidator.service-bw.de/
https://ecosio.com/de/peppol-und-xml-dokumente-online-validieren/

View file

@ -5,14 +5,12 @@
import genshi.template
import os
import html
from trytond.exceptions import UserError
from trytond.i18n import gettext
from trytond.modules.edocument_uncefact.edocument import Invoice
from decimal import Decimal
from trytond.modules.edocument_uncefact.edocument import Invoice
from .mixin import EdocumentMixin
class Invoice(Invoice):
class XRechnung(EdocumentMixin, Invoice):
'EDocument XRechnung'
__name__ = 'edocument.xrechnung.invoice'
@ -52,90 +50,48 @@ class Invoice(Invoice):
if notes:
return '; '.join(notes)
def invoice_line_tax(self, line):
""" get tax of invoice-line,
fire exception if no/multiple taxes exists
def _get_template(self, version):
""" load our own template if 'version' is ours
"""
if len(line.invoice_taxes) != 1:
raise UserError(gettext(
'edocument_xrechnung.msg_linetax_invalid_number',
linename=line.rec_name,
numtax=len(line.invoice_taxes)))
loader = genshi.template.TemplateLoader(
os.path.join(os.path.dirname(__file__), 'template'),
auto_reload=True)
allowed_cat = ['AE', 'L', 'M', 'E', 'S', 'Z', 'G', 'O', 'K', 'B']
unece_category_code = self.get_category_code(line.invoice_taxes[0].tax)
if unece_category_code not in allowed_cat:
raise UserError(gettext(
'edocument_xrechnung.msg_linetax_invalid_catcode',
taxname=line.invoice_taxes[0].tax.rec_name,
allowed=', '.join(allowed_cat)))
if version in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']:
file_name = {
'380': 'XRechnung_invoice.xml',
'389': 'XRechnung_invoice.xml',
'381': 'XRechnung_credit.xml',
'261': 'XRechnung_credit.xml',
}.get(self.type_code)
return line.invoice_taxes[0].tax
if file_name:
return loader.load(os.path.join(version, file_name))
else:
raise ValueError('invalid type-code "%s"' % self.type_code)
else:
return super()._get_template(version)
def taxident_data(self, tax_identifier):
""" get tax-scheme-id and codes
"""
result = {'code': None, 'id': None}
# end XRechnung
if tax_identifier:
if tax_identifier.type == 'de_vat':
result['code'] = 'DE%s' % tax_identifier.code
result['id'] = 'VAT'
return result
def tax_rate(self, tax):
""" get tax-rate in procent
"""
return (tax.rate * Decimal('100.0')).quantize(Decimal('0.01'))
def uom_unece_code(self, line):
""" 'line': invoice.line
"""
if len(line.unit.unece_code or '') == 0:
raise UserError(gettext(
'edocument_xrechnung.msg_uom_code_missing',
uomname=line.unit.rec_name))
return line.unit.unece_code
def get_category_code(self, tax):
while tax:
if tax.unece_category_code:
return tax.unece_category_code
break
tax = tax.parent
def tax_category_code(self, tax):
""" read tax-category, fire exception if missing
"""
unece_category_code = self.get_category_code(tax)
if not unece_category_code:
raise UserError(gettext(
'edocument_xrechnung.mds_tax_category_missing',
taxname=tax.rec_name))
return unece_category_code
def quote_text(self, text):
""" replace critical chars
"""
if text:
return html.quote(text)
class FacturX(EdocumentMixin, Invoice):
'Factur-X'
__name__ = 'edocument.facturxext.invoice'
def _get_template(self, version):
""" load our own template if 'version' is ours
"""
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'))
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)
return super()._get_template(version)
# end Invoice
# end FacturX

View file

@ -14,6 +14,10 @@ msgctxt "model:ir.message,text:mds_tax_category_missing"
msgid "The UNECE tax category is not configured for tax '%(taxname)s'."
msgstr "Für die Steuer '%(taxname)s' ist die UNECE-Steuerkategorie nicht konfiguriert."
msgctxt "model:ir.message,text:msg_tax_code_missing"
msgid "The UNECE tax code is not configured for tax '%(taxname)s'."
msgstr "Für die Steuer '%(taxname)s' ist der UNECE Steuercode nicht konfiguriert."
msgctxt "model:ir.message,text:msg_uom_code_missing"
msgid "The UNECE uom code is not configured for unit '%(uomname)s'."
msgstr "Für die Einheit '%(uomname)s' ist der UNECE-Einheitencode nicht konfiguriert."
@ -26,6 +30,18 @@ msgctxt "model:ir.message,text:msg_linetax_invalid_catcode"
msgid "Invalid category code at tax '%(taxname)s' (allowed: %(allowed)s)."
msgstr "Ungültiger Kategoriecode an der Steuer '%(taxname)s' (erlaubt: %(allowed)s)."
msgctxt "model:ir.message,text:msg_no_seller_address"
msgid "There is no address for the seller party '%(sellerparty)s'."
msgstr "Für die Verkäuferpartei '%(sellerparty)s' existiert keine Adresse."
msgctxt "model:ir.message,text:msg_no_buyer_address"
msgid "There is no address for the buyer party '%(buyerparty)s'."
msgstr "Für die Käuferpartei '%(sellerparty)s' existiert keine Adresse."
msgctxt "model:ir.message,text:msg_no_address_country"
msgid "No country is specified for the address of the party '%(party)s'."
msgstr "Für die Adresse der Partei '%(party)s' ist kein Land festgelegt."
#######################
# party.configuration #
@ -47,9 +63,12 @@ msgid "X-Rechnung Route-ID"
msgstr "X-Rechnung Leitweg-ID"
msgctxt "help:party.party,xrechnung_routeid:"
msgid "Enables the need for an XRechnung route ID at the party for exporting the XRechnung."
msgstr "Aktiviert die Notwendigkeit einer XRechnung-Leitweg-ID an der Partei für den Export der XRechnung."
msgid ""
"When activated an XRechnung route ID must be used for this party for X-Rechnung exports.\n"
"The route ID must be defined as identifier of type \"X-Rechnung Route-ID\"."
msgstr ""
"Bei Aktivierung muss eine XRechnung-Leitweg-ID bei Rechnungsexporten für diese Partei benutzt werden.\n"
"Die Leitweg-ID muss als Identifikator mit Typ \"X-Rechnung Route-ID\" angelegt werden."
###############
# account.tax #
@ -97,3 +116,31 @@ msgstr "Allgemeine indirekte Steuer der kanarischen Inseln"
msgctxt "selection:account.tax,xrtax_category:"
msgid "Tax on production; services and imports in Ceuta and Melilla"
msgstr "Steuer für Produktion; Dienstleistungen und Einfuhr in Ceuta und Melilla"
#########################
# account.configuration #
#########################
msgctxt "field:account.configuration,edocument_bank:"
msgid "Bank accounts"
msgstr "Bankkonten"
msgctxt "help:account.configuration,edocument_bank:"
msgid "The bank accounts listed here are output in the invoice XML."
msgstr "Die hier aufgeführten Bankkonten werden in der Rechnungs-XML ausgegeben."
################################
# edocument_xrechnung.bank_rel #
################################
msgctxt "model:edocument_xrechnung.bank_rel,name:"
msgid "Bank - eDocument - Relation"
msgstr "Bank - eDocument - Verknüpfung"
msgctxt "field:edocument_xrechnung.bank_rel,bankaccount:"
msgid "Account"
msgstr "Konto"
msgctxt "field:edocument_xrechnung.bank_rel,config:"
msgid "Configuration"
msgstr "Konfiguration"

View file

@ -10,6 +10,10 @@ msgctxt "model:ir.message,text:mds_tax_category_missing"
msgid "The UNECE tax category is not configured for tax '%(taxname)s'."
msgstr "The UNECE tax category is not configured for tax '%(taxname)s'."
msgctxt "model:ir.message,text:msg_tax_code_missing"
msgid "The UNECE tax code is not configured for tax '%(taxname)s'."
msgstr "The UNECE tax code is not configured for tax '%(taxname)s'."
msgctxt "model:ir.message,text:msg_uom_code_missing"
msgid "The UNECE uom code is not configured for unit '%(uomname)s'."
msgstr "The UNECE uom code is not configured for unit '%(uomname)s'."
@ -22,6 +26,18 @@ msgctxt "model:ir.message,text:msg_linetax_invalid_catcode"
msgid "Invalid category code at tax '%(taxname)s' (allowed: %(allowed)s)."
msgstr "Invalid category code at tax '%(taxname)s' (allowed: %(allowed)s)."
msgctxt "model:ir.message,text:msg_no_seller_address"
msgid "There is no address for the seller party '%(sellerparty)s'."
msgstr "There is no address for the seller party '%(sellerparty)s'."
msgctxt "model:ir.message,text:msg_no_buyer_address"
msgid "There is no address for the buyer party '%(buyerparty)s'."
msgstr "There is no address for the buyer party '%(buyerparty)s'."
msgctxt "model:ir.message,text:msg_no_address_country"
msgid "No country is specified for the address of the party '%(party)s'."
msgstr "No country is specified for the address of the party '%(party)s'."
msgctxt "selection:party.configuration,identifier_types:"
msgid "X-Rechnung Route-ID"
msgstr "X-Rechnung Route-ID"
@ -82,3 +98,23 @@ msgctxt "selection:account.tax,xrtax_category:"
msgid "Tax on production; services and imports in Ceuta and Melilla"
msgstr "Tax on production; services and imports in Ceuta and Melilla"
msgctxt "field:account.configuration,edocument_bank:"
msgid "Bank accounts"
msgstr "Bank accounts"
msgctxt "help:account.configuration,edocument_bank:"
msgid "The bank accounts listed here are output in the invoice XML."
msgstr "The bank accounts listed here are output in the invoice XML."
msgctxt "model:edocument_xrechnung.bank_rel,name:"
msgid "Bank - eDocument - Relation"
msgstr "Bank - eDocument - Relation"
msgctxt "field:edocument_xrechnung.bank_rel,bankaccount:"
msgid "Account"
msgstr "Account"
msgctxt "field:edocument_xrechnung.bank_rel,config:"
msgid "Configuration"
msgstr "Configuration"

View file

@ -11,6 +11,9 @@ full copyright notices and license terms. -->
<record model="ir.message" id="mds_tax_category_missing">
<field name="text">The UNECE tax category is not configured for tax '%(taxname)s'.</field>
</record>
<record model="ir.message" id="msg_tax_code_missing">
<field name="text">The UNECE tax code is not configured for tax '%(taxname)s'.</field>
</record>
<record model="ir.message" id="msg_uom_code_missing">
<field name="text">The UNECE uom code is not configured for unit '%(uomname)s'.</field>
</record>
@ -20,6 +23,15 @@ full copyright notices and license terms. -->
<record model="ir.message" id="msg_linetax_invalid_catcode">
<field name="text">Invalid category code at tax '%(taxname)s' (allowed: %(allowed)s).</field>
</record>
<record model="ir.message" id="msg_no_seller_address">
<field name="text">There is no address for the seller party '%(sellerparty)s'.</field>
</record>
<record model="ir.message" id="msg_no_buyer_address">
<field name="text">There is no address for the buyer party '%(buyerparty)s'.</field>
</record>
<record model="ir.message" id="msg_no_address_country">
<field name="text">No country is specified for the address of the party '%(party)s'.</field>
</record>
</data>
</tryton>

206
mixin.py Normal file
View file

@ -0,0 +1,206 @@
# -*- 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 decimal import Decimal
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.modules.product import round_price
class EdocumentMixin(object):
""" functions to get field values for xml
"""
__slots__ = ()
def company_bank_accounts(self):
""" get leist of bank account numbers, defined in config
Returns:
list: records of model bank.account.number
"""
Configuration = Pool().get('account.configuration')
result = []
cfg1 = Configuration.get_singleton()
if cfg1 and cfg1.edocument_bank:
result.extend(list(cfg1.edocument_bank))
else:
result.extend([
y
for x in self.invoice.company.party.bank_accounts
for y in x.numbers])
return result
@cached_property
def seller_trade_address(self):
""" get address of seller, throw exception if incomplete
Raises:
UserError: if no address
UserError: if no country on address
Returns:
record : model party.address
"""
result = super().seller_trade_address
if not result:
raise UserError(gettext(
'edocument_xrechnung.msg_no_seller_address',
sellerparty=self.invoice.company.rec_name
if self.invoice and self.invoice.company
else '-'))
if not result.country:
raise UserError(gettext(
'edocument_xrechnung.msg_no_address_country',
party=result.party.rec_name if result.party else '-'))
return result
@cached_property
def buyer_trade_address(self):
""" exception if no address
Returns:
record: model party.address
"""
if (self.invoice.type == 'out') and (
not self.invoice.invoice_address):
raise UserError(gettext(
'edocument_xrechnung.msg_no_buyer_address',
buyerparty=self.invoice.party.rec_name
if self.invoice and self.invoice.party
else '-'))
result = super().buyer_trade_address
if result and not result.country:
raise UserError(gettext(
'edocument_xrechnung.msg_no_address_country',
party=result.party.rec_name if result.party else '-'))
return result
def get_list_of_comments(self):
""" comment, to export in <ram:IncludedNote/>
Returns:
_type_: _description_
"""
result = []
if self.invoice.comment:
result.append({
'content': self.invoice.comment,
'subject_code': '',
'content_code': ''})
return result
def invoice_line_tax(self, line):
""" get tax of invoice-line,
fire exception if no/multiple taxes exists
"""
Tax = Pool().get('account.tax')
if len(line.taxes) != 1:
raise UserError(gettext(
'edocument_xrechnung.msg_linetax_invalid_number',
linename=line.rec_name,
numtax=len(line.taxes)))
taxlines = Tax.compute(
line.taxes, Decimal('1'), 1.0,
line.invoice.accounting_date or line.invoice.invoice_date)
assert len(taxlines) == 1
tax = taxlines[0]['tax']
allowed_cat = ['AE', 'L', 'M', 'E', 'S', 'Z', 'G', 'O', 'K', 'B']
unece_category_code = self.get_category_code(tax)
if unece_category_code not in allowed_cat:
raise UserError(gettext(
'edocument_xrechnung.msg_linetax_invalid_catcode',
taxname=tax.rec_name,
allowed=', '.join(allowed_cat)))
return tax
def taxident_data(self, tax_identifier):
""" get tax-scheme-id and codes
"""
result = {'code': None, 'id': None}
if tax_identifier:
if tax_identifier.type == 'de_vat':
result['code'] = 'DE%s' % tax_identifier.code
result['id'] = 'VAT'
return result
def tax_rate(self, tax):
""" get tax-rate in procent
"""
return (tax.rate * Decimal('100.0')).quantize(Decimal('0.01'))
def uom_unece_code(self, line):
""" 'line': invoice.line
"""
if len(line.unit.unece_code or '') == 0:
raise UserError(gettext(
'edocument_xrechnung.msg_uom_code_missing',
uomname=line.unit.rec_name))
return line.unit.unece_code
def tax_unece_code(self, tax):
""" 'tax': invoice.line
"""
unece_code = self.get_tax_unece_code(tax)
if not unece_code:
raise UserError(gettext(
'edocument_xrechnung.msg_tax_code_missing',
taxname=tax.rec_name))
return tax.unece_code
def get_tax_unece_code(self, tax):
while tax:
if tax.unece_code:
return tax.unece_code
break
tax = tax.parent
def get_category_code(self, tax):
while tax:
if tax.unece_category_code:
return tax.unece_category_code
break
tax = tax.parent
def tax_category_code(self, tax):
""" read tax-category, fire exception if missing
"""
unece_category_code = self.get_category_code(tax)
if not unece_category_code:
raise UserError(gettext(
'edocument_xrechnung.mds_tax_category_missing',
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)
# end EdocumentMixin

View file

@ -14,8 +14,10 @@ class Party(metaclass=PoolMeta):
xrechnung_routeid = fields.Boolean(
string='X-Rechnung Route-ID',
help='Enables the need for an XRechnung route ID at the party ' +
'for exporting the XRechnung.')
help='When activated an XRechnung route ID must be used '
'for this party for X-Rechnung exports.\n'
'The route ID must be defined as identifier of type '
'"X-Rechnung Route-ID".')
def get_xrechnung_route_id(self):
""" search for route-id at party, fire-exception if missing
@ -45,7 +47,7 @@ class Party(metaclass=PoolMeta):
Args:
records (list): records of party.party
"""
super(Party, cls).validate(records)
super().validate(records)
for record in records:
record.get_xrechnung_route_id()
@ -57,7 +59,7 @@ class PartyConfiguration(metaclass=PoolMeta):
@classmethod
def __setup__(cls):
super(PartyConfiguration, cls).__setup__()
super().__setup__()
cls.identifier_types.selection.append(
('edoc_route_id', 'X-Rechnung Route-ID'))

View file

@ -13,11 +13,9 @@ 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'))
@ -87,6 +85,8 @@ 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',
@ -99,7 +99,9 @@ setup(
info.get('xml', [])
+ ['tryton.cfg', 'locale/*.po', 'tests/*.py',
'template/*/*.xml', 'versiondep.txt', 'README.rst',
'tests/*/*/*/*.xsd']),
'tests/*/*/*/*.xsd', 'view/*.xml',
'tests/*/*.xsd', 'tests/*/*.sch', 'tests/*/*.xml',
'tests/*/*/*.xslt', 'tests/*/*/*.xml']),
},
install_requires=requires,

View file

@ -0,0 +1,154 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- This file is part of Tryton. The COPYRIGHT file at the top level of
this repository contains the full copyright notices and license terms. -->
<rsm:CrossIndustryInvoice
xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
xmlns:py="http://genshi.edgewall.org/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<py:def function="DateTime(value)">
<udt:DateTimeString format="102">${value.strftime('%Y%m%d')}</udt:DateTimeString>
</py:def>
<py:def function="TradeParty(party, address=None, tax_identifier=None)">
<ram:Name>${this.quote_text(party.name)}</ram:Name>
<ram:SpecifiedLegalOrganization>
<py:for each="id, attrs in this.party_legal_ids(party, address)">
<ram:ID py:attrs="attrs">${id}</ram:ID>
</py:for>
</ram:SpecifiedLegalOrganization>
<ram:PostalTradeAddress py:if="address">${TradeAddress(address)}</ram:PostalTradeAddress>
<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>
<py:def function="TradeAddress(address)">
<ram:PostcodeCode py:if="address.postal_code">${address.postal_code}</ram:PostcodeCode>
<py:with vars="lines = (address.street or '').splitlines()">
<ram:LineOne py:if="len(lines) > 0">${this.quote_text(lines[0])}</ram:LineOne>
<ram:LineTwo py:if="len(lines) > 1">${this.quote_text(lines[1])}</ram:LineTwo>
<ram:LineThree py:if="len(lines) > 2">${(lines[2])}</ram:LineThree>
</py:with>
<ram:CityName py:if="address.city">${(address.city)}</ram:CityName>
<ram:CountryID py:if="address.country">${address.country.code}</ram:CountryID>
<ram:CountrySubDivisionName py:if="address.subdivision">${this.quote_text(address.subdivision.name)}</ram:CountrySubDivisionName>
</py:def>
<py:def function="TradeTax(tax, amount=None, base=None)">
<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">${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>
</ram:ApplicableTradeTax>
</py:def>
<rsm:ExchangedDocumentContext>
<ram:GuidelineSpecifiedDocumentContextParameter>
<ram:ID>urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended</ram:ID>
</ram:GuidelineSpecifiedDocumentContextParameter>
</rsm:ExchangedDocumentContext>
<rsm:ExchangedDocument>
<ram:ID>${this.quote_text(this.invoice.number)}</ram:ID>
<ram:Name py:if="this.invoice.description">${this.quote_text(this.invoice.description)}</ram:Name>
<ram:TypeCode>${this.type_code}</ram:TypeCode>
<ram:IssueDateTime>
${DateTime(this.invoice.invoice_date)}
</ram:IssueDateTime>
<py:for each="comment_line in this.get_list_of_comments()">
<ram:IncludedNote>
<ram:ContentCode py:if="comment_line.get('content_code')">${comment_line['content_code']}</ram:ContentCode>
<ram:Content py:if="comment_line.get('content')">${comment_line['content']}</ram:Content>
<ram:SubjectCode py:if="comment_line.get('subject_content')">${comment_line['subject_content']}</ram:SubjectCode>
</ram:IncludedNote>
</py:for>
</rsm:ExchangedDocument>
<rsm:SupplyChainTradeTransaction>
<ram:IncludedSupplyChainTradeLineItem py:for="line_id, line in enumerate(this.lines, 1)">
<ram:AssociatedDocumentLineDocument>
<ram:LineID>${line_id}</ram:LineID>
</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 '')}</ram:Name>
<ram:Description py:if="line.description">${this.quote_text(line.description)}</ram:Description>
</ram:SpecifiedTradeProduct>
<ram:SpecifiedLineTradeAgreement>
<ram:NetPriceProductTradePrice>
<ram:ChargeAmount py:attrs="{'currencyID': this.invoice.currency.code}">${this.round_unitprice(line.unit_price)}</ram:ChargeAmount>
</ram:NetPriceProductTradePrice>
</ram:SpecifiedLineTradeAgreement>
<ram:SpecifiedLineTradeDelivery>
<ram:BilledQuantity py:attrs="{'unitCode': line.unit.unece_code} if line.unit and line.unit.unece_code else {}">${line.quantity * this.type_sign}</ram:BilledQuantity>
</ram:SpecifiedLineTradeDelivery>
<ram:SpecifiedLineTradeSettlement>
${TradeTax(this.invoice_line_tax(line))}
<ram:SpecifiedTradeSettlementLineMonetarySummation>
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${line.amount}</ram:LineTotalAmount>
</ram:SpecifiedTradeSettlementLineMonetarySummation>
</ram:SpecifiedLineTradeSettlement>
</ram:IncludedSupplyChainTradeLineItem>
<ram:ApplicableHeaderTradeAgreement>
<ram:SellerTradeParty>
${TradeParty(this.seller_trade_party, this.seller_trade_address, this.seller_trade_tax_identifier)}
</ram:SellerTradeParty>
<ram:BuyerTradeParty>
${TradeParty(this.buyer_trade_party, this.buyer_trade_address, this.buyer_trade_tax_identifier)}
</ram:BuyerTradeParty>
<ram:SellerOrderReferencedDocument py:if="this.invoice.type == 'in'">
<ram:IssuerAssignedID>${this.quote_text(this.invoice.reference)}</ram:IssuerAssignedID>
</ram:SellerOrderReferencedDocument>
<ram:BuyerOrderReferencedDocument py:if="this.invoice.type == 'out'">
<ram:IssuerAssignedID>${this.quote_text(this.invoice.reference)}</ram:IssuerAssignedID>
</ram:BuyerOrderReferencedDocument>
</ram:ApplicableHeaderTradeAgreement>
<ram:ApplicableHeaderTradeDelivery>
<ram:ShipToTradeParty py:if="this.ship_to_trade_party">
${TradeParty(this.ship_to_trade_party, this.ship_to_trade_address)}
</ram:ShipToTradeParty>
<ram:ShipFromTradeParty py:if="this.ship_from_trade_party">
${TradeParty(this.ship_from_trade_party, this.ship_from_trade_address)}
</ram:ShipFromTradeParty>
</ram:ApplicableHeaderTradeDelivery>
<ram:ApplicableHeaderTradeSettlement>
<ram:PaymentReference>${this.quote_text(this.payment_reference)}</ram:PaymentReference>
<ram:InvoiceCurrencyCode>${this.invoice.currency.code}</ram:InvoiceCurrencyCode>
<py:if test="len(this.company_bank_accounts()) == 0">
<ram:SpecifiedTradeSettlementPaymentMeans>
<ram:TypeCode>1</ram:TypeCode>
</ram:SpecifiedTradeSettlementPaymentMeans>
</py:if>
<py:for each="banknumber in this.company_bank_accounts()">
<ram:SpecifiedTradeSettlementPaymentMeans>
<ram:TypeCode>30</ram:TypeCode>
<ram:Information>Wire transfer</ram:Information>
<ram:PayeePartyCreditorFinancialAccount>
<ram:IBANID>${banknumber.number_compact}</ram:IBANID>
<ram:AccountName>${this.quote_text(banknumber.account.bank.party.rec_name)}</ram:AccountName>
</ram:PayeePartyCreditorFinancialAccount>
<ram:PayeeSpecifiedCreditorFinancialInstitution>
<ram:BICID>${banknumber.account.bank.bic}</ram:BICID>
</ram:PayeeSpecifiedCreditorFinancialInstitution>
</ram:SpecifiedTradeSettlementPaymentMeans>
</py:for>
<py:for each="tax in this.invoice.taxes">
${TradeTax(tax.tax, tax.amount, tax.base)}
</py:for>
<ram:SpecifiedTradePaymentTerms py:for="line in this.invoice.lines_to_pay">
<ram:Description py:if="this.invoice.payment_term and this.invoice.payment_term.description">${this.invoice.payment_term.description}</ram:Description>
<ram:DueDateDateTime>
${DateTime(line.maturity_date)}
</ram:DueDateDateTime>
<ram:PartialPaymentAmount py:attrs="{'currencyID': this.invoice.currency.code}">${(line.amount_second_currency or (line.debit - line.credit)) * this.type_sign}</ram:PartialPaymentAmount>
</ram:SpecifiedTradePaymentTerms>
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${this.invoice.untaxed_amount * this.type_sign}</ram:LineTotalAmount>
<ram:TaxBasisTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${this.invoice.untaxed_amount * this.type_sign}</ram:TaxBasisTotalAmount>
<ram:TaxTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${this.invoice.tax_amount * this.type_sign}</ram:TaxTotalAmount>
<ram:GrandTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${this.invoice.total_amount * this.type_sign}</ram:GrandTotalAmount>
<ram:DuePayableAmount py:attrs="{'currencyID': this.invoice.currency.code}">${this.invoice.amount_to_pay * this.type_sign}</ram:DuePayableAmount>
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
</ram:ApplicableHeaderTradeSettlement>
</rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
targetNamespace="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
elementFormDefault="qualified">
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="Factur-X_1.07.2_EXTENDED_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd"/>
<xs:import namespace="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" schemaLocation="Factur-X_1.07.2_EXTENDED_urn_un_unece_uncefact_data_standard_ReusableAggregateBusinessInformationEntity_100.xsd"/>
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_1.07.2_EXTENDED_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
<xs:element name="CrossIndustryInvoice" type="rsm:CrossIndustryInvoiceType"/>
<xs:complexType name="CrossIndustryInvoiceType">
<xs:sequence>
<xs:element name="ExchangedDocumentContext" type="ram:ExchangedDocumentContextType"/>
<xs:element name="ExchangedDocument" type="ram:ExchangedDocumentType"/>
<xs:element name="SupplyChainTradeTransaction" type="ram:SupplyChainTradeTransactionType"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
elementFormDefault="qualified">
<xs:simpleType name="AccountingAccountTypeCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="AccountingAccountTypeCodeType">
<xs:simpleContent>
<xs:extension base="qdt:AccountingAccountTypeCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="AllowanceChargeReasonCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="AllowanceChargeReasonCodeType">
<xs:simpleContent>
<xs:extension base="qdt:AllowanceChargeReasonCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="ContactTypeCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="ContactTypeCodeType">
<xs:simpleContent>
<xs:extension base="qdt:ContactTypeCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="CountryIDContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="CountryIDType">
<xs:simpleContent>
<xs:extension base="qdt:CountryIDContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="CurrencyCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="CurrencyCodeType">
<xs:simpleContent>
<xs:extension base="qdt:CurrencyCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="DeliveryTermsCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="DeliveryTermsCodeType">
<xs:simpleContent>
<xs:extension base="qdt:DeliveryTermsCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="DocumentCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="DocumentCodeType">
<xs:simpleContent>
<xs:extension base="qdt:DocumentCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="FormattedDateTimeFormatContentType">
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:complexType name="FormattedDateTimeType">
<xs:sequence>
<xs:element name="DateTimeString">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="format" type="qdt:FormattedDateTimeFormatContentType" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="LineStatusCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="LineStatusCodeType">
<xs:simpleContent>
<xs:extension base="qdt:LineStatusCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="PartyRoleCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="PartyRoleCodeType">
<xs:simpleContent>
<xs:extension base="qdt:PartyRoleCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="PaymentMeansCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="PaymentMeansCodeType">
<xs:simpleContent>
<xs:extension base="qdt:PaymentMeansCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="ReferenceCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="ReferenceCodeType">
<xs:simpleContent>
<xs:extension base="qdt:ReferenceCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="TaxCategoryCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="TaxCategoryCodeType">
<xs:simpleContent>
<xs:extension base="qdt:TaxCategoryCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="TaxTypeCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="TaxTypeCodeType">
<xs:simpleContent>
<xs:extension base="qdt:TaxTypeCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="TimeReferenceCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="TimeReferenceCodeType">
<xs:simpleContent>
<xs:extension base="qdt:TimeReferenceCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
<xs:simpleType name="TransportModeCodeContentType">
<xs:restriction base="xs:token"/>
</xs:simpleType>
<xs:complexType name="TransportModeCodeType">
<xs:simpleContent>
<xs:extension base="qdt:TransportModeCodeContentType"/>
</xs:simpleContent>
</xs:complexType>
</xs:schema>

View file

@ -0,0 +1,465 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
targetNamespace="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
elementFormDefault="qualified">
<xs:import namespace="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" schemaLocation="Factur-X_1.07.2_EXTENDED_urn_un_unece_uncefact_data_standard_QualifiedDataType_100.xsd"/>
<xs:import namespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" schemaLocation="Factur-X_1.07.2_EXTENDED_urn_un_unece_uncefact_data_standard_UnqualifiedDataType_100.xsd"/>
<xs:complexType name="AdvancePaymentType">
<xs:sequence>
<xs:element name="PaidAmount" type="udt:AmountType"/>
<xs:element name="FormattedReceivedDateTime" type="qdt:FormattedDateTimeType" minOccurs="0"/>
<xs:element name="IncludedTradeTax" type="ram:TradeTaxType" maxOccurs="unbounded"/>
<xs:element name="InvoiceSpecifiedReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="CreditorFinancialAccountType">
<xs:sequence>
<xs:element name="IBANID" type="udt:IDType" minOccurs="0"/>
<xs:element name="AccountName" type="udt:TextType" minOccurs="0"/>
<xs:element name="ProprietaryID" type="udt:IDType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="CreditorFinancialInstitutionType">
<xs:sequence>
<xs:element name="BICID" type="udt:IDType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="DebtorFinancialAccountType">
<xs:sequence>
<xs:element name="IBANID" type="udt:IDType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="DocumentContextParameterType">
<xs:sequence>
<xs:element name="ID" type="udt:IDType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="DocumentLineDocumentType">
<xs:sequence>
<xs:element name="LineID" type="udt:IDType"/>
<xs:element name="ParentLineID" type="udt:IDType" minOccurs="0"/>
<xs:element name="LineStatusCode" type="qdt:LineStatusCodeType" minOccurs="0"/>
<xs:element name="LineStatusReasonCode" type="udt:CodeType" minOccurs="0"/>
<xs:element name="IncludedNote" type="ram:NoteType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ExchangedDocumentContextType">
<xs:sequence>
<xs:element name="TestIndicator" type="udt:IndicatorType" minOccurs="0"/>
<xs:element name="BusinessProcessSpecifiedDocumentContextParameter" type="ram:DocumentContextParameterType" minOccurs="0"/>
<xs:element name="GuidelineSpecifiedDocumentContextParameter" type="ram:DocumentContextParameterType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ExchangedDocumentType">
<xs:sequence>
<xs:element name="ID" type="udt:IDType"/>
<xs:element name="Name" type="udt:TextType" minOccurs="0"/>
<xs:element name="TypeCode" type="qdt:DocumentCodeType"/>
<xs:element name="IssueDateTime" type="udt:DateTimeType"/>
<xs:element name="CopyIndicator" type="udt:IndicatorType" minOccurs="0"/>
<xs:element name="LanguageID" type="udt:IDType" minOccurs="0"/>
<xs:element name="IncludedNote" type="ram:NoteType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="EffectiveSpecifiedPeriod" type="ram:SpecifiedPeriodType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="HeaderTradeAgreementType">
<xs:sequence>
<xs:element name="BuyerReference" type="udt:TextType" minOccurs="0"/>
<xs:element name="SellerTradeParty" type="ram:TradePartyType"/>
<xs:element name="BuyerTradeParty" type="ram:TradePartyType"/>
<xs:element name="SalesAgentTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="BuyerTaxRepresentativeTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="SellerTaxRepresentativeTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="ProductEndUserTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="ApplicableTradeDeliveryTerms" type="ram:TradeDeliveryTermsType" minOccurs="0"/>
<xs:element name="SellerOrderReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="BuyerOrderReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="QuotationReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="ContractReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="AdditionalReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="BuyerAgentTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="SpecifiedProcuringProject" type="ram:ProcuringProjectType" minOccurs="0"/>
<xs:element name="UltimateCustomerOrderReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="HeaderTradeDeliveryType">
<xs:sequence>
<xs:element name="RelatedSupplyChainConsignment" type="ram:SupplyChainConsignmentType" minOccurs="0"/>
<xs:element name="ShipToTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="UltimateShipToTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="ShipFromTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="ActualDeliverySupplyChainEvent" type="ram:SupplyChainEventType" minOccurs="0"/>
<xs:element name="DespatchAdviceReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="ReceivingAdviceReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="DeliveryNoteReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="HeaderTradeSettlementType">
<xs:sequence>
<xs:element name="CreditorReferenceID" type="udt:IDType" minOccurs="0"/>
<xs:element name="PaymentReference" type="udt:TextType" minOccurs="0"/>
<xs:element name="TaxCurrencyCode" type="qdt:CurrencyCodeType" minOccurs="0"/>
<xs:element name="InvoiceCurrencyCode" type="qdt:CurrencyCodeType"/>
<xs:element name="InvoiceIssuerReference" type="udt:TextType" minOccurs="0"/>
<xs:element name="InvoicerTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="InvoiceeTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="PayeeTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="PayerTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="TaxApplicableTradeCurrencyExchange" type="ram:TradeCurrencyExchangeType" minOccurs="0"/>
<xs:element name="SpecifiedTradeSettlementPaymentMeans" type="ram:TradeSettlementPaymentMeansType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="ApplicableTradeTax" type="ram:TradeTaxType" maxOccurs="unbounded"/>
<xs:element name="BillingSpecifiedPeriod" type="ram:SpecifiedPeriodType" minOccurs="0"/>
<xs:element name="SpecifiedTradeAllowanceCharge" type="ram:TradeAllowanceChargeType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="SpecifiedLogisticsServiceCharge" type="ram:LogisticsServiceChargeType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="SpecifiedTradePaymentTerms" type="ram:TradePaymentTermsType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="SpecifiedTradeSettlementHeaderMonetarySummation" type="ram:TradeSettlementHeaderMonetarySummationType"/>
<xs:element name="InvoiceReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="ReceivableSpecifiedTradeAccountingAccount" type="ram:TradeAccountingAccountType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="SpecifiedAdvancePayment" type="ram:AdvancePaymentType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="LegalOrganizationType">
<xs:sequence>
<xs:element name="ID" type="udt:IDType" minOccurs="0"/>
<xs:element name="TradingBusinessName" type="udt:TextType" minOccurs="0"/>
<xs:element name="PostalTradeAddress" type="ram:TradeAddressType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="LineTradeAgreementType">
<xs:sequence>
<xs:element name="SellerOrderReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="BuyerOrderReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="QuotationReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="ContractReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="AdditionalReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="GrossPriceProductTradePrice" type="ram:TradePriceType" minOccurs="0"/>
<xs:element name="NetPriceProductTradePrice" type="ram:TradePriceType"/>
<xs:element name="UltimateCustomerOrderReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="LineTradeDeliveryType">
<xs:sequence>
<xs:element name="BilledQuantity" type="udt:QuantityType"/>
<xs:element name="ChargeFreeQuantity" type="udt:QuantityType" minOccurs="0"/>
<xs:element name="PackageQuantity" type="udt:QuantityType" minOccurs="0"/>
<xs:element name="ShipToTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="UltimateShipToTradeParty" type="ram:TradePartyType" minOccurs="0"/>
<xs:element name="ActualDeliverySupplyChainEvent" type="ram:SupplyChainEventType" minOccurs="0"/>
<xs:element name="DespatchAdviceReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="ReceivingAdviceReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="DeliveryNoteReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="LineTradeSettlementType">
<xs:sequence>
<xs:element name="ApplicableTradeTax" type="ram:TradeTaxType" maxOccurs="unbounded"/>
<xs:element name="BillingSpecifiedPeriod" type="ram:SpecifiedPeriodType" minOccurs="0"/>
<xs:element name="SpecifiedTradeAllowanceCharge" type="ram:TradeAllowanceChargeType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="SpecifiedTradeSettlementLineMonetarySummation" type="ram:TradeSettlementLineMonetarySummationType"/>
<xs:element name="InvoiceReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0"/>
<xs:element name="AdditionalReferencedDocument" type="ram:ReferencedDocumentType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="ReceivableSpecifiedTradeAccountingAccount" type="ram:TradeAccountingAccountType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="LogisticsServiceChargeType">
<xs:sequence>
<xs:element name="Description" type="udt:TextType"/>
<xs:element name="AppliedAmount" type="udt:AmountType"/>
<xs:element name="AppliedTradeTax" type="ram:TradeTaxType" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="LogisticsTransportMovementType">
<xs:sequence>
<xs:element name="ModeCode" type="qdt:TransportModeCodeType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="NoteType">
<xs:sequence>
<xs:element name="ContentCode" type="udt:CodeType" minOccurs="0"/>
<xs:element name="Content" type="udt:TextType" minOccurs="0"/>
<xs:element name="SubjectCode" type="udt:CodeType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ProcuringProjectType">
<xs:sequence>
<xs:element name="ID" type="udt:IDType"/>
<xs:element name="Name" type="udt:TextType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ProductCharacteristicType">
<xs:sequence>
<xs:element name="TypeCode" type="udt:CodeType" minOccurs="0"/>
<xs:element name="Description" type="udt:TextType"/>
<xs:element name="ValueMeasure" type="udt:MeasureType" minOccurs="0"/>
<xs:element name="Value" type="udt:TextType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ProductClassificationType">
<xs:sequence>
<xs:element name="ClassCode" type="udt:CodeType" minOccurs="0"/>
<xs:element name="ClassName" type="udt:TextType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ReferencedDocumentType">
<xs:sequence>
<xs:element name="IssuerAssignedID" type="udt:IDType" minOccurs="0"/>
<xs:element name="URIID" type="udt:IDType" minOccurs="0"/>
<xs:element name="LineID" type="udt:IDType" minOccurs="0"/>
<xs:element name="TypeCode" type="qdt:DocumentCodeType" minOccurs="0"/>
<xs:element name="Name" type="udt:TextType" minOccurs="0"/>
<xs:element name="AttachmentBinaryObject" type="udt:BinaryObjectType" minOccurs="0"/>
<xs:element name="ReferenceTypeCode" type="qdt:ReferenceCodeType" minOccurs="0"/>
<xs:element name="FormattedIssueDateTime" type="qdt:FormattedDateTimeType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ReferencedProductType">
<xs:sequence>
<xs:element name="ID" type="udt:IDType" minOccurs="0"/>
<xs:element name="GlobalID" type="udt:IDType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="SellerAssignedID" type="udt:IDType" minOccurs="0"/>
<xs:element name="BuyerAssignedID" type="udt:IDType" minOccurs="0"/>
<xs:element name="IndustryAssignedID" type="udt:IDType" minOccurs="0"/>
<xs:element name="Name" type="udt:TextType"/>
<xs:element name="Description" type="udt:TextType" minOccurs="0"/>
<xs:element name="UnitQuantity" type="udt:QuantityType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="SpecifiedPeriodType">
<xs:sequence>
<xs:element name="Description" type="udt:TextType" minOccurs="0"/>
<xs:element name="StartDateTime" type="udt:DateTimeType" minOccurs="0"/>
<xs:element name="EndDateTime" type="udt:DateTimeType" minOccurs="0"/>
<xs:element name="CompleteDateTime" type="udt:DateTimeType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="SupplyChainConsignmentType">
<xs:sequence>
<xs:element name="SpecifiedLogisticsTransportMovement" type="ram:LogisticsTransportMovementType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="SupplyChainEventType">
<xs:sequence>
<xs:element name="OccurrenceDateTime" type="udt:DateTimeType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="SupplyChainTradeLineItemType">
<xs:sequence>
<xs:element name="AssociatedDocumentLineDocument" type="ram:DocumentLineDocumentType"/>
<xs:element name="SpecifiedTradeProduct" type="ram:TradeProductType"/>
<xs:element name="SpecifiedLineTradeAgreement" type="ram:LineTradeAgreementType"/>
<xs:element name="SpecifiedLineTradeDelivery" type="ram:LineTradeDeliveryType"/>
<xs:element name="SpecifiedLineTradeSettlement" type="ram:LineTradeSettlementType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="SupplyChainTradeTransactionType">
<xs:sequence>
<xs:element name="IncludedSupplyChainTradeLineItem" type="ram:SupplyChainTradeLineItemType" maxOccurs="unbounded"/>
<xs:element name="ApplicableHeaderTradeAgreement" type="ram:HeaderTradeAgreementType"/>
<xs:element name="ApplicableHeaderTradeDelivery" type="ram:HeaderTradeDeliveryType"/>
<xs:element name="ApplicableHeaderTradeSettlement" type="ram:HeaderTradeSettlementType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TaxRegistrationType">
<xs:sequence>
<xs:element name="ID" type="udt:IDType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeAccountingAccountType">
<xs:sequence>
<xs:element name="ID" type="udt:IDType"/>
<xs:element name="TypeCode" type="qdt:AccountingAccountTypeCodeType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeAddressType">
<xs:sequence>
<xs:element name="PostcodeCode" type="udt:CodeType" minOccurs="0"/>
<xs:element name="LineOne" type="udt:TextType" minOccurs="0"/>
<xs:element name="LineTwo" type="udt:TextType" minOccurs="0"/>
<xs:element name="LineThree" type="udt:TextType" minOccurs="0"/>
<xs:element name="CityName" type="udt:TextType" minOccurs="0"/>
<xs:element name="CountryID" type="qdt:CountryIDType"/>
<xs:element name="CountrySubDivisionName" type="udt:TextType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeAllowanceChargeType">
<xs:sequence>
<xs:element name="ChargeIndicator" type="udt:IndicatorType"/>
<xs:element name="SequenceNumeric" type="udt:NumericType" minOccurs="0"/>
<xs:element name="CalculationPercent" type="udt:PercentType" minOccurs="0"/>
<xs:element name="BasisAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="BasisQuantity" type="udt:QuantityType" minOccurs="0"/>
<xs:element name="ActualAmount" type="udt:AmountType"/>
<xs:element name="ReasonCode" type="qdt:AllowanceChargeReasonCodeType" minOccurs="0"/>
<xs:element name="Reason" type="udt:TextType" minOccurs="0"/>
<xs:element name="CategoryTradeTax" type="ram:TradeTaxType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeContactType">
<xs:sequence>
<xs:element name="PersonName" type="udt:TextType" minOccurs="0"/>
<xs:element name="DepartmentName" type="udt:TextType" minOccurs="0"/>
<xs:element name="TypeCode" type="qdt:ContactTypeCodeType" minOccurs="0"/>
<xs:element name="TelephoneUniversalCommunication" type="ram:UniversalCommunicationType" minOccurs="0"/>
<xs:element name="FaxUniversalCommunication" type="ram:UniversalCommunicationType" minOccurs="0"/>
<xs:element name="EmailURIUniversalCommunication" type="ram:UniversalCommunicationType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeCountryType">
<xs:sequence>
<xs:element name="ID" type="qdt:CountryIDType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeCurrencyExchangeType">
<xs:sequence>
<xs:element name="SourceCurrencyCode" type="qdt:CurrencyCodeType"/>
<xs:element name="TargetCurrencyCode" type="qdt:CurrencyCodeType"/>
<xs:element name="ConversionRate" type="udt:RateType"/>
<xs:element name="ConversionRateDateTime" type="udt:DateTimeType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeDeliveryTermsType">
<xs:sequence>
<xs:element name="DeliveryTypeCode" type="qdt:DeliveryTermsCodeType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradePartyType">
<xs:sequence>
<xs:element name="ID" type="udt:IDType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="GlobalID" type="udt:IDType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="Name" type="udt:TextType" minOccurs="0"/>
<xs:element name="RoleCode" type="qdt:PartyRoleCodeType" minOccurs="0"/>
<xs:element name="Description" type="udt:TextType" minOccurs="0"/>
<xs:element name="SpecifiedLegalOrganization" type="ram:LegalOrganizationType" minOccurs="0"/>
<xs:element name="DefinedTradeContact" type="ram:TradeContactType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="PostalTradeAddress" type="ram:TradeAddressType" minOccurs="0"/>
<xs:element name="URIUniversalCommunication" type="ram:UniversalCommunicationType" minOccurs="0"/>
<xs:element name="SpecifiedTaxRegistration" type="ram:TaxRegistrationType" minOccurs="0" maxOccurs="2"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradePaymentDiscountTermsType">
<xs:sequence>
<xs:element name="BasisDateTime" type="udt:DateTimeType" minOccurs="0"/>
<xs:element name="BasisPeriodMeasure" type="udt:MeasureType" minOccurs="0"/>
<xs:element name="BasisAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="CalculationPercent" type="udt:PercentType" minOccurs="0"/>
<xs:element name="ActualDiscountAmount" type="udt:AmountType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradePaymentPenaltyTermsType">
<xs:sequence>
<xs:element name="BasisDateTime" type="udt:DateTimeType" minOccurs="0"/>
<xs:element name="BasisPeriodMeasure" type="udt:MeasureType" minOccurs="0"/>
<xs:element name="BasisAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="CalculationPercent" type="udt:PercentType" minOccurs="0"/>
<xs:element name="ActualPenaltyAmount" type="udt:AmountType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradePaymentTermsType">
<xs:sequence>
<xs:element name="Description" type="udt:TextType" minOccurs="0"/>
<xs:element name="DueDateDateTime" type="udt:DateTimeType" minOccurs="0"/>
<xs:element name="DirectDebitMandateID" type="udt:IDType" minOccurs="0"/>
<xs:element name="PartialPaymentAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="ApplicableTradePaymentPenaltyTerms" type="ram:TradePaymentPenaltyTermsType" minOccurs="0"/>
<xs:element name="ApplicableTradePaymentDiscountTerms" type="ram:TradePaymentDiscountTermsType" minOccurs="0"/>
<xs:element name="PayeeTradeParty" type="ram:TradePartyType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradePriceType">
<xs:sequence>
<xs:element name="ChargeAmount" type="udt:AmountType"/>
<xs:element name="BasisQuantity" type="udt:QuantityType" minOccurs="0"/>
<xs:element name="AppliedTradeAllowanceCharge" type="ram:TradeAllowanceChargeType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="IncludedTradeTax" type="ram:TradeTaxType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeProductInstanceType">
<xs:sequence>
<xs:element name="BatchID" type="udt:IDType" minOccurs="0"/>
<xs:element name="SupplierAssignedSerialID" type="udt:IDType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeProductType">
<xs:sequence>
<xs:element name="ID" type="udt:IDType" minOccurs="0"/>
<xs:element name="GlobalID" type="udt:IDType" minOccurs="0"/>
<xs:element name="SellerAssignedID" type="udt:IDType" minOccurs="0"/>
<xs:element name="BuyerAssignedID" type="udt:IDType" minOccurs="0"/>
<xs:element name="IndustryAssignedID" type="udt:IDType" minOccurs="0"/>
<xs:element name="ModelID" type="udt:IDType" minOccurs="0"/>
<xs:element name="Name" type="udt:TextType"/>
<xs:element name="Description" type="udt:TextType" minOccurs="0"/>
<xs:element name="BatchID" type="udt:IDType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="BrandName" type="udt:TextType" minOccurs="0"/>
<xs:element name="ModelName" type="udt:TextType" minOccurs="0"/>
<xs:element name="ApplicableProductCharacteristic" type="ram:ProductCharacteristicType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="DesignatedProductClassification" type="ram:ProductClassificationType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="IndividualTradeProductInstance" type="ram:TradeProductInstanceType" minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="OriginTradeCountry" type="ram:TradeCountryType" minOccurs="0"/>
<xs:element name="IncludedReferencedProduct" type="ram:ReferencedProductType" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeSettlementFinancialCardType">
<xs:sequence>
<xs:element name="ID" type="udt:IDType"/>
<xs:element name="CardholderName" type="udt:TextType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeSettlementHeaderMonetarySummationType">
<xs:sequence>
<xs:element name="LineTotalAmount" type="udt:AmountType"/>
<xs:element name="ChargeTotalAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="AllowanceTotalAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="TaxBasisTotalAmount" type="udt:AmountType"/>
<xs:element name="TaxTotalAmount" type="udt:AmountType" minOccurs="0" maxOccurs="2"/>
<xs:element name="RoundingAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="GrandTotalAmount" type="udt:AmountType"/>
<xs:element name="TotalPrepaidAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="DuePayableAmount" type="udt:AmountType"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeSettlementLineMonetarySummationType">
<xs:sequence>
<xs:element name="LineTotalAmount" type="udt:AmountType"/>
<xs:element name="ChargeTotalAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="AllowanceTotalAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="TaxTotalAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="GrandTotalAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="TotalAllowanceChargeAmount" type="udt:AmountType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeSettlementPaymentMeansType">
<xs:sequence>
<xs:element name="TypeCode" type="qdt:PaymentMeansCodeType"/>
<xs:element name="Information" type="udt:TextType" minOccurs="0"/>
<xs:element name="ApplicableTradeSettlementFinancialCard" type="ram:TradeSettlementFinancialCardType" minOccurs="0"/>
<xs:element name="PayerPartyDebtorFinancialAccount" type="ram:DebtorFinancialAccountType" minOccurs="0"/>
<xs:element name="PayeePartyCreditorFinancialAccount" type="ram:CreditorFinancialAccountType" minOccurs="0"/>
<xs:element name="PayeeSpecifiedCreditorFinancialInstitution" type="ram:CreditorFinancialInstitutionType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="TradeTaxType">
<xs:sequence>
<xs:element name="CalculatedAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="TypeCode" type="qdt:TaxTypeCodeType"/>
<xs:element name="ExemptionReason" type="udt:TextType" minOccurs="0"/>
<xs:element name="BasisAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="LineTotalBasisAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="AllowanceChargeBasisAmount" type="udt:AmountType" minOccurs="0"/>
<xs:element name="CategoryCode" type="qdt:TaxCategoryCodeType"/>
<xs:element name="ExemptionReasonCode" type="udt:CodeType" minOccurs="0"/>
<xs:element name="TaxPointDate" type="udt:DateType" minOccurs="0"/>
<xs:element name="DueDateTypeCode" type="qdt:TimeReferenceCodeType" minOccurs="0"/>
<xs:element name="RateApplicablePercent" type="udt:PercentType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="UniversalCommunicationType">
<xs:sequence>
<xs:element name="URIID" type="udt:IDType" minOccurs="0"/>
<xs:element name="CompleteNumber" type="udt:TextType" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

View file

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100"
elementFormDefault="qualified">
<xs:complexType name="AmountType">
<xs:simpleContent>
<xs:extension base="xs:decimal">
<xs:attribute name="currencyID" type="xs:token" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="BinaryObjectType">
<xs:simpleContent>
<xs:extension base="xs:base64Binary">
<xs:attribute name="mimeCode" type="xs:token" use="required"/>
<xs:attribute name="filename" type="xs:string" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="CodeType">
<xs:simpleContent>
<xs:extension base="xs:token">
<xs:attribute name="listID" type="xs:token" use="optional"/>
<xs:attribute name="listVersionID" type="xs:token"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="DateTimeType">
<xs:choice>
<xs:element name="DateTimeString">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="format" type="xs:string" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
<xs:complexType name="DateType">
<xs:choice>
<xs:element name="DateString">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="format" type="xs:string" use="required"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
<xs:complexType name="IDType">
<xs:simpleContent>
<xs:extension base="xs:token">
<xs:attribute name="schemeID" type="xs:token"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="IndicatorType">
<xs:choice>
<xs:element name="Indicator" type="xs:boolean"/>
</xs:choice>
</xs:complexType>
<xs:complexType name="MeasureType">
<xs:simpleContent>
<xs:extension base="xs:decimal">
<xs:attribute name="unitCode" type="xs:token"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="NumericType">
<xs:simpleContent>
<xs:extension base="xs:decimal"/>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="PercentType">
<xs:simpleContent>
<xs:extension base="xs:decimal"/>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="QuantityType">
<xs:simpleContent>
<xs:extension base="xs:decimal">
<xs:attribute name="unitCode" type="xs:token"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="RateType">
<xs:simpleContent>
<xs:extension base="xs:decimal"/>
</xs:simpleContent>
</xs:complexType>
<xs:complexType name="TextType">
<xs:simpleContent>
<xs:extension base="xs:string"/>
</xs:simpleContent>
</xs:complexType>
</xs:schema>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -5,19 +5,206 @@
from lxml import etree
import os
from unittest.mock import Mock
from decimal import Decimal
from datetime import date
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 trytond.modules.company.tests import create_company, set_company
from trytond.modules.account.tests import create_chart, get_fiscalyear
from trytond.exceptions import UserError
def set_invoice_sequences(fiscalyear):
pool = Pool()
Sequence = pool.get('ir.sequence.strict')
SequenceType = pool.get('ir.sequence.type')
InvoiceSequence = pool.get('account.fiscalyear.invoice_sequence')
ModelData = pool.get('ir.model.data')
sequence = Sequence(
name=fiscalyear.name,
sequence_type=SequenceType(ModelData.get_id(
'account_invoice', 'sequence_type_account_invoice')),
company=fiscalyear.company)
sequence.save()
fiscalyear.invoice_sequences = []
invoice_sequence = InvoiceSequence()
invoice_sequence.fiscalyear = fiscalyear
invoice_sequence.in_invoice_sequence = sequence
invoice_sequence.in_credit_note_sequence = sequence
invoice_sequence.out_invoice_sequence = sequence
invoice_sequence.out_credit_note_sequence = sequence
invoice_sequence.save()
return fiscalyear
class EdocTestCase(ModuleTestCase):
'Test e-rechnung module'
module = 'edocument_xrechnung'
def prep_fiscalyear(self, company1):
""" prepare fiscal year, sequences...
"""
pool = Pool()
FiscalYear = pool.get('account.fiscalyear')
fisc_year = get_fiscalyear(company1, today=date(2024, 1, 15))
set_invoice_sequences(fisc_year)
self.assertEqual(len(fisc_year.invoice_sequences), 1)
FiscalYear.create_period([fisc_year])
def prep_company(self):
""" create company, add country and bank-account
"""
pool = Pool()
Country = pool.get('country.country')
Party = pool.get('party.party')
Bank = pool.get('bank')
BankAccount = pool.get('bank.account')
country_de, = Country.create([{
'name': 'Germany',
'code': 'DE',
'code3': 'DEU'}])
company = create_company('m-ds')
Party.write(*[[company.party], {
'addresses': [('write', [company.party.addresses[0]], {
'country': country_de.id})]}])
bank_party, = Party.create([{
'name': 'Bank 123',
'addresses': [('create', [{}])]}])
bank, = Bank.create([{'party': bank_party.id}])
BankAccount.create([{
'bank': bank.id,
'owners': [('add', [company.party.id])],
'numbers': [('create', [{
'type': 'iban',
'number': 'DE02300209000106531065'}])]}])
return company
def prep_invoice(self, credit_note=False):
""" add invoice
"""
pool = Pool()
Invoice = pool.get('account.invoice')
Taxes = pool.get('account.tax')
Account = pool.get('account.account')
Journal = pool.get('account.journal')
Currency = pool.get('currency.currency')
Uom = pool.get('product.uom')
Country = pool.get('country.country')
Party = pool.get('party.party')
country_de, = Country.search([('code', '=', 'DE')])
customer, = Party.create([{
'name': 'Customer',
'identifiers': [('create', [{
'type': 'edoc_route_id', 'code': 'xrechn-route-id-123'}])],
'addresses': [('create', [{
'invoice': True,
'street': 'Customer Street 1',
'postal_code': '12345',
'city': 'Usertown',
'country': country_de.id,
}])],
}])
currency1, = Currency.search([('code', '=', 'usd')])
tax, = Taxes.search([('name', '=', '20% VAT')])
Taxes.write(*[
[tax],
{'unece_code': 'GST', 'unece_category_code': 'S',
'legal_notice': 'Legal Notice'}])
account_lst = Account.search([
('name', 'in', ['Main Revenue', 'Main Receivable'])
], order=[('name', 'ASC')])
self.assertEqual(len(account_lst), 2)
self.assertEqual(account_lst[0].name, 'Main Receivable')
journ_lst = Journal.search([('name', '=', 'Revenue')])
self.assertEqual(len(journ_lst), 1)
to_create_invoice = [{
'type': 'out',
'description': 'description of invoice',
'comment': 'note line 1\nnote line 2',
'invoice_date': date(2024, 7, 1),
'party': customer.id,
'invoice_address': customer.addresses[0].id,
'account': account_lst[0].id,
'journal': journ_lst[0].id,
'currency': currency1.id,
'lines': [('create', [{
'type': 'line',
'quantity': 2.0 if not credit_note else -2.0,
'description': 'Product 1',
'unit': Uom.search([('symbol', '=', 'u')])[0].id,
'unit_price': Decimal('50.0'),
'taxes': [('add', [tax.id])],
'account': account_lst[1].id,
'currency': currency1.id,
}])],
}]
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(len(inv_lst.move.lines), 3)
return inv_lst
@with_transaction()
def test_xrechn_bank_account_owned(self):
""" check field 'company_owned' on bank.account.number
"""
pool = Pool()
BankAccount = pool.get('bank.account')
AccountNumber = pool.get('bank.account.number')
Bank = pool.get('bank')
Party = pool.get('party.party')
company = create_company()
with set_company(company):
bank_party, = Party.create([{
'name': 'Bank 123',
'addresses': [('create', [{}])]}])
customer_party, = Party.create([{
'name': 'Someone',
'addresses': [('create', [{}])]}])
bank, = Bank.create([{'party': bank_party.id}])
acc_company, acc_other, = BankAccount.create([
{
'bank': bank.id,
'owners': [('add', [company.party.id])],
'numbers': [('create', [
{'type': 'iban', 'number': 'DE02300209000106531065'}])]
}, {
'bank': bank.id,
'owners': [('add', [customer_party.id])],
'numbers': [('create', [
{'type': 'iban', 'number': 'DE02200505501015871393'}])]
}])
self.assertEqual(len(acc_company.numbers), 1)
self.assertEqual(acc_company.numbers[0].company_owned, True)
self.assertEqual(len(acc_other.numbers), 1)
self.assertEqual(acc_other.numbers[0].company_owned, False)
company_numbers = AccountNumber.search(
[('company_owned', '=', True)])
self.assertEqual(len(company_numbers), 1)
self.assertEqual(company_numbers[0].id, acc_company.numbers[0].id)
other_numbers = AccountNumber.search(
[('company_owned', '=', False)])
self.assertEqual(len(other_numbers), 1)
self.assertEqual(other_numbers[0].id, acc_other.numbers[0].id)
@with_transaction()
def test_xrechn_check_validator(self):
""" check validation of optional route-id
@ -46,51 +233,55 @@ 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')
company = self.prep_company()
with set_company(company):
create_chart(company=company, tax=True)
self.prep_fiscalyear(company)
invoice = self.prep_invoice()
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
"""
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()
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')
]
company = self.prep_company()
with set_company(company):
create_chart(company=company, tax=True)
self.prep_fiscalyear(company)
invoice = self.prep_invoice()
template = Template(invoice)
template = Template(invoice)
schema_file = os.path.join(
os.path.dirname(__file__), 'os-UBL-2.1',
'xsd', 'maindoc', 'UBL-Invoice-2.1.xsd')
schema_file = os.path.join(
os.path.dirname(__file__), 'os-UBL-2.1',
'xsd', 'maindoc', 'UBL-Invoice-2.1.xsd')
for x in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']:
invoice_string = template.render(x)
invoice_xml = etree.fromstring(invoice_string)
schema = etree.XMLSchema(etree.parse(schema_file))
schema.assertValid(invoice_xml)
for x in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']:
invoice_string = template.render(x)
invoice_xml = etree.fromstring(invoice_string)
schema = etree.XMLSchema(etree.parse(schema_file))
schema.assertValid(invoice_xml)
@with_transaction()
def test_xrechn_export_xml_creditnote(self):
@ -98,59 +289,24 @@ class EdocTestCase(ModuleTestCase):
"""
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()
company = self.prep_company()
with set_company(company):
create_chart(company=company, tax=True)
self.prep_fiscalyear(company)
invoice = self.prep_invoice(credit_note=True)
# 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')
template = Template(invoice)
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')
]
schema_file = os.path.join(
os.path.dirname(__file__), 'os-UBL-2.1',
'xsd', 'maindoc', 'UBL-CreditNote-2.1.xsd')
template = Template(invoice)
schema_file = os.path.join(
os.path.dirname(__file__), 'os-UBL-2.1',
'xsd', 'maindoc', 'UBL-CreditNote-2.1.xsd')
for x in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']:
invoice_string = template.render(x)
invoice_xml = etree.fromstring(invoice_string)
schema = etree.XMLSchema(etree.parse(schema_file))
schema.assertValid(invoice_xml)
# invoice_string = template.render('XRechnung-2.2')
# with open('xrechnung-test-creditnote.xml', 'wt') as fhdl:
# fhdl.write(invoice_string.decode('utf8'))
for x in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']:
invoice_string = template.render(x)
invoice_xml = etree.fromstring(invoice_string)
schema = etree.XMLSchema(etree.parse(schema_file))
schema.assertValid(invoice_xml)
# end EdocTestCase

21
tests/validate_xml.py Normal file
View file

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

View file

@ -1,5 +1,5 @@
[tryton]
version=7.0.4
version=7.0.10
depends:
edocument_uncefact
party
@ -7,4 +7,5 @@ depends:
account_invoice
xml:
message.xml
configuration.xml
party.xml

View file

@ -0,0 +1,15 @@
<?xml version="1.0"?>
<!-- This file is part of the account-datev-module from m-ds for Tryton.
The COPYRIGHT file at the top level of this repository contains the
full copyright notices and license terms. -->
<data>
<xpath expr="/form/separator[@id='currency_exchange']" position="before">
<separator id="edocument" colspan="4" string="eDocument"/>
<field name="edocument_bank" colspan="2" height="200"/>
<newline/>
</xpath>
</data>