Skip to content

Commit 7735549

Browse files
SirTakobieLBati
authored andcommitted
[14.0] l10n_it_fatturapa_in: forward port of OCA#2788
1 parent e81f853 commit 7735549

7 files changed

Lines changed: 288 additions & 53 deletions

File tree

l10n_it_fatturapa_in/models/attachment.py

Lines changed: 107 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
import logging
2+
13
from odoo import _, api, fields, models
24
from odoo.tools import format_date
35

6+
from ..wizard import efattura
7+
8+
_logger = logging.getLogger(__name__)
9+
410
SELF_INVOICE_TYPES = ("TD16", "TD17", "TD18", "TD19", "TD20", "TD21", "TD27", "TD28")
511

612

@@ -43,8 +49,13 @@ class FatturaPAAttachmentIn(models.Model):
4349
compute="_compute_e_invoice_validation_error"
4450
)
4551

52+
e_invoice_parsing_error = fields.Text(
53+
compute="_compute_e_invoice_parsing_error",
54+
store=True,
55+
)
56+
4657
is_self_invoice = fields.Boolean(
47-
"Contains self invoices", compute="_compute_xml_data", store=True
58+
"Contains self invoices", compute="_compute_is_self_invoice", store=True
4859
)
4960

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

8697
def recompute_xml_fields(self):
87-
self._compute_xml_data()
98+
# Pretend the attachment has been modified
99+
# and trigger a recomputation:
100+
# this recomputes all fields whose value
101+
# is extracted from the attachment
102+
self.modified(["ir_attachment_id"])
103+
self.recompute()
88104
self._compute_registered()
89105

106+
def get_invoice_obj(self):
107+
"""
108+
Parse the invoice into a lxml.etree.ElementTree object.
109+
If the parsing goes wrong:
110+
- log the error
111+
- save the parsing error in field `e_invoice_parsing_error`
112+
- return `False`
113+
:rtype: lxml.etree.ElementTree or bool.
114+
"""
115+
self.ensure_one()
116+
invoice_obj = False
117+
try:
118+
xml_string = self.get_xml_string()
119+
invoice_obj = efattura.CreateFromDocument(xml_string)
120+
except Exception as e:
121+
error_msg = _("Impossible to parse XML for {att_name}: {error_msg}").format(
122+
att_name=self.display_name,
123+
error_msg=e,
124+
)
125+
_logger.error(error_msg)
126+
self.e_invoice_parsing_error = error_msg
127+
else:
128+
self.e_invoice_parsing_error = False
129+
return invoice_obj
130+
131+
@api.depends("ir_attachment_id.datas")
132+
def _compute_is_self_invoice(self):
133+
for att in self:
134+
att.is_self_invoice = False
135+
if not att.datas:
136+
return
137+
fatt = att.get_invoice_obj()
138+
if fatt:
139+
for invoice_body in fatt.FatturaElettronicaBody:
140+
document_type = (
141+
invoice_body.DatiGenerali.DatiGeneraliDocumento.TipoDocumento
142+
)
143+
if document_type in SELF_INVOICE_TYPES:
144+
att.is_self_invoice = True
145+
break
146+
147+
@api.depends("ir_attachment_id.datas")
148+
def _compute_e_invoice_parsing_error(self):
149+
for att in self:
150+
if not att.datas:
151+
return
152+
att.get_invoice_obj()
153+
90154
@api.depends("ir_attachment_id.datas")
91155
def _compute_xml_data(self):
92156
for att in self:
93157
att.xml_supplier_id = False
94158
att.invoices_number = False
95159
att.invoices_total = False
96160
att.invoices_date = False
97-
att.is_self_invoice = False
98-
if not att.ir_attachment_id.datas:
161+
if not att.datas:
162+
return
163+
fatt = att.get_invoice_obj()
164+
if not fatt:
165+
# Set default values and carry on
99166
continue
100-
wiz_obj = self.env["wizard.import.fatturapa"].with_context(
101-
from_attachment=att
102-
)
103-
fatt = wiz_obj.get_invoice_obj(att)
104-
cedentePrestatore = fatt.FatturaElettronicaHeader.CedentePrestatore
105-
partner_id = wiz_obj.getCedPrest(cedentePrestatore)
106-
att.xml_supplier_id = partner_id
107-
att.invoices_number = len(fatt.FatturaElettronicaBody)
108-
att.invoices_total = 0
167+
# Look into each invoice to compute the following values
109168
invoices_date = []
110169
for invoice_body in fatt.FatturaElettronicaBody:
111-
dgd = invoice_body.DatiGenerali.DatiGeneraliDocumento
112-
att.invoices_total += float(dgd.ImportoTotaleDocumento or 0)
170+
# Assign this directly so that rounding is applied each time
171+
att.invoices_total += float(
172+
invoice_body.DatiGenerali.DatiGeneraliDocumento.ImportoTotaleDocumento
173+
or 0
174+
)
175+
176+
document_date = invoice_body.DatiGenerali.DatiGeneraliDocumento.Data
113177
invoice_date = format_date(
114178
att.with_context(lang=att.env.user.lang).env,
115-
fields.Date.from_string(dgd.Data),
179+
fields.Date.from_string(document_date),
116180
)
117181
if invoice_date not in invoices_date:
118182
invoices_date.append(invoice_date)
119-
if dgd.TipoDocumento in SELF_INVOICE_TYPES:
120-
att.is_self_invoice = True
121-
att.invoices_date = " ".join(invoices_date)
183+
184+
att.update(
185+
dict(
186+
invoices_date=" ".join(invoices_date),
187+
)
188+
)
189+
190+
# We don't need to look into each invoice
191+
# for the following fields
192+
att.invoices_number = len(fatt.FatturaElettronicaBody)
193+
194+
# Partner creation that may happen in `getCedPrest`
195+
# triggers a recomputation
196+
# that messes up the cache of some fields if they are set
197+
# (more properly, put in cache) afterwards;
198+
# this happens for `is_self_invoice` for instance.
199+
# That is why we set it as the last field.
200+
cedentePrestatore = fatt.FatturaElettronicaHeader.CedentePrestatore
201+
wiz_obj = self.env["wizard.import.fatturapa"].with_context(
202+
from_attachment=att
203+
)
204+
partner_id = wiz_obj.getCedPrest(cedentePrestatore)
205+
att.xml_supplier_id = partner_id
122206
inconsistencies = wiz_obj.env.context.get("inconsistencies", False)
123207
att.inconsistencies = inconsistencies
124208

