From bdd0c364836bc4ea30f23f1f9740ca6bb8bdadef Mon Sep 17 00:00:00 2001 From: Frederik Jaeckel Date: Mon, 9 Dec 2024 13:24:39 +0100 Subject: [PATCH] facturx: check unece codes at tax --- __init__.py | 4 +- edocument.py | 138 ++++-------------- locale/de.po | 4 + locale/en.po | 4 + message.xml | 3 + mixin.py | 109 ++++++++++++++ template/Factur-X-1.07.2-extended/invoice.xml | 10 +- 7 files changed, 154 insertions(+), 118 deletions(-) create mode 100644 mixin.py diff --git a/__init__.py b/__init__.py index cb9e647..d20da9a 100644 --- a/__init__.py +++ b/__init__.py @@ -4,13 +4,13 @@ # full copyright notices and license terms. from trytond.pool import Pool -from .edocument import Invoice, FacturX +from .edocument import XRechnung, FacturX from .party import PartyConfiguration, Party def register(): Pool.register( - Invoice, + XRechnung, FacturX, Party, PartyConfiguration, diff --git a/edocument.py b/edocument.py index b5e5a4c..d196140 100644 --- a/edocument.py +++ b/edocument.py @@ -5,50 +5,12 @@ import genshi.template import os -import html -from trytond.exceptions import UserError -from trytond.i18n import gettext -from trytond.modules.edocument_uncefact.edocument import Invoice from decimal import Decimal +from trytond.modules.edocument_uncefact.edocument import Invoice +from .mixin import EdocumentMixin -class FacturX(Invoice): - 'Factur-X' - __name__ = 'edocument.facturxext.invoice' - - def get_list_of_comments(self): - """ comment, to export in - - Returns: - _type_: _description_ - """ - result = [] - if self.invoice.comment: - result.append({ - 'content': self.invoice.comment, - 'subject_code': '', - 'content_code': ''}) - return result - - def _get_template(self, version): - """ load our own template if 'version' is ours - """ - loader = genshi.template.TemplateLoader( - os.path.join(os.path.dirname(__file__), 'template'), - auto_reload=True) - - if version == 'Factur-X-1.07.2-extended': - if self.type_code in ['380', '389', '381', '261']: - return loader.load(os.path.join(version, 'invoice.xml')) - else: - raise ValueError('invalid type-code "%s"' % self.type_code) - else: - return super(Invoice, self)._get_template(version) - -# end FacturX - - -class Invoice(Invoice): +class XRechnung(EdocumentMixin, Invoice): 'EDocument XRechnung' __name__ = 'edocument.xrechnung.invoice' @@ -88,74 +50,6 @@ class Invoice(Invoice): if notes: return '; '.join(notes) - def invoice_line_tax(self, line): - """ get tax of invoice-line, - fire exception if no/multiple taxes exists - """ - if len(line.invoice_taxes) != 1: - raise UserError(gettext( - 'edocument_xrechnung.msg_linetax_invalid_number', - linename=line.rec_name, - numtax=len(line.invoice_taxes))) - - allowed_cat = ['AE', 'L', 'M', 'E', 'S', 'Z', 'G', 'O', 'K', 'B'] - 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=line.invoice_taxes[0].tax.rec_name, - allowed=', '.join(allowed_cat))) - - return line.invoice_taxes[0].tax - - def taxident_data(self, tax_identifier): - """ get tax-scheme-id and codes - """ - result = {'code': None, 'id': None} - - if tax_identifier: - if tax_identifier.type == 'de_vat': - result['code'] = 'DE%s' % tax_identifier.code - result['id'] = 'VAT' - return result - - def tax_rate(self, tax): - """ get tax-rate in procent - """ - return (tax.rate * Decimal('100.0')).quantize(Decimal('0.01')) - - def uom_unece_code(self, line): - """ 'line': invoice.line - """ - if len(line.unit.unece_code or '') == 0: - raise UserError(gettext( - 'edocument_xrechnung.msg_uom_code_missing', - uomname=line.unit.rec_name)) - return line.unit.unece_code - - def get_category_code(self, tax): - while tax: - if tax.unece_category_code: - return tax.unece_category_code - break - tax = tax.parent - - def tax_category_code(self, tax): - """ read tax-category, fire exception if missing - """ - unece_category_code = self.get_category_code(tax) - if not unece_category_code: - raise UserError(gettext( - 'edocument_xrechnung.mds_tax_category_missing', - taxname=tax.rec_name)) - return unece_category_code - - def quote_text(self, text): - """ replace critical chars - """ - if text: - return html.quote(text) - def _get_template(self, version): """ load our own template if 'version' is ours """ @@ -176,6 +70,28 @@ class Invoice(Invoice): else: raise ValueError('invalid type-code "%s"' % self.type_code) else: - return super(Invoice, self)._get_template(version) + return super(XRechnung, self)._get_template(version) -# end Invoice +# end XRechnung + + +class FacturX(EdocumentMixin, Invoice): + 'Factur-X' + __name__ = 'edocument.facturxext.invoice' + + def _get_template(self, version): + """ load our own template if 'version' is ours + """ + loader = genshi.template.TemplateLoader( + os.path.join(os.path.dirname(__file__), 'template'), + auto_reload=True) + + if version == 'Factur-X-1.07.2-extended': + if self.type_code in ['380', '389', '381', '261']: + return loader.load(os.path.join(version, 'invoice.xml')) + else: + raise ValueError('invalid type-code "%s"' % self.type_code) + else: + return super(FacturX, self)._get_template(version) + +# end FacturX diff --git a/locale/de.po b/locale/de.po index c8098e7..ecc828d 100644 --- a/locale/de.po +++ b/locale/de.po @@ -14,6 +14,10 @@ msgctxt "model:ir.message,text:mds_tax_category_missing" msgid "The UNECE tax category is not configured for tax '%(taxname)s'." msgstr "Für die Steuer '%(taxname)s' ist die UNECE-Steuerkategorie nicht konfiguriert." +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-Einheitencode nicht konfiguriert." + msgctxt "model:ir.message,text:msg_uom_code_missing" msgid "The UNECE uom code is not configured for unit '%(uomname)s'." msgstr "Für die Einheit '%(uomname)s' ist der UNECE-Einheitencode nicht konfiguriert." diff --git a/locale/en.po b/locale/en.po index ab8715b..465895a 100644 --- a/locale/en.po +++ b/locale/en.po @@ -10,6 +10,10 @@ msgctxt "model:ir.message,text:mds_tax_category_missing" msgid "The UNECE tax category is not configured for tax '%(taxname)s'." msgstr "The UNECE tax category is not configured for tax '%(taxname)s'." +msgctxt "model:ir.message,text:msg_tax_code_missing" +msgid "The UNECE tax code is not configured for tax '%(taxname)s'." +msgstr "The UNECE tax code is not configured for tax '%(taxname)s'." + msgctxt "model:ir.message,text:msg_uom_code_missing" msgid "The UNECE uom code is not configured for unit '%(uomname)s'." msgstr "The UNECE uom code is not configured for unit '%(uomname)s'." diff --git a/message.xml b/message.xml index f7a77ac..f2c47db 100644 --- a/message.xml +++ b/message.xml @@ -11,6 +11,9 @@ full copyright notices and license terms. --> The UNECE tax category is not configured for tax '%(taxname)s'. + + The UNECE tax code is not configured for tax '%(taxname)s'. + The UNECE uom code is not configured for unit '%(uomname)s'. diff --git a/mixin.py b/mixin.py new file mode 100644 index 0000000..adcb478 --- /dev/null +++ b/mixin.py @@ -0,0 +1,109 @@ +# -*- 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 decimal import Decimal +import html +from trytond.exceptions import UserError +from trytond.i18n import gettext + + +class EdocumentMixin(object): + """ functions to get field values for xml + """ + __slots__ = () + + def get_list_of_comments(self): + """ comment, to export in + + Returns: + _type_: _description_ + """ + result = [] + if self.invoice.comment: + result.append({ + 'content': self.invoice.comment, + 'subject_code': '', + 'content_code': ''}) + return result + + def invoice_line_tax(self, line): + """ get tax of invoice-line, + fire exception if no/multiple taxes exists + """ + if len(line.invoice_taxes) != 1: + raise UserError(gettext( + 'edocument_xrechnung.msg_linetax_invalid_number', + linename=line.rec_name, + numtax=len(line.invoice_taxes))) + + allowed_cat = ['AE', 'L', 'M', 'E', 'S', 'Z', 'G', 'O', 'K', 'B'] + 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=line.invoice_taxes[0].tax.rec_name, + allowed=', '.join(allowed_cat))) + + return line.invoice_taxes[0].tax + + def taxident_data(self, tax_identifier): + """ get tax-scheme-id and codes + """ + result = {'code': None, 'id': None} + + if tax_identifier: + if tax_identifier.type == 'de_vat': + result['code'] = 'DE%s' % tax_identifier.code + result['id'] = 'VAT' + return result + + def tax_rate(self, tax): + """ get tax-rate in procent + """ + return (tax.rate * Decimal('100.0')).quantize(Decimal('0.01')) + + def uom_unece_code(self, line): + """ 'line': invoice.line + """ + if len(line.unit.unece_code or '') == 0: + raise UserError(gettext( + 'edocument_xrechnung.msg_uom_code_missing', + uomname=line.unit.rec_name)) + return line.unit.unece_code + + def tax_unece_code(self, tax): + """ 'tax': invoice.line + """ + 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_category_code(self, tax): + while tax: + if tax.unece_category_code: + return tax.unece_category_code + break + tax = tax.parent + + def tax_category_code(self, tax): + """ read tax-category, fire exception if missing + """ + unece_category_code = self.get_category_code(tax) + if not unece_category_code: + raise UserError(gettext( + 'edocument_xrechnung.mds_tax_category_missing', + taxname=tax.rec_name)) + return unece_category_code + + def quote_text(self, text): + """ replace critical chars + """ + if text: + return html.quote(text) + +# end EdocumentMixin diff --git a/template/Factur-X-1.07.2-extended/invoice.xml b/template/Factur-X-1.07.2-extended/invoice.xml index c10307f..ff32e24 100644 --- a/template/Factur-X-1.07.2-extended/invoice.xml +++ b/template/Factur-X-1.07.2-extended/invoice.xml @@ -37,10 +37,10 @@ this repository contains the full copyright notices and license terms. --> ${amount * this.type_sign} - ${tax.unece_code} + ${this.tax_unece_code(tax)} ${tax.legal_notice} ${base * this.type_sign} - ${tax.unece_category_code} + ${this.tax_category_code(tax)} ${tax.rate * 100} @@ -69,9 +69,9 @@ this repository contains the full copyright notices and license terms. --> ${line_id} - - ${line.product.code} - ${line.product.name} + + ${line.product.code} + ${line.product.name if line.product else ''} ${line.description}