Skip to content

Commit

Permalink
[14.0] l10n_it_fatturapa_in: forward port of OCA#2788
Browse files Browse the repository at this point in the history
  • Loading branch information
SirTakobi authored and TheMule71 committed Sep 8, 2023
1 parent 4c464f4 commit 7d46163
Show file tree
Hide file tree
Showing 7 changed files with 289 additions and 53 deletions.
130 changes: 107 additions & 23 deletions l10n_it_fatturapa_in/models/attachment.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import logging

from odoo import _, api, fields, models
from odoo.tools import format_date

from ..wizard import efattura

_logger = logging.getLogger(__name__)

SELF_INVOICE_TYPES = ("TD16", "TD17", "TD18", "TD19", "TD20", "TD21", "TD27", "TD28")


Expand Down Expand Up @@ -43,8 +49,13 @@ class FatturaPAAttachmentIn(models.Model):
compute="_compute_e_invoice_validation_error"
)

e_invoice_parsing_error = fields.Text(
compute="_compute_e_invoice_parsing_error",
store=True,
)

is_self_invoice = fields.Boolean(
"Contains self invoices", compute="_compute_xml_data", store=True
"Contains self invoices", compute="_compute_is_self_invoice", store=True
)

inconsistencies = fields.Text(compute="_compute_xml_data", store=True)
Expand Down Expand Up @@ -84,41 +95,114 @@ def _compute_e_invoice_validation_error(self):
att.e_invoice_validation_message = "\n\n".join(error_messages)

def recompute_xml_fields(self):
self._compute_xml_data()
# Pretend the attachment has been modified
# and trigger a recomputation:
# this recomputes all fields whose value
# is extracted from the attachment
self.modified(["ir_attachment_id"])
self.recompute()
self._compute_registered()

def get_invoice_obj(self):
"""
Parse the invoice into a lxml.etree.ElementTree object.
If the parsing goes wrong:
- log the error
- save the parsing error in field `e_invoice_parsing_error`
- return `False`
:rtype: lxml.etree.ElementTree or bool.
"""
self.ensure_one()
invoice_obj = False
try:
xml_string = self.get_xml_string()
invoice_obj = efattura.CreateFromDocument(xml_string)
except Exception as e:
error_msg = _("Impossible to parse XML for {att_name}: {error_msg}").format(
att_name=self.display_name,
error_msg=e,
)
_logger.error(error_msg)
self.e_invoice_parsing_error = error_msg
else:
self.e_invoice_parsing_error = False
return invoice_obj

@api.depends("ir_attachment_id.datas")
def _compute_is_self_invoice(self):
for att in self:
att.is_self_invoice = False
if not att.datas:
return
fatt = att.get_invoice_obj()
if fatt:
for invoice_body in fatt.FatturaElettronicaBody:
document_type = (
invoice_body.DatiGenerali.DatiGeneraliDocumento.TipoDocumento
)
if document_type in SELF_INVOICE_TYPES:
att.is_self_invoice = True
break

@api.depends("ir_attachment_id.datas")
def _compute_e_invoice_parsing_error(self):
for att in self:
if not att.datas:
return
att.get_invoice_obj()

@api.depends("ir_attachment_id.datas")
def _compute_xml_data(self):
for att in self:
att.xml_supplier_id = False
att.invoices_number = False
att.invoices_total = False
att.invoices_date = False
att.is_self_invoice = False
if not att.ir_attachment_id.datas:
if not att.datas:
return
fatt = att.get_invoice_obj()
if not fatt:
# Set default values and carry on
continue
wiz_obj = self.env["wizard.import.fatturapa"].with_context(
from_attachment=att
)
fatt = wiz_obj.get_invoice_obj(att)
cedentePrestatore = fatt.FatturaElettronicaHeader.CedentePrestatore
partner_id = wiz_obj.getCedPrest(cedentePrestatore)
att.xml_supplier_id = partner_id
att.invoices_number = len(fatt.FatturaElettronicaBody)
att.invoices_total = 0
# Look into each invoice to compute the following values
invoices_date = []
for invoice_body in fatt.FatturaElettronicaBody:
dgd = invoice_body.DatiGenerali.DatiGeneraliDocumento
att.invoices_total += float(dgd.ImportoTotaleDocumento or 0)
# Assign this directly so that rounding is applied each time
att.invoices_total += float(
invoice_body.DatiGenerali.DatiGeneraliDocumento.ImportoTotaleDocumento
or 0
)

document_date = invoice_body.DatiGenerali.DatiGeneraliDocumento.Data
invoice_date = format_date(
att.with_context(lang=att.env.user.lang).env,
fields.Date.from_string(dgd.Data),
fields.Date.from_string(document_date),
)
if invoice_date not in invoices_date:
invoices_date.append(invoice_date)
if dgd.TipoDocumento in SELF_INVOICE_TYPES:
att.is_self_invoice = True
att.invoices_date = " ".join(invoices_date)

att.update(
dict(
invoices_date=" ".join(invoices_date),
)
)

# We don't need to look into each invoice
# for the following fields
att.invoices_number = len(fatt.FatturaElettronicaBody)

