Compare commits

..

No commits in common. "main" and "ver-7.0.7" have entirely different histories.

11 changed files with 152 additions and 258 deletions

View file

@ -1,5 +1,6 @@
Copyright (C) 2021-2025 martin-data services. Copyright (C) 2015-2023 Cédric Krier.
Copyright (C) 2024-2025 Mathias Behrle Copyright (C) 2015-2023 B2CK SPRL.
Copyright (C) 2021-2023 martin-data services.
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU General Public License as published by

View file

@ -14,15 +14,6 @@ Requires
Changes 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* *7.0.7 - 10.12.2024*
- add iban to xml-export - add iban to xml-export

View file

@ -9,6 +9,3 @@ validator:
überweisungsdaten ü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://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

@ -70,7 +70,7 @@ class XRechnung(EdocumentMixin, Invoice):
else: else:
raise ValueError('invalid type-code "%s"' % self.type_code) raise ValueError('invalid type-code "%s"' % self.type_code)
else: else:
return super()._get_template(version) return super(XRechnung, self)._get_template(version)
# end XRechnung # end XRechnung
@ -92,6 +92,6 @@ class FacturX(EdocumentMixin, Invoice):
else: else:
raise ValueError('invalid type-code "%s"' % self.type_code) raise ValueError('invalid type-code "%s"' % self.type_code)
else: else:
return super()._get_template(version) return super(FacturX, self)._get_template(version)
# end FacturX # end FacturX

View file

@ -16,7 +16,7 @@ msgstr "Für die Steuer '%(taxname)s' ist die UNECE-Steuerkategorie nicht konfig
msgctxt "model:ir.message,text:msg_tax_code_missing" msgctxt "model:ir.message,text:msg_tax_code_missing"
msgid "The UNECE tax code is not configured for tax '%(taxname)s'." 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." msgstr "Für die Steuer '%(taxname)s' ist der UNECE-Einheitencode nicht konfiguriert."
msgctxt "model:ir.message,text:msg_uom_code_missing" msgctxt "model:ir.message,text:msg_uom_code_missing"
msgid "The UNECE uom code is not configured for unit '%(uomname)s'." msgid "The UNECE uom code is not configured for unit '%(uomname)s'."
@ -63,12 +63,9 @@ msgid "X-Rechnung Route-ID"
msgstr "X-Rechnung Leitweg-ID" msgstr "X-Rechnung Leitweg-ID"
msgctxt "help:party.party,xrechnung_routeid:" msgctxt "help:party.party,xrechnung_routeid:"
msgid "" msgid "Enables the need for an XRechnung route ID at the party for exporting the XRechnung."
"When activated an XRechnung route ID must be used for this party for X-Rechnung exports.\n" msgstr "Aktiviert die Notwendigkeit einer XRechnung-Leitweg-ID an der Partei für den Export der XRechnung."
"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 # # account.tax #

View file

