[FIX] l10n_it_edi: format_alphanumeric

The Italian edi system accepts utf-8 encoded documents, but actually the
contents of the fields have to be latin-1 encoded. Format-alphanumeric
should remove any non-latin1 characters and replace them with a ?

Several fields to are also truncated before the function is applied.
This is so that these fields better match the specification. This will
help to prevent edi rejections in the future.

A test is also included to test with a few lines that should / should
not be adapted in by the format_alphanumeric function. Along with the
addition of the test, the test partner italian_partner_a's is_company
field is changed to True, since the partner is a company. The expected
xml has been altered to match the changes to partner_a.

closes odoo/odoo#89494

Task-id: 2826424
X-original-commit: 712758fc2505abfa271636caaa8ba38436086f32
Signed-off-by: Josse Colpaert <jco@odoo.com>
This commit is contained in:
dbkosky
2022-04-22 19:07:26 +02:00
committed by dbkosky
parent c336dddab8
commit d7c6033f99
4 changed files with 99 additions and 26 deletions
+24 -24
View File
@@ -8,18 +8,18 @@
<CodiceArticolo t-if="line.product_id.barcode">
<!--2.2.1.3-->
<CodiceTipo>EAN</CodiceTipo>
<CodiceValore t-esc="line.product_id.barcode"/>
<CodiceValore t-esc="format_alphanumeric(line.product_id.barcode)"/>
</CodiceArticolo>
<CodiceArticolo t-if="line.product_id.default_code">
<CodiceTipo>INTERNAL</CodiceTipo>
<CodiceValore t-esc="line.product_id.default_code"/>
<CodiceValore t-esc="format_alphanumeric(line.product_id.default_code)"/>
</CodiceArticolo>
<Descrizione>
<t t-esc="line.name[:1000]"/>
<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)"/>
<UnitaMisura t-if="line.product_uom_id.category_id != env.ref('uom.product_uom_categ_unit')" t-esc="line.product_uom_id.name"/>
<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)"/>
<ScontoMaggiorazione t-if="line.discount != 0">
<!-- [2.2.1.10] -->
@@ -36,9 +36,9 @@
<template id="account_invoice_it_FatturaPA_sede">
<Sede>
<Indirizzo><t t-if="partner.street" t-esc="partner.street"/> <t t-if="partner.street2" t-esc="partner.street2"/></Indirizzo>
<Indirizzo><t t-if="partner.street" t-esc="format_alphanumeric(partner.street)"/> <t t-if="partner.street2" t-esc="format_alphanumeric(partner.street2)"/></Indirizzo>
<CAP><t t-if="partner.country_id.code != 'IT'" t-esc="'00000'"/><t t-else="" t-esc="partner.zip"/></CAP>
<Comune t-esc="partner.city"/>
<Comune t-esc="format_alphanumeric(partner.city)"/>
<Provincia t-if="partner.country_id.code == 'IT'" t-esc="partner.state_id.code"/>
<Nazione t-esc="partner.country_id.code"/>
</Sede>
@@ -54,14 +54,14 @@
<IdPaese t-esc="get_vat_country(record.company_id.vat)"/>
<IdCodice t-esc="record.company_id.l10n_it_codice_fiscale or get_vat_number(record.company_id.vat)"/>
</IdTrasmittente>
<ProgressivoInvio t-esc="record.name.replace('/','')[-10:]"/>
<ProgressivoInvio t-esc="format_alphanumeric(record.name.replace('/','')[-10:])"/>
<FormatoTrasmissione t-esc="formato_trasmissione"/>
<CodiceDestinatario t-if="record.commercial_partner_id.l10n_it_pa_index and record.commercial_partner_id.country_id.code == 'IT'" t-esc="record.commercial_partner_id.l10n_it_pa_index.upper()"/>
<CodiceDestinatario t-if="not record.commercial_partner_id.l10n_it_pa_index and record.commercial_partner_id.country_id.code == 'IT'" t-esc="'0000000'"/>
<CodiceDestinatario t-if="record.commercial_partner_id.country_id.code != 'IT'" t-esc="'XXXXXXX'"/>
<ContattiTrasmittente>
<Telefono t-if="format_phone(record.company_id.partner_id.phone)" t-esc="format_phone(record.company_id.partner_id.phone)"/>
<Telefono t-if="not format_phone(record.company_id.partner_id.phone) and format_phone(record.company_id.partner_id.mobile)" t-esc="format_phone(record.company_id.partner_id.mobile)"/>
<Telefono t-if="format_phone(record.company_id.partner_id.phone)" t-esc="format_alphanumeric(format_phone(record.company_id.partner_id.phone))"/>
<Telefono t-if="not format_phone(record.company_id.partner_id.phone) and format_phone(record.company_id.partner_id.mobile)" t-esc="format_alphanumeric(format_phone(record.company_id.partner_id.mobile))"/>
<Email t-if="record.company_id.email" t-esc="record.company_id.email"/>
</ContattiTrasmittente>
<PECDestinatario t-if="record.commercial_partner_id.l10n_it_pec_email" t-esc="record.commercial_partner_id.l10n_it_pec_email"/>
@@ -74,7 +74,7 @@
</IdFiscaleIVA>
<CodiceFiscale t-if="record.company_id.l10n_it_codice_fiscale" t-esc="record.company_id.l10n_it_codice_fiscale"/>
<Anagrafica>
<Denominazione t-esc="record.company_id.partner_id.display_name"/>
<Denominazione t-esc="format_alphanumeric(record.company_id.partner_id.display_name[:80])"/>
</Anagrafica>
<RegimeFiscale t-esc="record.company_id.l10n_it_tax_system"/>
</DatiAnagrafici>
@@ -84,7 +84,7 @@
<IscrizioneREA t-if="record.company_id.l10n_it_has_eco_index">
<!--1.2.4-->
<Ufficio t-esc="record.company_id.l10n_it_eco_index_office.code"/>
<NumeroREA t-esc="record.company_id.l10n_it_eco_index_number"/>
<NumeroREA t-esc="format_alphanumeric(record.company_id.l10n_it_eco_index_number)"/>
<CapitaleSociale t-if="record.company_id.l10n_it_eco_index_share_capital != 0" t-esc="format_numbers_two(record.company_id.l10n_it_eco_index_share_capital)"/>
<SocioUnico t-if="record.company_id.l10n_it_eco_index_sole_shareholder != 'NO'" t-esc="record.company_id.l10n_it_eco_index_sole_shareholder"/>
<StatoLiquidazione t-esc="record.company_id.l10n_it_eco_index_liquidation_state"/>
@@ -99,9 +99,9 @@
</IdFiscaleIVA>
<CodiceFiscale t-if="record.company_id.l10n_it_tax_representative_partner_id.l10n_it_codice_fiscale" t-esc="record.company_id.l10n_it_tax_representative_partner_id.l10n_it_codice_fiscale"/>
<Anagrafica>
<Denominazione t-if="record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="record.company_id.l10n_it_tax_representative_partner_id.display_name"/>
<Nome t-if="not record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="' '.join(record.company_id.l10n_it_tax_representative_partner_id.name.split()[:1])"/>
<Cognome t-if="not record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="' '.join(record.company_id.l10n_it_tax_representative_partner_id.name.split()[1:])"/>
<Denominazione t-if="record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="format_alphanumeric(record.company_id.l10n_it_tax_representative_partner_id.display_name[:80])"/>
<Nome t-if="not record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="format_alphanumeric(' '.join(record.company_id.l10n_it_tax_representative_partner_id.name.split()[:1])[:60])"/>
<Cognome t-if="not record.company_id.l10n_it_tax_representative_partner_id.is_company" t-esc="format_alphanumeric(' '.join(record.company_id.l10n_it_tax_representative_partner_id.name.split()[1:])[:60])"/>
</Anagrafica>
</DatiAnagrafici>
</RappresentanteFiscale>
@@ -122,9 +122,9 @@
<CodiceFiscale t-if="not record.commercial_partner_id.vat" t-esc="record.commercial_partner_id.l10n_it_codice_fiscale"/>
<CodiceFiscale t-if="not record.commercial_partner_id.vat and not record.commercial_partner_id.l10n_it_codice_fiscale" t-esc="99999999999"/>
<Anagrafica>
<Denominazione t-if="record.commercial_partner_id.is_company" t-esc="record.commercial_partner_id.display_name"/>
<Nome t-if="not record.commercial_partner_id.is_company" t-esc="' '.join(record.commercial_partner_id.name.split()[:1])"/>
<Cognome t-if="not record.commercial_partner_id.is_company" t-esc="' '.join(record.commercial_partner_id.name.split()[1:])"/>
<Denominazione t-if="record.commercial_partner_id.is_company" t-esc="format_alphanumeric(record.commercial_partner_id.display_name[:80])"/>
<Nome t-if="not record.commercial_partner_id.is_company" t-esc="format_alphanumeric(' '.join(record.commercial_partner_id.name.split()[:1])[:60])"/>
<Cognome t-if="not record.commercial_partner_id.is_company" t-esc="format_alphanumeric(' '.join(record.commercial_partner_id.name.split()[1:])[:60])"/>
</Anagrafica>
</DatiAnagrafici>
<t t-call="l10n_it_edi.account_invoice_it_FatturaPA_sede">
@@ -139,7 +139,7 @@
<TipoDocumento t-esc="document_type"/>
<Divisa t-esc="currency.name"/>
<Data t-esc="format_date(record.invoice_date)"/>
<Numero t-esc="record.name[-20:]"/>
<Numero t-esc="format_alphanumeric(record.name[-20:])"/>
<DatiBollo t-if="record.l10n_it_stamp_duty">
<!--2.1.1.6-->
<BolloVirtuale>SI</BolloVirtuale>
@@ -147,11 +147,11 @@
</DatiBollo>
</DatiGeneraliDocumento>
<DatiOrdineAcquisto t-if="record.ref">
<IdDocumento t-esc="record.ref[:20]" />
<IdDocumento t-esc="format_alphanumeric(record.ref[:20])"/>
</DatiOrdineAcquisto>
<DatiDDT t-if="record.l10n_it_ddt_id">
<!--2.1.8-->
<NumeroDDT t-esc="record.l10n_it_ddt_id.name"/>
<NumeroDDT t-esc="format_alphanumeric(record.l10n_it_ddt_id.name[-20:])"/>
<DataDDT t-esc="format_date(record.l10n_it_ddt_id.date)"/>
</DatiDDT>
</DatiGenerali>
@@ -172,7 +172,7 @@
<ImponibileImporto t-esc="format_monetary(abs(tax_dict['base_amount']), currency)"/>
<Imposta t-esc="format_monetary(abs(tax_dict['tax_amount']), currency)"/>
<EsigibilitaIVA t-if="not tax_dict['tax'].l10n_it_has_exoneration or tax_dict['tax'].l10n_it_kind_exoneration=='N6'" t-esc="tax_dict['tax'].l10n_it_vat_due_date"/>
<RiferimentoNormativo t-if="tax_dict['tax'].l10n_it_has_exoneration" t-esc="tax_dict['tax'].l10n_it_law_reference"/>
<RiferimentoNormativo t-if="tax_dict['tax'].l10n_it_has_exoneration" t-esc="format_alphanumeric(tax_dict['tax'].l10n_it_law_reference[:100])"/>
</DatiRiepilogo>
</t>
</DatiBeniServizi>
@@ -185,15 +185,15 @@
<ModalitaPagamento>MP05</ModalitaPagamento>
<DataScadenzaPagamento t-esc="format_date(payment.date_maturity)"/>
<ImportoPagamento t-esc="format_monetary(abs(payment.price_total), currency)"/>
<IstitutoFinanziario t-if="company_bank_account.bank_id" t-esc="company_bank_account.bank_id.name[:80]"/>
<IstitutoFinanziario t-if="company_bank_account.bank_id" t-esc="format_alphanumeric(company_bank_account.bank_id.name[:80])"/>
<IBAN t-if="company_bank_account.acc_type == 'iban'" t-esc="company_bank_account.sanitized_acc_number"/>
<BIC t-if="company_bank_account.acc_type == 'bank' and company_bank_account.bank_id.bic" t-esc="company_bank_account.bank_id.bic"/>
<CodicePagamento t-esc="record.payment_reference[:60]"/>
<CodicePagamento t-esc="format_alphanumeric(record.payment_reference[:60])"/>
</DettaglioPagamento>
</t>
</DatiPagamento>
<Allegati t-if="pdf">
<NomeAttachment t-esc="pdf_name"/>
<NomeAttachment t-esc="format_alphanumeric(pdf_name[:60])"/>
<FormatoAttachment>PDF</FormatoAttachment>
<Attachment t-esc="pdf"/>
</Allegati>
@@ -142,6 +142,9 @@ class AccountMove(models.Model):
return True
return False
def format_alphanumeric(text_to_convert):
return text_to_convert.encode('latin-1', 'replace').decode('latin-1') if text_to_convert else False
formato_trasmissione = "FPA12" if self._is_commercial_partner_pa() else "FPR12"
if self.move_type == 'out_invoice':
@@ -179,6 +182,7 @@ class AccountMove(models.Model):
'format_numbers': format_numbers,
'format_numbers_two': format_numbers_two,
'format_phone': format_phone,
'format_alphanumeric': format_alphanumeric,
'discount_type': discount_type,
'get_vat_number': get_vat_number,
'get_vat_country': get_vat_country,
@@ -37,8 +37,7 @@
<IdCodice>00465840031</IdCodice>
</IdFiscaleIVA>
<Anagrafica>
<Nome>Alessi</Nome>
<Cognome></Cognome>
<Denominazione>Alessi</Denominazione>
</Anagrafica>
</DatiAnagrafici>
<Sede>
@@ -55,6 +55,7 @@ class TestItEdi(AccountEdiTestCommon):
'zip': '28887',
'city': 'Milan',
'company_id': cls.company.id,
'is_company': True,
})
cls.standard_line = {
@@ -124,6 +125,27 @@ class TestItEdi(AccountEdiTestCommon):
],
})
cls.non_latin_and_latin_invoice = cls.env['account.move'].with_company(cls.company).create({
'move_type': 'out_invoice',
'invoice_date': datetime.date(2022, 3, 24),
'partner_id': cls.italian_partner_a.id,
'partner_bank_id': cls.test_bank.id,
'invoice_line_ids': [
(0, 0, {
**cls.standard_line,
'name': 'ʢ◉ᴥ◉ʡ',
}),
(0, 0, {
**cls.standard_line,
'name': '–-',
}),
(0, 0, {
**cls.standard_line,
'name': 'this should be the same as it was',
}),
],
})
# We create this because we are unable to post without a proxy user existing
cls.proxy_user = cls.env['account_edi_proxy_client.user'].create({
'id_client': 'l10n_it_edi_sdicoop_test',
@@ -137,6 +159,7 @@ class TestItEdi(AccountEdiTestCommon):
cls.price_included_invoice._post()
cls.partial_discount_invoice._post()
cls.full_discount_invoice._post()
cls.non_latin_and_latin_invoice._post()
cls.edi_basis_xml = cls._get_test_file_content('IT00470550013_basis.xml')
@@ -316,3 +339,50 @@ class TestItEdi(AccountEdiTestCommon):
''')
invoice_etree = self.with_applied_xpath(invoice_etree, "<xpath expr='.//Allegati' position='replace'/>")
self.assertXmlTreeEqual(invoice_etree, expected_etree)
@freeze_time('2020-03-24')
def test_non_latin_and_latin_inovice(self):
invoice_etree = etree.fromstring(self.non_latin_and_latin_invoice._export_as_xml())
expected_etree = self.with_applied_xpath(
etree.fromstring(self.edi_basis_xml),
'''
<xpath expr="//FatturaElettronicaBody//DatiBeniServizi" position="replace">
<DatiBeniServizi>
<DettaglioLinee>
<NumeroLinea>1</NumeroLinea>
<Descrizione>?????</Descrizione>
<Quantita>1.00</Quantita>
<PrezzoUnitario>800.400000</PrezzoUnitario>
<PrezzoTotale>800.40</PrezzoTotale>
<AliquotaIVA>22.00</AliquotaIVA>
</DettaglioLinee>
<DettaglioLinee>
<NumeroLinea>2</NumeroLinea>
<Descrizione>?-</Descrizione>
<Quantita>1.00</Quantita>
<PrezzoUnitario>800.400000</PrezzoUnitario>
<PrezzoTotale>800.40</PrezzoTotale>
<AliquotaIVA>22.00</AliquotaIVA>
</DettaglioLinee>
<DettaglioLinee>
<NumeroLinea>3</NumeroLinea>
<Descrizione>this should be the same as it was</Descrizione>
<Quantita>1.00</Quantita>
<PrezzoUnitario>800.400000</PrezzoUnitario>
<PrezzoTotale>800.40</PrezzoTotale>
<AliquotaIVA>22.00</AliquotaIVA>
</DettaglioLinee>
<DatiRiepilogo>
<AliquotaIVA>22.00</AliquotaIVA>
<ImponibileImporto>2401.20</ImponibileImporto>
<Imposta>528.27</Imposta>
<EsigibilitaIVA>I</EsigibilitaIVA>
</DatiRiepilogo>
</DatiBeniServizi>
</xpath>
<xpath expr="//DettaglioPagamento//ImportoPagamento" position="inside">
2929.47
</xpath>
''')
invoice_etree = self.with_applied_xpath(invoice_etree, "<xpath expr='.//Allegati' position='replace'/>")
self.assertXmlTreeEqual(invoice_etree, expected_etree)