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
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]}

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."
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 #

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."
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"

View file

@ -17,6 +17,15 @@
<record model="ir.message" id="msg_not_our_company">
<field name="text">The buyer party '%(partytxt)s' differs from the corporate party.</field>
</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>
</tryton>