@ -10,7 +10,6 @@ from trytond.exceptions import UserError
from trytond.i18n import gettext from trytond.i18n import gettext
from trytond.tools import cached_property from trytond.tools import cached_property
from trytond.pool import Pool from trytond.pool import Pool
from trytond.modules.product import round_price
class EdocumentMixin(object): class EdocumentMixin(object):
@ -48,7 +47,7 @@ class EdocumentMixin(object):
Returns: Returns:
record : model party.address record : model party.address
""" """
result = super().seller_trade_address result = super(EdocumentMixin, self).seller_trade_address
if not result: if not result:
raise UserError(gettext( raise UserError(gettext(
'edocument_xrechnung.msg_no_seller_address', 'edocument_xrechnung.msg_no_seller_address',
@ -58,7 +57,7 @@ class EdocumentMixin(object):
if not result.country: if not result.country:
raise UserError(gettext( raise UserError(gettext(
'edocument_xrechnung.msg_no_address_country', 'edocument_xrechnung.msg_no_address_country',
party=result.party.rec_name if result.party else '-')) party=result.party))
return result return result
@cached_property @cached_property
@ -76,11 +75,11 @@ class EdocumentMixin(object):
if self.invoice and self.invoice.party if self.invoice and self.invoice.party
else '-')) else '-'))
result = super().buyer_trade_address result = super(EdocumentMixin, self).buyer_trade_address
if result and not result.country: if result and not result.country:
raise UserError(gettext( raise UserError(gettext(
'edocument_xrechnung.msg_no_address_country', 'edocument_xrechnung.msg_no_address_country',
party=result.party.rec_name if result.party else '-')) party=result.party))
return result return result
def get_list_of_comments(self): def get_list_of_comments(self):
@ -101,29 +100,21 @@ class EdocumentMixin(object):
""" get tax of invoice-line, """ get tax of invoice-line,
fire exception if no/multiple taxes exists fire exception if no/multiple taxes exists
""" """
Tax = Pool().get('account.tax') if len(line.invoice_taxes) != 1:
if len(line.taxes) != 1:
raise UserError(gettext( raise UserError(gettext(
'edocument_xrechnung.msg_linetax_invalid_number', 'edocument_xrechnung.msg_linetax_invalid_number',
linename=line.rec_name, linename=line.rec_name,
numtax=len(line.taxes))) numtax=len(line.invoice_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'] allowed_cat = ['AE', 'L', 'M', 'E', 'S', 'Z', 'G', 'O', 'K', 'B']
unece_category_code = self.get_category_code(tax) unece_category_code = self.get_category_code(line.invoice_taxes[0].tax)
if unece_category_code not in allowed_cat: if unece_category_code not in allowed_cat:
raise UserError(gettext( raise UserError(gettext(
'edocument_xrechnung.msg_linetax_invalid_catcode', 'edocument_xrechnung.msg_linetax_invalid_catcode',
taxname=tax.rec_name, taxname=line.invoice_taxes[0].tax.rec_name,
allowed=', '.join(allowed_cat))) allowed=', '.join(allowed_cat)))
return tax return line.invoice_taxes[0].tax
def taxident_data(self, tax_identifier): def taxident_data(self, tax_identifier):
""" get tax-scheme-id and codes """ get tax-scheme-id and codes
@ -153,20 +144,12 @@ class EdocumentMixin(object):
def tax_unece_code(self, tax): def tax_unece_code(self, tax):
""" 'tax': invoice.line """ 'tax': invoice.line
""" """
unece_code = self.get_tax_unece_code(tax) if not (tax.unece_code or ''):
if not unece_code:
raise UserError(gettext( raise UserError(gettext(
'edocument_xrechnung.msg_tax_code_missing', 'edocument_xrechnung.msg_tax_code_missing',
taxname=tax.rec_name)) taxname=tax.rec_name))
return tax.unece_code 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): def get_category_code(self, tax):
while tax: while tax:
if tax.unece_category_code: if tax.unece_category_code:
@ -184,19 +167,6 @@ class EdocumentMixin(object):
taxname=tax.rec_name)) taxname=tax.rec_name))
return unece_category_code 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): def quote_text(self, text):
""" replace critical chars """ replace critical chars
""" """

View file

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

View file

@ -13,9 +13,11 @@ here = path.abspath(path.dirname(__file__))
MODULE = 'edocument_xrechnung' MODULE = 'edocument_xrechnung'
PREFIX = 'mds' PREFIX = 'mds'
# Get the long description from the README file
with open(path.join(here, 'README.rst'), encoding='utf-8') as f: with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
long_description = f.read() long_description = f.read()
# tryton.cfg einlesen
config = ConfigParser() config = ConfigParser()
config.readfp(open('tryton.cfg')) config.readfp(open('tryton.cfg'))
info = dict(config.items('tryton')) info = dict(config.items('tryton'))
@ -85,8 +87,6 @@ setup(
'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
], ],
keywords='tryton xrechnung edcoument', keywords='tryton xrechnung edcoument',
@ -99,7 +99,7 @@ setup(
info.get('xml', []) info.get('xml', [])
+ ['tryton.cfg', 'locale/*.po', 'tests/*.py', + ['tryton.cfg', 'locale/*.po', 'tests/*.py',
'template/*/*.xml', 'versiondep.txt', 'README.rst', 'template/*/*.xml', 'versiondep.txt', 'README.rst',
'tests/*/*/*/*.xsd', 'view/*.xml', 'tests/*/*/*/*.xsd',
'tests/*/*.xsd', 'tests/*/*.sch', 'tests/*/*.xml', 'tests/*/*.xsd', 'tests/*/*.sch', 'tests/*/*.xml',
'tests/*/*/*.xslt', 'tests/*/*/*.xml']), 'tests/*/*/*.xslt', 'tests/*/*/*.xml']),
}, },

View file

@ -76,14 +76,16 @@ this repository contains the full copyright notices and license terms. -->
</ram:SpecifiedTradeProduct> </ram:SpecifiedTradeProduct>
<ram:SpecifiedLineTradeAgreement> <ram:SpecifiedLineTradeAgreement>
<ram:NetPriceProductTradePrice> <ram:NetPriceProductTradePrice>
<ram:ChargeAmount py:attrs="{'currencyID': this.invoice.currency.code}">${this.round_unitprice(line.unit_price)}</ram:ChargeAmount> <ram:ChargeAmount py:attrs="{'currencyID': this.invoice.currency.code}">${this.invoice.currency.round(line.unit_price)}</ram:ChargeAmount>
</ram:NetPriceProductTradePrice> </ram:NetPriceProductTradePrice>
</ram:SpecifiedLineTradeAgreement> </ram:SpecifiedLineTradeAgreement>
<ram:SpecifiedLineTradeDelivery> <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: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:SpecifiedLineTradeDelivery>
<ram:SpecifiedLineTradeSettlement> <ram:SpecifiedLineTradeSettlement>
${TradeTax(this.invoice_line_tax(line))} <py:for each="tax in line.invoice_taxes">
${TradeTax(tax.tax)}
</py:for>
<ram:SpecifiedTradeSettlementLineMonetarySummation> <ram:SpecifiedTradeSettlementLineMonetarySummation>
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${line.amount}</ram:LineTotalAmount> <ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${line.amount}</ram:LineTotalAmount>
</ram:SpecifiedTradeSettlementLineMonetarySummation> </ram:SpecifiedTradeSettlementLineMonetarySummation>

View file

