[REF] l10n_it_edi: modify edi prepare function and template

Several fields in the l10n_it_edi XML template are adapted fairly often
as work progresses on l10n_it_edi/l10n_it_edi_sdicoop. This refactor
moves the computation of these field to the python code in
_prepare_fattura_pa_export_values.

The computation is done for the:
(DettaglioLinee) Invoice Lines:
NumeroLinea (line number), Descrizione (description),
PrezzoUnitario (unit price), PrezzoTotale (line subtotal),

(DatiRiepilogo) Tax Lines:
Arrotondamento (tax rounding),
ImponibileImporto (tax base), Imposta (tax amount).

These are computed by the new functions
_l10n_it_edi_prepare_line_details and _l10n_it_edi_prepare_tax_details
and returned as lists of dictionaries, which are passed to the function
that renders the template.

The template is modified on the above fields, to reference the above
dictionaries, instead of performing the computation in situ.

Moving the computation of these fields to the python code has the
benefit of:
a) easier, more concise computation of these terms
b) not requiring users to upgrade the l10n_it_edi module in order to
benefit from future changes to these fields (they will only need to
upgrade once).

Part-of: odoo/odoo#98534
This commit is contained in:
dbkosky
2022-08-22 16:48:51 +02:00
committed by dbkosky
parent c3fb4349c9
commit 58fb7c1df1
2 changed files with 71 additions and 42 deletions
+12 -30
View File
@@ -3,14 +3,8 @@
<data>
<template id="account_invoice_line_it_FatturaPA">
<t t-if="rc_refund">
<t t-set="price_subtotal" t-value="-line.price_subtotal"/>
</t>
<t t-else="">
<t t-set="price_subtotal" t-value="line.price_subtotal"/>
</t>
<DettaglioLinee>
<NumeroLinea t-esc="line_counter"/>
<NumeroLinea t-esc="line_dict['line_number']"/>
<CodiceArticolo t-if="line.product_id.barcode">
<CodiceTipo>EAN</CodiceTipo>
<CodiceValore t-esc="format_alphanumeric(line.product_id.barcode)"/>
@@ -19,18 +13,15 @@
<CodiceTipo>INTERNAL</CodiceTipo>
<CodiceValore t-esc="format_alphanumeric(line.product_id.default_code)"/>
</CodiceArticolo>
<Descrizione>
<t t-esc="format_alphanumeric(line.name[:1000])"/>
<t t-if="not line.name" t-esc="'NO NAME'"/>
</Descrizione>
<Quantita t-esc="format_numbers(line.quantity)"/>
<Descrizione t-esc="format_alphanumeric(line_dict['description'])"/>
<Quantita t-esc="format_numbers(abs(line.quantity))"/>
<UnitaMisura t-if="line.product_uom_id.category_id != env.ref('uom.product_uom_categ_unit')" t-esc="format_alphanumeric(line.product_uom_id.name)"/>
<PrezzoUnitario t-esc="'%.06f' % (price_subtotal / (( 1 - (line.discount or 0.0) / 100.0) * line.quantity) if line.quantity and line.discount != 100.0 else line.price_unit)"/>
<PrezzoUnitario t-esc="'%.06f' % (line_dict['unit_price'])"/>
<ScontoMaggiorazione t-if="line.discount != 0">
<Tipo t-esc="discount_type(line.discount)"/>
<Percentuale t-esc="format_numbers(abs(line.discount))"/>
</ScontoMaggiorazione>
<PrezzoTotale t-esc="format_monetary(price_subtotal, currency)"/>
<PrezzoTotale t-esc="format_monetary(line_dict['subtotal_price'], currency)"/>
<AliquotaIVA t-if="line.tax_ids.amount_type == 'percent'" t-esc="format_numbers(line.tax_ids.amount)"/>
<AliquotaIVA t-elif="line.tax_ids.amount_type != 'percent'" t-esc="'0.00'"/>
<Natura t-if="line.tax_ids.l10n_it_has_exoneration" t-esc="line.tax_ids.l10n_it_kind_exoneration"/>
@@ -149,29 +140,20 @@
</DatiDDT>
</DatiGenerali>
<DatiBeniServizi>
<t t-set="line_counter" t-value="0"/>
<t t-foreach="record.invoice_line_ids.filtered(lambda l: l.display_type not in ('line_note', 'line_section'))" t-as="line">
<t t-set="line_counter" t-value="line_counter + 1"/>
<t t-set="taxes" t-value="line.tax_ids.compute_all(line.price_unit)"/>
<t t-foreach="invoice_lines" t-as="line_dict">
<t t-set="line" t-value="line_dict['line']"/>
<t t-call="l10n_it_edi.account_invoice_line_it_FatturaPA"/>
</t>
<t t-foreach="tax_details['tax_details']" t-as="tax_name">
<t t-set="tax_dict" t-value="tax_details['tax_details'][tax_name]"/>
<t t-set="tax" t-value="tax_dict['tax']"/>
<t t-foreach="tax_lines" t-as="tax_line">
<t t-set="tax" t-value="tax_line['tax']"/>
<t t-set="has_exoneration" t-value="tax.l10n_it_has_exoneration"/>
<t t-set="kind_exoneration" t-value="tax.l10n_it_kind_exoneration"/>
<DatiRiepilogo>
<AliquotaIVA t-esc="format_numbers(tax.amount)"/>
<Natura t-if="has_exoneration" t-esc="kind_exoneration"/>
<Arrotondamento t-if="tax_dict.get('rounding')" t-esc="format_numbers(tax_dict['rounding'])"/>
<t t-if="rc_refund">
<ImponibileImporto t-esc="format_monetary(tax_dict['base_amount_currency'], currency)"/>
<Imposta t-esc="format_monetary(tax_dict['tax_amount_currency'], currency)"/>
</t>
<t t-else="">
<ImponibileImporto t-esc="format_monetary(abs(tax_dict['base_amount_currency']), currency)"/>
<Imposta t-esc="format_monetary(abs(tax_dict['tax_amount_currency']), currency)"/>
</t>
<Arrotondamento t-if="tax_line['rounding']" t-esc="format_numbers(tax_line['rounding'])"/>
<ImponibileImporto t-esc="format_monetary(tax_line['base_amount'], currency)"/>
<Imposta t-esc="format_monetary(tax_line['tax_amount'], currency)"/>
<EsigibilitaIVA t-if="not has_exoneration or kind_exoneration == 'N6'" t-esc="tax.l10n_it_vat_due_date"/>
<RiferimentoNormativo t-if="has_exoneration" t-esc="format_alphanumeric(tax.l10n_it_law_reference[:100])"/>
</DatiRiepilogo>
+59 -12
View File
@@ -80,6 +80,60 @@ class AccountMove(models.Model):
"""
return len(self.commercial_partner_id.l10n_it_pa_index or '') == 6
def _l10n_it_edi_prepare_fatturapa_line_details(self, rc_refund=False):
""" Returns a list of dictionaries passed to the template for the invoice lines (DettaglioLinee)
"""
invoice_lines = []
lines = self.invoice_line_ids.filtered(lambda l: not l.display_type in ('line_note', 'line_section'))
for num, line in enumerate(lines):
# In the case of reverse charge refund, the values for the unit price and total price should be negative
price_subtotal = line.price_subtotal if not rc_refund else -line.price_subtotal
# Unit price
unit_price = 0
if line.quantity and line.discount != 100.0:
unit_price = price_subtotal / ((1 - (line.discount or 0.0) / 100.0) * line.quantity)
else:
unit_price = line.price_unit
line_dict = {
'line': line,
'line_number': num + 1,
'description': line.name or 'NO NAME',
'unit_price': unit_price,
'subtotal_price': price_subtotal,
}
invoice_lines.append(line_dict)
return invoice_lines
def _l10n_it_edi_prepare_fatturapa_tax_details(self, tax_details, rc_refund=False):
""" Returns a list of dictionaries passed to the template for the invoice lines (DatiRiepilogo)
"""
# Constraints within the edi make local rounding on price included taxes a problem.
# To solve this there is a <Arrotondamento> or 'rounding' field, such that:
# taxable base = sum(taxable base for each unit) + Arrotondamento
for _tax_name, tax_dict in tax_details['tax_details'].items():
base_amount_currency = tax_dict['base_amount_currency']
tax_amount_currency = tax_dict['tax_amount_currency']
tax_rate = tax_dict['tax'].amount
if tax_dict['tax'].price_include and tax_dict['tax'].amount_type == 'percent':
expected_base_amount_currency = tax_amount_currency * 100 / tax_rate if tax_rate else False
if expected_base_amount_currency and float_compare(base_amount_currency, expected_base_amount_currency, 2):
tax_dict['rounding'] = base_amount_currency - (tax_amount_currency * 100 / tax_rate)
tax_dict['base_amount_currency'] = base_amount_currency - tax_dict['rounding']
tax_lines = []
for _tax_name, tax_dict in tax_details['tax_details'].items():
tax = tax_dict['tax']
tax_line_dict = {
'tax': tax,
'rounding': tax_dict.get('rounding', False),
'base_amount': abs(tax_dict['base_amount_currency']) if not rc_refund else tax_dict['base_amount_currency'],
'tax_amount': abs(tax_dict['tax_amount_currency']) if not rc_refund else tax_dict['tax_amount_currency'],
}
tax_lines.append(tax_line_dict)
return tax_lines
def _prepare_fatturapa_export_values(self):
self.ensure_one()
@@ -145,21 +199,9 @@ class AccountMove(models.Model):
pdf = base64.b64encode(pdf).decode()
pdf_name = re.sub(r'\W+', '', self.name) + '.pdf'
# Constraints within the edi make local rounding on price included taxes a problem.
# To solve this there is a <Arrotondamento> or 'rounding' field, such that:
# taxable base = sum(taxable base for each unit) + Arrotondamento
tax_details = self._prepare_edi_tax_details(
filter_to_apply=lambda l: l['tax_repartition_line_id'].factor_percent >= 0
)
for _tax_name, tax_dict in tax_details['tax_details'].items():
base_amount = tax_dict['base_amount_currency']
tax_amount = tax_dict['tax_amount_currency']
tax_rate = tax_dict['tax'].amount
if tax_dict['tax'].price_include and tax_dict['tax'].amount_type == 'percent':
expected_base_amount = tax_amount * 100 / tax_rate if tax_rate else False
if expected_base_amount and float_compare(base_amount, expected_base_amount, 2):
tax_dict['rounding'] = base_amount - (tax_amount * 100 / tax_rate)
tax_dict['base_amount_currency'] = base_amount - tax_dict['rounding']
company = self.company_id
partner = self.commercial_partner_id
@@ -182,6 +224,9 @@ class AccountMove(models.Model):
if rc_refund:
document_total = -abs(document_total)
invoice_lines = self._l10n_it_edi_prepare_fatturapa_line_details(rc_refund)
tax_lines = self._l10n_it_edi_prepare_fatturapa_tax_details(tax_details, rc_refund)
# Create file content.
template_values = {
'record': self,
@@ -219,6 +264,8 @@ class AccountMove(models.Model):
'get_vat_country': get_vat_country,
'in_eu': in_eu,
'rc_refund': rc_refund,
'invoice_lines': invoice_lines,
'tax_lines': tax_lines,
}
return template_values