diff --git a/COPYRIGHT b/COPYRIGHT
index 27458f9..d99fbe4 100644
--- a/COPYRIGHT
+++ b/COPYRIGHT
@@ -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
diff --git a/README.rst b/README.rst
index 9eabda9..bb0c979 100644
--- a/README.rst
+++ b/README.rst
@@ -14,19 +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
-
-*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
diff --git a/__init__.py b/__init__.py
index a7f7f9c..d20da9a 100644
--- a/__init__.py
+++ b/__init__.py
@@ -5,17 +5,12 @@
from trytond.pool import Pool
from .edocument import XRechnung, FacturX
-from .bank import AccountNumber
from .party import PartyConfiguration, Party
-from .configuration import Configuration, BankEdocumentRel
def register():
Pool.register(
- AccountNumber,
XRechnung,
- Configuration,
- BankEdocumentRel,
FacturX,
Party,
PartyConfiguration,
diff --git a/bank.py b/bank.py
deleted file mode 100644
index 1d6f923..0000000
--- a/bank.py
+++ /dev/null
@@ -1,97 +0,0 @@
-# -*- 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
diff --git a/configuration.py b/configuration.py
deleted file mode 100644
index d0aa911..0000000
--- a/configuration.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- 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
diff --git a/configuration.xml b/configuration.xml
deleted file mode 100644
index 630a828..0000000
--- a/configuration.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
- account.configuration
-
- configuration_form
-
-
-
-
diff --git a/docs/xrechnung.txt b/docs/xrechnung.txt
index dab13fa..020bd2a 100644
--- a/docs/xrechnung.txt
+++ b/docs/xrechnung.txt
@@ -6,11 +6,3 @@ 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/
-
-https://www.e-rechnungs-checker.de/
diff --git a/edocument.py b/edocument.py
index 395df5d..d196140 100644
--- a/edocument.py
+++ b/edocument.py
@@ -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
diff --git a/locale/de.po b/locale/de.po
index dc3b72c..d8156a9 100644
--- a/locale/de.po
+++ b/locale/de.po
@@ -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"
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"
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"
msgctxt "help:party.party,xrechnung_routeid:"
-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."
+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."
+
###############
# account.tax #
@@ -116,31 +113,3 @@ 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"
diff --git a/locale/en.po b/locale/en.po
index b0353ce..a4978ea 100644
--- a/locale/en.po
+++ b/locale/en.po
@@ -98,23 +98,3 @@ 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"
-
diff --git a/mixin.py b/mixin.py
index dc235ac..f27804f 100644
--- a/mixin.py
+++ b/mixin.py
@@ -9,9 +9,6 @@ import html
from trytond.exceptions import UserError
from trytond.i18n import gettext
from trytond.tools import cached_property
-from trytond.pool import Pool
-from trytond.transaction import Transaction
-from trytond.modules.product import round_price
class EdocumentMixin(object):
@@ -19,25 +16,6 @@ class EdocumentMixin(object):
"""
__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
@@ -49,7 +27,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',
@@ -59,7 +37,7 @@ class EdocumentMixin(object):
if not result.country:
raise UserError(gettext(
'edocument_xrechnung.msg_no_address_country',
- party=result.party.rec_name if result.party else '-'))
+ party=result.party))
return result
@cached_property
@@ -77,11 +55,11 @@ 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',
- party=result.party.rec_name if result.party else '-'))
+ party=result.party))
return result
def get_list_of_comments(self):
@@ -102,29 +80,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
@@ -154,58 +124,12 @@ class EdocumentMixin(object):
def tax_unece_code(self, tax):
""" 'tax': invoice.line
"""
- unece_code = self.get_tax_unece_code(tax)
- if not unece_code:
+ if not (tax.unece_code or ''):
raise UserError(gettext(
'edocument_xrechnung.msg_tax_code_missing',
taxname=tax.rec_name))
return tax.unece_code
- def get_line_amount(self, line):
- """ get amount of current invoice-line,
- depends on modegross of invoice, set used-modegross to 'net'
-
- Args:
- line (record): model account.invoice.line
- """
- if not hasattr(line, 'modegross'):
- return line.amount
-
- if line.modegross == 'net':
- return line.amount
- elif line.modegross == 'gross':
- # get net-amount
- # copy from account_invoice/invoice.py:2416-2434
- currency = (
- line.invoice.currency
- if line.invoice else line.currency)
-
- amount = (Decimal(str(line.quantity or 0)) * (
- line.unit_price or Decimal(0)))
- invoice_type = (
- line.invoice.type
- if line.invoice else line.invoice_type)
-
- if (invoice_type == 'in'
- and line.taxes_deductible_rate is not None
- and line.taxes_deductible_rate != 1):
- with Transaction().set_context(_deductible_rate=1):
- tax_amount = sum(
- t['amount'] for t in line._get_taxes().values())
- non_deductible_amount = (
- tax_amount * (1 - line.taxes_deductible_rate))
- amount += non_deductible_amount
- if currency:
- return currency.round(amount)
- return amount
-
- def get_tax_unece_code(self, tax):
- while tax:
- if tax.unece_code:
- return tax.unece_code
- break
- tax = tax.parent
-
def get_category_code(self, tax):
while tax:
if tax.unece_category_code:
@@ -223,49 +147,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
+ return html.quote(text)
# end EdocumentMixin
diff --git a/party.py b/party.py
index de60ce8..cc020c6 100644
--- a/party.py
+++ b/party.py
@@ -14,10 +14,8 @@ class Party(metaclass=PoolMeta):
xrechnung_routeid = fields.Boolean(
string='X-Rechnung Route-ID',
- 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".')
+ help='Enables the need for an XRechnung route ID at the party ' +
+ 'for exporting the XRechnung.')
def get_xrechnung_route_id(self):
""" search for route-id at party, fire-exception if missing
@@ -47,7 +45,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 +57,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'))
diff --git a/setup.py b/setup.py
index c82d777..04b912a 100644
--- a/setup.py
+++ b/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']),
},
diff --git a/template/Factur-X-1.07.2-extended/invoice.xml b/template/Factur-X-1.07.2-extended/invoice.xml
index a4620c5..ff32e24 100644
--- a/template/Factur-X-1.07.2-extended/invoice.xml
+++ b/template/Factur-X-1.07.2-extended/invoice.xml
@@ -12,33 +12,33 @@ this repository contains the full copyright notices and license terms. -->
${value.strftime('%Y%m%d')}
- ${this.quote_text(party.name)}
+ ${party.name}
${id}
${TradeAddress(address)}
-
+
${tax_identifier.code}
${address.postal_code}
- ${this.quote_text(lines[0])}
- ${this.quote_text(lines[1])}
- ${(lines[2])}
+ ${lines[0]}
+ ${lines[1]}
+ ${lines[2]}
- ${(address.city)}
+ ${address.city}
${address.country.code}
- ${this.quote_text(address.subdivision.name)}
+ ${address.subdivision.name}
${amount * this.type_sign}
${this.tax_unece_code(tax)}
- ${tax.legal_notice}
+ ${tax.legal_notice}
${base * this.type_sign}
${this.tax_category_code(tax)}
${tax.rate * 100}
@@ -50,8 +50,8 @@ this repository contains the full copyright notices and license terms. -->
- ${this.quote_text(this.invoice.number)}
- ${this.quote_text(this.invoice.description)}
+ ${this.invoice.number}
+ ${this.invoice.description}
${this.type_code}
${DateTime(this.invoice.invoice_date)}
@@ -71,21 +71,23 @@ this repository contains the full copyright notices and license terms. -->
${line.product.code}
- ${this.quote_text(line.product.name if line.product else line.description if line.description else 'name not set')}
- ${this.quote_text(line.description if line.product else '')}
+ ${line.product.name if line.product else ''}
+ ${line.description}
- ${this.round_unitprice(line.unit_price)}
+ ${this.invoice.currency.round(line.unit_price)}
${line.quantity * this.type_sign}
- ${TradeTax(this.invoice_line_tax(line))}
+
+ ${TradeTax(tax.tax)}
+
- ${this.get_line_amount(line)}
+ ${line.amount}
@@ -97,10 +99,10 @@ this repository contains the full copyright notices and license terms. -->
${TradeParty(this.buyer_trade_party, this.buyer_trade_address, this.buyer_trade_tax_identifier)}
- ${this.quote_text(this.invoice.reference)}
+ ${this.invoice.reference}
- ${this.quote_text(this.invoice.reference)}
+ ${this.invoice.reference}
@@ -112,26 +114,11 @@ this repository contains the full copyright notices and license terms. -->
- ${this.quote_text(this.payment_reference)}
+ ${this.payment_reference}
${this.invoice.currency.code}
-
-
- 1
-
-
-
-
- 30
- Wire transfer
-
- ${banknumber.number_compact}
- ${this.quote_text(banknumber.account.bank.party.rec_name)}
-
-
- ${banknumber.account.bank.bic}
-
-
-
+
+ 1
+
${TradeTax(tax.tax, tax.amount, tax.base)}
diff --git a/tests/test_edocument.py b/tests/test_edocument.py
index 179957d..eaa75df 100644
--- a/tests/test_edocument.py
+++ b/tests/test_edocument.py
@@ -5,227 +5,19 @@
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.company.tests import create_company, set_company
-from trytond.modules.account.tests import create_chart, get_fiscalyear
+from trytond.modules.edocument_uncefact.tests.test_module import get_invoice
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
- """
- 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
@@ -254,106 +46,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 +98,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 +144,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
diff --git a/tryton.cfg b/tryton.cfg
index ecd8916..aeb1d45 100644
--- a/tryton.cfg
+++ b/tryton.cfg
@@ -1,14 +1,10 @@
[tryton]
-version=7.0.10
+version=7.0.6
depends:
edocument_uncefact
party
bank
account_invoice
-extras_depend:
- sale_point_invoice
- product_grossprice
xml:
message.xml
- configuration.xml
party.xml
diff --git a/view/configuration_form.xml b/view/configuration_form.xml
deleted file mode 100644
index 244d830..0000000
--- a/view/configuration_form.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-