Compare commits
No commits in common. "main" and "various_fixes" have entirely different histories.
main
...
various_fi
10 changed files with 149 additions and 402 deletions
|
@ -1,5 +1,6 @@
|
|||
Copyright (C) 2021-2025 martin-data services.
|
||||
Copyright (C) 2024-2025 Mathias Behrle
|
||||
Copyright (C) 2015-2023 Cédric Krier.
|
||||
Copyright (C) 2015-2023 B2CK SPRL.
|
||||
Copyright (C) 2021-2023 martin-data services.
|
||||
|
||||
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
|
||||
|
|
|
@ -14,11 +14,6 @@ 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
|
||||
|
|
|
@ -9,8 +9,3 @@ validator:
|
|||
|
||||
ü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/
|
||||
|
||||
https://www.e-rechnungs-checker.de/
|
||||
|
|
|
@ -70,7 +70,7 @@ class XRechnung(EdocumentMixin, Invoice):
|
|||
else:
|
||||
raise ValueError('invalid type-code "%s"' % self.type_code)
|
||||
else:
|
||||
return super()._get_template(version)
|
||||
return super(XRechnung, self)._get_template(version)
|
||||
|
||||
# end XRechnung
|
||||
|
||||
|
@ -92,6 +92,6 @@ class FacturX(EdocumentMixin, Invoice):
|
|||
else:
|
||||
raise ValueError('invalid type-code "%s"' % self.type_code)
|
||||
else:
|
||||
return super()._get_template(version)
|
||||
return super(FacturX, self)._get_template(version)
|
||||
|
||||
# end FacturX
|
||||
|
|
98
mixin.py
98
mixin.py
|
@ -10,8 +10,6 @@ from trytond.exceptions import UserError
|
|||
from trytond.i18n import gettext
|
||||
from trytond.tools import cached_property
|
||||
from trytond.pool import Pool
|
||||
from trytond.transaction import Transaction
|
||||
from trytond.modules.product import round_price
|
||||
|
||||
|
||||
class EdocumentMixin(object):
|
||||
|
@ -49,7 +47,7 @@ class EdocumentMixin(object):
|
|||
Returns:
|
||||
record : model party.address
|
||||
"""
|
||||
result = super().seller_trade_address
|
||||
result = super(EdocumentMixin, self).seller_trade_address
|
||||
if not result:
|
||||
raise UserError(gettext(
|
||||
'edocument_xrechnung.msg_no_seller_address',
|
||||
|
@ -77,7 +75,7 @@ class EdocumentMixin(object):
|
|||
if self.invoice and self.invoice.party
|
||||
else '-'))
|
||||
|
||||
result = super().buyer_trade_address
|
||||
result = super(EdocumentMixin, self).buyer_trade_address
|
||||
if result and not result.country:
|
||||
raise UserError(gettext(
|
||||
'edocument_xrechnung.msg_no_address_country',
|
||||
|
@ -102,29 +100,21 @@ class EdocumentMixin(object):
|
|||
""" get tax of invoice-line,
|
||||
fire exception if no/multiple taxes exists
|
||||
"""
|
||||
Tax = Pool().get('account.tax')
|
||||
|
||||
if len(line.taxes) != 1:
|
||||
if len(line.invoice_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']
|
||||
numtax=len(line.invoice_taxes)))
|
||||
|
||||
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:
|
||||
raise UserError(gettext(
|
||||
'edocument_xrechnung.msg_linetax_invalid_catcode',
|
||||
taxname=tax.rec_name,
|
||||
taxname=line.invoice_taxes[0].tax.rec_name,
|
||||
allowed=', '.join(allowed_cat)))
|
||||
|
||||
return tax
|
||||
return line.invoice_taxes[0].tax
|
||||
|
||||
def taxident_data(self, tax_identifier):
|
||||
""" get tax-scheme-id and codes
|
||||
|
@ -161,41 +151,6 @@ class EdocumentMixin(object):
|
|||
taxname=tax.rec_name))
|
||||
return tax.unece_code
|
||||
|
||||
def get_line_amount(self, line):
|
||||
""" get amount of current invoice-line,
|
||||
depends on modegross of invoice, set used-modegross to 'net'
|
||||
|
||||
Args:
|
||||
line (record): model account.invoice.line
|
||||
"""
|
||||
if line.modegross == 'net':
|
||||
return line.amount
|
||||
elif line.modegross == 'gross':
|
||||
# get net-amount
|
||||
# copy from account_invoice/invoice.py:2416-2434
|
||||
currency = (
|
||||
line.invoice.currency
|
||||
if line.invoice else line.currency)
|
||||
|
||||
amount = (Decimal(str(line.quantity or 0)) * (
|
||||
line.unit_price or Decimal(0)))
|
||||
invoice_type = (
|
||||
line.invoice.type
|
||||
if line.invoice else line.invoice_type)
|
||||
|
||||
if (invoice_type == 'in'
|
||||
and line.taxes_deductible_rate is not None
|
||||
and line.taxes_deductible_rate != 1):
|
||||
with Transaction().set_context(_deductible_rate=1):
|
||||
tax_amount = sum(
|
||||
t['amount'] for t in line._get_taxes().values())
|
||||
non_deductible_amount = (
|
||||
tax_amount * (1 - line.taxes_deductible_rate))
|
||||
amount += non_deductible_amount
|
||||
if currency:
|
||||
return currency.round(amount)
|
||||
return amount
|
||||
|
||||
def get_tax_unece_code(self, tax):
|
||||
while tax:
|
||||
if tax.unece_code:
|
||||
|
@ -220,49 +175,10 @@ class EdocumentMixin(object):
|
|||
taxname=tax.rec_name))
|
||||
return unece_category_code
|
||||
|
||||
def round_unitprice(self, value):
|
||||
""" round value by digits in unit_price of account.invoice.line
|
||||
|
||||
Args:
|
||||
value (Decimal): unit-price
|
||||
|
||||
Returns:
|
||||
Decimal: rounded value
|
||||
"""
|
||||
if value is not None:
|
||||
return round_price(value)
|
||||
return value
|
||||
|
||||
def quote_text(self, text):
|
||||
""" replace critical chars
|
||||
"""
|
||||
if text:
|
||||
return html.escape(text)
|
||||
|
||||
def _party_legal_types(self):
|
||||
""" get list of identifier-types to be used as
|
||||
legal-ids
|
||||
"""
|
||||
return ['de_handelsregisternummer']
|
||||
|
||||
def party_legal_ids(self, party, address):
|
||||
""" get list of legal-ids of party
|
||||
|
||||
Args:
|
||||
party (record): model party.party
|
||||
address (record): model party.address
|
||||
"""
|
||||
result = super().party_legal_ids(party, address)
|
||||
|
||||
legal_types = self._party_legal_types()
|
||||
if party and party.identifiers:
|
||||
for x in party.identifiers:
|
||||
if x.type in legal_types:
|
||||
if x.address:
|
||||
if x.address == address:
|
||||
result.append((x.rec_name, {'schemeID': '0002'}))
|
||||
else:
|
||||
result.append((x.rec_name, {'schemeID': '0002'}))
|
||||
return result
|
||||
|
||||
# end EdocumentMixin
|
||||
|
|
4
party.py
4
party.py
|
@ -47,7 +47,7 @@ class Party(metaclass=PoolMeta):
|
|||
Args:
|
||||
records (list): records of party.party
|
||||
"""
|
||||
super().validate(records)
|
||||
super(Party, cls).validate(records)
|
||||
for record in records:
|
||||
record.get_xrechnung_route_id()
|
||||
|
||||
|
@ -59,7 +59,7 @@ class PartyConfiguration(metaclass=PoolMeta):
|
|||
|
||||
@classmethod
|
||||
def __setup__(cls):
|
||||
super().__setup__()
|
||||
super(PartyConfiguration, cls).__setup__()
|
||||
cls.identifier_types.selection.append(
|
||||
('edoc_route_id', 'X-Rechnung Route-ID'))
|
||||
|
||||
|
|
6
setup.py
6
setup.py
|
@ -13,9 +13,11 @@ here = path.abspath(path.dirname(__file__))
|
|||
MODULE = 'edocument_xrechnung'
|
||||
PREFIX = 'mds'
|
||||
|
||||
# Get the long description from the README file
|
||||
with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
|
||||
long_description = f.read()
|
||||
|
||||
# tryton.cfg einlesen
|
||||
config = ConfigParser()
|
||||
config.readfp(open('tryton.cfg'))
|
||||
info = dict(config.items('tryton'))
|
||||
|
@ -85,8 +87,6 @@ setup(
|
|||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Programming Language :: Python :: 3.12',
|
||||
],
|
||||
|
||||
keywords='tryton xrechnung edcoument',
|
||||
|
@ -99,7 +99,7 @@ setup(
|
|||
info.get('xml', [])
|
||||
+ ['tryton.cfg', 'locale/*.po', 'tests/*.py',
|
||||
'template/*/*.xml', 'versiondep.txt', 'README.rst',
|
||||
'tests/*/*/*/*.xsd', 'view/*.xml',
|
||||
'tests/*/*/*/*.xsd',
|
||||
'tests/*/*.xsd', 'tests/*/*.sch', 'tests/*/*.xml',
|
||||
'tests/*/*/*.xslt', 'tests/*/*/*.xml']),
|
||||
},
|
||||
|
|
|
@ -19,7 +19,7 @@ this repository contains the full copyright notices and license terms. -->
|
|||
</py:for>
|
||||
</ram:SpecifiedLegalOrganization>
|
||||
<ram:PostalTradeAddress py:if="address">${TradeAddress(address)}</ram:PostalTradeAddress>
|
||||
<ram:SpecifiedTaxRegistration py:if="tax_identifier and tax_identifier.type.endswith('_vat')">
|
||||
<ram:SpecifiedTaxRegistration py:if="tax_identifier and tax_identifier.type == 'eu_vat'">
|
||||
<ram:ID schemeID='VA'>${tax_identifier.code}</ram:ID>
|
||||
</ram:SpecifiedTaxRegistration>
|
||||
</py:def>
|
||||
|
@ -38,7 +38,7 @@ this repository contains the full copyright notices and license terms. -->
|
|||
<ram:ApplicableTradeTax>
|
||||
<ram:CalculatedAmount py:if="amount" py:attrs="{'currencyID': this.invoice.currency.code}">${amount * this.type_sign}</ram:CalculatedAmount>
|
||||
<ram:TypeCode>${this.tax_unece_code(tax)}</ram:TypeCode>
|
||||
<ram:ExemptionReason py:if="tax.legal_notice and not this.tax_category_code(tax)">${tax.legal_notice}</ram:ExemptionReason>
|
||||
<ram:ExemptionReason py:if="tax.legal_notice">${tax.legal_notice}</ram:ExemptionReason>
|
||||
<ram:BasisAmount py:if="base">${base * this.type_sign}</ram:BasisAmount>
|
||||
<ram:CategoryCode>${this.tax_category_code(tax)}</ram:CategoryCode>
|
||||
<ram:RateApplicablePercent py:if="tax.type == 'percentage'">${tax.rate * 100}</ram:RateApplicablePercent>
|
||||
|
@ -71,21 +71,23 @@ this repository contains the full copyright notices and license terms. -->
|
|||
</ram:AssociatedDocumentLineDocument>
|
||||
<ram:SpecifiedTradeProduct>
|
||||
<ram:ID py:if="line.product and line.product.code">${line.product.code}</ram:ID>
|
||||
<ram:Name>${this.quote_text(line.product.name if line.product else line.description if line.description else 'name not set')}</ram:Name>
|
||||
<ram:Description py:if="line.description">${this.quote_text(line.description if line.product else '')}</ram:Description>
|
||||
<ram:Name>${this.quote_text(line.product.name if line.product else '')}</ram:Name>
|
||||
<ram:Description py:if="line.description">${this.quote_text(line.description)}</ram:Description>
|
||||
</ram:SpecifiedTradeProduct>
|
||||
<ram:SpecifiedLineTradeAgreement>
|
||||
<ram:NetPriceProductTradePrice>
|
||||
<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: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))}
|
||||
<py:for each="tax in line.invoice_taxes">
|
||||
${TradeTax(tax.tax)}
|
||||
</py:for>
|
||||
<ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${this.get_line_amount(line)}</ram:LineTotalAmount>
|
||||
<ram:LineTotalAmount py:attrs="{'currencyID': this.invoice.currency.code}">${line.amount}</ram:LineTotalAmount>
|
||||
</ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||
</ram:SpecifiedLineTradeSettlement>
|
||||
</ram:IncludedSupplyChainTradeLineItem>
|
||||
|
|
|
@ -5,180 +5,20 @@
|
|||
|
||||
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, activate_module)
|
||||
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'
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
activate_module([
|
||||
'edocument_uncefact', 'party', 'bank',
|
||||
'account_invoice', 'sale_point_invoice',
|
||||
'product_grossprice'], 'en')
|
||||
|
||||
def prep_fiscalyear(self, company1):
|
||||
""" prepare fiscal year, sequences...
|
||||
"""
|
||||
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], {
|
||||
'identifiers': [('create', [
|
||||
# post.de
|
||||
{'type': 'de_handelsregisternummer', 'code': 'Bonn HRB 6792'},
|
||||
{'type': 'de_vat', 'code': 'DE 169838187'},
|
||||
])],
|
||||
'addresses': [('write', [company.party.addresses[0]], {
|
||||
'country': country_de.id})]}])
|
||||
|
||||
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, modegross='net'):
|
||||
""" 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')])
|
||||
Currency.write(*[[currency1], {'code': 'USD'}])
|
||||
|
||||
tax, = Taxes.search([('name', '=', '20% VAT')])
|
||||
Taxes.write(*[
|
||||
[tax],
|
||||
{'unece_code': 'VAT', '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',
|
||||
'modegross': modegross,
|
||||
'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,
|
||||
}])],
|
||||
}]
|
||||
|
||||
if modegross == 'gross':
|
||||
to_create_invoice[0]['lines'][0][1][0]['unit_gross_price'] = (
|
||||
Decimal('50.0') * Decimal('1.2'))
|
||||
|
||||
inv_lst, = Invoice.create(to_create_invoice)
|
||||
inv_lst.on_change_lines()
|
||||
inv_lst.save()
|
||||
Invoice.validate_invoice([inv_lst])
|
||||
Invoice.post([inv_lst])
|
||||
self.assertEqual(inv_lst.currency.code, 'USD')
|
||||
self.assertEqual(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
|
||||
|
@ -254,106 +94,51 @@ class EdocTestCase(ModuleTestCase):
|
|||
{'identifiers': [('delete', [party.identifiers[0].id])]}]
|
||||
)
|
||||
|
||||
@with_transaction()
|
||||
def test_xrechn_export_facturx_gross(self):
|
||||
""" run export - factur-x, modegross='gross'
|
||||
"""
|
||||
pool = Pool()
|
||||
Template = pool.get('edocument.facturxext.invoice')
|
||||
|
||||
company = self.prep_company()
|
||||
with set_company(company):
|
||||
create_chart(company=company, tax=True)
|
||||
self.prep_fiscalyear(company)
|
||||
invoice = self.prep_invoice(modegross='gross')
|
||||
|
||||
template = Template(invoice)
|
||||
|
||||
schema_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'Factur-X_1.07.2_EXTENDED',
|
||||
'Factur-X_1.07.2_EXTENDED.xsd')
|
||||
|
||||
invoice_string = template.render('Factur-X-1.07.2-extended')
|
||||
with open('gross_invoice_string.xml', 'wb') as fhdl:
|
||||
fhdl.write(invoice_string)
|
||||
|
||||
invoice_xml = etree.fromstring(invoice_string)
|
||||
|
||||
# check values in xml
|
||||
nodes = invoice_xml.xpath(self._readxml_xpath([
|
||||
'rsm:CrossIndustryInvoice', 'rsm:SupplyChainTradeTransaction',
|
||||
'ram:ApplicableHeaderTradeAgreement', 'ram:SellerTradeParty',
|
||||
'ram:SpecifiedLegalOrganization', 'ram:ID']),
|
||||
namespaces=invoice_xml.nsmap)
|
||||
self.assertEqual(nodes[0].text, 'Bonn HRB 6792')
|
||||
|
||||
nodes = invoice_xml.xpath(self._readxml_xpath([
|
||||
'rsm:CrossIndustryInvoice', 'rsm:SupplyChainTradeTransaction',
|
||||
'ram:IncludedSupplyChainTradeLineItem',
|
||||
'ram:SpecifiedLineTradeSettlement',
|
||||
'ram:SpecifiedTradeSettlementLineMonetarySummation',
|
||||
'ram:LineTotalAmount']),
|
||||
namespaces=invoice_xml.nsmap)
|
||||
self.assertEqual(nodes[0].text, '100.00')
|
||||
|
||||
schema = etree.XMLSchema(etree.parse(schema_file))
|
||||
schema.assertValid(invoice_xml)
|
||||
|
||||
def _readxml_xpath(self, tags):
|
||||
""" generate xpath
|
||||
|
||||
Args:
|
||||
tags (list): list of string or integer to build path
|
||||
"""
|
||||
parts = []
|
||||
for x in tags:
|
||||
if isinstance(x, str):
|
||||
parts.append(x)
|
||||
elif isinstance(x, int):
|
||||
if parts[-1].endswith(']'):
|
||||
raise ValueError('multiple list selector')
|
||||
parts[-1] += '[%d]' % x
|
||||
result = '/' + '/'.join(parts)
|
||||
return result
|
||||
|
||||
@with_transaction()
|
||||
def test_xrechn_export_facturx(self):
|
||||
""" run export - factur-x
|
||||
"""
|
||||
pool = Pool()
|
||||
Template = pool.get('edocument.facturxext.invoice')
|
||||
Identifier = pool.get('party.identifier')
|
||||
Party = pool.get('party.party')
|
||||
Bank = pool.get('bank')
|
||||
BankAccount = pool.get('bank.account')
|
||||
BankNumber = pool.get('bank.account.number')
|
||||
|
||||
company = self.prep_company()
|
||||
with set_company(company):
|
||||
create_chart(company=company, tax=True)
|
||||
self.prep_fiscalyear(company)
|
||||
invoice = self.prep_invoice()
|
||||
invoice = get_invoice()
|
||||
invoice.payment_term_date = date.today()
|
||||
invoice.party.get_xrechnung_route_id = Mock(
|
||||
return_value='xrechn-route-id-123')
|
||||
invoice.company.party.bank_accounts = [
|
||||
Mock(
|
||||
spec=BankAccount,
|
||||
currency=invoice.currency,
|
||||
bank=Mock(spec=Bank, party=Mock(spec=Party, name='Bank')),
|
||||
owners=[invoice.company.party],
|
||||
numbers=[Mock(spec=BankNumber, type='other', number='123456')],
|
||||
)]
|
||||
invoice.description = 'description of invoice'
|
||||
invoice.comment = 'note line 1\nnote line 2'
|
||||
invoice.taxes[0].tax.rate = Decimal('0.1')
|
||||
invoice.identifiers = [
|
||||
Mock(
|
||||
spec=Identifier,
|
||||
type='edoc_route_id',
|
||||
code='xrechn-route-id-123')
|
||||
]
|
||||
|
||||
template = Template(invoice)
|
||||
template = Template(invoice)
|
||||
|
||||
self.assertEqual(
|
||||
template.party_legal_ids(invoice.company_party, None),
|
||||
[('Bonn HRB 6792', {'schemeID': '0002'})])
|
||||
schema_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'Factur-X_1.07.2_EXTENDED',
|
||||
'Factur-X_1.07.2_EXTENDED.xsd')
|
||||
|
||||
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)
|
||||
|
||||
# check values in xml
|
||||
nodes = invoice_xml.xpath(self._readxml_xpath([
|
||||
'rsm:CrossIndustryInvoice', 'rsm:SupplyChainTradeTransaction',
|
||||
'ram:ApplicableHeaderTradeAgreement', 'ram:SellerTradeParty',
|
||||
'ram:SpecifiedLegalOrganization', 'ram:ID']),
|
||||
namespaces=invoice_xml.nsmap)
|
||||
self.assertEqual(nodes[0].text, 'Bonn HRB 6792')
|
||||
|
||||
schema = etree.XMLSchema(etree.parse(schema_file))
|
||||
schema.assertValid(invoice_xml)
|
||||
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):
|
||||
|
@ -361,24 +146,45 @@ 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')
|
||||
|
||||
company = self.prep_company()
|
||||
with set_company(company):
|
||||
create_chart(company=company, tax=True)
|
||||
self.prep_fiscalyear(company)
|
||||
invoice = self.prep_invoice()
|
||||
invoice = get_invoice()
|
||||
invoice.payment_term_date = date.today()
|
||||
invoice.party.get_xrechnung_route_id = Mock(
|
||||
return_value='xrechn-route-id-123')
|
||||
invoice.company.party.bank_accounts = [
|
||||
Mock(
|
||||
spec=BankAccount,
|
||||
currency=invoice.currency,
|
||||
bank=Mock(spec=Bank, party=Mock(spec=Party, name='Bank')),
|
||||
owners=[invoice.company.party],
|
||||
numbers=[Mock(spec=BankNumber, type='other', number='123456')],
|
||||
)]
|
||||
invoice.description = 'description of invoice'
|
||||
invoice.comment = 'note line 1\nnote line 2'
|
||||
invoice.taxes[0].tax.rate = Decimal('0.1')
|
||||
invoice.identifiers = [
|
||||
Mock(
|
||||
spec=Identifier,
|
||||
type='edoc_route_id',
|
||||
code='xrechn-route-id-123')
|
||||
]
|
||||
|
||||
template = Template(invoice)
|
||||
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):
|
||||
|
@ -386,24 +192,59 @@ 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')
|
||||
|
||||
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)
|
||||
invoice = get_invoice()
|
||||
|
||||
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(
|
||||
os.path.dirname(__file__), 'os-UBL-2.1',
|
||||
'xsd', 'maindoc', 'UBL-CreditNote-2.1.xsd')
|
||||
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')
|
||||
]
|
||||
|
||||
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)
|
||||
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'))
|
||||
|
||||
# end EdocTestCase
|
||||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
[tryton]
|
||||
version=7.0.10
|
||||
version=7.0.9
|
||||
depends:
|
||||
edocument_uncefact
|
||||
party
|
||||
bank
|
||||
account_invoice
|
||||
extras_depend:
|
||||
sale_point_invoice
|
||||
product_grossprice
|
||||
xml:
|
||||
message.xml
|
||||
configuration.xml
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue