facturx: check unece codes at tax
This commit is contained in:
parent
70f079a0dc
commit
bdd0c36483
7 changed files with 154 additions and 118 deletions
|
@ -4,13 +4,13 @@
|
||||||
# full copyright notices and license terms.
|
# full copyright notices and license terms.
|
||||||
|
|
||||||
from trytond.pool import Pool
|
from trytond.pool import Pool
|
||||||
from .edocument import Invoice, FacturX
|
from .edocument import XRechnung, FacturX
|
||||||
from .party import PartyConfiguration, Party
|
from .party import PartyConfiguration, Party
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
Pool.register(
|
Pool.register(
|
||||||
Invoice,
|
XRechnung,
|
||||||
FacturX,
|
FacturX,
|
||||||
Party,
|
Party,
|
||||||
PartyConfiguration,
|
PartyConfiguration,
|
||||||
|
|
138
edocument.py
138
edocument.py
|
@ -5,50 +5,12 @@
|
||||||
|
|
||||||
import genshi.template
|
import genshi.template
|
||||||
import os
|
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 decimal import Decimal
|
||||||
|
from trytond.modules.edocument_uncefact.edocument import Invoice
|
||||||
|
from .mixin import EdocumentMixin
|
||||||
|
|
||||||
|
|
||||||
class FacturX(Invoice):
|
class XRechnung(EdocumentMixin, Invoice):
|
||||||
'Factur-X'
|
|
||||||
__name__ = 'edocument.facturxext.invoice'
|
|
||||||
|
|
||||||
def get_list_of_comments(self):
|
|
||||||
""" comment, to export in <ram:IncludedNote/>
|
|
||||||
|
|
||||||
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):
|
|
||||||
'EDocument XRechnung'
|
'EDocument XRechnung'
|
||||||
__name__ = 'edocument.xrechnung.invoice'
|
__name__ = 'edocument.xrechnung.invoice'
|
||||||
|
|
||||||
|
@ -88,74 +50,6 @@ class Invoice(Invoice):
|
||||||
if notes:
|
if notes:
|
||||||
return '; '.join(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):
|
def _get_template(self, version):
|
||||||
""" load our own template if 'version' is ours
|
""" load our own template if 'version' is ours
|
||||||
"""
|
"""
|
||||||
|
@ -176,6 +70,28 @@ class Invoice(Invoice):
|
||||||
else:
|
else:
|
||||||
raise ValueError('invalid type-code "%s"' % self.type_code)
|
raise ValueError('invalid type-code "%s"' % self.type_code)
|
||||||
else:
|
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
|
||||||
|
|
|
@ -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'."
|
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."
|
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"
|
msgctxt "model:ir.message,text:msg_uom_code_missing"
|
||||||
msgid "The UNECE uom code is not configured for unit '%(uomname)s'."
|
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."
|
msgstr "Für die Einheit '%(uomname)s' ist der UNECE-Einheitencode nicht konfiguriert."
|
||||||
|
|
|
@ -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'."
|
msgid "The UNECE tax category is not configured for tax '%(taxname)s'."
|
||||||
msgstr "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"
|
msgctxt "model:ir.message,text:msg_uom_code_missing"
|
||||||
msgid "The UNECE uom code is not configured for unit '%(uomname)s'."
|
msgid "The UNECE uom code is not configured for unit '%(uomname)s'."
|
||||||
msgstr "The UNECE uom code is not configured for unit '%(uomname)s'."
|
msgstr "The UNECE uom code is not configured for unit '%(uomname)s'."
|
||||||
|
|
|
@ -11,6 +11,9 @@ full copyright notices and license terms. -->
|
||||||
<record model="ir.message" id="mds_tax_category_missing">
|
<record model="ir.message" id="mds_tax_category_missing">
|
||||||
<field name="text">The UNECE tax category is not configured for tax '%(taxname)s'.</field>
|
<field name="text">The UNECE tax category is not configured for tax '%(taxname)s'.</field>
|
||||||
</record>
|
</record>
|
||||||
|
<record model="ir.message" id="msg_tax_code_missing">
|
||||||
|
<field name="text">The UNECE tax code is not configured for tax '%(taxname)s'.</field>
|
||||||
|
</record>
|
||||||
<record model="ir.message" id="msg_uom_code_missing">
|
<record model="ir.message" id="msg_uom_code_missing">
|
||||||
<field name="text">The UNECE uom code is not configured for unit '%(uomname)s'.</field>
|
<field name="text">The UNECE uom code is not configured for unit '%(uomname)s'.</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
109
mixin.py
Normal file
109
mixin.py
Normal file
|
@ -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 <ram:IncludedNote/>
|
||||||
|
|
||||||
|
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
|
|
@ -37,10 +37,10 @@ this repository contains the full copyright notices and license terms. -->
|
||||||
<py:def function="TradeTax(tax, amount=None, base=None)">
|
<py:def function="TradeTax(tax, amount=None, base=None)">
|
||||||
<ram:ApplicableTradeTax>
|
<ram:ApplicableTradeTax>
|
||||||
<ram:CalculatedAmount py:if="amount" py:attrs="{'currencyID': this.invoice.currency.code}">${amount * this.type_sign}</ram:CalculatedAmount>
|
<ram:CalculatedAmount py:if="amount" py:attrs="{'currencyID': this.invoice.currency.code}">${amount * this.type_sign}</ram:CalculatedAmount>
|
||||||
<ram:TypeCode py:if="tax.unece_code">${tax.unece_code}</ram:TypeCode>
|
<ram:TypeCode>${this.tax_unece_code(tax)}</ram:TypeCode>
|
||||||
<ram:ExemptionReason py:if="tax.legal_notice">${tax.legal_notice}</ram:ExemptionReason>
|
<ram:ExemptionReason py:if="tax.legal_notice">${tax.legal_notice}</ram:ExemptionReason>
|
||||||
<ram:BasisAmount py:if="base">${base * this.type_sign}</ram:BasisAmount>
|
<ram:BasisAmount py:if="base">${base * this.type_sign}</ram:BasisAmount>
|
||||||
<ram:CategoryCode py:if="tax.unece_category_code">${tax.unece_category_code}</ram:CategoryCode>
|
<ram:CategoryCode>${this.tax_category_code(tax)}</ram:CategoryCode>
|
||||||
<ram:RateApplicablePercent py:if="tax.type == 'percentage'">${tax.rate * 100}</ram:RateApplicablePercent>
|
<ram:RateApplicablePercent py:if="tax.type == 'percentage'">${tax.rate * 100}</ram:RateApplicablePercent>
|
||||||
</ram:ApplicableTradeTax>
|
</ram:ApplicableTradeTax>
|
||||||
</py:def>
|
</py:def>
|
||||||
|
@ -69,9 +69,9 @@ this repository contains the full copyright notices and license terms. -->
|
||||||
<ram:AssociatedDocumentLineDocument>
|
<ram:AssociatedDocumentLineDocument>
|
||||||
<ram:LineID>${line_id}</ram:LineID>
|
<ram:LineID>${line_id}</ram:LineID>
|
||||||
</ram:AssociatedDocumentLineDocument>
|
</ram:AssociatedDocumentLineDocument>
|
||||||
<ram:SpecifiedTradeProduct py:if="line.product">
|
<ram:SpecifiedTradeProduct>
|
||||||
<ram:ID py:if="line.product.code">${line.product.code}</ram:ID>
|
<ram:ID py:if="line.product and line.product.code">${line.product.code}</ram:ID>
|
||||||
<ram:Name>${line.product.name}</ram:Name>
|
<ram:Name>${line.product.name if line.product else ''}</ram:Name>
|
||||||
<ram:Description py:if="line.description">${line.description}</ram:Description>
|
<ram:Description py:if="line.description">${line.description}</ram:Description>
|
||||||
</ram:SpecifiedTradeProduct>
|
</ram:SpecifiedTradeProduct>
|
||||||
<ram:SpecifiedLineTradeAgreement>
|
<ram:SpecifiedLineTradeAgreement>
|
||||||
|
|
Loading…
Reference in a new issue