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: +