Merge branch 'main' into 7.0
This commit is contained in:
commit
675e103fd9
17 changed files with 869 additions and 56 deletions
1
.env
Normal file
1
.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
PYTHONPATH=~/Projekte/tr70/lib/python3.10/site-packages
|
2
.hgignore → .gitignore
vendored
2
.hgignore → .gitignore
vendored
|
@ -1,4 +1,4 @@
|
||||||
syntax: glob
|
*.pyc
|
||||||
build/*
|
build/*
|
||||||
dist/*
|
dist/*
|
||||||
mds_account_invoice_xrechnung.egg-info/*
|
mds_account_invoice_xrechnung.egg-info/*
|
|
@ -7,10 +7,13 @@ from trytond.pool import Pool
|
||||||
from .wizard_runreport import RunXRechnungReport, RunXRechnungReportStart
|
from .wizard_runreport import RunXRechnungReport, RunXRechnungReportStart
|
||||||
from .invoice import InvoiceLine
|
from .invoice import InvoiceLine
|
||||||
from .xreport import XReport
|
from .xreport import XReport
|
||||||
|
from .configuration import ConfigurationXRechnungexport, Configuration
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
Pool.register(
|
Pool.register(
|
||||||
|
Configuration,
|
||||||
|
ConfigurationXRechnungexport,
|
||||||
InvoiceLine,
|
InvoiceLine,
|
||||||
RunXRechnungReportStart,
|
RunXRechnungReportStart,
|
||||||
module='account_invoice_xrechnung', type_='model')
|
module='account_invoice_xrechnung', type_='model')
|
||||||
|
|
62
configuration.py
Normal file
62
configuration.py
Normal file
|
@ -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
|
15
configuration.xml
Normal file
15
configuration.xml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- 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. -->
|
||||||
|
<tryton>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<record model="ir.ui.view" id="configuration_view_form">
|
||||||
|
<field name="model">account.configuration</field>
|
||||||
|
<field name="inherit" ref="account.configuration_view_form"/>
|
||||||
|
<field name="name">configuration_form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</tryton>
|
|
@ -4,7 +4,7 @@
|
||||||
# this repository contains the full copyright notices and license terms.
|
# this repository contains the full copyright notices and license terms.
|
||||||
|
|
||||||
from trytond.pool import PoolMeta
|
from trytond.pool import PoolMeta
|
||||||
from trytond.pyson import Eval, And, Or
|
from trytond.pyson import Eval, And, Or, Bool
|
||||||
|
|
||||||
|
|
||||||
class InvoiceLine(metaclass=PoolMeta):
|
class InvoiceLine(metaclass=PoolMeta):
|
||||||
|
@ -17,10 +17,7 @@ class InvoiceLine(metaclass=PoolMeta):
|
||||||
cls.unit.states['required'],
|
cls.unit.states['required'],
|
||||||
And(
|
And(
|
||||||
Eval('type') == 'line',
|
Eval('type') == 'line',
|
||||||
Eval('quantity', None) != None,
|
Bool(Eval('quantity'))))
|
||||||
),
|
cls.unit.depends.update(['type', 'quantity'])
|
||||||
)
|
|
||||||
cls.unit.depends.add('type')
|
|
||||||
cls.unit.depends.add('quantity')
|
|
||||||
|
|
||||||
# end Invoice
|
# end Invoice
|
||||||
|
|
136
locale/de.po
136
locale/de.po
|
@ -3,6 +3,22 @@ msgid ""
|
||||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
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 #
|
# ir.action #
|
||||||
#############
|
#############
|
||||||
|
@ -30,6 +46,30 @@ msgctxt "field:account_invoice_xrechnung.runrep.start,edocument:"
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "Typ"
|
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:"
|
msgctxt "field:account_invoice_xrechnung.runrep.start,as_zip:"
|
||||||
msgid "ZIP-File"
|
msgid "ZIP-File"
|
||||||
msgstr "ZIP-Datei"
|
msgstr "ZIP-Datei"
|
||||||
|
@ -57,3 +97,99 @@ msgstr "Export"
|
||||||
msgctxt "model:account_invoice_xrechnung.export,name:"
|
msgctxt "model:account_invoice_xrechnung.export,name:"
|
||||||
msgid "eDocument Export"
|
msgid "eDocument Export"
|
||||||
msgstr "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"
|
||||||
|
|
127
locale/en.po
127
locale/en.po
|
@ -2,6 +2,18 @@
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr "Content-Type: text/plain; charset=utf-8\n"
|
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"
|
msgctxt "model:ir.action,name:act_wizard_report"
|
||||||
msgid "eDocument Export"
|
msgid "eDocument Export"
|
||||||
msgstr "eDocument Export"
|
msgstr "eDocument Export"
|
||||||
|
@ -22,6 +34,30 @@ msgctxt "field:account_invoice_xrechnung.runrep.start,edocument:"
|
||||||
msgid "Type"
|
msgid "Type"
|
||||||
msgstr "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:"
|
msgctxt "field:account_invoice_xrechnung.runrep.start,as_zip:"
|
||||||
msgid "ZIP-File"
|
msgid "ZIP-File"
|
||||||
msgstr "ZIP-File"
|
msgstr "ZIP-File"
|
||||||
|
@ -38,3 +74,94 @@ msgctxt "wizard_button:account_invoice_xrechnung.runrep,start,export:"
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "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"
|
||||||
|
|
|
@ -8,6 +8,12 @@
|
||||||
<record model="ir.message" id="msg_invoice_must_posted">
|
<record model="ir.message" id="msg_invoice_must_posted">
|
||||||
<field name="text">Invoice '%(invname)s' must be posted.</field>
|
<field name="text">Invoice '%(invname)s' must be posted.</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record model="ir.message" id="msg_no_report_found">
|
||||||
|
<field name="text">No report found for invoices in PDF format.</field>
|
||||||
|
</record>
|
||||||
|
<record model="ir.message" id="msg_invalid_cachecontent">
|
||||||
|
<field name="text">No PDF has yet been generated for the invoice '%(invoice_name)s' or the saved document has an incorrect format.</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
</data>
|
</data>
|
||||||
</tryton>
|
</tryton>
|
||||||
|
|
18
setup.py
18
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
|
from setuptools import setup
|
||||||
# To use a consistent encoding
|
|
||||||
from codecs import open
|
from codecs import open
|
||||||
from os import path
|
from os import path
|
||||||
import re
|
import re
|
||||||
try:
|
from configparser import ConfigParser
|
||||||
from configparser import ConfigParser
|
|
||||||
except ImportError:
|
|
||||||
from ConfigParser import ConfigParser
|
|
||||||
|
|
||||||
here = path.abspath(path.dirname(__file__))
|
here = path.abspath(path.dirname(__file__))
|
||||||
MODULE = 'account_invoice_xrechnung'
|
MODULE = 'account_invoice_xrechnung'
|
||||||
|
@ -20,7 +17,6 @@ PREFIX = 'mds'
|
||||||
with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
|
with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
|
||||||
long_description = f.read()
|
long_description = f.read()
|
||||||
|
|
||||||
# tryton.cfg einlesen
|
|
||||||
config = ConfigParser()
|
config = ConfigParser()
|
||||||
config.readfp(open('tryton.cfg'))
|
config.readfp(open('tryton.cfg'))
|
||||||
info = dict(config.items('tryton'))
|
info = dict(config.items('tryton'))
|
||||||
|
@ -42,7 +38,7 @@ with open(path.join(here, 'versiondep.txt'), encoding='utf-8') as f:
|
||||||
major_version = 7
|
major_version = 7
|
||||||
minor_version = 0
|
minor_version = 0
|
||||||
|
|
||||||
requires = ['python-slugify']
|
requires = ['python-slugify', 'pypdf', 'factur-x']
|
||||||
for dep in info.get('depends', []):
|
for dep in info.get('depends', []):
|
||||||
if not re.match(r'(ir|res|webdav)(\W|$)', dep):
|
if not re.match(r'(ir|res|webdav)(\W|$)', dep):
|
||||||
if dep in modversion.keys():
|
if dep in modversion.keys():
|
||||||
|
@ -87,9 +83,9 @@ setup(
|
||||||
'Natural Language :: English',
|
'Natural Language :: English',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'License :: OSI Approved :: GNU General Public License (GPL)',
|
'License :: OSI Approved :: GNU General Public License (GPL)',
|
||||||
'Programming Language :: Python :: 3.7',
|
|
||||||
'Programming Language :: Python :: 3.8',
|
'Programming Language :: Python :: 3.8',
|
||||||
'Programming Language :: Python :: 3.9',
|
'Programming Language :: Python :: 3.9',
|
||||||
|
'Programming Language :: Python :: 3.10',
|
||||||
],
|
],
|
||||||
|
|
||||||
keywords='tryton account invoice xrechnung edocument',
|
keywords='tryton account invoice xrechnung edocument',
|
||||||
|
|
|
@ -3,18 +3,235 @@
|
||||||
# from m-ds for Tryton. The COPYRIGHT file at the top level of
|
# from m-ds for Tryton. The COPYRIGHT file at the top level of
|
||||||
# this repository contains the full copyright notices and license terms.
|
# 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.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):
|
class InvoiceTestCase(ModuleTestCase):
|
||||||
'Test invoice module'
|
'Test invoice module'
|
||||||
module = 'account_invoice_xrechnung'
|
module = 'account_invoice_xrechnung'
|
||||||
|
|
||||||
@with_transaction()
|
def prep_fiscalyear(self, company1):
|
||||||
def test_xrechnung(self):
|
""" prepare fiscal year, sequences...
|
||||||
""" run default tests
|
|
||||||
"""
|
"""
|
||||||
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
|
# end InvoiceTestCase
|
||||||
|
|
||||||
|
|
104
tests/xml_data.py
Normal file
104
tests/xml_data.py
Normal file
|
@ -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 = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<rsm:CrossIndustryInvoice xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100" xmlns:qdt="urn:un:unece:uncefact:data:standard:QualifiedDataType:100" xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100" xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<rsm:ExchangedDocumentContext>
|
||||||
|
<ram:GuidelineSpecifiedDocumentContextParameter>
|
||||||
|
<ram:ID>urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended</ram:ID>
|
||||||
|
</ram:GuidelineSpecifiedDocumentContextParameter>
|
||||||
|
</rsm:ExchangedDocumentContext>
|
||||||
|
<rsm:ExchangedDocument>
|
||||||
|
<ram:ID>1</ram:ID>
|
||||||
|
<ram:Name>Parts</ram:Name>
|
||||||
|
<ram:TypeCode>380</ram:TypeCode>
|
||||||
|
<ram:IssueDateTime>
|
||||||
|
<udt:DateTimeString format="102">20240701</udt:DateTimeString>
|
||||||
|
</ram:IssueDateTime>
|
||||||
|
</rsm:ExchangedDocument>
|
||||||
|
<rsm:SupplyChainTradeTransaction>
|
||||||
|
<ram:IncludedSupplyChainTradeLineItem>
|
||||||
|
<ram:AssociatedDocumentLineDocument>
|
||||||
|
<ram:LineID>1</ram:LineID>
|
||||||
|
</ram:AssociatedDocumentLineDocument>
|
||||||
|
<ram:SpecifiedTradeProduct>
|
||||||
|
<ram:Name></ram:Name>
|
||||||
|
<ram:Description>Product 1</ram:Description>
|
||||||
|
</ram:SpecifiedTradeProduct>
|
||||||
|
<ram:SpecifiedLineTradeAgreement>
|
||||||
|
<ram:NetPriceProductTradePrice>
|
||||||
|
<ram:ChargeAmount currencyID="usd">50.00</ram:ChargeAmount>
|
||||||
|
</ram:NetPriceProductTradePrice>
|
||||||
|
</ram:SpecifiedLineTradeAgreement>
|
||||||
|
<ram:SpecifiedLineTradeDelivery>
|
||||||
|
<ram:BilledQuantity unitCode="C62">2.0</ram:BilledQuantity>
|
||||||
|
</ram:SpecifiedLineTradeDelivery>
|
||||||
|
<ram:SpecifiedLineTradeSettlement>
|
||||||
|
<ram:ApplicableTradeTax>
|
||||||
|
<ram:TypeCode>GST</ram:TypeCode>
|
||||||
|
<ram:CategoryCode>S</ram:CategoryCode>
|
||||||
|
<ram:RateApplicablePercent>20.0</ram:RateApplicablePercent>
|
||||||
|
</ram:ApplicableTradeTax>
|
||||||
|
<ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||||
|
<ram:LineTotalAmount currencyID="usd">100.00</ram:LineTotalAmount>
|
||||||
|
</ram:SpecifiedTradeSettlementLineMonetarySummation>
|
||||||
|
</ram:SpecifiedLineTradeSettlement>
|
||||||
|
</ram:IncludedSupplyChainTradeLineItem>
|
||||||
|
<ram:ApplicableHeaderTradeAgreement>
|
||||||
|
<ram:SellerTradeParty>
|
||||||
|
<ram:Name>m-ds</ram:Name>
|
||||||
|
<ram:SpecifiedLegalOrganization>
|
||||||
|
</ram:SpecifiedLegalOrganization>
|
||||||
|
<ram:PostalTradeAddress>
|
||||||
|
<ram:CountryID>DE</ram:CountryID>
|
||||||
|
</ram:PostalTradeAddress>
|
||||||
|
</ram:SellerTradeParty>
|
||||||
|
<ram:BuyerTradeParty>
|
||||||
|
<ram:Name>Payee</ram:Name>
|
||||||
|
<ram:SpecifiedLegalOrganization>
|
||||||
|
</ram:SpecifiedLegalOrganization>
|
||||||
|
<ram:PostalTradeAddress>
|
||||||
|
<ram:PostcodeCode>12345</ram:PostcodeCode>
|
||||||
|
<ram:LineOne>Applicant Street 1</ram:LineOne>
|
||||||
|
<ram:CityName>Usertown</ram:CityName>
|
||||||
|
<ram:CountryID>DE</ram:CountryID>
|
||||||
|
</ram:PostalTradeAddress>
|
||||||
|
</ram:BuyerTradeParty>
|
||||||
|
<ram:BuyerOrderReferencedDocument>
|
||||||
|
<ram:IssuerAssignedID/>
|
||||||
|
</ram:BuyerOrderReferencedDocument>
|
||||||
|
</ram:ApplicableHeaderTradeAgreement>
|
||||||
|
<ram:ApplicableHeaderTradeDelivery>
|
||||||
|
</ram:ApplicableHeaderTradeDelivery>
|
||||||
|
<ram:ApplicableHeaderTradeSettlement>
|
||||||
|
<ram:PaymentReference>1</ram:PaymentReference>
|
||||||
|
<ram:InvoiceCurrencyCode>usd</ram:InvoiceCurrencyCode>
|
||||||
|
<ram:SpecifiedTradeSettlementPaymentMeans>
|
||||||
|
<ram:TypeCode>1</ram:TypeCode>
|
||||||
|
</ram:SpecifiedTradeSettlementPaymentMeans>
|
||||||
|
<ram:ApplicableTradeTax>
|
||||||
|
<ram:CalculatedAmount currencyID="usd">20.00</ram:CalculatedAmount>
|
||||||
|
<ram:TypeCode>GST</ram:TypeCode>
|
||||||
|
<ram:BasisAmount>100.00</ram:BasisAmount>
|
||||||
|
<ram:CategoryCode>S</ram:CategoryCode>
|
||||||
|
<ram:RateApplicablePercent>20.0</ram:RateApplicablePercent>
|
||||||
|
</ram:ApplicableTradeTax>
|
||||||
|
<ram:SpecifiedTradePaymentTerms>
|
||||||
|
<ram:DueDateDateTime>
|
||||||
|
<udt:DateTimeString format="102">%(datetoday)s</udt:DateTimeString>
|
||||||
|
</ram:DueDateDateTime>
|
||||||
|
<ram:PartialPaymentAmount currencyID="usd">120.00</ram:PartialPaymentAmount>
|
||||||
|
</ram:SpecifiedTradePaymentTerms>
|
||||||
|
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
||||||
|
<ram:LineTotalAmount currencyID="usd">100.00</ram:LineTotalAmount>
|
||||||
|
<ram:TaxBasisTotalAmount currencyID="usd">100.00</ram:TaxBasisTotalAmount>
|
||||||
|
<ram:TaxTotalAmount currencyID="usd">20.00</ram:TaxTotalAmount>
|
||||||
|
<ram:GrandTotalAmount currencyID="usd">120.00</ram:GrandTotalAmount>
|
||||||
|
<ram:DuePayableAmount currencyID="usd">120.00</ram:DuePayableAmount>
|
||||||
|
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
|
||||||
|
</ram:ApplicableHeaderTradeSettlement>
|
||||||
|
</rsm:SupplyChainTradeTransaction>
|
||||||
|
</rsm:CrossIndustryInvoice>"""
|
|
@ -6,5 +6,6 @@ depends:
|
||||||
edocument_xrechnung
|
edocument_xrechnung
|
||||||
xml:
|
xml:
|
||||||
message.xml
|
message.xml
|
||||||
|
configuration.xml
|
||||||
wizard_runreport.xml
|
wizard_runreport.xml
|
||||||
xreport.xml
|
xreport.xml
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
edocument_xrechnung;7.0.3;7.0.999;mds
|
edocument_xrechnung;7.0.5;7.0.999;mds
|
||||||
|
|
17
view/configuration_form.xml
Normal file
17
view/configuration_form.xml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!-- This file is part of the account-datev-module from m-ds for Tryton.
|
||||||
|
The COPYRIGHT file at the top level of this repository contains the
|
||||||
|
full copyright notices and license terms. -->
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<xpath expr="/form/separator[@id='currency_exchange']" position="before">
|
||||||
|
|
||||||
|
<separator id="xrechnung" colspan="4" string="ZUGFeRD - e-Invoice"/>
|
||||||
|
<label name="xrechn_zugferd_report"/>
|
||||||
|
<field name="xrechn_zugferd_report"/>
|
||||||
|
<label name="xrechn_default"/>
|
||||||
|
<field name="xrechn_default"/>
|
||||||
|
|
||||||
|
</xpath>
|
||||||
|
|
||||||
|
</data>
|
|
@ -12,14 +12,21 @@ from trytond.transaction import Transaction
|
||||||
|
|
||||||
|
|
||||||
sel_edocument = [
|
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'),
|
('edocument.uncefact.invoice', 'CII CrossIndustryInvoice D16B'),
|
||||||
]
|
]
|
||||||
|
|
||||||
edoc_versions = {
|
edoc_versions = {
|
||||||
'edocument.xrechnung.invoice': 'XRechnung-2.2',
|
'edocument.xrechnung.invoice-2.2': 'XRechnung-2.2',
|
||||||
'edocument.uncefact.invoice': '16B-CII',
|
'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):
|
class RunXRechnungReportStart(ModelView):
|
||||||
|
@ -45,7 +52,7 @@ class RunXRechnungReportStart(ModelView):
|
||||||
def default_edocument(cls):
|
def default_edocument(cls):
|
||||||
""" default xrechnung
|
""" default xrechnung
|
||||||
"""
|
"""
|
||||||
return 'edocument.xrechnung.invoice'
|
return 'edocument.xrechnung.invoice-3.0'
|
||||||
|
|
||||||
# end RunXRechnungReportStart
|
# end RunXRechnungReportStart
|
||||||
|
|
||||||
|
@ -62,35 +69,75 @@ class RunXRechnungReport(Wizard):
|
||||||
buttons=[
|
buttons=[
|
||||||
Button(string='Cancel', state='end', icon='tryton-cancel'),
|
Button(string='Cancel', state='end', icon='tryton-cancel'),
|
||||||
Button(string='Export', state='export', icon='tryton-export'),
|
Button(string='Export', state='export', icon='tryton-export'),
|
||||||
],
|
])
|
||||||
)
|
|
||||||
|
|
||||||
def default_start(self, fields):
|
def default_start(self, fields):
|
||||||
""" set defaults
|
""" set defaults
|
||||||
"""
|
"""
|
||||||
context = Transaction().context
|
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)])
|
invoice = Invoice.browse([context.get('active_id', -1)])
|
||||||
result = {
|
result = {
|
||||||
'edocument': 'edocument.xrechnung.invoice',
|
'edocument': WizRepStart.default_edocument(),
|
||||||
'invoice': context.get('active_id', -1),
|
'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
|
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):
|
def do_export(self, action):
|
||||||
""" run export
|
""" run export
|
||||||
"""
|
"""
|
||||||
if self.start.state != 'posted':
|
if self.start.state != 'posted':
|
||||||
raise UserError(gettext(
|
raise UserError(gettext(
|
||||||
'account_invoice_xrechnung.msg_invoice_must_posted',
|
'account_invoice_xrechnung.msg_invoice_must_posted',
|
||||||
invname=self.start.invoice.rec_name,
|
invname=self.start.invoice.rec_name))
|
||||||
))
|
|
||||||
return action, {
|
data = {
|
||||||
'invoice': self.start.invoice.id,
|
'invoice': self.start.invoice.id,
|
||||||
'edocument': self.start.edocument,
|
'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
|
# end RunXRechnungReport
|
||||||
|
|
122
xreport.py
122
xreport.py
|
@ -4,10 +4,13 @@
|
||||||
# this repository contains the full copyright notices and license terms.
|
# this repository contains the full copyright notices and license terms.
|
||||||
|
|
||||||
import zipfile
|
import zipfile
|
||||||
|
from facturx import generate_from_binary
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from slugify import slugify
|
||||||
from trytond.report import Report
|
from trytond.report import Report
|
||||||
from trytond.pool import Pool
|
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
|
from .wizard_runreport import edoc_versions
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,31 +31,112 @@ class XReport(Report):
|
||||||
def execute(cls, ids, data):
|
def execute(cls, ids, data):
|
||||||
""" skip export-engine, run edocument-xml-convert
|
""" skip export-engine, run edocument-xml-convert
|
||||||
"""
|
"""
|
||||||
pool = Pool()
|
def export_data(exp_content, fname, ext, data2):
|
||||||
IrDate = pool.get('ir.date')
|
""" get tuple to return from report.execute,
|
||||||
Invoice = pool.get('account.invoice')
|
|
||||||
EDocument = pool.get(data['edocument'])
|
|
||||||
|
|
||||||
invoice, = Invoice.browse([data['invoice']])
|
|
||||||
template = EDocument(invoice)
|
|
||||||
invoice_string = template.render(edoc_versions[data['edocument']])
|
|
||||||
|
|
||||||
file_name = slugify('%(date)s-%(descr)s' % {
|
Args:
|
||||||
'date': IrDate.today().isoformat().replace('-', ''),
|
exp_content (bytes or str): result data of report
|
||||||
'descr': invoice.rec_name,
|
fname (str): file name
|
||||||
},
|
ext (str): extension
|
||||||
max_length=100, word_boundary=True, save_order=True)
|
data2 (dict): data
|
||||||
|
|
||||||
if data['as_zip'] is True:
|
Returns:
|
||||||
|
tuple: return value of report
|
||||||
|
"""
|
||||||
|
if data2['as_zip'] is True:
|
||||||
return (
|
return (
|
||||||
'zip',
|
'zip',
|
||||||
cls.compress_as_zip('%(fname)s.%(ext)s' % {
|
cls.compress_as_zip(
|
||||||
'fname': file_name,
|
'%(fname)s.%(ext)s' % {
|
||||||
'ext': 'xml',
|
'fname': fname, 'ext': ext},
|
||||||
}, invoice_string),
|
exp_content),
|
||||||
False,
|
False,
|
||||||
file_name)
|
file_name)
|
||||||
else:
|
else:
|
||||||
return ('xml', invoice_string, False, file_name)
|
return (ext, exp_content, False, fname)
|
||||||
|
|
||||||
|
pool = Pool()
|
||||||
|
IrDate = pool.get('ir.date')
|
||||||
|
Invoice = pool.get('account.invoice')
|
||||||
|
|
||||||
|
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_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)
|
||||||
|
|
||||||
|
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 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
|
# end XReport
|
||||||
|
|
Loading…
Reference in a new issue