[FIX] l10n_it_edi: negative template values for reverse charge refunds
When issuing a refund for a reverse charge bill, the document type should be the same as the parent bill type (either TD16, TD17 or TD18, rather than TD04), and the values of the lines and totals should be negative. To fix this the values are made negative dependent upon whether the invoice is a 'rc_refund' as in a "reverse charge refund". The way that the document type is ascertained is also changed in order to include the 'in_refund' (for the case of the reverse charge refund). The way document_total (ImportoTotaleDocumento) has been made conditionally negative in the case when the invoice is a reverse charge refund. A test has been added for the reverse charge refund case, and the test taxes have been adapted so that the correct tags are being used (and so that tags are used on the refund lines). closes odoo/odoo#97619 Ticket-id: 2936967 X-original-commit: ca2f4c8e1babe9444bc2c6a7421eafb63f1c9346 Signed-off-by: Josse Colpaert <jco@odoo.com> Signed-off-by: Daniel Kosky (dako) <dako@odoo.com>
This commit is contained in:
@@ -3,6 +3,12 @@
|
||||
<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"/>
|
||||
<CodiceArticolo t-if="line.product_id.barcode">
|
||||
@@ -19,12 +25,12 @@
|
||||
</Descrizione>
|
||||
<Quantita t-esc="format_numbers(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' % (line.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' % (price_subtotal / (( 1 - (line.discount or 0.0) / 100.0) * line.quantity) if line.quantity and line.discount != 100.0 else line.price_unit)"/>
|
||||
<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(line.price_subtotal, currency)"/>
|
||||
<PrezzoTotale t-esc="format_monetary(price_subtotal, 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"/>
|
||||
@@ -158,8 +164,14 @@
|
||||
<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'])"/>
|
||||
<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 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>
|
||||
<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>
|
||||
|
||||
@@ -217,14 +217,14 @@ class AccountEdiFormat(models.Model):
|
||||
|
||||
def _l10n_it_document_type_mapping(self):
|
||||
return {
|
||||
'TD01': dict(move_type='out_invoice', import_type='in_invoice'),
|
||||
'TD04': dict(move_type='out_refund', import_type='in_refund'),
|
||||
'TD07': dict(move_type='out_invoice', import_type='in_invoice', simplified=True),
|
||||
'TD08': dict(move_type='out_refund', import_type='in_refund', simplified=True),
|
||||
'TD09': dict(move_type='out_invoice', import_type='in_invoice', simplified=True),
|
||||
'TD17': dict(move_type='in_invoice', import_type='out_invoice', self_invoice=True, services_or_goods="service"),
|
||||
'TD18': dict(move_type='in_invoice', import_type='out_invoice', self_invoice=True, services_or_goods="consu", partner_in_eu=True),
|
||||
'TD19': dict(move_type='in_invoice', import_type='out_invoice', self_invoice=True, services_or_goods="consu", goods_in_italy=True),
|
||||
'TD01': dict(move_types=['out_invoice'], import_type='in_invoice'),
|
||||
'TD04': dict(move_types=['out_refund'], import_type='in_refund'),
|
||||
'TD07': dict(move_types=['out_invoice'], import_type='in_invoice', simplified=True),
|
||||
'TD08': dict(move_types=['out_refund'], import_type='in_refund', simplified=True),
|
||||
'TD09': dict(move_types=['out_invoice'], import_type='in_invoice', simplified=True),
|
||||
'TD17': dict(move_types=['in_invoice', 'in_refund'], import_type='out_invoice', self_invoice=True, services_or_goods="service"),
|
||||
'TD18': dict(move_types=['in_invoice', 'in_refund'], import_type='out_invoice', self_invoice=True, services_or_goods="consu", partner_in_eu=True),
|
||||
'TD19': dict(move_types=['in_invoice', 'in_refund'], import_type='out_invoice', self_invoice=True, services_or_goods="consu", goods_in_italy=True),
|
||||
}
|
||||
|
||||
def _l10n_it_get_document_type(self, invoice):
|
||||
@@ -237,7 +237,7 @@ class AccountEdiFormat(models.Model):
|
||||
info_services_or_goods = infos.get('services_or_goods', "both")
|
||||
info_partner_in_eu = infos.get('partner_in_eu', False)
|
||||
if all([
|
||||
invoice.move_type == infos.get('move_type', False),
|
||||
invoice.move_type in infos.get('move_types', False),
|
||||
is_self_invoice == infos.get('self_invoice', False),
|
||||
is_simplified == infos.get('simplified', False),
|
||||
info_services_or_goods in ("both", services_or_goods),
|
||||
|
||||
@@ -171,11 +171,16 @@ class AccountMove(models.Model):
|
||||
or (partner.country_id.code == 'IT' and '0000000')
|
||||
or 'XXXXXXX')
|
||||
|
||||
# Represent if the document is a reverse charge refund in a single variable
|
||||
rc_refund = self.move_type == 'in_refund' and document_type in ['TD16', 'TD17', 'TD18']
|
||||
|
||||
# Self-invoices are technically -100%/+100% repartitioned
|
||||
# but functionally need to be exported as 100%
|
||||
document_total = self.amount_total
|
||||
if is_self_invoice:
|
||||
document_total += sum([v['tax_amount_currency'] for k, v in tax_details['tax_details'].items()])
|
||||
document_total += sum([abs(v['tax_amount_currency']) for k, v in tax_details['tax_details'].items()])
|
||||
if rc_refund:
|
||||
document_total = -abs(document_total)
|
||||
|
||||
# Create file content.
|
||||
template_values = {
|
||||
@@ -212,7 +217,8 @@ class AccountMove(models.Model):
|
||||
'normalize_codice_fiscale': partner._l10n_it_normalize_codice_fiscale,
|
||||
'get_vat_number': get_vat_number,
|
||||
'get_vat_country': get_vat_country,
|
||||
'in_eu': in_eu
|
||||
'in_eu': in_eu,
|
||||
'rc_refund': rc_refund,
|
||||
}
|
||||
return template_values
|
||||
|
||||
|
||||
@@ -55,13 +55,13 @@ class TestItEdiReverseCharge(TestItEdiCommon):
|
||||
'amount_type': 'percent',
|
||||
'type_tax_use': 'purchase',
|
||||
'invoice_repartition_line_ids': repartition_lines(
|
||||
RepartitionLine(100, 'base', ('+03',)),
|
||||
RepartitionLine(100, 'tax', ('+vj9',)),
|
||||
RepartitionLine(-100, 'tax', False)),
|
||||
RepartitionLine(100, 'base', ('+03', '+vj9')),
|
||||
RepartitionLine(100, 'tax', ('+5v',)),
|
||||
RepartitionLine(-100, 'tax', ('-4v',))),
|
||||
'refund_repartition_line_ids': repartition_lines(
|
||||
RepartitionLine(100, 'base', False),
|
||||
RepartitionLine(100, 'base', ('-03', '-vj9')),
|
||||
RepartitionLine(100, 'tax', False),
|
||||
RepartitionLine(-100, 'tax', False))
|
||||
RepartitionLine(-100, 'tax', False)),
|
||||
}
|
||||
# Purchase tax 4% with Reverse Charge
|
||||
cls.purchase_tax_4p = cls.env['account.tax'].with_company(cls.company).create(tax_data)
|
||||
@@ -74,10 +74,15 @@ class TestItEdiReverseCharge(TestItEdiCommon):
|
||||
**tax_data,
|
||||
'name': 'Tax 4% purchase Reverse Charge, in Italy',
|
||||
'invoice_repartition_line_ids': repartition_lines(
|
||||
RepartitionLine(100, 'base', ('+03',)),
|
||||
RepartitionLine(100, 'tax', ('+vj3',)),
|
||||
RepartitionLine(100, 'base', ('+03', '+vj3')),
|
||||
RepartitionLine(100, 'tax', ('+5v',)),
|
||||
RepartitionLine(-100, 'tax', ('-4v',))),
|
||||
'refund_repartition_line_ids': repartition_lines(
|
||||
RepartitionLine(100, 'base', ('-03', '-vj3')),
|
||||
RepartitionLine(100, 'tax', False),
|
||||
RepartitionLine(-100, 'tax', False)),
|
||||
}
|
||||
|
||||
cls.purchase_tax_4p_already_in_italy = cls.env['account.tax'].with_company(cls.company).create(tax_data_4p_already_in_italy)
|
||||
cls.line_tax_4p_already_in_italy = cls.standard_line.copy()
|
||||
cls.line_tax_4p_already_in_italy['tax_ids'] = [(6, 0, cls.purchase_tax_4p_already_in_italy.ids)]
|
||||
@@ -153,11 +158,15 @@ class TestItEdiReverseCharge(TestItEdiCommon):
|
||||
),
|
||||
}
|
||||
cls.reverse_charge_bill_2 = cls.env['account.move'].with_company(cls.company).create(bill_data_2)
|
||||
cls.reverse_charge_refund = cls.reverse_charge_bill.with_company(cls.company)._reverse_moves([{
|
||||
'invoice_date': fields.Date.from_string('2022-03-24'),
|
||||
}])
|
||||
|
||||
# Posting moves -----------
|
||||
cls.reverse_charge_invoice._post()
|
||||
cls.reverse_charge_bill._post()
|
||||
cls.reverse_charge_bill_2._post()
|
||||
cls.reverse_charge_refund._post()
|
||||
|
||||
def _cleanup_etree(self, content, xpaths=None):
|
||||
xpaths = {
|
||||
@@ -195,3 +204,26 @@ class TestItEdiReverseCharge(TestItEdiCommon):
|
||||
"(//DettaglioLinee/Descrizione)[2]": "<Descrizione/>",
|
||||
}
|
||||
)
|
||||
|
||||
def test_reverse_charge_refund(self):
|
||||
self._test_invoice_with_sample_file(
|
||||
self.reverse_charge_refund,
|
||||
"reverse_charge_bill.xml",
|
||||
xpaths_result={
|
||||
"//DatiGeneraliDocumento/Numero": "<Numero/>",
|
||||
"//DatiPagamento/DettaglioPagamento/DataScadenzaPagamento": "<DataScadenzaPagamento/>",
|
||||
},
|
||||
xpaths_file={
|
||||
"//DatiGeneraliDocumento/Numero": "<Numero/>",
|
||||
"//DatiGeneraliDocumento/ImportoTotaleDocumento": "<ImportoTotaleDocumento>-1808.91</ImportoTotaleDocumento>",
|
||||
"//DatiPagamento/DettaglioPagamento/DataScadenzaPagamento": "<DataScadenzaPagamento/>",
|
||||
"(//DettaglioLinee/PrezzoUnitario)[1]": "<PrezzoUnitario>-800.400000</PrezzoUnitario>",
|
||||
"(//DettaglioLinee/PrezzoUnitario)[2]": "<PrezzoUnitario>-800.400000</PrezzoUnitario>",
|
||||
"(//DettaglioLinee/PrezzoTotale)[1]": "<PrezzoTotale>-800.40</PrezzoTotale>",
|
||||
"(//DettaglioLinee/PrezzoTotale)[2]": "<PrezzoTotale>-800.40</PrezzoTotale>",
|
||||
"(//DatiRiepilogo/ImponibileImporto)[1]": "<ImponibileImporto>-800.40</ImponibileImporto>",
|
||||
"(//DatiRiepilogo/ImponibileImporto)[2]": "<ImponibileImporto>-800.40</ImponibileImporto>",
|
||||
"(//DatiRiepilogo/Imposta)[1]": "<Imposta>-176.09</Imposta>",
|
||||
"(//DatiRiepilogo/Imposta)[2]": "<Imposta>-32.02</Imposta>",
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user