read invoice-line, taxes

This commit is contained in:
Frederik Jaeckel 2025-01-10 17:25:05 +01:00
parent 5d68a2c2b4
commit 18103d8e80
4 changed files with 189 additions and 6 deletions

View file

@ -7,7 +7,7 @@
import os.path import os.path
from lxml import etree from lxml import etree
from datetime import datetime from datetime import datetime, date
from decimal import Decimal from decimal import Decimal
from trytond.pool import PoolMeta, Pool from trytond.pool import PoolMeta, Pool
from trytond.transaction import Transaction from trytond.transaction import Transaction
@ -110,7 +110,9 @@ class Incoming(metaclass=PoolMeta):
if nodes: if nodes:
for node1 in nodes: for node1 in nodes:
result.append( 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: if not allow_list:
break break
@ -414,10 +416,118 @@ class Incoming(metaclass=PoolMeta):
lines_data.append( lines_data.append(
self._readxml_invoice_line(xmltree, xpath_line_item, x)) self._readxml_invoice_line(xmltree, xpath_line_item, x))
print('\n## lines_data:', lines_data) 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') raise ValueError('stop')
return invoice 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): def _readxml_invoice_line(self, xmldata, xpth, pos):
""" read invoice-line from xml """ read invoice-line from xml
@ -529,7 +639,7 @@ class Incoming(metaclass=PoolMeta):
'ram:SpecifiedLineTradeAgreement', 'ram:SpecifiedLineTradeAgreement',
'ram:NetPriceProductTradePrice'], [ 'ram:NetPriceProductTradePrice'], [
('ram:ChargeAmount', 'amount', Decimal), ('ram:ChargeAmount', 'amount', Decimal),
('ram:BasisQuantity', 'basequantity', Decimal)]) ('ram:BasisQuantity', 'basequantity', Decimal)])[0]
# notice ignored field # notice ignored field
if self._readxml_getvalue(xmldata, xpath_netprice + [ if self._readxml_getvalue(xmldata, xpath_netprice + [
'ram:AppliedTradeAllowanceCharge']): 'ram:AppliedTradeAllowanceCharge']):
@ -546,13 +656,14 @@ class Incoming(metaclass=PoolMeta):
'ram:SpecifiedLineTradeAgreement', 'ram:SpecifiedLineTradeAgreement',
'ram:GrossPriceProductTradePrice'], [ 'ram:GrossPriceProductTradePrice'], [
('ram:ChargeAmount', 'amount', Decimal), ('ram:ChargeAmount', 'amount', Decimal),
('ram:BasisQuantity', 'basequantity', Decimal)]) ('ram:BasisQuantity', 'basequantity', Decimal)])[0]
# notice ignored field # notice ignored field
if self._readxml_getvalue(xmldata, xpath_grossprice + [ if self._readxml_getvalue(xmldata, xpath_grossprice + [
'ram:AppliedTradeAllowanceCharge']): 'ram:AppliedTradeAllowanceCharge']):
result['convert_note'].append( result['convert_note'].append(
'skip: ' + self._readxml_xpath( 'skip: ' + self._readxml_xpath(
xpath_grossprice + ['ram:AppliedTradeAllowanceCharge'])) xpath_grossprice +
['ram:AppliedTradeAllowanceCharge']))
# quantity # quantity
xpath_quantity = xpth + [pos] + ['ram:SpecifiedLineTradeDelivery'] xpath_quantity = xpth + [pos] + ['ram:SpecifiedLineTradeDelivery']
@ -561,7 +672,7 @@ class Incoming(metaclass=PoolMeta):
('ram:BilledQuantity', 'billed', Decimal), ('ram:BilledQuantity', 'billed', Decimal),
('ram:ChargeFreeQuantity', 'chargefree', Decimal), ('ram:ChargeFreeQuantity', 'chargefree', Decimal),
('ram:PackageQuantity', 'package', Decimal), ('ram:PackageQuantity', 'package', Decimal),
]) ])[0]
# notice ignored fields # notice ignored fields
for x in [ for x in [
'ShipToTradeParty', 'UltimateShipToTradeParty', 'ShipToTradeParty', 'UltimateShipToTradeParty',
@ -575,6 +686,45 @@ class Incoming(metaclass=PoolMeta):
'skip: ' + self._readxml_xpath(xp_to_check)) 'skip: ' + self._readxml_xpath(xp_to_check))
# taxes # 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 # skip None values
return {x: result[x] for x in result.keys() if result[x]} return {x: result[x] for x in result.keys() if result[x]}

View file

@ -22,6 +22,18 @@ msgctxt "model:ir.message,text:msg_not_our_company"
msgid "The buyer party '%(partytxt)s' differs from the corporate party." msgid "The buyer party '%(partytxt)s' differs from the corporate party."
msgstr "Die Käuferpartei '%(partytxt)s' weicht von der Unternehmenspartei ab." 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 # # document.incoming.configuration #

View file

@ -18,6 +18,18 @@ msgctxt "model:ir.message,text:msg_not_our_company"
msgid "The buyer party '%(partytxt)s' differs from the corporate party." msgid "The buyer party '%(partytxt)s' differs from the corporate party."
msgstr "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:" msgctxt "field:document.incoming.configuration,create_supplier:"
msgid "Create Supplier Party" msgid "Create Supplier Party"
msgstr "Create Supplier Party" msgstr "Create Supplier Party"

View file

@ -17,6 +17,15 @@
<record model="ir.message" id="msg_not_our_company"> <record model="ir.message" id="msg_not_our_company">
<field name="text">The buyer party '%(partytxt)s' differs from the corporate party.</field> <field name="text">The buyer party '%(partytxt)s' differs from the corporate party.</field>
</record> </record>
<record model="ir.message" id="msg_no_prodcat_configured">
<field name="text">There is no product category configured for XML import in Document-Incoming.</field>
</record>
<record model="ir.message" id="msg_no_prodcat_found">
<field name="text">No product category with a %(percent)s %% tax found.</field>
</record>
<record model="ir.message" id="msg_numtaxes_invalid">
<field name="text">Invalid number of taxes in line %(descr)s: wanted='%(taxin)s', found='%(taxout)s'.</field>
</record>
</data> </data>
</tryton> </tryton>