diff --git a/document.py b/document.py
index d0aa3e7..a769ed2 100644
--- a/document.py
+++ b/document.py
@@ -6,15 +6,18 @@
# https://portal3.gefeg.com/projectdata/invoice/deliverables/installed/publishingproject/xrechnung%20%28german%20cius%29/xrechnung_crossindustryinvoice%3B%202017-10-18.scm/html/021.htm?https://portal3.gefeg.com/projectdata/invoice/deliverables/installed/publishingproject/xrechnung%20%28german%20cius%29/xrechnung_crossindustryinvoice%3B%202017-10-18.scm/html/025.htm
import os.path
+import json
from lxml import etree
from datetime import datetime, date
from decimal import Decimal
from trytond.pool import PoolMeta, Pool
+from trytond.report import Report
from trytond.transaction import Transaction
from trytond.exceptions import UserError
from trytond.i18n import gettext
from trytond.model import fields
from trytond.pyson import Eval
+from trytond.protocols.jsonrpc import JSONEncoder
xml_types = [
@@ -410,21 +413,18 @@ class Incoming(metaclass=PoolMeta):
# get number of invoice-lines
num_lines = len(xmltree.xpath(
self._readxml_xpath(xpath_line_item), namespaces=xmltree.nsmap))
- print('\n## num_lines:', num_lines)
lines_data = []
for x in range(1, num_lines + 1):
lines_data.append(
self._readxml_invoice_line(xmltree, xpath_line_item, x))
- print('\n## lines_data:', lines_data)
lines = [
self._readxml_getinvoiceline(invoice, x)
for x in lines_data]
+ for x in range(len(lines)):
+ lines[x].sequence = x + 1
invoice.lines = lines
- for pos in range(len(lines)):
- invoice.lines[pos].on_change_account()
- invoice.on_change_lines()
- raise ValueError('stop')
+ invoice.on_change_lines()
return invoice
def _readxml_getinvoiceline(self, invoice, line_data):
@@ -437,10 +437,11 @@ class Incoming(metaclass=PoolMeta):
pool = Pool()
Line = pool.get('account.invoice.line')
Tax = pool.get('account.tax')
+ Uom = pool.get('product.uom')
Configuration = pool.get('document.incoming.configuration')
+ ModelData = pool.get('ir.model.data')
cfg1 = Configuration.get_singleton()
- print('\n## line_data-vor:', line_data)
def get_tax_by_percent(percent):
""" search for tax by percent of supplier tax
@@ -453,7 +454,7 @@ class Incoming(metaclass=PoolMeta):
no matching tax was found
Returns:
- record: model 'account.tax
+ tuple: (model 'account.tax, model 'product.category')
"""
if not (cfg1 and cfg1.product_category):
raise UserError(gettext(
@@ -466,23 +467,34 @@ class Incoming(metaclass=PoolMeta):
current_taxes = Tax.compute(
[s_tax], Decimal('1'), 1.0, invoice.invoice_date)
- # deny result of multiple or none taxes
+ # skip result of multiple or none taxes
if len(current_taxes) != 1:
continue
if (current_taxes[0]['tax'].rate == percent) and (
current_taxes[0]['tax'].type == 'percentage'):
# found it
- return s_tax
+ return (s_tax, pcat)
# no product category found
raise UserError(gettext(
'document_incoming_invoice_xml.msg_no_prodcat_found',
percent=percent * Decimal('100.0')))
+ # uom
+ xml_uom = Uom(ModelData.get_id('product', 'uom_unit'))
+ for x_attr in line_data.get('attributes', []):
+ x_uom = x_attr.get('uom', None)
+ if not x_uom:
+ continue
+ units = Uom.search([('symbol', '=', x_uom)])
+ if units:
+ xml_uom = units[0]
+
line = Line(
invoice=invoice,
type='line',
+ unit=xml_uom,
quantity=line_data.get('quantity', {}).pop('billed', None),
unit_price=line_data.get('unit_net_price', {}).pop('amount', None))
line_no = line_data.pop('line_no', None)
@@ -494,40 +506,73 @@ class Incoming(metaclass=PoolMeta):
line_data.pop('description', None)]
line.description = '; '.join([x for x in descr if x])
- # taxes
- taxes = []
- line_taxes = line_data.get('taxes', [])
+ # get taxes and product-category from settings
+ tax_and_category = []
+ line_taxes = line_data.pop('taxes', [])
for x in line_taxes:
percent = x.get('percent', None)
if (x.get('type', '') == 'VAT') and (percent is not None):
percent = percent / Decimal('100')
- taxes.append(get_tax_by_percent(percent))
- # remove not-found-taxes
- taxes = [x for x in taxes if x]
+ tax_and_category.append(get_tax_by_percent(percent))
+
# check result
- if len(line_taxes) != len(taxes):
+ if len(line_taxes) != len(tax_and_category):
raise UserError(gettext(
'document_incoming_invoice_xml.msg_numtaxes_invalid',
descr=line.description,
taxin='|'.join([
str(x.get('percent', '-')) for x in line_taxes]),
taxout='|'.join([
- str(x.rate * Decimal('100.0')) for x in taxes])))
- line.taxes = taxes
+ str(x[0].rate * Decimal('100.0'))
+ for x in tax_and_category])))
- #line.account =
+ # set taxes and account for product
+ line.taxes = [x[0] for x in tax_and_category]
+ expense_accounts = [
+ x[1].account_expense
+ for x in tax_and_category if x[1].account_expense]
+ if expense_accounts:
+ line.account = expense_accounts[0]
+
+ # check if calculated 'amount' matches with amount from xml-data
+ xml_amount = line_data.get('total', {}).pop('amount', None)
+ if xml_amount is not None:
+ if xml_amount != line.get_amount(None):
+ raise UserError(gettext(
+ 'document_incoming_invoice_xml.msg_line_amount_invalid',
+ linetxt=line.description,
+ calcsum=Report.format_currency(
+ line.get_amount(None), None, invoice.currency),
+ xmlsum=Report.format_currency(
+ xml_amount, None, invoice.currency)))
# cleanup used values
- for x in ['quantity', 'unit_net_price']:
+ for x in ['quantity', 'unit_net_price', 'total']:
if not line_data.get(x, {}):
del line_data[x]
+ # note of line
+ notes = [line_data.pop('line_note', None)]
+ # skipped xml-data
+ convert_notes = line_data.pop('convert_note', None)
+ if convert_notes:
+ notes.extend(
+ [' '] +
+ [gettext('document_incoming_invoice_xml.msg_convert_note')] +
+ convert_notes
+ if isinstance(convert_notes, list) else [str(convert_notes)])
+ # values not used to create invoice-line
+ if line_data:
+ notes.extend([
+ ' ',
+ gettext(
+ 'document_incoming_invoice_xml.msg_unused_linevalues'),
+ json.dumps(line_data, cls=JSONEncoder, indent=3)])
+ line.note = '\n'.join(x for x in notes if x)
+
line.on_change_invoice()
-
- print('-- line_data-nach:', line_data, (line,))
return line
-
def _readxml_invoice_line(self, xmldata, xpth, pos):
""" read invoice-line from xml
@@ -712,7 +757,7 @@ class Incoming(metaclass=PoolMeta):
('ram:TaxTotalAmount', 'tax', Decimal),
('ram:GrandTotalAmount', 'grand', Decimal),
('ram:TotalAllowanceChargeAmount', 'feecharge', Decimal),
- ])
+ ])[0]
# notice ignored fields
for x in [
diff --git a/locale/de.po b/locale/de.po
index 10ce046..5f8d1e0 100644
--- a/locale/de.po
+++ b/locale/de.po
@@ -34,6 +34,18 @@ msgctxt "model:ir.message,text:msg_numtaxes_invalid"
msgid "Invalid number of taxes in line %(descr)s: wanted='%(taxin)s', found='%(taxout)s'."
msgstr "Ungültige Anzahl Steuern in Zeile %(descr)s: gesucht='%(taxin)s', gefunden='%(taxout)s'."
+msgctxt "model:ir.message,text:msg_line_amount_invalid"
+msgid "The calculated sum '%(calcsum)s' of the line '%(linetxt)s' differs from the value '%(xmlsum)s' from the XML data."
+msgstr "Die berechnete Summe '%(calcsum)s' der Zeile '%(linetxt)s' weicht vom Wert '%(xmlsum)s' aus den XML-Daten ab."
+
+msgctxt "model:ir.message,text:msg_convert_note"
+msgid "The following XML data has not been imported:"
+msgstr "Folgende XML-Daten wurden nicht importiert:"
+
+msgctxt "model:ir.message,text:msg_unused_linevalues"
+msgid "The following data was not used to generate the invoice line:"
+msgstr "Die folgenden Daten wurden für die Erzeugung der Rechnungszeile nicht verwendet:"
+
###################################
# document.incoming.configuration #
diff --git a/locale/en.po b/locale/en.po
index c24e952..aec4cc4 100644
--- a/locale/en.po
+++ b/locale/en.po
@@ -30,6 +30,18 @@ msgctxt "model:ir.message,text:msg_numtaxes_invalid"
msgid "Invalid number of taxes in line %(descr)s: wanted='%(taxin)s', found='%(taxout)s'."
msgstr "Invalid number of taxes in line %(descr)s: wanted='%(taxin)s', found='%(taxout)s'."
+msgctxt "model:ir.message,text:msg_line_amount_invalid"
+msgid "The calculated sum '%(calcsum)s' of the line '%(linetxt)s' differs from the value '%(xmlsum)s' from the XML data."
+msgstr "The calculated sum '%(calcsum)s' of the line '%(linetxt)s' differs from the value '%(xmlsum)s' from the XML data."
+
+msgctxt "model:ir.message,text:msg_convert_note"
+msgid "The following XML data has not been imported:"
+msgstr "The following XML data has not been imported:"
+
+msgctxt "model:ir.message,text:msg_unused_linevalues"
+msgid "The following data was not used to generate the invoice line:"
+msgstr "The following data was not used to generate the invoice line:"
+
msgctxt "field:document.incoming.configuration,create_supplier:"
msgid "Create Supplier Party"
msgstr "Create Supplier Party"
diff --git a/message.xml b/message.xml
index 19ebe70..e65f1a2 100644
--- a/message.xml
+++ b/message.xml
@@ -26,6 +26,15 @@
Invalid number of taxes in line %(descr)s: wanted='%(taxin)s', found='%(taxout)s'.
+
+ The calculated sum '%(calcsum)s' of the line '%(linetxt)s' differs from the value '%(xmlsum)s' from the XML data.
+
+
+ The following XML data was not used:
+
+
+ The following data was not used to generate the invoice line:
+