@ -5,159 +5,20 @@
from lxml import etree from lxml import etree
import os import os
from unittest.mock import Mock
from decimal import Decimal from decimal import Decimal
from datetime import date from datetime import date
from trytond.tests.test_tryton import ModuleTestCase, with_transaction from trytond.tests.test_tryton import ModuleTestCase, with_transaction
from trytond.pool import Pool 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.company.tests import create_company, set_company
from trytond.modules.account.tests import create_chart, get_fiscalyear
from trytond.exceptions import UserError 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): class EdocTestCase(ModuleTestCase):
'Test e-rechnung module' 'Test e-rechnung module'
module = 'edocument_xrechnung' 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() @with_transaction()
def test_xrechn_bank_account_owned(self): def test_xrechn_bank_account_owned(self):
""" check field 'company_owned' on bank.account.number """ check field 'company_owned' on bank.account.number
@ -239,24 +100,45 @@ class EdocTestCase(ModuleTestCase):
""" """
pool = Pool() pool = Pool()
Template = pool.get('edocument.facturxext.invoice') 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')
company = self.prep_company() invoice = get_invoice()
with set_company(company): invoice.payment_term_date = date.today()
create_chart(company=company, tax=True) invoice.party.get_xrechnung_route_id = Mock(
self.prep_fiscalyear(company) return_value='xrechn-route-id-123')
invoice = self.prep_invoice() 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) template = Template(invoice)
schema_file = os.path.join( schema_file = os.path.join(
os.path.dirname(__file__), os.path.dirname(__file__),
'Factur-X_1.07.2_EXTENDED', 'Factur-X_1.07.2_EXTENDED',
'Factur-X_1.07.2_EXTENDED.xsd') 'Factur-X_1.07.2_EXTENDED.xsd')
invoice_string = template.render('Factur-X-1.07.2-extended') invoice_string = template.render('Factur-X-1.07.2-extended')
invoice_xml = etree.fromstring(invoice_string) invoice_xml = etree.fromstring(invoice_string)
schema = etree.XMLSchema(etree.parse(schema_file)) schema = etree.XMLSchema(etree.parse(schema_file))
schema.assertValid(invoice_xml) schema.assertValid(invoice_xml)
@with_transaction() @with_transaction()
def test_xrechn_export_xml_invoice(self): def test_xrechn_export_xml_invoice(self):
@ -264,24 +146,45 @@ class EdocTestCase(ModuleTestCase):
""" """
pool = Pool() pool = Pool()
Template = pool.get('edocument.xrechnung.invoice') 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')
company = self.prep_company() invoice = get_invoice()
with set_company(company): invoice.payment_term_date = date.today()
create_chart(company=company, tax=True) invoice.party.get_xrechnung_route_id = Mock(
self.prep_fiscalyear(company) return_value='xrechn-route-id-123')
invoice = self.prep_invoice() 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) template = Template(invoice)
schema_file = os.path.join( schema_file = os.path.join(
os.path.dirname(__file__), 'os-UBL-2.1', os.path.dirname(__file__), 'os-UBL-2.1',
'xsd', 'maindoc', 'UBL-Invoice-2.1.xsd') 'xsd', 'maindoc', 'UBL-Invoice-2.1.xsd')
for x in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']: for x in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']:
invoice_string = template.render(x) invoice_string = template.render(x)
invoice_xml = etree.fromstring(invoice_string) invoice_xml = etree.fromstring(invoice_string)
schema = etree.XMLSchema(etree.parse(schema_file)) schema = etree.XMLSchema(etree.parse(schema_file))
schema.assertValid(invoice_xml) schema.assertValid(invoice_xml)
@with_transaction() @with_transaction()
def test_xrechn_export_xml_creditnote(self): def test_xrechn_export_xml_creditnote(self):
@ -289,24 +192,59 @@ class EdocTestCase(ModuleTestCase):
""" """
pool = Pool() pool = Pool()
Template = pool.get('edocument.xrechnung.invoice') 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')
company = self.prep_company() invoice = get_invoice()
with set_company(company):
create_chart(company=company, tax=True)
self.prep_fiscalyear(company)
invoice = self.prep_invoice(credit_note=True)
template = Template(invoice) # credit note
invoice.lines[0].quantity = -1
invoice.lines[0].amount = Decimal('-100.0')
invoice.taxes[0].base = Decimal('-100.0')
invoice.taxes[0].amount = Decimal('-10.0')
invoice.untaxed_amount = Decimal('-100.0')
invoice.tax_amount = Decimal('-10.0')
invoice.total_amount = Decimal('-110.0')
invoice.lines_to_pay[0].debit = Decimal('-110.0')
schema_file = os.path.join( invoice.party.get_xrechnung_route_id = Mock(
os.path.dirname(__file__), 'os-UBL-2.1', return_value='xrechn-route-id-123')
'xsd', 'maindoc', 'UBL-CreditNote-2.1.xsd') 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')
]
for x in ['XRechnung-2.2', 'XRechnung-2.3', 'XRechnung-3.0']: template = Template(invoice)
invoice_string = template.render(x)
invoice_xml = etree.fromstring(invoice_string) schema_file = os.path.join(
schema = etree.XMLSchema(etree.parse(schema_file)) os.path.dirname(__file__), 'os-UBL-2.1',
schema.assertValid(invoice_xml) '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'))
# end EdocTestCase # end EdocTestCase

View file

@ -1,5 +1,5 @@
[tryton] [tryton]
version=7.0.10 version=7.0.7
depends: depends:
edocument_uncefact edocument_uncefact
party party