@@ -151,12 +235,12 @@ def extract_attachments(self, AttachmentsData, invoice_id):
151235
@api.depends("ir_attachment_id.datas")
152236
def _compute_linked_invoice_id_xml(self):
153237
for att in self:
238+
att.linked_invoice_id_xml = ""
239+
if not att.datas:
240+
return
154241
if isinstance(att.id, int):
155242
att.linked_invoice_id_xml = ""
156-
wiz_obj = self.env["wizard.import.fatturapa"].with_context(
157-
from_attachment=att
158-
)
159-
fatt = wiz_obj.get_invoice_obj(att)
243+
fatt = att.get_invoice_obj()
160244
if fatt:
161245
for invoice_body in fatt.FatturaElettronicaBody:
162246
if (
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<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">
3+
<FatturaElettronicaHeader xmlns="">
4+
<DatiTrasmissione>
5+
<IdTrasmittente>
6+
<IdPaese>IT</IdPaese>
7+
<IdCodice>12345670017</IdCodice>
8+
</IdTrasmittente>
9+
<ProgressivoInvio>0006299335</ProgressivoInvio>
10+
<FormatoTrasmissione>FPR12</FormatoTrasmissione>
11+
<CodiceDestinatario>0000000</CodiceDestinatario>
12+
</DatiTrasmissione>
13+
<CedentePrestatore>
14+
<DatiAnagrafici>
15+
<IdFiscaleIVA>
16+
<IdPaese>IT</IdPaese>
17+
<IdCodice>12345670017</IdCodice>
18+
</IdFiscaleIVA>
19+
<Anagrafica>
20+
<Denominazione>Pippolo S.p.A.</Denominazione>
21+
</Anagrafica>
22+
<RegimeFiscale>RF01</RegimeFiscale>
23+
</DatiAnagrafici>
24+
<Sede>
25+
<Indirizzo>Via Tal dei tali</Indirizzo>
26+
<CAP>20123</CAP>
27+
<Comune>Milano</Comune>
28+
<Provincia>MI</Provincia>
29+
<Nazione>IT</Nazione>
30+
</Sede>
31+
</CedentePrestatore>
32+
<CessionarioCommittente>
33+
<DatiAnagrafici>
34+
<IdFiscaleIVA>
35+
<IdPaese>IT</IdPaese>
36+
<IdCodice>11531111117</IdCodice>
37+
</IdFiscaleIVA>
38+
<CodiceFiscale>11531111117</CodiceFiscale>
39+
<Anagrafica>
40+
<Denominazione>Mkt s.r.l.</Denominazione>
41+
</Anagrafica>
42+
</DatiAnagrafici>
43+
<Sede>
44+
<Indirizzo>Via talaltri</Indirizzo>
45+
<CAP>20145</CAP>
46+
<Comune>Milano</Comune>
47+
<Provincia>MI</Provincia>
48+
<Nazione>IT</Nazione>
49+
</Sede>
50+
</CessionarioCommittente>
51+
<TerzoIntermediarioOSoggettoEmittente>
52+
<DatiAnagrafici>
53+
<IdFiscaleIVA>
54+
<IdPaese>IT</IdPaese>
55+
<IdCodice>03331111116</IdCodice>
56+
</IdFiscaleIVA>
57+
<CodiceFiscale>03331111116</CodiceFiscale>
58+
<Anagrafica>
59+
<Denominazione>RossiDati Srl</Denominazione>
60+
</Anagrafica>
61+
</DatiAnagrafici>
62+
</TerzoIntermediarioOSoggettoEmittente>
63+
<SoggettoEmittente>TZ</SoggettoEmittente>
64+
</FatturaElettronicaHeader>
65+
<FatturaElettronicaBody xmlns="">
66+
<DatiGenerali>
67+
<DatiGeneraliDocumento>
68+
<TipoDocumento>TD01</TipoDocumento>
69+
<Divisa>EUR</Divisa>
70+
<Data>2022-03-10</Data>
71+
<Numero>17606</Numero>
72+
<ImportoTotaleDocumento>109.80</ImportoTotaleDocumento>
73+
<Causale>DA PAGARE</Causale>
74+
</DatiGeneraliDocumento>
75+
</DatiGenerali>
76+
<DatiBeniServizi>
77+
<DettaglioLinee>
78+
<NumeroLinea>1</NumeroLinea>
79+
<Descrizione>Op. 3367462 - Canone annuo F@X IN Periodo: 2022-03-05 / 2023-03-05
80+
Numero: 0114121701</Descrizione>
81+
<Quantita>1.00000000</Quantita>
82+
<PrezzoUnitario>30.00000000</PrezzoUnitario>
83+
<PrezzoTotale>30.00000000</PrezzoTotale>
84+
<AliquotaIVA>22.00</AliquotaIVA>
85+
</DettaglioLinee>
86+
<DettaglioLinee>
87+
<NumeroLinea>2</NumeroLinea>
88+
<Descrizione>Op. 3368479 - Canone annuo F@X IN Periodo: 2022-03-01 / 2023-03-01
89+
Numero: 03311570073</Descrizione>
90+
<Quantita>1.00000000</Quantita>
91+
<PrezzoUnitario>30.00000000</PrezzoUnitario>
92+
<PrezzoTotale>30.00000000</PrezzoTotale>
93+
<AliquotaIVA>22.00</AliquotaIVA>
94+
</DettaglioLinee>
95+
<DettaglioLinee>
96+
<NumeroLinea>3</NumeroLinea>
97+
<Descrizione>Op. 3369124 - Canone annuo F@X IN Periodo: 2022-02-27 / 2023-02-27
98+
Numero: 0293650722</Descrizione>
99+
<Quantita>1.00000000</Quantita>
100+
<PrezzoUnitario>30.00000000</PrezzoUnitario>
101+
<PrezzoTotale>30.00000000</PrezzoTotale>
102+
<AliquotaIVA>22.00</AliquotaIVA>
103+
</DettaglioLinee>
104+
<DatiRiepilogo>
105+
<AliquotaIVA>22.00</AliquotaIVA>
106+
<ImponibileImporto>90.00</ImponibileImporto>
107+
<Imposta>19.80</Imposta>
108+
<EsigibilitaIVA>I</EsigibilitaIVA>
109+
</DatiRiepilogo>
110+
</DatiBeniServizi>
111+
<DatiPagamento>
112+
<CondizioniPagamento>TP02</CondizioniPagamento>
113+
<DettaglioPagamento>
114+
<ModalitaPagamento>MP05</ModalitaPagamento>
115+
<DataScadenzaPagamento>2022-03-10</DataScadenzaPagamento>
116+
<ImportoPagamento>109.80</ImportoPagamento>
117+
<IstitutoFinanziario>Banca Sella Via Vincenzo Monti, 33 Milano</IstitutoFinanziario>
118+
<IBAN>IT90B0326801602111111111111</IBAN>
119+
<ABI>03268</ABI>
120+
<CAB>01602</CAB>
121+
</DettaglioPagamento>
122+
</DatiPagamento>
123+
</FatturaElettronicaBody>
124+
</FatturaElettronica>

l10n_it_fatturapa_in/tests/fatturapa_common.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -281,16 +281,10 @@ def create_attachment(self, name, file_name, module_name=None):
281281
return attach
282282

283283
def run_wizard(
284-
self,
285-
name,
286-
file_name,
287-
mode="import",
288-
wiz_values=None,
289-
module_name=None,
284+
self, name, file_name, mode="import", wiz_values=None, module_name=None
290285
):
291286
if module_name is None:
292287
module_name = "l10n_it_fatturapa_in"
293-
294288
attach = self.create_attachment(name, file_name, module_name=module_name)
295289
attach.e_invoice_received_date = fields.Datetime.now()
296290
attach_id = attach.id

l10n_it_fatturapa_in/tests/test_import_fatturapa_xml.py

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,13 +165,13 @@ def test_05_xml_import(self):
165165
def test_06_import_except(self):
166166
# File not exist Exception
167167
self.assertRaises(Exception, self.run_wizard, "test6_Exception", "")
168-
# fake Signed file is passed , generate orm_exception
169-
self.assertRaises(
170-
UserError,
171-
self.run_wizard,
172-
"test6_orm_exception",
173-
"IT05979361218_fake.xml.p7m",
174-
)
168+
169+
# fake Signed file is passed , generate parsing error
170+
with mute_logger("odoo.addons.l10n_it_fatturapa_in.models.attachment"):
171+
attachment = self.create_attachment(
172+
"test6_orm_exception", "IT05979361218_fake.xml.p7m"
173+
)
174+
self.assertIn("Invalid xml", attachment.e_invoice_parsing_error)
175175

176176
def test_07_xml_import(self):
177177
# 2 lines with quantity != 1 and discounts
@@ -892,6 +892,21 @@ def test_51_xml_import(self):
892892
invoice = self.invoice_model.browse(invoice_ids)
893893
self.assertTrue(invoice.fatturapa_attachment_in_id.is_self_invoice)
894894

895+
def test_52_12_xml_import(self):
896+
"""
897+
Check that an XML with syntax error is created,
898+
but it shows a parsing error.
899+
"""
900+
with mute_logger("odoo.addons.l10n_it_fatturapa_in.models.attachment"):
901+
attachment = self.create_attachment(
902+
"test52_12",
903+
"ZGEXQROO37831_anonimizzata.xml",
904+
)
905+
self.assertIn(
906+
"Impossible to parse XML for test52_12:",
907+
attachment.e_invoice_parsing_error or "",
908+
)
909+
895910
def test_53_xml_import(self):
896911
"""
897912
Check that VAT of non-IT partner is not checked.

l10n_it_fatturapa_in/views/account_view.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@
2727
<field name="inconsistencies" nolabel="1" />
2828
</bold>
2929
</div>
30+
<div
31+
class="alert alert-warning"
32+
role="alert"
33+
style="margin-bottom:0px;"
34+
attrs="{'invisible': [('e_invoice_parsing_error','=',False)]}"
35+
>
36+
<bold><field name="e_invoice_parsing_error" nolabel="1" /></bold>
37+
</div>
3038
<field name="e_invoice_validation_error" invisible="1" />
3139
</xpath>
3240
<button name="ftpa_preview" position="after">

0 commit comments

Comments
 (0)