# Partner creation that may happen in `getCedPrest`
# triggers a recomputation
# that messes up the cache of some fields if they are set
# (more properly, put in cache) afterwards;
# this happens for `is_self_invoice` for instance.
# That is why we set it as the last field.
cedentePrestatore = fatt.FatturaElettronicaHeader.CedentePrestatore
wiz_obj = self.env["wizard.import.fatturapa"].with_context(
from_attachment=att
)
partner_id = wiz_obj.getCedPrest(cedentePrestatore)
att.xml_supplier_id = partner_id
inconsistencies = wiz_obj.env.context.get("inconsistencies", False)
att.inconsistencies = inconsistencies

Expand Down Expand Up @@ -151,12 +235,12 @@ def extract_attachments(self, AttachmentsData, invoice_id):
@api.depends("ir_attachment_id.datas")
def _compute_linked_invoice_id_xml(self):
for att in self:
att.linked_invoice_id_xml = ""
if not att.datas:
return
if isinstance(att.id, int):
att.linked_invoice_id_xml = ""
wiz_obj = self.env["wizard.import.fatturapa"].with_context(
from_attachment=att
)
fatt = wiz_obj.get_invoice_obj(att)
fatt = att.get_invoice_obj()
if fatt:
for invoice_body in fatt.FatturaElettronicaBody:
if (
Expand Down
124 changes: 124 additions & 0 deletions l10n_it_fatturapa_in/tests/data/ZGEXQROO37831_anonimizzata.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="utf-8"?>
<FatturaElettronica xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" versione="FPR12" xmlns="http://ivaservizi.agenziaentrate.gov.it/ docs/xsd/fatture/v1.2">
<FatturaElettronicaHeader xmlns="">
<DatiTrasmissione>
<IdTrasmittente>
<IdPaese>IT</IdPaese>
<IdCodice>12345670017</IdCodice>
</IdTrasmittente>
<ProgressivoInvio>0006299335</ProgressivoInvio>
<FormatoTrasmissione>FPR12</FormatoTrasmissione>
<CodiceDestinatario>0000000</CodiceDestinatario>
</DatiTrasmissione>
<CedentePrestatore>
<DatiAnagrafici>
<IdFiscaleIVA>
<IdPaese>IT</IdPaese>
<IdCodice>12345670017</IdCodice>
</IdFiscaleIVA>
<Anagrafica>
<Denominazione>Pippolo S.p.A.</Denominazione>
</Anagrafica>
<RegimeFiscale>RF01</RegimeFiscale>
</DatiAnagrafici>
<Sede>
<Indirizzo>Via Tal dei tali</Indirizzo>
<CAP>20123</CAP>
<Comune>Milano</Comune>
<Provincia>MI</Provincia>
<Nazione>IT</Nazione>
</Sede>
</CedentePrestatore>
<CessionarioCommittente>
<DatiAnagrafici>
<IdFiscaleIVA>
<IdPaese>IT</IdPaese>
<IdCodice>11531111117</IdCodice>
</IdFiscaleIVA>
<CodiceFiscale>11531111117</CodiceFiscale>
<Anagrafica>
<Denominazione>Mkt s.r.l.</Denominazione>
</Anagrafica>
</DatiAnagrafici>
<Sede>
<Indirizzo>Via talaltri</Indirizzo>
<CAP>20145</CAP>
<Comune>Milano</Comune>
<Provincia>MI</Provincia>
<Nazione>IT</Nazione>
</Sede>
</CessionarioCommittente>
<TerzoIntermediarioOSoggettoEmittente>
<DatiAnagrafici>
<IdFiscaleIVA>
<IdPaese>IT</IdPaese>
<IdCodice>03331111116</IdCodice>
</IdFiscaleIVA>
<CodiceFiscale>03331111116</CodiceFiscale>
<Anagrafica>
<Denominazione>RossiDati Srl</Denominazione>
</Anagrafica>
</DatiAnagrafici>
</TerzoIntermediarioOSoggettoEmittente>
<SoggettoEmittente>TZ</SoggettoEmittente>
</FatturaElettronicaHeader>
<FatturaElettronicaBody xmlns="">
<DatiGenerali>
<DatiGeneraliDocumento>
<TipoDocumento>TD01</TipoDocumento>
<Divisa>EUR</Divisa>
<Data>2022-03-10</Data>
<Numero>17606</Numero>
<ImportoTotaleDocumento>109.80</ImportoTotaleDocumento>
<Causale>DA PAGARE</Causale>
</DatiGeneraliDocumento>
</DatiGenerali>
<DatiBeniServizi>
<DettaglioLinee>
<NumeroLinea>1</NumeroLinea>
<Descrizione>Op. 3367462 - Canone annuo F@X IN Periodo: 2022-03-05 / 2023-03-05
Numero: 0114121701</Descrizione>
<Quantita>1.00000000</Quantita>
<PrezzoUnitario>30.00000000</PrezzoUnitario>
<PrezzoTotale>30.00000000</PrezzoTotale>
<AliquotaIVA>22.00</AliquotaIVA>
</DettaglioLinee>
<DettaglioLinee>
<NumeroLinea>2</NumeroLinea>
<Descrizione>Op. 3368479 - Canone annuo F@X IN Periodo: 2022-03-01 / 2023-03-01
Numero: 03311570073</Descrizione>
<Quantita>1.00000000</Quantita>
<PrezzoUnitario>30.00000000</PrezzoUnitario>
<PrezzoTotale>30.00000000</PrezzoTotale>
<AliquotaIVA>22.00</AliquotaIVA>
</DettaglioLinee>
<DettaglioLinee>
<NumeroLinea>3</NumeroLinea>
<Descrizione>Op. 3369124 - Canone annuo F@X IN Periodo: 2022-02-27 / 2023-02-27
Numero: 0293650722</Descrizione>
<Quantita>1.00000000</Quantita>
<PrezzoUnitario>30.00000000</PrezzoUnitario>
<PrezzoTotale>30.00000000</PrezzoTotale>
<AliquotaIVA>22.00</AliquotaIVA>
</DettaglioLinee>
<DatiRiepilogo>
<AliquotaIVA>22.00</AliquotaIVA>
<ImponibileImporto>90.00</ImponibileImporto>
<Imposta>19.80</Imposta>
<EsigibilitaIVA>I</EsigibilitaIVA>
</DatiRiepilogo>
</DatiBeniServizi>
<DatiPagamento>
<CondizioniPagamento>TP02</CondizioniPagamento>
<DettaglioPagamento>
<ModalitaPagamento>MP05</ModalitaPagamento>
<DataScadenzaPagamento>2022-03-10</DataScadenzaPagamento>
<ImportoPagamento>109.80</ImportoPagamento>
<IstitutoFinanziario>Banca Sella Via Vincenzo Monti, 33 Milano</IstitutoFinanziario>
<IBAN>IT90B0326801602111111111111</IBAN>
<ABI>03268</ABI>
<CAB>01602</CAB>
</DettaglioPagamento>
</DatiPagamento>
</FatturaElettronicaBody>
</FatturaElettronica>
9 changes: 2 additions & 7 deletions l10n_it_fatturapa_in/tests/fatturapa_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,23 +134,18 @@ def create_res_bank(self):
def create_attachment(self, name, file_name, module_name=None):
if module_name is None:
module_name = "l10n_it_fatturapa_in"

attach_form = Form(self.attach_model)
attach_form.name = name
attach_form.datas = self.getFile(file_name, module_name=module_name)[1]
attach = attach_form.save()
return attach

def run_wizard(
self,
name,
file_name,
mode="import",
wiz_values=None,
module_name=None,
self, name, file_name, mode="import", wiz_values=None, module_name=None
):
if module_name is None:
module_name = "l10n_it_fatturapa_in"

attach = self.create_attachment(name, file_name, module_name=module_name)
attach_id = attach.id
if mode == "import":
Expand Down
29 changes: 22 additions & 7 deletions l10n_it_fatturapa_in/tests/test_import_fatturapa_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,13 @@ def test_05_xml_import(self):
def test_06_import_except(self):
# File not exist Exception
self.assertRaises(Exception, self.run_wizard, "test6_Exception", "")
# fake Signed file is passed , generate orm_exception
self.assertRaises(
UserError,
self.run_wizard,
"test6_orm_exception",
"IT05979361218_fake.xml.p7m",
)

# fake Signed file is passed , generate parsing error
with mute_logger("odoo.addons.l10n_it_fatturapa_in.models.attachment"):
attachment = self.create_attachment(
"test6_orm_exception", "IT05979361218_fake.xml.p7m"
)
self.assertIn("Invalid xml", attachment.e_invoice_parsing_error)

def test_07_xml_import(self):
# 2 lines with quantity != 1 and discounts
Expand Down Expand Up @@ -895,6 +895,21 @@ def test_51_xml_import(self):
invoice = self.invoice_model.browse(invoice_ids)
self.assertTrue(invoice.fatturapa_attachment_in_id.is_self_invoice)

def test_52_12_xml_import(self):
"""
Check that an XML with syntax error is created,
but it shows a parsing error.
"""
with mute_logger("odoo.addons.l10n_it_fatturapa_in.models.attachment"):
attachment = self.create_attachment(
"test52_12",
"ZGEXQROO37831_anonimizzata.xml",
)
self.assertIn(
"Impossible to parse XML for test52_12:",
attachment.e_invoice_parsing_error or "",
)

def test_52_xml_import(self):
# we test partner creation, too
# make sure a partner with the same vat is already in the DB
Expand Down
8 changes: 8 additions & 0 deletions l10n_it_fatturapa_in/views/account_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
<field name="inconsistencies" nolabel="1" />
</bold>
</div>
<div
class="alert alert-warning"
role="alert"
style="margin-bottom:0px;"
attrs="{'invisible': [('e_invoice_parsing_error','=',False)]}"
>
<bold><field name="e_invoice_parsing_error" nolabel="1" /></bold>
</div>
<field name="e_invoice_validation_error" invisible="1" />
</xpath>
<button name="ftpa_preview" position="after">
Expand Down
Loading

0 comments on commit 7d46163

Please sign in to comment.