odoo-addon-l10n-it-edi-extension 18.0.1.0.0.30__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo/addons/l10n_it_edi_extension/README.rst +493 -0
- odoo/addons/l10n_it_edi_extension/__init__.py +951 -0
- odoo/addons/l10n_it_edi_extension/__manifest__.py +37 -0
- odoo/addons/l10n_it_edi_extension/controllers/__init__.py +1 -0
- odoo/addons/l10n_it_edi_extension/controllers/portal.py +27 -0
- odoo/addons/l10n_it_edi_extension/data/FoglioStileAssoSoftware.xsl +3150 -0
- odoo/addons/l10n_it_edi_extension/data/invoice_it_template.xml +50 -0
- odoo/addons/l10n_it_edi_extension/data/res.city.it.code.csv +13898 -0
- odoo/addons/l10n_it_edi_extension/i18n/l10n_it_edi_extension.pot +1167 -0
- odoo/addons/l10n_it_edi_extension/i18n/l10n_it_edi_fatturapa.pot +44 -0
- odoo/addons/l10n_it_edi_extension/models/__init__.py +15 -0
- odoo/addons/l10n_it_edi_extension/models/account_journal.py +152 -0
- odoo/addons/l10n_it_edi_extension/models/account_move.py +765 -0
- odoo/addons/l10n_it_edi_extension/models/account_move_line.py +10 -0
- odoo/addons/l10n_it_edi_extension/models/ir_attachment.py +37 -0
- odoo/addons/l10n_it_edi_extension/models/l10n_it_edi_activity_progress.py +14 -0
- odoo/addons/l10n_it_edi_extension/models/l10n_it_edi_article_code.py +15 -0
- odoo/addons/l10n_it_edi_extension/models/l10n_it_edi_discount_rise_price.py +25 -0
- odoo/addons/l10n_it_edi_extension/models/l10n_it_edi_line.py +48 -0
- odoo/addons/l10n_it_edi_extension/models/l10n_it_edi_line_other_data.py +17 -0
- odoo/addons/l10n_it_edi_extension/models/l10n_it_edi_summary_data.py +77 -0
- odoo/addons/l10n_it_edi_extension/models/res_city_it_code.py +83 -0
- odoo/addons/l10n_it_edi_extension/models/res_company.py +62 -0
- odoo/addons/l10n_it_edi_extension/models/res_partner.py +64 -0
- odoo/addons/l10n_it_edi_extension/readme/CONFIGURE.md +45 -0
- odoo/addons/l10n_it_edi_extension/readme/CONTRIBUTORS.md +5 -0
- odoo/addons/l10n_it_edi_extension/readme/DESCRIPTION.md +160 -0
- odoo/addons/l10n_it_edi_extension/security/ir.model.access.csv +14 -0
- odoo/addons/l10n_it_edi_extension/static/description/icon.png +0 -0
- odoo/addons/l10n_it_edi_extension/static/description/index.html +832 -0
- odoo/addons/l10n_it_edi_extension/tests/__init__.py +2 -0
- odoo/addons/l10n_it_edi_extension/tests/import_xmls/IT01234567890_FPR03.xml +166 -0
- odoo/addons/l10n_it_edi_extension/tests/import_xmls/IT02780790107_11004.xml +216 -0
- odoo/addons/l10n_it_edi_extension/tests/import_xmls/IT02780790107_11005.xml +224 -0
- odoo/addons/l10n_it_edi_extension/tests/import_xmls/IT05979361218_003.xml +107 -0
- odoo/addons/l10n_it_edi_extension/tests/import_xmls/test.png +0 -0
- odoo/addons/l10n_it_edi_extension/tests/import_xmls/xml_import.zip +0 -0
- odoo/addons/l10n_it_edi_extension/tests/test_fiscalcode.py +126 -0
- odoo/addons/l10n_it_edi_extension/tests/test_import_edi_extension_xml.py +350 -0
- odoo/addons/l10n_it_edi_extension/views/company_view.xml +41 -0
- odoo/addons/l10n_it_edi_extension/views/l10n_it_view.xml +164 -0
- odoo/addons/l10n_it_edi_extension/views/res_partner_view.xml +19 -0
- odoo/addons/l10n_it_edi_extension/wizards/__init__.py +2 -0
- odoo/addons/l10n_it_edi_extension/wizards/compute_fc.py +176 -0
- odoo/addons/l10n_it_edi_extension/wizards/compute_fc_view.xml +65 -0
- odoo/addons/l10n_it_edi_extension/wizards/l10n_it_edi_import_file_wizard.py +98 -0
- odoo/addons/l10n_it_edi_extension/wizards/l10n_it_edi_import_file_wizard.xml +46 -0
- odoo_addon_l10n_it_edi_extension-18.0.1.0.0.30.dist-info/METADATA +513 -0
- odoo_addon_l10n_it_edi_extension-18.0.1.0.0.30.dist-info/RECORD +51 -0
- odoo_addon_l10n_it_edi_extension-18.0.1.0.0.30.dist-info/WHEEL +5 -0
- odoo_addon_l10n_it_edi_extension-18.0.1.0.0.30.dist-info/top_level.txt +1 -0
@@ -0,0 +1,765 @@
|
|
1
|
+
# Copyright 2025 Giuseppe Borruso - Dinamiche Aziendali srl
|
2
|
+
# Copyright 2025 Simone Rubino
|
3
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
|
4
|
+
|
5
|
+
from odoo import api, fields, models
|
6
|
+
from odoo.exceptions import UserError
|
7
|
+
from odoo.tools import float_compare, html2plaintext
|
8
|
+
|
9
|
+
from odoo.addons.base.models.ir_qweb_fields import Markup
|
10
|
+
from odoo.addons.l10n_it_edi.models.account_move import get_date, get_float, get_text
|
11
|
+
|
12
|
+
|
13
|
+
class AccountMoveInherit(models.Model):
|
14
|
+
_inherit = "account.move"
|
15
|
+
|
16
|
+
l10n_it_edi_protocol_number = fields.Char(size=64, copy=False)
|
17
|
+
l10n_it_edi_tax_representative_id = fields.Many2one(
|
18
|
+
"res.partner", string="Tax Representative"
|
19
|
+
)
|
20
|
+
l10n_it_edi_sender = fields.Selection(
|
21
|
+
[("CC", "Assignee / Partner"), ("TZ", "Third Person")], string="Sender"
|
22
|
+
)
|
23
|
+
l10n_it_edi_attachment_preview_link = fields.Char(
|
24
|
+
string="Preview link",
|
25
|
+
compute="_compute_l10n_it_edi_attachment_preview_link",
|
26
|
+
)
|
27
|
+
l10n_it_edi_line_ids = fields.One2many(
|
28
|
+
"l10n_it_edi.line",
|
29
|
+
"invoice_id",
|
30
|
+
string="E-Invoice Lines",
|
31
|
+
readonly=True,
|
32
|
+
copy=False,
|
33
|
+
)
|
34
|
+
l10n_it_edi_summary_ids = fields.One2many(
|
35
|
+
"l10n_it_edi.summary_data",
|
36
|
+
"invoice_id",
|
37
|
+
string="E-Invoice Summary Data",
|
38
|
+
copy=False,
|
39
|
+
)
|
40
|
+
l10n_it_edi_activity_progress_ids = fields.One2many(
|
41
|
+
"l10n_it_edi.activity_progress",
|
42
|
+
"invoice_id",
|
43
|
+
string="E-Invoice Activity Progress",
|
44
|
+
copy=False,
|
45
|
+
)
|
46
|
+
l10n_it_edi_rounding = fields.Float(
|
47
|
+
string="Rounding",
|
48
|
+
readonly=True,
|
49
|
+
help="Possible total amount rounding on the document (negative sign allowed)",
|
50
|
+
copy=False,
|
51
|
+
)
|
52
|
+
l10n_edi_it_art73 = fields.Boolean(
|
53
|
+
string="Art. 73",
|
54
|
+
readonly=True,
|
55
|
+
help="Indicates whether the document has been issued according to "
|
56
|
+
"methods and terms laid down in a ministerial decree under the "
|
57
|
+
"terms of Article 73 of Italian Presidential Decree 633/72 (this "
|
58
|
+
"enables the seller/provider to issue in the same year several "
|
59
|
+
"documents with same number)",
|
60
|
+
copy=False,
|
61
|
+
)
|
62
|
+
l10n_it_edi_related_invoice_code = fields.Char(
|
63
|
+
string="Related Invoice Code", copy=False
|
64
|
+
)
|
65
|
+
l10n_it_edi_related_invoice_date = fields.Date(
|
66
|
+
string="Related Invoice Date", copy=False
|
67
|
+
)
|
68
|
+
l10n_it_edi_stabile_organizzazione_indirizzo = fields.Char(
|
69
|
+
string="Organization Address",
|
70
|
+
help="The fields must be entered only when the seller/provider is "
|
71
|
+
"non-resident, with a stable organization in Italy. Address of "
|
72
|
+
"the stable organization in Italy (street name, square, etc.)",
|
73
|
+
readonly=True,
|
74
|
+
copy=False,
|
75
|
+
)
|
76
|
+
l10n_it_edi_stabile_organizzazione_civico = fields.Char(
|
77
|
+
string="Organization Street Number",
|
78
|
+
help="Street number of the address (no need to specify if already "
|
79
|
+
"present in the address field)",
|
80
|
+
readonly=True,
|
81
|
+
copy=False,
|
82
|
+
)
|
83
|
+
l10n_it_edi_stabile_organizzazione_cap = fields.Char(
|
84
|
+
string="Organization ZIP", help="ZIP Code", readonly=True, copy=False
|
85
|
+
)
|
86
|
+
l10n_it_edi_stabile_organizzazione_comune = fields.Char(
|
87
|
+
string="Organization Municipality",
|
88
|
+
help="Municipality or city to which the Stable Organization refers",
|
89
|
+
readonly=True,
|
90
|
+
copy=False,
|
91
|
+
)
|
92
|
+
l10n_it_edi_stabile_organizzazione_provincia = fields.Char(
|
93
|
+
string="Organization Province",
|
94
|
+
help="Acronym of the Province to which the municipality indicated "
|
95
|
+
"in the information element 1.2.3.4 <Comune> belongs. "
|
96
|
+
"Must be filled if the information element 1.2.3.6 <Nazione> is "
|
97
|
+
"equal to IT",
|
98
|
+
readonly=True,
|
99
|
+
copy=False,
|
100
|
+
)
|
101
|
+
l10n_it_edi_stabile_organizzazione_nazione = fields.Char(
|
102
|
+
string="Organization Country",
|
103
|
+
help="Country code according to the ISO 3166-1 alpha-2 code standard",
|
104
|
+
readonly=True,
|
105
|
+
copy=False,
|
106
|
+
)
|
107
|
+
l10n_it_edi_amount_untaxed = fields.Monetary(
|
108
|
+
string="E-Invoice Untaxed Amount", readonly=True
|
109
|
+
)
|
110
|
+
l10n_it_edi_amount_tax = fields.Monetary(
|
111
|
+
string="E-Invoice Tax Amount", readonly=True
|
112
|
+
)
|
113
|
+
l10n_it_edi_amount_total = fields.Monetary(
|
114
|
+
string="E-Invoice Total Amount",
|
115
|
+
compute="_compute_l10n_it_amount_total",
|
116
|
+
readonly=True,
|
117
|
+
)
|
118
|
+
l10n_it_edi_validation_message = fields.Text(
|
119
|
+
compute="_compute_l10n_it_edi_validation_message"
|
120
|
+
)
|
121
|
+
|
122
|
+
# -------------------------------------------------------------------------
|
123
|
+
# Computes
|
124
|
+
# -------------------------------------------------------------------------
|
125
|
+
|
126
|
+
@api.depends("l10n_it_edi_attachment_id")
|
127
|
+
def _compute_l10n_it_edi_attachment_preview_link(self):
|
128
|
+
for move in self:
|
129
|
+
if move.l10n_it_edi_attachment_id:
|
130
|
+
move.l10n_it_edi_attachment_preview_link = (
|
131
|
+
move.get_base_url()
|
132
|
+
+ f"/fatturapa/preview/{move.l10n_it_edi_attachment_id.id}"
|
133
|
+
)
|
134
|
+
else:
|
135
|
+
move.l10n_it_edi_attachment_preview_link = ""
|
136
|
+
|
137
|
+
@api.depends(
|
138
|
+
"l10n_it_edi_amount_untaxed", "l10n_it_edi_amount_tax", "l10n_it_edi_rounding"
|
139
|
+
)
|
140
|
+
def _compute_l10n_it_amount_total(self):
|
141
|
+
for move in self:
|
142
|
+
move.l10n_it_edi_amount_total = sum(
|
143
|
+
[
|
144
|
+
move.l10n_it_edi_amount_untaxed,
|
145
|
+
move.l10n_it_edi_amount_tax,
|
146
|
+
move.l10n_it_edi_rounding,
|
147
|
+
]
|
148
|
+
)
|
149
|
+
|
150
|
+
@api.depends(
|
151
|
+
"move_type",
|
152
|
+
"state",
|
153
|
+
"amount_untaxed",
|
154
|
+
"amount_tax",
|
155
|
+
"amount_total",
|
156
|
+
"l10n_it_edi_attachment_id",
|
157
|
+
"l10n_it_edi_amount_untaxed",
|
158
|
+
"l10n_it_edi_amount_tax",
|
159
|
+
"l10n_it_edi_rounding",
|
160
|
+
)
|
161
|
+
def _compute_l10n_it_edi_validation_message(self):
|
162
|
+
self.l10n_it_edi_validation_message = ""
|
163
|
+
|
164
|
+
invoices_to_check = self.filtered(
|
165
|
+
lambda inv: inv.is_purchase_document()
|
166
|
+
and inv.state in ["draft", "posted"]
|
167
|
+
and inv.l10n_it_edi_attachment_id
|
168
|
+
)
|
169
|
+
for invoice in invoices_to_check:
|
170
|
+
error_messages = list()
|
171
|
+
|
172
|
+
if error_message := invoice._l10n_it_edi_check_amount_untaxed():
|
173
|
+
error_messages.append(error_message)
|
174
|
+
|
175
|
+
if error_message := invoice._l10n_it_edi_check_amount_tax():
|
176
|
+
error_messages.append(error_message)
|
177
|
+
|
178
|
+
if error_message := invoice._l10n_it_edi_check_amount_total():
|
179
|
+
error_messages.append(error_message)
|
180
|
+
|
181
|
+
if not error_messages:
|
182
|
+
continue
|
183
|
+
invoice.l10n_it_edi_validation_message = ",\n".join(error_messages) + "."
|
184
|
+
|
185
|
+
# -------------------------------------------------------------------------
|
186
|
+
# Business actions
|
187
|
+
# -------------------------------------------------------------------------
|
188
|
+
|
189
|
+
def action_l10n_it_edi_attachment_preview(self):
|
190
|
+
self.ensure_one()
|
191
|
+
|
192
|
+
return {
|
193
|
+
"type": "ir.actions.act_url",
|
194
|
+
"name": "Show preview",
|
195
|
+
"url": self.l10n_it_edi_attachment_preview_link,
|
196
|
+
"target": "new",
|
197
|
+
}
|
198
|
+
|
199
|
+
# -------------------------------------------------------------------------
|
200
|
+
# Helpers
|
201
|
+
# -------------------------------------------------------------------------
|
202
|
+
|
203
|
+
def _l10n_it_edi_add_base_lines_xml_values(
|
204
|
+
self, base_lines_aggregated_values, is_downpayment
|
205
|
+
):
|
206
|
+
res = super()._l10n_it_edi_add_base_lines_xml_values(
|
207
|
+
base_lines_aggregated_values, is_downpayment
|
208
|
+
)
|
209
|
+
for base_line, _aggregated_values in base_lines_aggregated_values:
|
210
|
+
line = base_line["record"]
|
211
|
+
base_line["it_values"].update(
|
212
|
+
{
|
213
|
+
"admin_ref": line.l10n_it_edi_admin_ref or None,
|
214
|
+
}
|
215
|
+
)
|
216
|
+
return res
|
217
|
+
|
218
|
+
def _l10n_it_edi_get_values(self, pdf_values=None):
|
219
|
+
res = super()._l10n_it_edi_get_values(pdf_values)
|
220
|
+
|
221
|
+
causale_list = []
|
222
|
+
if self.narration:
|
223
|
+
try:
|
224
|
+
narration_text = html2plaintext(self.narration)
|
225
|
+
except Exception:
|
226
|
+
narration_text = ""
|
227
|
+
|
228
|
+
# max length of Causale is 200
|
229
|
+
for causale in narration_text.split("\n"):
|
230
|
+
if not causale:
|
231
|
+
continue
|
232
|
+
causale_list_200 = [
|
233
|
+
causale[i : i + 200] for i in range(0, len(causale), 200)
|
234
|
+
]
|
235
|
+
for causale200 in causale_list_200:
|
236
|
+
causale_list.append(causale200)
|
237
|
+
|
238
|
+
res["causale"] = causale_list
|
239
|
+
|
240
|
+
return res
|
241
|
+
|
242
|
+
def _l10n_it_edi_get_extra_info(
|
243
|
+
self, company, document_type, body_tree, incoming=True
|
244
|
+
):
|
245
|
+
extra_info, message_to_log = super()._l10n_it_edi_get_extra_info(
|
246
|
+
company, document_type, body_tree, incoming=incoming
|
247
|
+
)
|
248
|
+
|
249
|
+
if sender := get_text(body_tree, "//SoggettoEmittente"):
|
250
|
+
self.l10n_it_edi_sender = sender
|
251
|
+
|
252
|
+
if elements_stabile_organizzazione := body_tree.xpath(
|
253
|
+
"//StabileOrganizzazione"
|
254
|
+
):
|
255
|
+
element_stabile_organizzazione = elements_stabile_organizzazione[0]
|
256
|
+
self.update(
|
257
|
+
{
|
258
|
+
"l10n_it_edi_stabile_organizzazione_indirizzo": get_text(
|
259
|
+
element_stabile_organizzazione, ".//Indirizzo"
|
260
|
+
),
|
261
|
+
"l10n_it_edi_stabile_organizzazione_civico": get_date(
|
262
|
+
element_stabile_organizzazione, ".//NumeroCivico"
|
263
|
+
),
|
264
|
+
"l10n_it_edi_stabile_organizzazione_cap": get_date(
|
265
|
+
element_stabile_organizzazione, ".//CAP"
|
266
|
+
),
|
267
|
+
"l10n_it_edi_stabile_organizzazione_comune": get_date(
|
268
|
+
element_stabile_organizzazione, ".//Comune"
|
269
|
+
),
|
270
|
+
"l10n_it_edi_stabile_organizzazione_provincia": get_date(
|
271
|
+
element_stabile_organizzazione, ".//Provincia"
|
272
|
+
),
|
273
|
+
"l10n_it_edi_stabile_organizzazione_nazione": get_date(
|
274
|
+
element_stabile_organizzazione, ".//Nazione"
|
275
|
+
),
|
276
|
+
}
|
277
|
+
)
|
278
|
+
|
279
|
+
if rounding := get_float(body_tree, ".//DatiGeneraliDocumento/Arrotondamento"):
|
280
|
+
self.l10n_it_edi_rounding = rounding
|
281
|
+
|
282
|
+
if get_text(body_tree, "//DatiGeneraliDocumento/Art73"):
|
283
|
+
self.l10n_edi_it_art73 = True
|
284
|
+
|
285
|
+
if elements_sal := body_tree.xpath(".//DatiGenerali/DatiSAL"):
|
286
|
+
self.env["l10n_it_edi.activity_progress"].create(
|
287
|
+
[
|
288
|
+
{
|
289
|
+
"activity_progress": get_text(
|
290
|
+
element_sal, ".//RiferimentoFase"
|
291
|
+
),
|
292
|
+
"invoice_id": self.id,
|
293
|
+
}
|
294
|
+
for element_sal in elements_sal
|
295
|
+
],
|
296
|
+
)
|
297
|
+
|
298
|
+
for xpath, label in [
|
299
|
+
(
|
300
|
+
".//DatiGenerali/DatiTrasporto",
|
301
|
+
self.env._("Transport informations from XML file:"),
|
302
|
+
),
|
303
|
+
(".//DatiVeicoli", self.env._("Vehicle informations from XML file:")),
|
304
|
+
]:
|
305
|
+
if body_tree.xpath(xpath):
|
306
|
+
message = Markup("<br/>").join(
|
307
|
+
(label, self._compose_info_message(body_tree, xpath))
|
308
|
+
)
|
309
|
+
message_to_log.append(message)
|
310
|
+
|
311
|
+
if elements_parent_invoice := body_tree.xpath(
|
312
|
+
".//DatiGenerali/FatturaPrincipale"
|
313
|
+
):
|
314
|
+
for element_parent_invoice in elements_parent_invoice:
|
315
|
+
self.write(
|
316
|
+
{
|
317
|
+
"l10n_it_edi_related_invoice_code": get_text(
|
318
|
+
element_parent_invoice, ".//NumeroFatturaPrincipale"
|
319
|
+
),
|
320
|
+
"l10n_it_edi_related_invoice_date": get_date(
|
321
|
+
element_parent_invoice, ".//DataFatturaPrincipale"
|
322
|
+
),
|
323
|
+
}
|
324
|
+
)
|
325
|
+
|
326
|
+
tag_name = (
|
327
|
+
".//DettaglioLinee"
|
328
|
+
if not extra_info["simplified"]
|
329
|
+
else ".//DatiBeniServizi"
|
330
|
+
)
|
331
|
+
if elements_line := body_tree.xpath(tag_name):
|
332
|
+
for element_line in elements_line:
|
333
|
+
self.l10n_it_edi_amount_untaxed += get_float(
|
334
|
+
element_line, ".//PrezzoTotale"
|
335
|
+
)
|
336
|
+
|
337
|
+
if elements_summary := body_tree.xpath(".//DatiBeniServizi/DatiRiepilogo"):
|
338
|
+
self.env["l10n_it_edi.summary_data"].create(
|
339
|
+
[
|
340
|
+
{
|
341
|
+
"tax_rate": get_float(element_summary, ".//AliquotaIVA"),
|
342
|
+
"non_taxable_nature": get_text(element_summary, ".//Natura"),
|
343
|
+
"incidental_charges": get_float(
|
344
|
+
element_summary, ".//SpeseAccessorie"
|
345
|
+
),
|
346
|
+
"rounding": get_float(element_summary, ".//Arrotondamento"),
|
347
|
+
"amount_untaxed": get_float(
|
348
|
+
element_summary, ".//ImponibileImporto"
|
349
|
+
),
|
350
|
+
"amount_tax": get_float(element_summary, ".//Imposta"),
|
351
|
+
"payability": get_text(element_summary, ".//EsigibilitaIVA"),
|
352
|
+
"law_reference": get_text(
|
353
|
+
element_summary, ".//RiferimentoNormativo"
|
354
|
+
),
|
355
|
+
"invoice_id": self.id,
|
356
|
+
}
|
357
|
+
for element_summary in elements_summary
|
358
|
+
]
|
359
|
+
)
|
360
|
+
for element_summary in elements_summary:
|
361
|
+
self.l10n_it_edi_amount_tax += get_float(element_summary, ".//Imposta")
|
362
|
+
|
363
|
+
extra_info["l10n_it_edi_ext_body_tree"] = body_tree
|
364
|
+
return extra_info, message_to_log
|
365
|
+
|
366
|
+
def _l10n_it_edi_update_partner(self, xml_tree, role, partner):
|
367
|
+
vals = self._l10n_it_edi_extension_prepare_partner_values(xml_tree, role)
|
368
|
+
partner.update(vals)
|
369
|
+
return partner
|
370
|
+
|
371
|
+
def _l10n_it_edi_ext_import_summary_line(self, element, extra_info=None):
|
372
|
+
messages_to_log = []
|
373
|
+
company = self.company_id
|
374
|
+
percentage = get_float(element, ".//AliquotaIVA")
|
375
|
+
extra_domain = extra_info.get(
|
376
|
+
"type_tax_use_domain", [("type_tax_use", "=", "purchase")]
|
377
|
+
)
|
378
|
+
l10n_it_exempt_reason = get_text(element, ".//Natura").upper() or False
|
379
|
+
tax = self._l10n_it_edi_search_tax_for_import(
|
380
|
+
company,
|
381
|
+
percentage,
|
382
|
+
extra_domain,
|
383
|
+
l10n_it_exempt_reason=l10n_it_exempt_reason,
|
384
|
+
)
|
385
|
+
if tax:
|
386
|
+
self.invoice_line_ids += self.env["account.move.line"].create(
|
387
|
+
{
|
388
|
+
"move_id": self.id,
|
389
|
+
"name": self.env._(
|
390
|
+
"Summary for tax amount %(percentage)s",
|
391
|
+
percentage=percentage,
|
392
|
+
),
|
393
|
+
"price_unit": get_float(element, ".//ImponibileImporto"),
|
394
|
+
"tax_ids": tax.ids,
|
395
|
+
}
|
396
|
+
)
|
397
|
+
else:
|
398
|
+
messages_to_log.append(
|
399
|
+
Markup("<br/>").join(
|
400
|
+
(
|
401
|
+
self.env._(
|
402
|
+
"Tax not found for summary line "
|
403
|
+
"with percentage %(percentage)s.",
|
404
|
+
percentage=percentage,
|
405
|
+
),
|
406
|
+
self._compose_info_message(element, "."),
|
407
|
+
)
|
408
|
+
)
|
409
|
+
)
|
410
|
+
|
411
|
+
return messages_to_log
|
412
|
+
|
413
|
+
def _l10n_it_edi_import_line(self, element, move_line, extra_info=None):
|
414
|
+
if extra_info is None:
|
415
|
+
extra_info = dict()
|
416
|
+
messages_to_log = []
|
417
|
+
company = move_line.company_id
|
418
|
+
import_detail_level = (
|
419
|
+
move_line.partner_id.l10n_it_edi_import_detail_level
|
420
|
+
or company.l10n_it_edi_import_detail_level
|
421
|
+
)
|
422
|
+
if import_detail_level == "min":
|
423
|
+
move_line.unlink()
|
424
|
+
line_description = " ".join(get_text(element, ".//Descrizione").split())
|
425
|
+
messages_to_log.append(
|
426
|
+
Markup("<br/>").join(
|
427
|
+
(
|
428
|
+
self.env._(
|
429
|
+
"Line with description %(line_description)s "
|
430
|
+
"has been skipped "
|
431
|
+
"because import detail level is minimum.",
|
432
|
+
line_description=line_description,
|
433
|
+
),
|
434
|
+
self._compose_info_message(element, "."),
|
435
|
+
)
|
436
|
+
)
|
437
|
+
)
|
438
|
+
elif (
|
439
|
+
body_tree := extra_info.get("l10n_it_edi_ext_body_tree")
|
440
|
+
) is not None and import_detail_level == "tax":
|
441
|
+
move_line.unlink()
|
442
|
+
tax_level_imported = extra_info.get("l10n_it_edi_ext_tax_level_imported")
|
443
|
+
if not tax_level_imported:
|
444
|
+
for summary_line in body_tree.xpath(".//DatiBeniServizi/DatiRiepilogo"):
|
445
|
+
messages_to_log += self._l10n_it_edi_ext_import_summary_line(
|
446
|
+
summary_line, extra_info=extra_info
|
447
|
+
)
|
448
|
+
extra_info["l10n_it_edi_ext_tax_level_imported"] = True
|
449
|
+
elif import_detail_level == "max":
|
450
|
+
# Admin. ref.
|
451
|
+
if admin_ref := get_text(element, ".//RiferimentoAmministrazione"):
|
452
|
+
move_line.l10n_it_edi_admin_ref = admin_ref
|
453
|
+
|
454
|
+
vals = {
|
455
|
+
"line_number": int(get_text(element, ".//NumeroLinea")),
|
456
|
+
"service_type": get_text(element, ".//TipoCessionePrestazione"),
|
457
|
+
"name": " ".join(get_text(element, ".//Descrizione").split()),
|
458
|
+
"qty": float(get_text(element, ".//Quantita") or 0),
|
459
|
+
"uom": get_text(element, ".//UnitaMisura"),
|
460
|
+
"period_start_date": get_date(element, ".//DataInizioPeriodo"),
|
461
|
+
"period_end_date": get_date(element, ".//DataFinePeriodo"),
|
462
|
+
"unit_price": get_float(element, ".//PrezzoUnitario"),
|
463
|
+
"total_price": get_float(element, ".//PrezzoTotale"),
|
464
|
+
"tax_amount": get_float(element, ".//AliquotaIVA"),
|
465
|
+
"wt_amount": get_text(element, ".//Ritenuta"),
|
466
|
+
"tax_kind": get_text(element, ".//Natura").upper(),
|
467
|
+
"invoice_line_id": move_line.id,
|
468
|
+
"invoice_id": move_line.move_id.id,
|
469
|
+
}
|
470
|
+
einvoice_line = self.env["l10n_it_edi.line"].create(vals)
|
471
|
+
|
472
|
+
if elements_code := element.xpath(".//CodiceArticolo"):
|
473
|
+
self.env["l10n_it_edi.article_code"].create(
|
474
|
+
[
|
475
|
+
{
|
476
|
+
"name": get_text(element_code, ".//CodiceTipo"),
|
477
|
+
"code_val": get_text(element_code, ".//CodiceValore"),
|
478
|
+
"l10n_it_edi_line_id": einvoice_line.id,
|
479
|
+
}
|
480
|
+
for element_code in elements_code
|
481
|
+
]
|
482
|
+
)
|
483
|
+
|
484
|
+
if elements_discount := element.xpath(".//ScontoMaggiorazione"):
|
485
|
+
self.env["l10n_it_edi.discount_rise_price"].create(
|
486
|
+
[
|
487
|
+
{
|
488
|
+
"name": get_text(element_discount, ".//Tipo"),
|
489
|
+
"percentage": get_float(element_discount, ".//Percentuale"),
|
490
|
+
"amount": get_float(element_discount, ".//Importo"),
|
491
|
+
"l10n_it_edi_line_id": einvoice_line.id,
|
492
|
+
}
|
493
|
+
for element_discount in elements_discount
|
494
|
+
]
|
495
|
+
)
|
496
|
+
|
497
|
+
if elements_other_data := element.xpath(".//AltriDatiGestionali"):
|
498
|
+
self.env["l10n_it_edi.line_other_data"].create(
|
499
|
+
[
|
500
|
+
{
|
501
|
+
"name": get_text(element_other_data, ".//TipoDato"),
|
502
|
+
"text_ref": get_text(
|
503
|
+
element_other_data, ".//RiferimentoTesto"
|
504
|
+
),
|
505
|
+
"num_ref": get_float(
|
506
|
+
element_other_data, ".//RiferimentoNumero"
|
507
|
+
),
|
508
|
+
"date_ref": get_date(
|
509
|
+
element_other_data, ".//RiferimentoData"
|
510
|
+
),
|
511
|
+
"l10n_it_edi_line_id": einvoice_line.id,
|
512
|
+
}
|
513
|
+
for element_other_data in elements_other_data
|
514
|
+
]
|
515
|
+
)
|
516
|
+
|
517
|
+
messages_to_log += super()._l10n_it_edi_import_line(
|
518
|
+
element, move_line, extra_info=extra_info
|
519
|
+
)
|
520
|
+
else:
|
521
|
+
raise UserError(
|
522
|
+
self.env._(
|
523
|
+
"Import detail level %(import_detail_level)s not supported.\n"
|
524
|
+
"Please set an import detail level in company %(company)s.",
|
525
|
+
import_detail_level=import_detail_level,
|
526
|
+
company=company.name,
|
527
|
+
)
|
528
|
+
)
|
529
|
+
return messages_to_log
|
530
|
+
|
531
|
+
def _l10n_it_edi_ext_check_amount(self, amount, edi_amount, message):
|
532
|
+
if (
|
533
|
+
edi_amount
|
534
|
+
and float_compare(
|
535
|
+
amount,
|
536
|
+
abs(edi_amount),
|
537
|
+
precision_rounding=self.currency_id.rounding,
|
538
|
+
)
|
539
|
+
!= 0
|
540
|
+
):
|
541
|
+
return message
|
542
|
+
|
543
|
+
def _l10n_it_edi_check_amount_untaxed(self):
|
544
|
+
return self._l10n_it_edi_ext_check_amount(
|
545
|
+
self.amount_untaxed - self.l10n_it_edi_rounding,
|
546
|
+
self.l10n_it_edi_amount_untaxed,
|
547
|
+
self.env._(
|
548
|
+
"Untaxed amount (%(amount_untaxed)s}) "
|
549
|
+
"minus rounding (%(rounding)s}) "
|
550
|
+
"does not match with "
|
551
|
+
"e-invoice untaxed amount %(edi_amount_untaxed)s)",
|
552
|
+
amount_untaxed=self.amount_untaxed,
|
553
|
+
rounding=self.l10n_it_edi_rounding,
|
554
|
+
edi_amount_untaxed=self.l10n_it_edi_amount_untaxed,
|
555
|
+
),
|
556
|
+
)
|
557
|
+
|
558
|
+
def _l10n_it_edi_check_amount_tax(self):
|
559
|
+
return self._l10n_it_edi_ext_check_amount(
|
560
|
+
self.amount_tax,
|
561
|
+
self.l10n_it_edi_amount_tax,
|
562
|
+
self.env._(
|
563
|
+
"Taxed amount (%(tax_amount)s}) "
|
564
|
+
"does not match with "
|
565
|
+
"e-invoice taxed amount (%(edi_tax_amount)s)",
|
566
|
+
tax_amount=self.amount_tax,
|
567
|
+
edi_tax_amount=self.l10n_it_edi_amount_tax,
|
568
|
+
),
|
569
|
+
)
|
570
|
+
|
571
|
+
def _l10n_it_edi_check_amount_total(self):
|
572
|
+
return self._l10n_it_edi_ext_check_amount(
|
573
|
+
self.amount_total,
|
574
|
+
self.l10n_it_edi_amount_total,
|
575
|
+
self.env._(
|
576
|
+
"Total amount (%(total_amount)s) "
|
577
|
+
"does not match with "
|
578
|
+
"e-invoice total amount (%(edi_total_amount)s)",
|
579
|
+
total_amount=self.amount_total,
|
580
|
+
edi_total_amount=self.l10n_it_edi_amount_total,
|
581
|
+
),
|
582
|
+
)
|
583
|
+
|
584
|
+
def _l10n_it_edi_extend_partner_info(self, partner_role, partner_info):
|
585
|
+
if partner_role == "buyer":
|
586
|
+
partner_info_xpath = "//CessionarioCommittente"
|
587
|
+
elif partner_role == "seller":
|
588
|
+
partner_info_xpath = "//CedentePrestatore"
|
589
|
+
elif partner_role == "tax_representative":
|
590
|
+
partner_info_xpath = "//RappresentanteFiscale"
|
591
|
+
else:
|
592
|
+
raise UserError(
|
593
|
+
self.env._(
|
594
|
+
"Role %(role)s is not supported for partner creation/update",
|
595
|
+
role=partner_role,
|
596
|
+
)
|
597
|
+
)
|
598
|
+
|
599
|
+
partner_info.update(
|
600
|
+
{
|
601
|
+
"city_xpath": f"{partner_info_xpath}//Comune",
|
602
|
+
"codice_fiscale_xpath": f"{partner_info_xpath}//CodiceFiscale",
|
603
|
+
"country_code_xpath": f"{partner_info_xpath}//IdPaese",
|
604
|
+
"email_xpath": f"{partner_info_xpath}//Email",
|
605
|
+
"eori_code_xpath": f"{partner_info_xpath}//CodEORI",
|
606
|
+
"first_name_xpath": f"{partner_info_xpath}//Nome",
|
607
|
+
"last_name_xpath": f"{partner_info_xpath}//Cognome",
|
608
|
+
"name_xpath": f"{partner_info_xpath}//Denominazione",
|
609
|
+
"phone_xpath": f"{partner_info_xpath}//Telefono",
|
610
|
+
"register_code_xpath": f"{partner_info_xpath}//NumeroIscrizioneAlbo",
|
611
|
+
"register_regdate_xpath": f"{partner_info_xpath}//DataIscrizioneAlbo",
|
612
|
+
"register_state_xpath": f"{partner_info_xpath}//ProvinciaAlbo",
|
613
|
+
"register_xpath": f"{partner_info_xpath}//AlboProfessionale",
|
614
|
+
"state_xpath": f"{partner_info_xpath}//Provincia",
|
615
|
+
"street_number_xpath": f"{partner_info_xpath}//NumeroCivico",
|
616
|
+
"street_xpath": f"{partner_info_xpath}//Indirizzo",
|
617
|
+
"vat_xpath": f"{partner_info_xpath}//IdCodice",
|
618
|
+
"zip_xpath": f"{partner_info_xpath}//CAP",
|
619
|
+
}
|
620
|
+
)
|
621
|
+
|
622
|
+
@api.model
|
623
|
+
def _l10n_it_buyer_seller_info(self):
|
624
|
+
buyer_seller_info = super()._l10n_it_buyer_seller_info()
|
625
|
+
for role, partner_info in buyer_seller_info.items():
|
626
|
+
self._l10n_it_edi_extend_partner_info(role, partner_info)
|
627
|
+
return buyer_seller_info
|
628
|
+
|
629
|
+
def _l10n_it_edi_extension_get_partner_info_by_role(self, tree, role):
|
630
|
+
if role in ("buyer", "seller"):
|
631
|
+
buyer_seller_info = self._l10n_it_buyer_seller_info()
|
632
|
+
partner_info = buyer_seller_info[role]
|
633
|
+
else:
|
634
|
+
partner_info = dict()
|
635
|
+
self._l10n_it_edi_extend_partner_info(role, partner_info)
|
636
|
+
return partner_info
|
637
|
+
|
638
|
+
def _l10n_it_edi_extension_prepare_partner_values(self, tree, role):
|
639
|
+
if partner_info := self._l10n_it_edi_extension_get_partner_info_by_role(
|
640
|
+
tree, role
|
641
|
+
):
|
642
|
+
vals = dict()
|
643
|
+
for field_name, partner_info_xpath in [
|
644
|
+
("city", "city_xpath"),
|
645
|
+
("email", "email_xpath"),
|
646
|
+
("l10n_edi_it_eori_code", "eori_code_xpath"),
|
647
|
+
("l10n_edi_it_register_code", "register_code_xpath"),
|
648
|
+
("l10n_edi_it_register", "register_xpath"),
|
649
|
+
("l10n_edi_it_register_regdate", "register_regdate_xpath"),
|
650
|
+
("l10n_it_codice_fiscale", "codice_fiscale_xpath"),
|
651
|
+
("phone", "phone_xpath"),
|
652
|
+
("vat", "vat_xpath"),
|
653
|
+
("zip", "zip_xpath"),
|
654
|
+
]:
|
655
|
+
if value := get_text(tree, partner_info[partner_info_xpath]):
|
656
|
+
vals[field_name] = value
|
657
|
+
|
658
|
+
country_code = get_text(tree, partner_info["country_code_xpath"])
|
659
|
+
if country := self.env["res.country"].search(
|
660
|
+
[
|
661
|
+
("code", "=", country_code),
|
662
|
+
],
|
663
|
+
limit=1,
|
664
|
+
):
|
665
|
+
vals["country_id"] = country.id
|
666
|
+
|
667
|
+
if province := get_text(tree, partner_info["state_xpath"]):
|
668
|
+
if found_province := self.env["res.country.state"].search(
|
669
|
+
[
|
670
|
+
("code", "=", province),
|
671
|
+
("country_id", "=", country.id),
|
672
|
+
],
|
673
|
+
limit=1,
|
674
|
+
):
|
675
|
+
vals["state_id"] = found_province.id
|
676
|
+
else:
|
677
|
+
message = self.env._(
|
678
|
+
"Province (%(province)s) not present in your system",
|
679
|
+
province=province,
|
680
|
+
)
|
681
|
+
self.sudo().message_post(body=message)
|
682
|
+
|
683
|
+
if register_province := get_text(
|
684
|
+
tree, partner_info["register_state_xpath"]
|
685
|
+
):
|
686
|
+
if found_province := self.env["res.country.state"].search(
|
687
|
+
[
|
688
|
+
("code", "=", register_province),
|
689
|
+
("country_id", "=", country.id),
|
690
|
+
],
|
691
|
+
limit=1,
|
692
|
+
):
|
693
|
+
vals["l10n_edi_it_register_province_id"] = found_province.id
|
694
|
+
else:
|
695
|
+
message = self.env._(
|
696
|
+
"Register Province (%(register_province)s) not present in "
|
697
|
+
"your system",
|
698
|
+
register_province=register_province,
|
699
|
+
)
|
700
|
+
self.sudo().message_post(body=message)
|
701
|
+
|
702
|
+
if address_parts := list(
|
703
|
+
filter(
|
704
|
+
None,
|
705
|
+
[
|
706
|
+
get_text(tree, partner_info["street_xpath"]),
|
707
|
+
get_text(tree, partner_info["street_number_xpath"]),
|
708
|
+
],
|
709
|
+
)
|
710
|
+
):
|
711
|
+
vals["street"] = " ".join(address_parts)
|
712
|
+
|
713
|
+
if name := get_text(tree, partner_info["name_xpath"]):
|
714
|
+
vals["name"] = name
|
715
|
+
vals["is_company"] = True
|
716
|
+
if first_name := get_text(tree, partner_info["first_name_xpath"]):
|
717
|
+
vals["firstname"] = first_name
|
718
|
+
if last_name := get_text(tree, partner_info["last_name_xpath"]):
|
719
|
+
vals["lastname"] = last_name
|
720
|
+
else:
|
721
|
+
vals = dict()
|
722
|
+
return vals
|
723
|
+
|
724
|
+
def _l10n_it_edi_extension_create_partner(self, invoice_data, role):
|
725
|
+
partner_values = self._l10n_it_edi_extension_prepare_partner_values(
|
726
|
+
invoice_data,
|
727
|
+
role,
|
728
|
+
)
|
729
|
+
if partner_values:
|
730
|
+
partner = self.env["res.partner"].create(partner_values)
|
731
|
+
else:
|
732
|
+
partner = self.env["res.partner"].browse()
|
733
|
+
return partner
|
734
|
+
|
735
|
+
def _l10n_it_edi_import_invoice(self, invoice, data, is_new):
|
736
|
+
invoice = super()._l10n_it_edi_import_invoice(invoice, data, is_new)
|
737
|
+
|
738
|
+
body_tree = data["xml_tree"]
|
739
|
+
is_incoming = self.is_purchase_document(include_receipts=True)
|
740
|
+
partner_role = "seller" if is_incoming else "buyer"
|
741
|
+
if (
|
742
|
+
invoice
|
743
|
+
and invoice.partner_id
|
744
|
+
and not invoice.partner_id.l10n_edi_it_electronic_invoice_no_contact_update
|
745
|
+
):
|
746
|
+
self._l10n_it_edi_update_partner(
|
747
|
+
body_tree, partner_role, invoice.partner_id
|
748
|
+
)
|
749
|
+
elif (
|
750
|
+
invoice
|
751
|
+
and not invoice.partner_id
|
752
|
+
and self.env.company.l10n_edi_it_create_partner
|
753
|
+
):
|
754
|
+
invoice.partner_id = self._l10n_it_edi_extension_create_partner(
|
755
|
+
body_tree,
|
756
|
+
partner_role,
|
757
|
+
)
|
758
|
+
|
759
|
+
if tax_representative := self._l10n_it_edi_extension_create_partner(
|
760
|
+
body_tree,
|
761
|
+
"tax_representative",
|
762
|
+
):
|
763
|
+
invoice.l10n_it_edi_tax_representative_id = tax_representative
|
764
|
+
|
765
|
+
return invoice
|