diff --git a/document.py b/document.py
index 16f9d30..d0aa3e7 100644
--- a/document.py
+++ b/document.py
@@ -7,7 +7,7 @@
import os.path
from lxml import etree
-from datetime import datetime
+from datetime import datetime, date
from decimal import Decimal
from trytond.pool import PoolMeta, Pool
from trytond.transaction import Transaction
@@ -110,7 +110,9 @@ class Incoming(metaclass=PoolMeta):
if nodes:
for node1 in nodes:
result.append(
- node1.text if vtype is None else vtype(node1.text))
+ node1.text if vtype is None
+ else self._readxml_convertdate(node1.text) if vtype == date
+ else vtype(node1.text))
if not allow_list:
break
@@ -414,10 +416,118 @@ class Incoming(metaclass=PoolMeta):
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]
+ invoice.lines = lines
+ for pos in range(len(lines)):
+ invoice.lines[pos].on_change_account()
+ invoice.on_change_lines()
raise ValueError('stop')
return invoice
+ def _readxml_getinvoiceline(self, invoice, line_data):
+ """ create invoice line in memory
+
+ Args:
+ line_data (dict): values from xml to create
+ invoice line
+ """
+ pool = Pool()
+ Line = pool.get('account.invoice.line')
+ Tax = pool.get('account.tax')
+ Configuration = pool.get('document.incoming.configuration')
+
+ 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
+
+ Args:
+ percent (Decimal): tax percent, 7% --> 0.07
+
+ Raises:
+ UserError: if no product category is configured or
+ no matching tax was found
+
+ Returns:
+ record: model 'account.tax
+ """
+ if not (cfg1 and cfg1.product_category):
+ raise UserError(gettext(
+ 'document_incoming_invoice_xml.msg_no_prodcat_configured'))
+
+ for pcat in cfg1.product_category:
+ for s_tax in pcat.supplier_taxes:
+ # get current taxes of supplier-tax at
+ # invoice-date
+ current_taxes = Tax.compute(
+ [s_tax], Decimal('1'), 1.0, invoice.invoice_date)
+
+ # deny 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
+
+ # no product category found
+ raise UserError(gettext(
+ 'document_incoming_invoice_xml.msg_no_prodcat_found',
+ percent=percent * Decimal('100.0')))
+
+ line = Line(
+ invoice=invoice,
+ type='line',
+ 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)
+
+ # description
+ descr = [
+ '[%s]' % line_no if line_no else None,
+ line_data.pop('name', None),
+ line_data.pop('description', None)]
+ line.description = '; '.join([x for x in descr if x])
+
+ # taxes
+ taxes = []
+ line_taxes = line_data.get('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]
+ # check result
+ if len(line_taxes) != len(taxes):
+ 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
+
+ #line.account =
+
+ # cleanup used values
+ for x in ['quantity', 'unit_net_price']:
+ if not line_data.get(x, {}):
+ del line_data[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
@@ -529,7 +639,7 @@ class Incoming(metaclass=PoolMeta):
'ram:SpecifiedLineTradeAgreement',
'ram:NetPriceProductTradePrice'], [
('ram:ChargeAmount', 'amount', Decimal),
- ('ram:BasisQuantity', 'basequantity', Decimal)])
+ ('ram:BasisQuantity', 'basequantity', Decimal)])[0]
# notice ignored field
if self._readxml_getvalue(xmldata, xpath_netprice + [
'ram:AppliedTradeAllowanceCharge']):
@@ -546,13 +656,14 @@ class Incoming(metaclass=PoolMeta):
'ram:SpecifiedLineTradeAgreement',
'ram:GrossPriceProductTradePrice'], [
('ram:ChargeAmount', 'amount', Decimal),
- ('ram:BasisQuantity', 'basequantity', Decimal)])
+ ('ram:BasisQuantity', 'basequantity', Decimal)])[0]
# notice ignored field
if self._readxml_getvalue(xmldata, xpath_grossprice + [
'ram:AppliedTradeAllowanceCharge']):
result['convert_note'].append(
'skip: ' + self._readxml_xpath(
- xpath_grossprice + ['ram:AppliedTradeAllowanceCharge']))
+ xpath_grossprice +
+ ['ram:AppliedTradeAllowanceCharge']))
# quantity
xpath_quantity = xpth + [pos] + ['ram:SpecifiedLineTradeDelivery']
@@ -561,7 +672,7 @@ class Incoming(metaclass=PoolMeta):
('ram:BilledQuantity', 'billed', Decimal),
('ram:ChargeFreeQuantity', 'chargefree', Decimal),
('ram:PackageQuantity', 'package', Decimal),
- ])
+ ])[0]
# notice ignored fields
for x in [
'ShipToTradeParty', 'UltimateShipToTradeParty',
@@ -575,6 +686,45 @@ class Incoming(metaclass=PoolMeta):
'skip: ' + self._readxml_xpath(xp_to_check))
# taxes
+ xpath_trade = xpth + [pos] + ['ram:SpecifiedLineTradeSettlement']
+ result['taxes'] = read_listdata([
+ 'ram:SpecifiedLineTradeSettlement',
+ 'ram:ApplicableTradeTax'], [
+ ('ram:CalculatedAmount', 'amount', Decimal),
+ ('ram:TypeCode', 'type'),
+ ('ram:ExemptionReason', 'reason'),
+ ('ram:BasisAmount', 'base', Decimal),
+ ('ram:LineTotalBasisAmount', 'basetotal', Decimal),
+ ('ram:AllowanceChargeBasisAmount', 'feebase', Decimal),
+ ('ram:CategoryCode', 'category_code'),
+ ('ram:ExemptionReasonCode', 'reason_code'),
+ ('ram:TaxPointDate', 'taxdate', date),
+ ('ram:DueDateTypeCode', 'duecode'),
+ ('ram:RateApplicablePercent', 'percent', Decimal)])
+
+ # total amounts
+ result['total'] = read_listdata([
+ 'ram:SpecifiedLineTradeSettlement',
+ 'ram:SpecifiedTradeSettlementLineMonetarySummation'], [
+ ('ram:LineTotalAmount', 'amount', Decimal),
+ ('ram:ChargeTotalAmount', 'charge', Decimal),
+ ('ram:AllowanceTotalAmount', 'fee', Decimal),
+ ('ram:TaxTotalAmount', 'tax', Decimal),
+ ('ram:GrandTotalAmount', 'grand', Decimal),
+ ('ram:TotalAllowanceChargeAmount', 'feecharge', Decimal),
+ ])
+
+ # notice ignored fields
+ for x in [
+ 'BillingSpecifiedPeriod', 'SpecifiedTradeAllowanceCharge',
+ 'ActualDeliverySupplyChainEvent',
+ 'InvoiceReferencedDocument',
+ 'AdditionalReferencedDocument',
+ 'ReceivableSpecifiedTradeAccountingAccount']:
+ xp_to_check = xpath_trade + ['ram:' + x]
+ if self._readxml_getvalue(xmldata, xp_to_check):
+ result['convert_note'].append(
+ 'skip: ' + self._readxml_xpath(xp_to_check))
# skip None values
return {x: result[x] for x in result.keys() if result[x]}
diff --git a/locale/de.po b/locale/de.po
index 55247a1..10ce046 100644
--- a/locale/de.po
+++ b/locale/de.po
@@ -22,6 +22,18 @@ msgctxt "model:ir.message,text:msg_not_our_company"
msgid "The buyer party '%(partytxt)s' differs from the corporate party."
msgstr "Die Käuferpartei '%(partytxt)s' weicht von der Unternehmenspartei ab."
+msgctxt "model:ir.message,text:msg_no_prodcat_configured"
+msgid "There is no product category configured for XML import in Document-Incoming."
+msgstr "Es ist in Document-Incoming keine Produktkategorie für den XML-Import kofiguriert."
+
+msgctxt "model:ir.message,text:msg_no_prodcat_found"
+msgid "No product category with a %(percent)s %% tax found."
+msgstr "Keine Produktkategorie mit einer Steuer von %(percent)s %% gefunden."
+
+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'."
+
###################################
# document.incoming.configuration #
diff --git a/locale/en.po b/locale/en.po
index c5e6a63..c24e952 100644
--- a/locale/en.po
+++ b/locale/en.po
@@ -18,6 +18,18 @@ msgctxt "model:ir.message,text:msg_not_our_company"
msgid "The buyer party '%(partytxt)s' differs from the corporate party."
msgstr "The buyer party '%(partytxt)s' differs from the corporate party."
+msgctxt "model:ir.message,text:msg_no_prodcat_configured"
+msgid "There is no product category configured for XML import in Document-Incoming."
+msgstr "There is no product category configured for XML import in Document-Incoming."
+
+msgctxt "model:ir.message,text:msg_no_prodcat_found"
+msgid "No product category with a %(percent)s %% tax found."
+msgstr "No product category with a %(percent)s %% tax found."
+
+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 "field:document.incoming.configuration,create_supplier:"
msgid "Create Supplier Party"
msgstr "Create Supplier Party"
diff --git a/message.xml b/message.xml
index dc825f7..19ebe70 100644
--- a/message.xml
+++ b/message.xml
@@ -17,6 +17,15 @@
The buyer party '%(partytxt)s' differs from the corporate party.
+
+ There is no product category configured for XML import in Document-Incoming.
+
+
+ No product category with a %(percent)s %% tax found.
+
+
+ Invalid number of taxes in line %(descr)s: wanted='%(taxin)s', found='%(taxout)s'.
+