diff --git a/.env b/.env
new file mode 100644
index 0000000..d0efcdf
--- /dev/null
+++ b/.env
@@ -0,0 +1 @@
+PYTHONPATH=~/Projekte/tr70/lib/python3.10/site-packages
\ No newline at end of file
diff --git a/.hgignore b/.gitignore
similarity index 87%
rename from .hgignore
rename to .gitignore
index 796d378..e349607 100644
--- a/.hgignore
+++ b/.gitignore
@@ -1,4 +1,4 @@
-syntax: glob
+*.pyc
build/*
dist/*
mds_account_invoice_xrechnung.egg-info/*
diff --git a/__init__.py b/__init__.py
index cb86728..f5712e6 100644
--- a/__init__.py
+++ b/__init__.py
@@ -7,10 +7,13 @@ from trytond.pool import Pool
from .wizard_runreport import RunXRechnungReport, RunXRechnungReportStart
from .invoice import InvoiceLine
from .xreport import XReport
+from .configuration import ConfigurationXRechnungexport, Configuration
def register():
Pool.register(
+ Configuration,
+ ConfigurationXRechnungexport,
InvoiceLine,
RunXRechnungReportStart,
module='account_invoice_xrechnung', type_='model')
diff --git a/configuration.py b/configuration.py
new file mode 100644
index 0000000..f4bedb2
--- /dev/null
+++ b/configuration.py
@@ -0,0 +1,62 @@
+# -*- coding: utf-8 -*-
+# This file is part of the account-invoice-xrechnung-module
+# from m-ds for Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+
+from trytond.pool import PoolMeta, Pool
+from trytond.model import ModelSQL, fields
+from trytond.modules.company.model import CompanyValueMixin
+from .wizard_runreport import sel_edocument
+
+
+class Configuration(metaclass=PoolMeta):
+ __name__ = 'account.configuration'
+
+ xrechn_default = fields.MultiValue(fields.Selection(
+ string='Export mode', selection=sel_edocument,
+ help='Pre-set export format for e-invoices.'))
+ xrechn_zugferd_report = fields.MultiValue(fields.Many2One(
+ model_name='ir.action.report',
+ domain=[
+ ('model', '=', 'account.invoice'),
+ ('extension', '=', 'pdf')],
+ string='ZUGFeRD-Report',
+ help='Report that is to be used to generate the ZUGFeRD PDF.'))
+
+ @classmethod
+ def multivalue_model(cls, field):
+ """ select table
+ """
+ pool = Pool()
+ if field in {'xrechn_zugferd_report', 'xrechn_default'}:
+ return pool.get('account_invoice_xrechnung.configuration')
+ return super(Configuration, cls).multivalue_model(field)
+
+ @classmethod
+ def default_xrechn_default(cls, **pattern):
+ return cls.multivalue_model('xrechn_default').default_xrechn_default()
+
+# end Configuration
+
+
+class ConfigurationXRechnungexport(ModelSQL, CompanyValueMixin):
+ "Account Configuration XRechnung Export"
+ __name__ = 'account_invoice_xrechnung.configuration'
+
+ xrechn_default = fields.Selection(
+ string='Export mode', selection=sel_edocument,
+ help='Pre-set export format for e-invoices.')
+ xrechn_zugferd_report = fields.Many2One(
+ model_name='ir.action.report',
+ string='ZUGFeRD-Report',
+ domain=[
+ ('model', '=', 'account.invoice'),
+ ('extension', '=', 'pdf')],
+ help='Report that is to be used to generate the ZUGFeRD PDF.')
+
+ @classmethod
+ def default_xrechn_default(cls, **pattern):
+ return 'edocument.facturxext.invoice-ferd'
+
+# end ConfigurationXRechnungexport
diff --git a/configuration.xml b/configuration.xml
new file mode 100644
index 0000000..fdcbe15
--- /dev/null
+++ b/configuration.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ account.configuration
+
+ configuration_form
+
+
+
+
diff --git a/invoice.py b/invoice.py
index 108d7b0..bbdeca2 100644
--- a/invoice.py
+++ b/invoice.py
@@ -4,7 +4,7 @@
# this repository contains the full copyright notices and license terms.
from trytond.pool import PoolMeta
-from trytond.pyson import Eval, And, Or
+from trytond.pyson import Eval, And, Or, Bool
class InvoiceLine(metaclass=PoolMeta):
@@ -17,10 +17,7 @@ class InvoiceLine(metaclass=PoolMeta):
cls.unit.states['required'],
And(
Eval('type') == 'line',
- Eval('quantity', None) != None,
- ),
- )
- cls.unit.depends.add('type')
- cls.unit.depends.add('quantity')
+ Bool(Eval('quantity'))))
+ cls.unit.depends.update(['type', 'quantity'])
# end Invoice
diff --git a/locale/de.po b/locale/de.po
index e877610..c09de26 100644
--- a/locale/de.po
+++ b/locale/de.po
@@ -3,6 +3,22 @@ msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
+##############
+# ir.message #
+##############
+msgctxt "model:ir.message,text:msg_invoice_must_posted"
+msgid "Invoice '%(invname)s' must be posted."
+msgstr "Rechnung '%(invname)s' muß festgeschrieben sein."
+
+msgctxt "model:ir.message,text:msg_no_report_found"
+msgid "No report found for invoices in PDF format."
+msgstr "Kein Report für Rechnungen im PDF-Format gefunden."
+
+msgctxt "model:ir.message,text:msg_invalid_cachecontent"
+msgid "No PDF has yet been generated for the invoice '%(invoice_name)s' or the saved document has an incorrect format."
+msgstr "Für die Rechnung '%(invoice_name)s' wurde noch keine PDF erzeugt oder das gespeicherte Dokument hat ein falsches Format."
+
+
#############
# ir.action #
#############
@@ -30,6 +46,30 @@ msgctxt "field:account_invoice_xrechnung.runrep.start,edocument:"
msgid "Type"
msgstr "Typ"
+msgctxt "selection:account_invoice_xrechnung.runrep.start,edocument:"
+msgid "XRechnung UBL Invoice 2.2"
+msgstr "XRechnung UBL Invoice 2.2"
+
+msgctxt "selection:account_invoice_xrechnung.runrep.start,edocument:"
+msgid "XRechnung UBL Invoice 2.3"
+msgstr "XRechnung UBL Invoice 2.3"
+
+msgctxt "selection:account_invoice_xrechnung.runrep.start,edocument:"
+msgid "XRechnung UBL Invoice 3.0"
+msgstr "XRechnung UBL Invoice 3.0"
+
+msgctxt "selection:account_invoice_xrechnung.runrep.start,edocument:"
+msgid "CII CrossIndustryInvoice D16B"
+msgstr "CII CrossIndustryInvoice D16B"
+
+msgctxt "selection:account_invoice_xrechnung.runrep.start,edocument:"
+msgid "Factur-X Extended"
+msgstr "Factur-X Extended"
+
+msgctxt "selection:account_invoice_xrechnung.runrep.start,edocument:"
+msgid "ZUGFeRD 2.3.2"
+msgstr "ZUGFeRD 2.3.2"
+
msgctxt "field:account_invoice_xrechnung.runrep.start,as_zip:"
msgid "ZIP-File"
msgstr "ZIP-Datei"
@@ -57,3 +97,99 @@ msgstr "Export"
msgctxt "model:account_invoice_xrechnung.export,name:"
msgid "eDocument Export"
msgstr "eDocument Export"
+
+
+#########################
+# account.configuration #
+#########################
+msgctxt "view:account.configuration:"
+msgid "ZUGFeRD - e-Invoice"
+msgstr "ZUGFeRD - e-Rechnung"
+
+msgctxt "field:account.configuration,xrechn_zugferd_report:"
+msgid "ZUGFeRD-Report"
+msgstr "ZUGFeRD-Report"
+
+msgctxt "help:account.configuration,xrechn_zugferd_report:"
+msgid "Report that is to be used to generate the ZUGFeRD PDF."
+msgstr "Report, welcher zum erzeugen der ZUGFeRD-PDF verwendet werden soll."
+
+msgctxt "field:account.configuration,xrechn_default:"
+msgid "Export mode"
+msgstr "Exportformat"
+
+msgctxt "help:account.configuration,xrechn_default:"
+msgid "Pre-set export format for e-invoices."
+msgstr "Voreingstelltes Exportformat für die eRechnung."
+
+msgctxt "selection:account.configuration,xrechn_default:"
+msgid "XRechnung UBL Invoice 2.2"
+msgstr "XRechnung UBL Invoice 2.2"
+
+msgctxt "selection:account.configuration,xrechn_default:"
+msgid "XRechnung UBL Invoice 2.3"
+msgstr "XRechnung UBL Invoice 2.3"
+
+msgctxt "selection:account.configuration,xrechn_default:"
+msgid "XRechnung UBL Invoice 3.0"
+msgstr "XRechnung UBL Invoice 3.0"
+
+msgctxt "selection:account.configuration,xrechn_default:"
+msgid "Factur-X Extended"
+msgstr "Factur-X Extended"
+
+msgctxt "selection:account.configuration,xrechn_default:"
+msgid "ZUGFeRD 2.3.2"
+msgstr "ZUGFeRD 2.3.2"
+
+msgctxt "selection:account.configuration,xrechn_default:"
+msgid "CII CrossIndustryInvoice D16B"
+msgstr "CII CrossIndustryInvoice D16B"
+
+
+###########################################
+# account_invoice_xrechnung.configuration #
+###########################################
+msgctxt "model:account_invoice_xrechnung.configuration:"
+msgid "Account Configuration XRechnung Export"
+msgstr "Buchhaltung Konfiguration XRechnung Export"
+
+msgctxt "field:account_invoice_xrechnung.configuration,xrechn_zugferd_report:"
+msgid "ZUGFeRD-Report"
+msgstr "ZUGFeRD-Report"
+
+msgctxt "help:account_invoice_xrechnung.configuration,xrechn_zugferd_report:"
+msgid "Report that is to be used to generate the ZUGFeRD PDF."
+msgstr "Report, welcher zum erzeugen der ZUGFeRD-PDF verwendet werden soll."
+
+msgctxt "field:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "Export mode"
+msgstr "Exportformat"
+
+msgctxt "help:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "Pre-set export format for e-invoices."
+msgstr "Voreingstelltes Exportformat für die eRechnung."
+
+msgctxt "selection:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "XRechnung UBL Invoice 2.2"
+msgstr "XRechnung UBL Invoice 2.2"
+
+msgctxt "selection:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "XRechnung UBL Invoice 2.3"
+msgstr "XRechnung UBL Invoice 2.3"
+
+msgctxt "selection:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "XRechnung UBL Invoice 3.0"
+msgstr "XRechnung UBL Invoice 3.0"
+
+msgctxt "selection:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "Factur-X Extended"
+msgstr "Factur-X Extended"
+
+msgctxt "selection:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "ZUGFeRD 2.3.2"
+msgstr "ZUGFeRD 2.3.2"
+
+msgctxt "selection:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "CII CrossIndustryInvoice D16B"
+msgstr "CII CrossIndustryInvoice D16B"
diff --git a/locale/en.po b/locale/en.po
index 5d9a36b..d6ec74c 100644
--- a/locale/en.po
+++ b/locale/en.po
@@ -2,6 +2,18 @@
msgid ""
msgstr "Content-Type: text/plain; charset=utf-8\n"
+msgctxt "model:ir.message,text:msg_invoice_must_posted"
+msgid "Invoice '%(invname)s' must be posted."
+msgstr "Invoice '%(invname)s' must be posted."
+
+msgctxt "model:ir.message,text:msg_no_report_found"
+msgid "No report found for invoices in PDF format."
+msgstr "No report found for invoices in PDF format."
+
+msgctxt "model:ir.message,text:msg_invalid_cachecontent"
+msgid "No PDF has yet been generated for the invoice '%(invoice_name)s' or the saved document has an incorrect format."
+msgstr "No PDF has yet been generated for the invoice '%(invoice_name)s' or the saved document has an incorrect format."
+
msgctxt "model:ir.action,name:act_wizard_report"
msgid "eDocument Export"
msgstr "eDocument Export"
@@ -22,6 +34,30 @@ msgctxt "field:account_invoice_xrechnung.runrep.start,edocument:"
msgid "Type"
msgstr "Type"
+msgctxt "selection:account_invoice_xrechnung.runrep.start,edocument:"
+msgid "XRechnung UBL Invoice 2.2"
+msgstr "XRechnung UBL Invoice 2.2"
+
+msgctxt "selection:account_invoice_xrechnung.runrep.start,edocument:"
+msgid "XRechnung UBL Invoice 2.3"
+msgstr "XRechnung UBL Invoice 2.3"
+
+msgctxt "selection:account_invoice_xrechnung.runrep.start,edocument:"
+msgid "XRechnung UBL Invoice 3.0"
+msgstr "XRechnung UBL Invoice 3.0"
+
+msgctxt "selection:account_invoice_xrechnung.runrep.start,edocument:"
+msgid "CII CrossIndustryInvoice D16B"
+msgstr "CII CrossIndustryInvoice D16B"
+
+msgctxt "selection:account_invoice_xrechnung.runrep.start,edocument:"
+msgid "Factur-X Extended"
+msgstr "Factur-X Extended"
+
+msgctxt "selection:account_invoice_xrechnung.runrep.start,edocument:"
+msgid "ZUGFeRD 2.3.2"
+msgstr "ZUGFeRD 2.3.2"
+
msgctxt "field:account_invoice_xrechnung.runrep.start,as_zip:"
msgid "ZIP-File"
msgstr "ZIP-File"
@@ -38,3 +74,94 @@ msgctxt "wizard_button:account_invoice_xrechnung.runrep,start,export:"
msgid "Export"
msgstr "Export"
+msgctxt "model:account_invoice_xrechnung.export,name:"
+msgid "eDocument Export"
+msgstr "eDocument Export"
+
+msgctxt "view:account.configuration:"
+msgid "ZUGFeRD - e-Invoice"
+msgstr "ZUGFeRD - e-Invoice"
+
+msgctxt "field:account.configuration,xrechn_zugferd_report:"
+msgid "ZUGFeRD-Report"
+msgstr "ZUGFeRD-Report"
+
+msgctxt "help:account.configuration,xrechn_zugferd_report:"
+msgid "Report that is to be used to generate the ZUGFeRD PDF."
+msgstr "Report that is to be used to generate the ZUGFeRD PDF."
+
+msgctxt "field:account.configuration,xrechn_default:"
+msgid "Export mode"
+msgstr "Export mode"
+
+msgctxt "help:account.configuration,xrechn_default:"
+msgid "Pre-set export format for e-invoices."
+msgstr "Pre-set export format for e-invoices."
+
+msgctxt "selection:account.configuration,xrechn_default:"
+msgid "XRechnung UBL Invoice 2.2"
+msgstr "XRechnung UBL Invoice 2.2"
+
+msgctxt "selection:account.configuration,xrechn_default:"
+msgid "XRechnung UBL Invoice 2.3"
+msgstr "XRechnung UBL Invoice 2.3"
+
+msgctxt "selection:account.configuration,xrechn_default:"
+msgid "XRechnung UBL Invoice 3.0"
+msgstr "XRechnung UBL Invoice 3.0"
+
+msgctxt "selection:account.configuration,xrechn_default:"
+msgid "Factur-X Extended"
+msgstr "Factur-X Extended"
+
+msgctxt "selection:account.configuration,xrechn_default:"
+msgid "ZUGFeRD 2.3.2"
+msgstr "ZUGFeRD 2.3.2"
+
+msgctxt "selection:account.configuration,xrechn_default:"
+msgid "CII CrossIndustryInvoice D16B"
+msgstr "CII CrossIndustryInvoice D16B"
+
+msgctxt "model:account_invoice_xrechnung.configuration:"
+msgid "Account Configuration XRechnung Export"
+msgstr "Account Configuration XRechnung Export"
+
+msgctxt "field:account_invoice_xrechnung.configuration,xrechn_zugferd_report:"
+msgid "ZUGFeRD-Report"
+msgstr "ZUGFeRD-Report"
+
+msgctxt "help:account_invoice_xrechnung.configuration,xrechn_zugferd_report:"
+msgid "Report that is to be used to generate the ZUGFeRD PDF."
+msgstr "Report that is to be used to generate the ZUGFeRD PDF."
+
+msgctxt "field:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "Export mode"
+msgstr "Export mode"
+
+msgctxt "help:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "Pre-set export format for e-invoices."
+msgstr "Pre-set export format for e-invoices."
+
+msgctxt "selection:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "XRechnung UBL Invoice 2.2"
+msgstr "XRechnung UBL Invoice 2.2"
+
+msgctxt "selection:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "XRechnung UBL Invoice 2.3"
+msgstr "XRechnung UBL Invoice 2.3"
+
+msgctxt "selection:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "XRechnung UBL Invoice 3.0"
+msgstr "XRechnung UBL Invoice 3.0"
+
+msgctxt "selection:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "Factur-X Extended"
+msgstr "Factur-X Extended"
+
+msgctxt "selection:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "ZUGFeRD 2.3.2"
+msgstr "ZUGFeRD 2.3.2"
+
+msgctxt "selection:account_invoice_xrechnung.configuration,xrechn_default:"
+msgid "CII CrossIndustryInvoice D16B"
+msgstr "CII CrossIndustryInvoice D16B"
diff --git a/message.xml b/message.xml
index 25b841f..4437b59 100644
--- a/message.xml
+++ b/message.xml
@@ -8,6 +8,12 @@
Invoice '%(invname)s' must be posted.
+
+ No report found for invoices in PDF format.
+
+
+ No PDF has yet been generated for the invoice '%(invoice_name)s' or the saved document has an incorrect format.
+
diff --git a/setup.py b/setup.py
index d97bb36..645635e 100644
--- a/setup.py
+++ b/setup.py
@@ -1,16 +1,13 @@
-""" Tryton module to add xrechnung-export to invoice
-"""
+# -*- coding: utf-8 -*-
+# This file is part of the account-invoice-xrechnung-module
+# from m-ds for Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
-# Always prefer setuptools over distutils
from setuptools import setup
-# To use a consistent encoding
from codecs import open
from os import path
import re
-try:
- from configparser import ConfigParser
-except ImportError:
- from ConfigParser import ConfigParser
+from configparser import ConfigParser
here = path.abspath(path.dirname(__file__))
MODULE = 'account_invoice_xrechnung'
@@ -20,7 +17,6 @@ PREFIX = 'mds'
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'))
@@ -42,7 +38,7 @@ with open(path.join(here, 'versiondep.txt'), encoding='utf-8') as f:
major_version = 7
minor_version = 0
-requires = ['python-slugify']
+requires = ['python-slugify', 'pypdf', 'factur-x']
for dep in info.get('depends', []):
if not re.match(r'(ir|res|webdav)(\W|$)', dep):
if dep in modversion.keys():
@@ -87,9 +83,9 @@ setup(
'Natural Language :: English',
'Operating System :: OS Independent',
'License :: OSI Approved :: GNU General Public License (GPL)',
- 'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
],
keywords='tryton account invoice xrechnung edocument',
diff --git a/tests/test_invoice.py b/tests/test_invoice.py
index a30b98f..621995b 100644
--- a/tests/test_invoice.py
+++ b/tests/test_invoice.py
@@ -3,18 +3,235 @@
# from m-ds for Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
+from decimal import Decimal
+from datetime import date
+from facturx import get_facturx_xml_from_pdf
+
from trytond.tests.test_tryton import ModuleTestCase, with_transaction
+from trytond.pool import Pool
+from trytond.transaction import Transaction
+from trytond.modules.company.tests import create_company, set_company
+from trytond.modules.account.tests import create_chart, get_fiscalyear
+from .xml_data import xml_from_pdf
+
+
+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 InvoiceTestCase(ModuleTestCase):
'Test invoice module'
module = 'account_invoice_xrechnung'
- @with_transaction()
- def test_xrechnung(self):
- """ run default tests
+ def prep_fiscalyear(self, company1):
+ """ prepare fiscal year, sequences...
"""
- pass
+ 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_invoice(self, party_customer):
+ """ 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')
+
+ currency1, = Currency.search([('code', '=', 'usd')])
+
+ tax_lst = Taxes.search([('name', '=', '20% VAT')])
+ self.assertEqual(len(tax_lst), 1)
+
+ 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': 'Parts',
+ 'invoice_date': date(2024, 7, 1),
+ 'party': party_customer.id,
+ 'invoice_address': party_customer.addresses[0].id,
+ 'account': account_lst[0].id,
+ 'journal': journ_lst[0].id,
+ 'currency': currency1.id,
+ 'lines': [('create', [{
+ 'type': 'line',
+ 'quantity': 2.0,
+ 'description': 'Product 1',
+ 'unit': Uom.search([('symbol', '=', 'u')])[0].id,
+ 'unit_price': Decimal('50.0'),
+ 'taxes': [('add', [tax_lst[0].id])],
+ 'account': account_lst[1].id,
+ 'currency': currency1.id,
+ }])],
+ }]
+ inv_lst, = Invoice.create(to_create_invoice)
+ inv_lst.on_change_lines()
+ inv_lst.save()
+ Invoice.validate_invoice([inv_lst])
+ Invoice.post([inv_lst])
+ self.assertEqual(inv_lst.currency.code, 'usd')
+ self.assertEqual(len(inv_lst.move.lines), 3)
+ return inv_lst
+
+ @with_transaction()
+ def test_xrechnung_configuration(self):
+ """ test configuration
+ """
+ pool = Pool()
+ Configuration = pool.get('account.configuration')
+ Party = pool.get('party.party')
+ Country = pool.get('country.country')
+ ActionReport = pool.get('ir.action.report')
+ ExportWiz = pool.get('account_invoice_xrechnung.runrep', type='wizard')
+ Tax = pool.get('account.tax')
+
+ country_de, = Country.create([{
+ 'name': 'Germany',
+ 'code': 'DE',
+ 'code3': 'DEU'}])
+ pty1, = Party.create([{
+ 'name': 'Payee',
+ 'addresses': [('create', [{
+ 'invoice': True,
+ 'street': 'Applicant Street 1',
+ 'postal_code': '12345',
+ 'city': 'Usertown',
+ 'country': country_de.id,
+ }])],
+ }])
+
+ company1 = create_company('m-ds')
+ Party.write(*[
+ [company1.party],
+ {'addresses': [(
+ 'write',
+ [company1.party.addresses[0]],
+ {'country': country_de.id})]}])
+
+ with set_company(company1):
+ with Transaction().set_context({'company': company1.id}):
+
+ # update report to 'pdf'
+ inv_report, = ActionReport.search([
+ ('model', '=', 'account.invoice'),
+ ('report_name', '=', 'account.invoice')])
+ self.assertEqual(inv_report.extension, '')
+ ActionReport.write(*[
+ [inv_report], {'extension': 'pdf'}])
+
+ cfg1 = Configuration(xrechn_zugferd_report=inv_report)
+ cfg1.save()
+ self.assertEqual(cfg1.xrechn_zugferd_report.name, 'Invoice')
+ create_chart(company=company1, tax=True)
+ self.prep_fiscalyear(company1)
+
+ tax, = Tax.search([('name', '=', '20% VAT')])
+ Tax.write(*[
+ [tax],
+ {'unece_code': 'GST', 'unece_category_code': 'S'}])
+ invoice = self.prep_invoice(pty1)
+
+ # start wizard with two selected records
+ with Transaction().set_context({
+ 'active_ids': [invoice.id],
+ 'active_id': invoice.id,
+ 'active_model': 'account.invoice'}):
+
+ (sess_id, start_state, end_state) = ExportWiz.create()
+ w_obj = ExportWiz(sess_id)
+ self.assertEqual(start_state, 'start')
+ self.assertEqual(end_state, 'end')
+ result = ExportWiz.execute(sess_id, {}, start_state)
+
+ self.assertEqual(
+ list(result['view']['defaults'].keys()), [
+ 'as_zip', 'edocument', 'invoice', 'state',
+ 'invoice.'])
+
+ data = {}
+ for x in result['view']['defaults'].keys():
+ if '.' in x:
+ continue
+ data[x] = result['view']['defaults'][x]
+ setattr(w_obj.start, x, data[x])
+ self.assertEqual(
+ w_obj.start.edocument,
+ 'edocument.facturxext.invoice-ferd')
+ self.assertEqual(w_obj.start.invoice, invoice)
+ self.assertEqual(w_obj.start.as_zip, True)
+ w_obj.start.as_zip = False
+ data['as_zip'] = False
+
+ # (action, data)
+ result = ExportWiz.execute(
+ sess_id, {'start': data}, 'export')
+ self.assertEqual(len(result['actions']), 1)
+ (action, data) = result['actions'][0]
+
+ self.assertEqual(
+ action['report_name'],
+ 'account_invoice_xrechnung.export')
+ self.assertEqual(action['type'], 'ir.action.report')
+ self.assertEqual(action['records'], 'selected')
+
+ # 2nd step, wizard told us which report we must execute
+ ReportExport = pool.get(
+ 'account_invoice_xrechnung.export',
+ type='report')
+ data2 = {}
+ data2.update(data)
+ data2['action_id'] = action['id']
+ data2['model'] = 'account.invoice'
+
+ (ext, pdfdata, dprint, fname) = ReportExport.execute(
+ [data['invoice']], data2)
+
+ # extract xml
+ (xml_fname, xml_frompdf) = get_facturx_xml_from_pdf(
+ pdfdata)
+
+ self.assertEqual(xml_fname, 'factur-x.xml')
+ self.assertEqual(
+ xml_frompdf.decode('utf8'),
+ xml_from_pdf % {
+ 'datetoday': date.today().strftime('%Y%m%d')})
+ ExportWiz.delete(sess_id)
# end InvoiceTestCase
diff --git a/tests/xml_data.py b/tests/xml_data.py
new file mode 100644
index 0000000..0c0eed9
--- /dev/null
+++ b/tests/xml_data.py
@@ -0,0 +1,104 @@
+# -*- coding: utf-8 -*-
+# This file is part of the account-invoice-xrechnung-module
+# from m-ds for Tryton. The COPYRIGHT file at the top level of
+# this repository contains the full copyright notices and license terms.
+
+
+xml_from_pdf = """
+
+
+
+ urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended
+
+
+
+ 1
+ Parts
+ 380
+
+ 20240701
+
+
+
+
+
+ 1
+
+
+
+ Product 1
+
+
+
+ 50.00
+
+
+
+ 2.0
+
+
+
+ GST
+ S
+ 20.0
+
+
+ 100.00
+
+
+
+
+
+ m-ds
+
+
+
+ DE
+
+
+
+ Payee
+
+
+
+ 12345
+ Applicant Street 1
+ Usertown
+ DE
+
+
+
+
+
+
+
+
+
+ 1
+ usd
+
+ 1
+
+
+ 20.00
+ GST
+ 100.00
+ S
+ 20.0
+
+
+
+ %(datetoday)s
+
+ 120.00
+
+
+ 100.00
+ 100.00
+ 20.00
+ 120.00
+ 120.00
+
+
+
+"""
diff --git a/tryton.cfg b/tryton.cfg
index dbf4944..b48d405 100644
--- a/tryton.cfg
+++ b/tryton.cfg
@@ -6,5 +6,6 @@ depends:
edocument_xrechnung
xml:
message.xml
+ configuration.xml
wizard_runreport.xml
xreport.xml
diff --git a/versiondep.txt b/versiondep.txt
index 32b59fc..258b3ff 100644
--- a/versiondep.txt
+++ b/versiondep.txt
@@ -1 +1 @@
-edocument_xrechnung;7.0.3;7.0.999;mds
+edocument_xrechnung;7.0.5;7.0.999;mds
diff --git a/view/configuration_form.xml b/view/configuration_form.xml
new file mode 100644
index 0000000..9e4b01f
--- /dev/null
+++ b/view/configuration_form.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wizard_runreport.py b/wizard_runreport.py
index f1876a8..621c7d7 100644
--- a/wizard_runreport.py
+++ b/wizard_runreport.py
@@ -12,14 +12,21 @@ from trytond.transaction import Transaction
sel_edocument = [
- ('edocument.xrechnung.invoice', 'XRechnung UBL Invoice 2.1.1'),
+ ('edocument.xrechnung.invoice-2.2', 'XRechnung UBL Invoice 2.2'),
+ ('edocument.xrechnung.invoice-2.3', 'XRechnung UBL Invoice 2.3'),
+ ('edocument.xrechnung.invoice-3.0', 'XRechnung UBL Invoice 3.0'),
+ ('edocument.facturxext.invoice', 'Factur-X Extended'),
+ ('edocument.facturxext.invoice-ferd', 'ZUGFeRD 2.3.2'),
('edocument.uncefact.invoice', 'CII CrossIndustryInvoice D16B'),
]
edoc_versions = {
- 'edocument.xrechnung.invoice': 'XRechnung-2.2',
- 'edocument.uncefact.invoice': '16B-CII',
- }
+ 'edocument.xrechnung.invoice-2.2': 'XRechnung-2.2',
+ 'edocument.xrechnung.invoice-2.3': 'XRechnung-2.3',
+ 'edocument.xrechnung.invoice-3.0': 'XRechnung-3.0',
+ 'edocument.facturxext.invoice': 'Factur-X-1.07.2-extended',
+ 'edocument.facturxext.invoice-ferd': 'Factur-X-1.07.2-extended',
+ 'edocument.uncefact.invoice': '16B-CII'}
class RunXRechnungReportStart(ModelView):
@@ -45,7 +52,7 @@ class RunXRechnungReportStart(ModelView):
def default_edocument(cls):
""" default xrechnung
"""
- return 'edocument.xrechnung.invoice'
+ return 'edocument.xrechnung.invoice-3.0'
# end RunXRechnungReportStart
@@ -62,35 +69,75 @@ class RunXRechnungReport(Wizard):
buttons=[
Button(string='Cancel', state='end', icon='tryton-cancel'),
Button(string='Export', state='export', icon='tryton-export'),
- ],
- )
+ ])
def default_start(self, fields):
""" set defaults
"""
context = Transaction().context
- Invoice = Pool().get('account.invoice')
+ pool = Pool()
+ Invoice = pool.get('account.invoice')
+ WizRepStart = pool.get('account_invoice_xrechnung.runrep.start')
+ Configuration = pool.get('account.configuration')
+ cfg1 = Configuration.get_singleton()
invoice = Invoice.browse([context.get('active_id', -1)])
result = {
- 'edocument': 'edocument.xrechnung.invoice',
+ 'edocument': WizRepStart.default_edocument(),
'invoice': context.get('active_id', -1),
- 'state': invoice[0].state if len(invoice) > 0 else '',
- }
+ 'state': invoice[0].state if invoice else ''}
+ if cfg1 and cfg1.xrechn_default:
+ result['edocument'] = cfg1.xrechn_default
return result
+ def generate_invoice_reports(self, data):
+ """ generate missing reports and store to db
+
+ Args:
+ data (dict): report-data
+ """
+ pool = Pool()
+ Invoice = pool.get('account.invoice')
+ XReport = pool.get('account_invoice_xrechnung.export', type='report')
+
+ invoices = Invoice.search([
+ ('id', '=', data['invoice']),
+ ('type', '=', 'out')])
+ to_generate = [x.id for x in invoices if not x.invoice_report_cache]
+
+ if to_generate:
+ report_action = XReport.get_used_report()
+
+ # run selected report on invoices w/o stored report-data
+ data2 = {}
+ data2.update(data)
+ data2['action_id'] = report_action.id
+ data2['model'] = report_action.model
+ data2['id'] = to_generate[0]
+ data2['ids'] = to_generate
+
+ RepInvoice = pool.get(report_action.report_name, type='report')
+ with Transaction().set_context({'with_rec_name': False}):
+ RepInvoice.execute(to_generate, data2)
+
def do_export(self, action):
""" run export
"""
if self.start.state != 'posted':
raise UserError(gettext(
'account_invoice_xrechnung.msg_invoice_must_posted',
- invname=self.start.invoice.rec_name,
- ))
- return action, {
+ invname=self.start.invoice.rec_name))
+
+ data = {
'invoice': self.start.invoice.id,
'edocument': self.start.edocument,
- 'as_zip': self.start.as_zip,
- }
+ 'as_zip': self.start.as_zip}
+
+ # if zugferd - generate missing report-cache-items
+ if data['edocument'] == 'edocument.facturxext.invoice-ferd':
+ # pdf is stored to db
+ self.generate_invoice_reports(data)
+
+ return action, data
# end RunXRechnungReport
diff --git a/xreport.py b/xreport.py
index 7a17b06..596afc4 100644
--- a/xreport.py
+++ b/xreport.py
@@ -4,10 +4,13 @@
# this repository contains the full copyright notices and license terms.
import zipfile
+from facturx import generate_from_binary
from io import BytesIO
+from slugify import slugify
from trytond.report import Report
from trytond.pool import Pool
-from slugify import slugify
+from trytond.exceptions import UserError
+from trytond.i18n import gettext
from .wizard_runreport import edoc_versions
@@ -28,31 +31,112 @@ class XReport(Report):
def execute(cls, ids, data):
""" skip export-engine, run edocument-xml-convert
"""
+ def export_data(exp_content, fname, ext, data2):
+ """ get tuple to return from report.execute,
+
+
+ Args:
+ exp_content (bytes or str): result data of report
+ fname (str): file name
+ ext (str): extension
+ data2 (dict): data
+
+ Returns:
+ tuple: return value of report
+ """
+ if data2['as_zip'] is True:
+ return (
+ 'zip',
+ cls.compress_as_zip(
+ '%(fname)s.%(ext)s' % {
+ 'fname': fname, 'ext': ext},
+ exp_content),
+ False,
+ file_name)
+ else:
+ return (ext, exp_content, False, fname)
+
pool = Pool()
IrDate = pool.get('ir.date')
Invoice = pool.get('account.invoice')
- EDocument = pool.get(data['edocument'])
+
+ document_para = data['edocument'].split('-')
+ EDocument = pool.get(document_para[0])
+ document_var = document_para[1] if len(document_para) > 1 else None
invoice, = Invoice.browse([data['invoice']])
template = EDocument(invoice)
- invoice_string = template.render(edoc_versions[data['edocument']])
+ invoice_xml = template.render(edoc_versions[data['edocument']])
file_name = slugify('%(date)s-%(descr)s' % {
- 'date': IrDate.today().isoformat().replace('-', ''),
- 'descr': invoice.rec_name,
- },
- max_length=100, word_boundary=True, save_order=True)
+ 'date': IrDate.today().isoformat().replace('-', ''),
+ 'descr': invoice.rec_name},
+ max_length=100, word_boundary=True, save_order=True)
- if data['as_zip'] is True:
- return (
- 'zip',
- cls.compress_as_zip('%(fname)s.%(ext)s' % {
- 'fname': file_name,
- 'ext': 'xml',
- }, invoice_string),
- False,
- file_name)
+ if document_var and (
+ document_var == 'ferd') and (
+ EDocument.__name__ == 'edocument.facturxext.invoice'):
+ # convert to zugferd
+ invoice_pdf = cls.get_zugferd_pdf(invoice, invoice_xml)
+ return export_data(invoice_pdf, file_name, 'pdf', data)
else:
- return ('xml', invoice_string, False, file_name)
+ return export_data(invoice_xml, file_name, 'xml', data)
+
+ @classmethod
+ def get_used_report(cls):
+ """ get report to use from config
+
+ Raises:
+ UserError: if not report was found
+
+ Returns:
+ record: ir.action.report
+ """
+ pool = Pool()
+ Configuration = pool.get('account.configuration')
+ ActionReport = pool.get('ir.action.report')
+
+ cfg1 = Configuration.get_singleton()
+ act_report = None
+ if cfg1 and cfg1.xrechn_zugferd_report:
+ act_report = cfg1.xrechn_zugferd_report
+ else:
+ # no report defined, use 1st found
+ act_report = ActionReport.search([
+ ('model', '=', 'account.invoice'),
+ ('extension', '=', 'pdf')], count=1)
+ if act_report:
+ act_report = act_report[0]
+ if not act_report:
+ raise UserError(gettext(
+ 'account_invoice_xrechnung.msg_no_report_found'))
+ return act_report
+
+ @classmethod
+ def get_zugferd_pdf(cls, invoice, invoice_xml):
+ """ generate ZugFeRD-PDF
+
+ Args:
+ invoice (record): model account.invoice
+ invoice_xml (str): xml-data
+ """
+ # pdf was already stored to db
+ if not (invoice.invoice_report_cache and (
+ invoice.invoice_report_format == 'pdf')):
+ raise UserError(gettext(
+ 'account_invoice_xrechnung.msg_invalid_cachecontent',
+ invoice_name=invoice.rec_name))
+
+ zugferd_pdf = generate_from_binary(
+ pdf_file=invoice.invoice_report_cache,
+ xml=invoice_xml,
+ check_xsd=True,
+ pdf_metadata={
+ 'author': invoice.company.rec_name,
+ 'keywords': 'Factur-X, Invoice, Tryton',
+ 'title': invoice.number,
+ 'subject': invoice.description},
+ lang='de-DE')
+ return zugferd_pdf
# end XReport