odoo-addon-l10n-es-aeat-sii-oca 16.0.1.3.2.1__py3-none-any.whl → 16.0.1.5.0.1__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_es_aeat_sii_oca/README.rst +1 -1
- odoo/addons/l10n_es_aeat_sii_oca/__manifest__.py +1 -1
- odoo/addons/l10n_es_aeat_sii_oca/data/aeat_sii_queue_job.xml +3 -3
- odoo/addons/l10n_es_aeat_sii_oca/i18n/bg.po +61 -18
- odoo/addons/l10n_es_aeat_sii_oca/i18n/ca.po +99 -28
- odoo/addons/l10n_es_aeat_sii_oca/i18n/cs.po +61 -15
- odoo/addons/l10n_es_aeat_sii_oca/i18n/de.po +61 -15
- odoo/addons/l10n_es_aeat_sii_oca/i18n/es.po +122 -36
- odoo/addons/l10n_es_aeat_sii_oca/i18n/es_CO.po +61 -15
- odoo/addons/l10n_es_aeat_sii_oca/i18n/es_CR.po +61 -15
- odoo/addons/l10n_es_aeat_sii_oca/i18n/eu.po +61 -18
- odoo/addons/l10n_es_aeat_sii_oca/i18n/fr.po +61 -18
- odoo/addons/l10n_es_aeat_sii_oca/i18n/gl.po +61 -18
- odoo/addons/l10n_es_aeat_sii_oca/i18n/hr.po +61 -18
- odoo/addons/l10n_es_aeat_sii_oca/i18n/l10n_es_aeat_sii_oca.pot +62 -16
- odoo/addons/l10n_es_aeat_sii_oca/i18n/nl.po +61 -15
- odoo/addons/l10n_es_aeat_sii_oca/i18n/pl.po +61 -18
- odoo/addons/l10n_es_aeat_sii_oca/i18n/pt.po +61 -18
- odoo/addons/l10n_es_aeat_sii_oca/i18n/pt_BR.po +61 -18
- odoo/addons/l10n_es_aeat_sii_oca/i18n/ru.po +61 -15
- odoo/addons/l10n_es_aeat_sii_oca/i18n/sl.po +61 -18
- odoo/addons/l10n_es_aeat_sii_oca/i18n/sv.po +61 -15
- odoo/addons/l10n_es_aeat_sii_oca/i18n/tr.po +61 -15
- odoo/addons/l10n_es_aeat_sii_oca/i18n/vi.po +61 -15
- odoo/addons/l10n_es_aeat_sii_oca/migrations/16.0.1.4.0/post-migration.py +18 -0
- odoo/addons/l10n_es_aeat_sii_oca/models/__init__.py +1 -0
- odoo/addons/l10n_es_aeat_sii_oca/models/account_move.py +126 -693
- odoo/addons/l10n_es_aeat_sii_oca/models/sii_mixin.py +915 -0
- odoo/addons/l10n_es_aeat_sii_oca/static/description/index.html +1 -1
- odoo/addons/l10n_es_aeat_sii_oca/tests/test_l10n_es_aeat_sii.py +38 -43
- {odoo_addon_l10n_es_aeat_sii_oca-16.0.1.3.2.1.dist-info → odoo_addon_l10n_es_aeat_sii_oca-16.0.1.5.0.1.dist-info}/METADATA +2 -2
- {odoo_addon_l10n_es_aeat_sii_oca-16.0.1.3.2.1.dist-info → odoo_addon_l10n_es_aeat_sii_oca-16.0.1.5.0.1.dist-info}/RECORD +34 -32
- {odoo_addon_l10n_es_aeat_sii_oca-16.0.1.3.2.1.dist-info → odoo_addon_l10n_es_aeat_sii_oca-16.0.1.5.0.1.dist-info}/WHEEL +0 -0
- {odoo_addon_l10n_es_aeat_sii_oca-16.0.1.3.2.1.dist-info → odoo_addon_l10n_es_aeat_sii_oca-16.0.1.5.0.1.dist-info}/top_level.txt +0 -0
@@ -7,25 +7,19 @@
|
|
7
7
|
# Copyright 2021 Tecnativa - João Marques
|
8
8
|
# Copyright 2022 ForgeFlow - Lois Rilo
|
9
9
|
# Copyright 2011-2023 Tecnativa - Pedro M. Baeza
|
10
|
+
# Copyright 2023 Aures Tic - Almudena de la Puente <almudena@aurestic.es>
|
11
|
+
# Copyright 2023 Aures Tic - Jose Zambudio <jose@aurestic.es>
|
10
12
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
11
13
|
|
12
14
|
import json
|
13
15
|
import logging
|
14
16
|
|
15
|
-
from requests import Session
|
16
|
-
|
17
17
|
from odoo import _, api, exceptions, fields, models
|
18
18
|
from odoo.modules.registry import Registry
|
19
|
-
from odoo.tools.float_utils import float_compare
|
20
19
|
|
20
|
+
SII_VALID_INVOICE_STATES = ["posted"]
|
21
21
|
_logger = logging.getLogger(__name__)
|
22
22
|
|
23
|
-
try:
|
24
|
-
from zeep import Client
|
25
|
-
from zeep.plugins import HistoryPlugin
|
26
|
-
from zeep.transports import Transport
|
27
|
-
except (ImportError, IOError) as err:
|
28
|
-
_logger.debug(err)
|
29
23
|
|
30
24
|
try:
|
31
25
|
from odoo.addons.queue_job.job import job
|
@@ -38,84 +32,19 @@ except ImportError:
|
|
38
32
|
|
39
33
|
job = empty_decorator_factory
|
40
34
|
|
41
|
-
SII_STATES = [
|
42
|
-
("not_sent", "Not sent"),
|
43
|
-
("sent", "Sent"),
|
44
|
-
("sent_w_errors", "Accepted with errors"),
|
45
|
-
("sent_modified", "Registered in SII but last modifications not sent"),
|
46
|
-
("cancelled", "Cancelled"),
|
47
|
-
("cancelled_modified", "Cancelled in SII but last modifications not sent"),
|
48
|
-
]
|
49
|
-
SII_VERSION = "1.1"
|
50
|
-
SII_MACRODATA_LIMIT = 100000000.0
|
51
|
-
SII_VALID_INVOICE_STATES = ["posted"]
|
52
|
-
|
53
35
|
|
54
|
-
|
55
|
-
""
|
56
|
-
|
57
|
-
3 units x 3,77 €/unit with 10% tax and you will be hit by the error
|
58
|
-
(on regular x86 architectures)."""
|
59
|
-
if isinstance(elem, dict):
|
60
|
-
for key, value in elem.items():
|
61
|
-
if key in search_keys:
|
62
|
-
elem[key] = round(elem[key], prec)
|
63
|
-
else:
|
64
|
-
round_by_keys(value, search_keys)
|
65
|
-
elif isinstance(elem, list):
|
66
|
-
for value in elem:
|
67
|
-
round_by_keys(value, search_keys)
|
36
|
+
class AccountMove(models.Model):
|
37
|
+
_name = "account.move"
|
38
|
+
_inherit = ["account.move", "sii.mixin"]
|
68
39
|
|
40
|
+
def _get_default_type(self):
|
41
|
+
context = self.env.context
|
42
|
+
return context.get("move_type", context.get("default_move_type"))
|
69
43
|
|
70
|
-
|
71
|
-
|
44
|
+
def _default_sii_refund_type(self):
|
45
|
+
inv_type = self._get_default_type()
|
46
|
+
return "I" if inv_type in ["out_refund", "in_refund"] else False
|
72
47
|
|
73
|
-
sii_description = fields.Text(
|
74
|
-
string="SII computed description",
|
75
|
-
compute="_compute_sii_description",
|
76
|
-
default="/",
|
77
|
-
store=True,
|
78
|
-
readonly=False,
|
79
|
-
copy=False,
|
80
|
-
)
|
81
|
-
sii_state = fields.Selection(
|
82
|
-
selection=SII_STATES,
|
83
|
-
string="SII send state",
|
84
|
-
default="not_sent",
|
85
|
-
readonly=True,
|
86
|
-
copy=False,
|
87
|
-
help="Indicates the state of this invoice in relation with the "
|
88
|
-
"presentation at the SII",
|
89
|
-
)
|
90
|
-
sii_csv = fields.Char(string="SII CSV", copy=False, readonly=True)
|
91
|
-
sii_return = fields.Text(string="SII Return", copy=False, readonly=True)
|
92
|
-
sii_header_sent = fields.Text(
|
93
|
-
string="SII last header sent",
|
94
|
-
copy=False,
|
95
|
-
readonly=True,
|
96
|
-
)
|
97
|
-
sii_content_sent = fields.Text(
|
98
|
-
string="SII last content sent",
|
99
|
-
copy=False,
|
100
|
-
readonly=True,
|
101
|
-
)
|
102
|
-
sii_send_error = fields.Text(string="SII Send Error", readonly=True, copy=False)
|
103
|
-
sii_send_failed = fields.Boolean(
|
104
|
-
string="SII send failed",
|
105
|
-
copy=False,
|
106
|
-
help="Indicates that the last attempt to communicate this invoice to "
|
107
|
-
"the SII has failed. See SII return for details",
|
108
|
-
)
|
109
|
-
sii_refund_type = fields.Selection(
|
110
|
-
selection=[
|
111
|
-
# ('S', 'By substitution'), - Removed as not fully supported
|
112
|
-
("I", "By differences"),
|
113
|
-
],
|
114
|
-
string="SII Refund Type",
|
115
|
-
compute="_compute_sii_refund_type",
|
116
|
-
store=True,
|
117
|
-
readonly=False,
|
118
|
-
)
|
119
48
|
sii_refund_specific_invoice_type = fields.Selection(
|
120
49
|
selection=[
|
121
50
|
("R1", "Error based on law and Art. 80 One and Two LIVA (R1)"),
|
@@ -127,28 +56,6 @@ class AccountMove(models.Model):
|
|
127
56
|
" of article 80 of LIVA for notifying to SII with the proper"
|
128
57
|
" invoice type.",
|
129
58
|
)
|
130
|
-
sii_account_registration_date = fields.Date(
|
131
|
-
string="SII account registration date",
|
132
|
-
readonly=True,
|
133
|
-
copy=False,
|
134
|
-
help="Indicates the account registration date set at the SII, which "
|
135
|
-
"must be the date when the invoice is recorded in the system and "
|
136
|
-
"is independent of the date of the accounting entry of the "
|
137
|
-
"invoice",
|
138
|
-
)
|
139
|
-
sii_registration_key_domain = fields.Char(
|
140
|
-
compute="_compute_sii_registration_key_domain",
|
141
|
-
string="SII registration key domain",
|
142
|
-
)
|
143
|
-
sii_registration_key = fields.Many2one(
|
144
|
-
comodel_name="aeat.sii.mapping.registration.keys",
|
145
|
-
string="SII registration key",
|
146
|
-
compute="_compute_sii_registration_key",
|
147
|
-
store=True,
|
148
|
-
readonly=False,
|
149
|
-
# required=True, This is not set as required here to avoid the
|
150
|
-
# set not null constraint warning
|
151
|
-
)
|
152
59
|
sii_registration_key_additional1 = fields.Many2one(
|
153
60
|
comodel_name="aeat.sii.mapping.registration.keys",
|
154
61
|
string="Additional SII registration key",
|
@@ -157,12 +64,6 @@ class AccountMove(models.Model):
|
|
157
64
|
comodel_name="aeat.sii.mapping.registration.keys",
|
158
65
|
string="Additional 2 SII registration key",
|
159
66
|
)
|
160
|
-
sii_registration_key_code = fields.Char(
|
161
|
-
related="sii_registration_key.code",
|
162
|
-
readonly=True,
|
163
|
-
string="SII Code",
|
164
|
-
)
|
165
|
-
sii_enabled = fields.Boolean(string="Enable SII", compute="_compute_sii_enabled")
|
166
67
|
sii_property_location = fields.Selection(
|
167
68
|
string="Real property location",
|
168
69
|
copy=False,
|
@@ -185,12 +86,6 @@ class AccountMove(models.Model):
|
|
185
86
|
string="Real property cadastrial code",
|
186
87
|
copy=False,
|
187
88
|
)
|
188
|
-
sii_macrodata = fields.Boolean(
|
189
|
-
string="MacroData",
|
190
|
-
help="Check to confirm that the invoice has an absolute amount "
|
191
|
-
"greater o equal to 100 000 000,00 euros.",
|
192
|
-
compute="_compute_macrodata",
|
193
|
-
)
|
194
89
|
sii_lc_operation = fields.Boolean(
|
195
90
|
string="Customs - Complementary settlement",
|
196
91
|
help="Check this mark if this invoice represents a complementary "
|
@@ -209,51 +104,23 @@ class AccountMove(models.Model):
|
|
209
104
|
|
210
105
|
@api.depends("move_type")
|
211
106
|
def _compute_sii_refund_type(self):
|
212
|
-
self.sii_refund_type = False
|
213
107
|
for record in self:
|
214
108
|
if "refund" in (record.move_type or ""):
|
215
109
|
record.sii_refund_type = "I"
|
110
|
+
else:
|
111
|
+
record.sii_refund_type = False
|
216
112
|
|
217
113
|
@api.depends("move_type")
|
218
114
|
def _compute_sii_registration_key_domain(self):
|
219
|
-
|
220
|
-
if record.move_type in {"out_invoice", "out_refund"}:
|
221
|
-
record.sii_registration_key_domain = "sale"
|
222
|
-
elif record.move_type in {"in_invoice", "in_refund"}:
|
223
|
-
record.sii_registration_key_domain = "purchase"
|
224
|
-
else:
|
225
|
-
record.sii_registration_key_domain = False
|
115
|
+
return super()._compute_sii_registration_key_domain()
|
226
116
|
|
227
|
-
@api.depends("
|
117
|
+
@api.depends("move_type")
|
228
118
|
def _compute_sii_registration_key(self):
|
229
|
-
|
230
|
-
if invoice.fiscal_position_id:
|
231
|
-
if "out" in invoice.move_type:
|
232
|
-
key = invoice.fiscal_position_id.sii_registration_key_sale
|
233
|
-
else:
|
234
|
-
key = invoice.fiscal_position_id.sii_registration_key_purchase
|
235
|
-
# Only assign sii_registration_key if it's set in the fiscal position
|
236
|
-
if key:
|
237
|
-
invoice.sii_registration_key = key
|
238
|
-
else:
|
239
|
-
domain = [
|
240
|
-
("code", "=", "01"),
|
241
|
-
("type", "=", "sale" if "out" in invoice.move_type else "purchase"),
|
242
|
-
]
|
243
|
-
sii_key_obj = self.env["aeat.sii.mapping.registration.keys"]
|
244
|
-
invoice.sii_registration_key = sii_key_obj.search(domain, limit=1)
|
119
|
+
return super()._compute_sii_registration_key()
|
245
120
|
|
246
121
|
@api.depends("amount_total")
|
247
122
|
def _compute_macrodata(self):
|
248
|
-
|
249
|
-
inv.sii_macrodata = (
|
250
|
-
float_compare(
|
251
|
-
abs(inv.amount_total_signed),
|
252
|
-
SII_MACRODATA_LIMIT,
|
253
|
-
precision_digits=2,
|
254
|
-
)
|
255
|
-
>= 0
|
256
|
-
)
|
123
|
+
return super()._compute_macrodata()
|
257
124
|
|
258
125
|
def _sii_get_partner(self):
|
259
126
|
return self.commercial_partner_id
|
@@ -292,67 +159,10 @@ class AccountMove(models.Model):
|
|
292
159
|
self._raise_exception_sii(_("invoice number"))
|
293
160
|
return super().write(vals)
|
294
161
|
|
295
|
-
def
|
296
|
-
"""
|
297
|
-
|
298
|
-
|
299
|
-
raise exceptions.UserError(
|
300
|
-
_("You cannot delete an invoice already registered at the " "SII.")
|
301
|
-
)
|
302
|
-
return super().unlink()
|
303
|
-
|
304
|
-
def _get_sii_taxes_map(self, codes):
|
305
|
-
"""Return the codes that correspond to that sii map line codes.
|
306
|
-
|
307
|
-
:param self: Single invoice record.
|
308
|
-
:param codes: List of code strings to get the mapping.
|
309
|
-
:return: Recordset with the corresponding codes
|
310
|
-
"""
|
311
|
-
self.ensure_one()
|
312
|
-
map_obj = self.env["aeat.sii.map"].sudo()
|
313
|
-
sii_map = map_obj.search(
|
314
|
-
[
|
315
|
-
"|",
|
316
|
-
("date_from", "<=", self.date),
|
317
|
-
("date_from", "=", False),
|
318
|
-
"|",
|
319
|
-
("date_to", ">=", self.date),
|
320
|
-
("date_to", "=", False),
|
321
|
-
],
|
322
|
-
limit=1,
|
323
|
-
)
|
324
|
-
tax_templates = sii_map.map_lines.filtered(lambda x: x.code in codes).taxes
|
325
|
-
return self.company_id.get_taxes_from_templates(tax_templates)
|
326
|
-
|
327
|
-
def _change_date_format(self, date):
|
328
|
-
datetimeobject = fields.Date.to_date(date)
|
329
|
-
new_date = datetimeobject.strftime("%d-%m-%Y")
|
330
|
-
return new_date
|
331
|
-
|
332
|
-
def _get_sii_header(self, tipo_comunicacion=False, cancellation=False):
|
333
|
-
"""Builds SII send header
|
334
|
-
|
335
|
-
:param tipo_comunicacion String 'A0': new reg, 'A1': modification
|
336
|
-
:param cancellation Bool True when the communitacion es for invoice
|
337
|
-
cancellation
|
338
|
-
:return Dict with header data depending on cancellation
|
339
|
-
"""
|
340
|
-
self.ensure_one()
|
341
|
-
company = self.company_id
|
342
|
-
if not company.vat:
|
343
|
-
raise exceptions.UserError(
|
344
|
-
_("No VAT configured for the company '{}'").format(company.name)
|
345
|
-
)
|
346
|
-
header = {
|
347
|
-
"IDVersionSii": SII_VERSION,
|
348
|
-
"Titular": {
|
349
|
-
"NombreRazon": self.company_id.name[0:120],
|
350
|
-
"NIF": company.partner_id._parse_aeat_vat_info()[2],
|
351
|
-
},
|
352
|
-
}
|
353
|
-
if not cancellation:
|
354
|
-
header.update({"TipoComunicacion": tipo_comunicacion})
|
355
|
-
return header
|
162
|
+
def _filter_sii_unlink_not_possible(self):
|
163
|
+
"""Filter records that we can delete to apply only to invoices."""
|
164
|
+
res = super()._filter_sii_unlink_not_possible()
|
165
|
+
return res.filtered(lambda x: x.is_invoice())
|
356
166
|
|
357
167
|
def _get_sii_tax_req(self, tax):
|
358
168
|
"""Get the associated req tax for the specified tax.
|
@@ -362,7 +172,7 @@ class AccountMove(models.Model):
|
|
362
172
|
:return: REQ tax (or empty recordset) linked to the provided tax.
|
363
173
|
"""
|
364
174
|
self.ensure_one()
|
365
|
-
taxes_req = self._get_sii_taxes_map(["RE"])
|
175
|
+
taxes_req = self._get_sii_taxes_map(["RE"], self._get_document_fiscal_date())
|
366
176
|
re_lines = self.line_ids.filtered(
|
367
177
|
lambda x: tax in x.tax_ids and x.tax_ids & taxes_req
|
368
178
|
)
|
@@ -402,32 +212,8 @@ class AccountMove(models.Model):
|
|
402
212
|
tax_dict["CuotaRecargoEquivalencia"] = tax_lines[req_tax]["amount"]
|
403
213
|
return tax_dict
|
404
214
|
|
405
|
-
def
|
406
|
-
|
407
|
-
the invoice communication."""
|
408
|
-
self.ensure_one()
|
409
|
-
if "DesgloseFactura" not in taxes_dict:
|
410
|
-
return False
|
411
|
-
country_code = self._get_sii_country_code()
|
412
|
-
sii_gen_type = self._get_sii_gen_type()
|
413
|
-
if "DesgloseTipoOperacion" in taxes_dict:
|
414
|
-
# DesgloseTipoOperacion and DesgloseFactura are Exclusive
|
415
|
-
return True
|
416
|
-
elif sii_gen_type in (2, 3):
|
417
|
-
# DesgloseTipoOperacion required for Intracommunity and
|
418
|
-
# Export operations
|
419
|
-
return True
|
420
|
-
elif sii_gen_type == 1 and country_code != "ES":
|
421
|
-
# DesgloseTipoOperacion required for national operations
|
422
|
-
# with 'IDOtro' in the SII identifier block
|
423
|
-
return True
|
424
|
-
elif sii_gen_type == 1 and (self._sii_get_partner().vat or "").startswith(
|
425
|
-
"ESN"
|
426
|
-
):
|
427
|
-
# DesgloseTipoOperacion required if customer's country is Spain and
|
428
|
-
# has a NIF which starts with 'N'
|
429
|
-
return True
|
430
|
-
return False
|
215
|
+
def _get_document_amount_total(self):
|
216
|
+
return self.amount_total_signed
|
431
217
|
|
432
218
|
def _get_sii_out_taxes(self): # noqa: C901
|
433
219
|
"""Get the taxes for sales invoices.
|
@@ -436,17 +222,21 @@ class AccountMove(models.Model):
|
|
436
222
|
"""
|
437
223
|
self.ensure_one()
|
438
224
|
taxes_dict = {}
|
439
|
-
taxes_sfesb = self._get_sii_taxes_map(["SFESB"])
|
440
|
-
taxes_sfesbe = self._get_sii_taxes_map(["SFESBE"])
|
441
|
-
taxes_sfesisp = self._get_sii_taxes_map(["SFESISP"])
|
225
|
+
taxes_sfesb = self._get_sii_taxes_map(["SFESB"], self.date)
|
226
|
+
taxes_sfesbe = self._get_sii_taxes_map(["SFESBE"], self.date)
|
227
|
+
taxes_sfesisp = self._get_sii_taxes_map(["SFESISP"], self.date)
|
442
228
|
# taxes_sfesisps = self._get_taxes_map(['SFESISPS'])
|
443
|
-
taxes_sfens = self._get_sii_taxes_map(["SFENS"])
|
444
|
-
taxes_sfess = self._get_sii_taxes_map(["SFESS"])
|
445
|
-
taxes_sfesse = self._get_sii_taxes_map(["SFESSE"])
|
446
|
-
taxes_sfesns = self._get_sii_taxes_map(["SFESNS"])
|
447
|
-
taxes_not_in_total = self._get_sii_taxes_map(["NotIncludedInTotal"])
|
448
|
-
taxes_not_in_total_neg = self._get_sii_taxes_map(
|
449
|
-
|
229
|
+
taxes_sfens = self._get_sii_taxes_map(["SFENS"], self.date)
|
230
|
+
taxes_sfess = self._get_sii_taxes_map(["SFESS"], self.date)
|
231
|
+
taxes_sfesse = self._get_sii_taxes_map(["SFESSE"], self.date)
|
232
|
+
taxes_sfesns = self._get_sii_taxes_map(["SFESNS"], self.date)
|
233
|
+
taxes_not_in_total = self._get_sii_taxes_map(["NotIncludedInTotal"], self.date)
|
234
|
+
taxes_not_in_total_neg = self._get_sii_taxes_map(
|
235
|
+
["NotIncludedInTotalNegative"], self.date
|
236
|
+
)
|
237
|
+
base_not_in_total = self._get_sii_taxes_map(
|
238
|
+
["BaseNotIncludedInTotal"], self.date
|
239
|
+
)
|
450
240
|
not_in_amount_total = 0
|
451
241
|
exempt_cause = self._get_sii_exempt_cause(taxes_sfesbe + taxes_sfesse)
|
452
242
|
tax_lines = self._get_aeat_tax_info()
|
@@ -565,15 +355,19 @@ class AccountMove(models.Model):
|
|
565
355
|
"""
|
566
356
|
self.ensure_one()
|
567
357
|
taxes_dict = {}
|
568
|
-
taxes_sfrs = self._get_sii_taxes_map(["SFRS"])
|
569
|
-
taxes_sfrsa = self._get_sii_taxes_map(["SFRSA"])
|
570
|
-
taxes_sfrisp = self._get_sii_taxes_map(["SFRISP"])
|
571
|
-
taxes_sfrns = self._get_sii_taxes_map(["SFRNS"])
|
572
|
-
taxes_sfrnd = self._get_sii_taxes_map(["SFRND"])
|
573
|
-
taxes_sfrbi = self._get_sii_taxes_map(["SFRBI"])
|
574
|
-
taxes_not_in_total = self._get_sii_taxes_map(["NotIncludedInTotal"])
|
575
|
-
taxes_not_in_total_neg = self._get_sii_taxes_map(
|
576
|
-
|
358
|
+
taxes_sfrs = self._get_sii_taxes_map(["SFRS"], self.date)
|
359
|
+
taxes_sfrsa = self._get_sii_taxes_map(["SFRSA"], self.date)
|
360
|
+
taxes_sfrisp = self._get_sii_taxes_map(["SFRISP"], self.date)
|
361
|
+
taxes_sfrns = self._get_sii_taxes_map(["SFRNS"], self.date)
|
362
|
+
taxes_sfrnd = self._get_sii_taxes_map(["SFRND"], self.date)
|
363
|
+
taxes_sfrbi = self._get_sii_taxes_map(["SFRBI"], self.date)
|
364
|
+
taxes_not_in_total = self._get_sii_taxes_map(["NotIncludedInTotal"], self.date)
|
365
|
+
taxes_not_in_total_neg = self._get_sii_taxes_map(
|
366
|
+
["NotIncludedInTotalNegative"], self.date
|
367
|
+
)
|
368
|
+
base_not_in_total = self._get_sii_taxes_map(
|
369
|
+
["BaseNotIncludedInTotal"], self.date
|
370
|
+
)
|
577
371
|
tax_amount = 0.0
|
578
372
|
not_in_amount_total = 0.0
|
579
373
|
tax_lines = self._get_aeat_tax_info()
|
@@ -617,157 +411,88 @@ class AccountMove(models.Model):
|
|
617
411
|
base_dict["DetalleIVA"].append(tax_dict)
|
618
412
|
return taxes_dict, tax_amount, not_in_amount_total
|
619
413
|
|
620
|
-
def
|
621
|
-
|
622
|
-
invoice are simplified or normal"""
|
623
|
-
partner = self._sii_get_partner()
|
624
|
-
is_simplified = partner.sii_simplified_invoice
|
625
|
-
return is_simplified
|
414
|
+
def _get_mapping_key(self):
|
415
|
+
return self.move_type
|
626
416
|
|
627
417
|
def _sii_check_exceptions(self):
|
628
|
-
|
629
|
-
self.ensure_one()
|
630
|
-
gen_type = self._get_sii_gen_type()
|
631
|
-
partner = self._sii_get_partner()
|
632
|
-
country_code = self._get_sii_country_code()
|
418
|
+
res = super()._sii_check_exceptions()
|
633
419
|
is_simplified_invoice = self._is_sii_simplified_invoice()
|
634
|
-
|
635
420
|
if is_simplified_invoice and self.move_type[:2] == "in":
|
636
421
|
raise exceptions.UserError(
|
637
422
|
_("You can't make a supplier simplified invoice.")
|
638
423
|
)
|
639
|
-
if (
|
640
|
-
(gen_type != 3 or country_code == "ES")
|
641
|
-
and not partner.vat
|
642
|
-
and not is_simplified_invoice
|
643
|
-
):
|
644
|
-
raise exceptions.UserError(_("The partner has not a VAT configured."))
|
645
|
-
if not self.company_id.chart_template_id:
|
646
|
-
raise exceptions.UserError(
|
647
|
-
_("You have to select what account chart template use this" " company.")
|
648
|
-
)
|
649
|
-
if not self.company_id.sii_enabled:
|
650
|
-
raise exceptions.UserError(_("This company doesn't have SII enabled."))
|
651
|
-
if not self.sii_enabled:
|
652
|
-
raise exceptions.UserError(_("This invoice is not SII enabled."))
|
653
424
|
if not self.ref and self.move_type in ["in_invoice", "in_refund"]:
|
654
425
|
raise exceptions.UserError(_("The supplier number invoice is required"))
|
655
|
-
|
656
|
-
def _get_account_registration_date(self):
|
657
|
-
"""Hook method to allow the setting of the account registration date
|
658
|
-
of each supplier invoice. The SII recommends to set the send date as
|
659
|
-
the default value (point 9.3 of the document
|
660
|
-
SII_Descripcion_ServicioWeb_v0.7.pdf), so by default we return
|
661
|
-
the current date or, if exists, the stored
|
662
|
-
sii_account_registration_date
|
663
|
-
:return String date in the format %Y-%m-%d"""
|
664
|
-
self.ensure_one()
|
665
|
-
return self.sii_account_registration_date or fields.Date.today()
|
426
|
+
return res
|
666
427
|
|
667
428
|
def _get_sii_invoice_type(self):
|
668
|
-
|
429
|
+
invoice_type = ""
|
669
430
|
if self.sii_lc_operation:
|
670
431
|
return "LC"
|
671
432
|
if self.move_type in ["in_invoice", "in_refund"]:
|
672
|
-
|
433
|
+
invoice_type = "R4" if self.move_type == "in_refund" else "F1"
|
673
434
|
elif self.move_type in ["out_invoice", "out_refund"]:
|
674
435
|
is_simplified = self._is_sii_simplified_invoice()
|
675
|
-
|
436
|
+
invoice_type = "F2" if is_simplified else "F1"
|
676
437
|
if self.move_type == "out_refund":
|
677
438
|
if self.sii_refund_specific_invoice_type:
|
678
|
-
|
439
|
+
invoice_type = self.sii_refund_specific_invoice_type
|
679
440
|
else:
|
680
|
-
|
681
|
-
return
|
441
|
+
invoice_type = "R5" if is_simplified else "R1"
|
442
|
+
return invoice_type
|
682
443
|
|
683
444
|
def _get_sii_invoice_dict_out(self, cancel=False):
|
684
|
-
|
685
|
-
out_invoice and out_refund.
|
686
|
-
|
687
|
-
:param cancel: It indicates if the dictionary is for sending a
|
688
|
-
cancellation of the invoice.
|
689
|
-
:return: invoices (dict) : Dict XML with data for this invoice.
|
690
|
-
"""
|
691
|
-
self.ensure_one()
|
692
|
-
invoice_date = self._change_date_format(self.invoice_date)
|
693
|
-
partner = self._sii_get_partner()
|
694
|
-
company = self.company_id
|
695
|
-
ejercicio = fields.Date.to_date(self.date).year
|
696
|
-
periodo = "%02d" % fields.Date.to_date(self.date).month
|
697
|
-
is_simplified_invoice = self._is_sii_simplified_invoice()
|
698
|
-
serial_number = (self.name or "")[0:60]
|
445
|
+
inv_dict = super()._get_sii_invoice_dict_out(cancel=cancel)
|
699
446
|
if self.thirdparty_invoice:
|
700
|
-
|
701
|
-
|
702
|
-
"
|
703
|
-
|
704
|
-
"
|
705
|
-
|
706
|
-
|
707
|
-
"NumSerieFacturaEmisor": serial_number,
|
708
|
-
"FechaExpedicionFacturaEmisor": invoice_date,
|
709
|
-
},
|
710
|
-
"PeriodoLiquidacion": {"Ejercicio": ejercicio, "Periodo": periodo},
|
711
|
-
}
|
712
|
-
if not cancel:
|
713
|
-
tipo_desglose, not_in_amount_total = self._get_sii_out_taxes()
|
714
|
-
amount_total = self.amount_total_signed - not_in_amount_total
|
715
|
-
inv_dict["FacturaExpedida"] = {
|
716
|
-
"TipoFactura": self._get_sii_invoice_type(),
|
717
|
-
"ClaveRegimenEspecialOTrascendencia": (self.sii_registration_key.code),
|
718
|
-
"DescripcionOperacion": self.sii_description,
|
719
|
-
"TipoDesglose": tipo_desglose,
|
720
|
-
"ImporteTotal": amount_total,
|
721
|
-
}
|
722
|
-
if self.thirdparty_invoice:
|
723
|
-
inv_dict["FacturaExpedida"]["EmitidaPorTercerosODestinatario"] = "S"
|
724
|
-
if self.sii_macrodata:
|
725
|
-
inv_dict["FacturaExpedida"].update(Macrodato="S")
|
726
|
-
if self.sii_registration_key_additional1:
|
727
|
-
inv_dict["FacturaExpedida"].update(
|
728
|
-
{
|
729
|
-
"ClaveRegimenEspecialOTrascendenciaAdicional1": (
|
730
|
-
self.sii_registration_key_additional1.code
|
731
|
-
)
|
732
|
-
}
|
733
|
-
)
|
734
|
-
if self.sii_registration_key_additional2:
|
735
|
-
inv_dict["FacturaExpedida"].update(
|
736
|
-
{
|
737
|
-
"ClaveRegimenEspecialOTrascendenciaAdicional2": (
|
738
|
-
self.sii_registration_key_additional2.code
|
739
|
-
)
|
740
|
-
}
|
741
|
-
)
|
742
|
-
if self.sii_registration_key.code in ["12", "13"]:
|
743
|
-
inv_dict["FacturaExpedida"]["DatosInmueble"] = {
|
744
|
-
"DetalleInmueble": {
|
745
|
-
"SituacionInmueble": self.sii_property_location,
|
746
|
-
"ReferenciaCatastral": (
|
747
|
-
self.sii_property_cadastrial_code or ""
|
748
|
-
),
|
749
|
-
}
|
447
|
+
inv_dict["FacturaExpedida"]["EmitidaPorTercerosODestinatario"] = "S"
|
448
|
+
if self.sii_registration_key_additional1:
|
449
|
+
inv_dict["FacturaExpedida"].update(
|
450
|
+
{
|
451
|
+
"ClaveRegimenEspecialOTrascendenciaAdicional1": (
|
452
|
+
self.sii_registration_key_additional1.code
|
453
|
+
)
|
750
454
|
}
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
"
|
455
|
+
)
|
456
|
+
if self.sii_registration_key_additional2:
|
457
|
+
inv_dict["FacturaExpedida"].update(
|
458
|
+
{
|
459
|
+
"ClaveRegimenEspecialOTrascendenciaAdicional2": (
|
460
|
+
self.sii_registration_key_additional2.code
|
461
|
+
)
|
462
|
+
}
|
463
|
+
)
|
464
|
+
if self.sii_registration_key.code in ["12", "13"]:
|
465
|
+
inv_dict["FacturaExpedida"]["DatosInmueble"] = {
|
466
|
+
"DetalleInmueble": {
|
467
|
+
"SituacionInmueble": self.sii_property_location,
|
468
|
+
"ReferenciaCatastral": (self.sii_property_cadastrial_code or ""),
|
469
|
+
}
|
470
|
+
}
|
471
|
+
exp_dict = inv_dict["FacturaExpedida"]
|
472
|
+
if self.move_type == "out_refund":
|
473
|
+
exp_dict["TipoRectificativa"] = self.sii_refund_type
|
474
|
+
if self.sii_refund_type == "S":
|
475
|
+
origin = self.refund_invoice_id
|
476
|
+
exp_dict["ImporteRectificacion"] = {
|
477
|
+
"BaseRectificada": abs(origin.amount_untaxed_signed),
|
478
|
+
"CuotaRectificada": abs(
|
479
|
+
origin.amount_total_signed - origin.amount_untaxed_signed
|
480
|
+
),
|
756
481
|
}
|
757
|
-
# Uso condicional de IDOtro/NIF
|
758
|
-
exp_dict["Contraparte"].update(self._get_sii_identifier())
|
759
|
-
if self.move_type == "out_refund":
|
760
|
-
exp_dict["TipoRectificativa"] = self.sii_refund_type
|
761
|
-
if self.sii_refund_type == "S":
|
762
|
-
origin = self.refund_invoice_id
|
763
|
-
exp_dict["ImporteRectificacion"] = {
|
764
|
-
"BaseRectificada": abs(origin.amount_untaxed_signed),
|
765
|
-
"CuotaRectificada": abs(
|
766
|
-
origin.amount_total_signed - origin.amount_untaxed_signed
|
767
|
-
),
|
768
|
-
}
|
769
482
|
return inv_dict
|
770
483
|
|
484
|
+
def _get_document_date(self):
|
485
|
+
return self.invoice_date
|
486
|
+
|
487
|
+
def _get_document_fiscal_date(self):
|
488
|
+
return self.date
|
489
|
+
|
490
|
+
def _get_document_serial_number(self):
|
491
|
+
serial_number = (self.name or "")[0:60]
|
492
|
+
if self.thirdparty_invoice:
|
493
|
+
serial_number = self.thirdparty_number[0:60]
|
494
|
+
return serial_number
|
495
|
+
|
771
496
|
def _get_sii_invoice_dict_in(self, cancel=False):
|
772
497
|
"""Build dict with data to send to AEAT WS for invoice types:
|
773
498
|
in_invoice and in_refund.
|
@@ -844,33 +569,6 @@ class AccountMove(models.Model):
|
|
844
569
|
}
|
845
570
|
return inv_dict
|
846
571
|
|
847
|
-
def _get_sii_invoice_dict(self):
|
848
|
-
self.ensure_one()
|
849
|
-
self._sii_check_exceptions()
|
850
|
-
inv_dict = {}
|
851
|
-
if self.move_type in ["out_invoice", "out_refund"]:
|
852
|
-
inv_dict = self._get_sii_invoice_dict_out()
|
853
|
-
elif self.move_type in ["in_invoice", "in_refund"]:
|
854
|
-
inv_dict = self._get_sii_invoice_dict_in()
|
855
|
-
round_by_keys(
|
856
|
-
inv_dict,
|
857
|
-
[
|
858
|
-
"BaseImponible",
|
859
|
-
"CuotaRepercutida",
|
860
|
-
"CuotaSoportada",
|
861
|
-
"TipoRecargoEquivalencia",
|
862
|
-
"CuotaRecargoEquivalencia",
|
863
|
-
"ImportePorArticulos7_14_Otros",
|
864
|
-
"ImporteTAIReglasLocalizacion",
|
865
|
-
"ImporteTotal",
|
866
|
-
"BaseRectificada",
|
867
|
-
"CuotaRectificada",
|
868
|
-
"CuotaDeducible",
|
869
|
-
"ImporteCompensacionREAGYP",
|
870
|
-
],
|
871
|
-
)
|
872
|
-
return inv_dict
|
873
|
-
|
874
572
|
def _get_cancel_sii_invoice_dict(self):
|
875
573
|
self.ensure_one()
|
876
574
|
self._sii_check_exceptions()
|
@@ -880,136 +578,6 @@ class AccountMove(models.Model):
|
|
880
578
|
return self._get_sii_invoice_dict_in(cancel=True)
|
881
579
|
return {}
|
882
580
|
|
883
|
-
def _connect_params_sii(self, mapping_key):
|
884
|
-
self.ensure_one()
|
885
|
-
agency = self.company_id.tax_agency_id
|
886
|
-
if not agency:
|
887
|
-
# We use spanish agency by default to keep old behavior with
|
888
|
-
# ir.config parameters. In the future it might be good to reinforce
|
889
|
-
# to explicitly set a tax agency in the company by raising an error
|
890
|
-
# here.
|
891
|
-
agency = self.env.ref("l10n_es_aeat.aeat_tax_agency_spain")
|
892
|
-
return agency._connect_params_sii(mapping_key, self.company_id)
|
893
|
-
|
894
|
-
def _connect_sii(self, mapping_key):
|
895
|
-
self.ensure_one()
|
896
|
-
public_crt, private_key = self.env["l10n.es.aeat.certificate"].get_certificates(
|
897
|
-
company=self.company_id
|
898
|
-
)
|
899
|
-
params = self._connect_params_sii(mapping_key)
|
900
|
-
session = Session()
|
901
|
-
session.cert = (public_crt, private_key)
|
902
|
-
transport = Transport(session=session)
|
903
|
-
history = HistoryPlugin()
|
904
|
-
client = Client(wsdl=params["wsdl"], transport=transport, plugins=[history])
|
905
|
-
return self._bind_sii(client, params["port_name"], params["address"])
|
906
|
-
|
907
|
-
def _bind_sii(self, client, port_name, address=None):
|
908
|
-
self.ensure_one()
|
909
|
-
service = client._get_service("siiService")
|
910
|
-
port = client._get_port(service, port_name)
|
911
|
-
address = address or port.binding_options["address"]
|
912
|
-
return client.create_service(port.binding.name, address)
|
913
|
-
|
914
|
-
def _process_invoice_for_sii_send(self):
|
915
|
-
"""Process invoices for sending to the SII. Adds general checks from
|
916
|
-
configuration parameters and invoice availability for SII. If the
|
917
|
-
invoice is to be sent the decides the send method: direct send or
|
918
|
-
via connector depending on 'Use connector' configuration"""
|
919
|
-
queue_obj = self.env["queue.job"].sudo()
|
920
|
-
for invoice in self:
|
921
|
-
company = invoice.company_id
|
922
|
-
if not company.use_connector:
|
923
|
-
invoice._send_invoice_to_sii()
|
924
|
-
else:
|
925
|
-
eta = company._get_sii_eta()
|
926
|
-
new_delay = (
|
927
|
-
invoice.sudo()
|
928
|
-
.with_context(company_id=company.id)
|
929
|
-
.with_delay(eta=eta if not invoice.sii_send_failed else False)
|
930
|
-
.confirm_one_invoice()
|
931
|
-
)
|
932
|
-
job = queue_obj.search([("uuid", "=", new_delay.uuid)], limit=1)
|
933
|
-
invoice.sudo().invoice_jobs_ids |= job
|
934
|
-
|
935
|
-
def _send_invoice_to_sii(self):
|
936
|
-
for invoice in self.filtered(lambda i: i.state in SII_VALID_INVOICE_STATES):
|
937
|
-
serv = invoice._connect_sii(invoice.move_type)
|
938
|
-
if invoice.sii_state == "not_sent":
|
939
|
-
tipo_comunicacion = "A0"
|
940
|
-
else:
|
941
|
-
tipo_comunicacion = "A1"
|
942
|
-
header = invoice._get_sii_header(tipo_comunicacion)
|
943
|
-
inv_vals = {
|
944
|
-
"sii_header_sent": json.dumps(header, indent=4),
|
945
|
-
}
|
946
|
-
try:
|
947
|
-
inv_dict = invoice._get_sii_invoice_dict()
|
948
|
-
inv_vals["sii_content_sent"] = json.dumps(inv_dict, indent=4)
|
949
|
-
if invoice.move_type in ["out_invoice", "out_refund"]:
|
950
|
-
res = serv.SuministroLRFacturasEmitidas(header, inv_dict)
|
951
|
-
elif invoice.move_type in ["in_invoice", "in_refund"]:
|
952
|
-
res = serv.SuministroLRFacturasRecibidas(header, inv_dict)
|
953
|
-
# TODO Facturas intracomunitarias 66 RIVA
|
954
|
-
# elif invoice.fiscal_position_id.id == self.env.ref(
|
955
|
-
# 'account.fp_intra').id:
|
956
|
-
# res = serv.SuministroLRDetOperacionIntracomunitaria(
|
957
|
-
# header, invoices)
|
958
|
-
res_line = res["RespuestaLinea"][0]
|
959
|
-
if res["EstadoEnvio"] == "Correcto":
|
960
|
-
inv_vals.update(
|
961
|
-
{
|
962
|
-
"sii_state": "sent",
|
963
|
-
"sii_csv": res["CSV"],
|
964
|
-
"sii_send_failed": False,
|
965
|
-
}
|
966
|
-
)
|
967
|
-
elif (
|
968
|
-
res["EstadoEnvio"] == "ParcialmenteCorrecto"
|
969
|
-
and res_line["EstadoRegistro"] == "AceptadoConErrores"
|
970
|
-
):
|
971
|
-
inv_vals.update(
|
972
|
-
{
|
973
|
-
"sii_state": "sent_w_errors",
|
974
|
-
"sii_csv": res["CSV"],
|
975
|
-
"sii_send_failed": True,
|
976
|
-
}
|
977
|
-
)
|
978
|
-
else:
|
979
|
-
inv_vals["sii_send_failed"] = True
|
980
|
-
if (
|
981
|
-
"sii_state" in inv_vals
|
982
|
-
and not invoice.sii_account_registration_date
|
983
|
-
and invoice.move_type[:2] == "in"
|
984
|
-
):
|
985
|
-
inv_vals[
|
986
|
-
"sii_account_registration_date"
|
987
|
-
] = self._get_account_registration_date()
|
988
|
-
inv_vals["sii_return"] = res
|
989
|
-
send_error = False
|
990
|
-
if res_line["CodigoErrorRegistro"]:
|
991
|
-
send_error = "{} | {}".format(
|
992
|
-
str(res_line["CodigoErrorRegistro"]),
|
993
|
-
str(res_line["DescripcionErrorRegistro"])[:60],
|
994
|
-
)
|
995
|
-
inv_vals["sii_send_error"] = send_error
|
996
|
-
invoice.write(inv_vals)
|
997
|
-
except Exception as fault:
|
998
|
-
new_cr = Registry(self.env.cr.dbname).cursor()
|
999
|
-
env = api.Environment(new_cr, self.env.uid, self.env.context)
|
1000
|
-
invoice = env["account.move"].browse(invoice.id)
|
1001
|
-
inv_vals.update(
|
1002
|
-
{
|
1003
|
-
"sii_send_failed": True,
|
1004
|
-
"sii_send_error": repr(fault)[:60],
|
1005
|
-
"sii_return": repr(fault),
|
1006
|
-
}
|
1007
|
-
)
|
1008
|
-
invoice.write(inv_vals)
|
1009
|
-
new_cr.commit()
|
1010
|
-
new_cr.close()
|
1011
|
-
raise
|
1012
|
-
|
1013
581
|
def _sii_invoice_dict_not_modified(self):
|
1014
582
|
self.ensure_one()
|
1015
583
|
to_send = self._get_sii_invoice_dict()
|
@@ -1033,7 +601,7 @@ class AccountMove(models.Model):
|
|
1033
601
|
company = invoice.company_id
|
1034
602
|
if company.sii_method != "auto":
|
1035
603
|
continue
|
1036
|
-
invoice.
|
604
|
+
invoice._process_sii_send()
|
1037
605
|
return res
|
1038
606
|
|
1039
607
|
def process_send_sii(self):
|
@@ -1047,22 +615,11 @@ class AccountMove(models.Model):
|
|
1047
615
|
"context": self.env.context,
|
1048
616
|
}
|
1049
617
|
|
1050
|
-
def
|
1051
|
-
|
1052
|
-
|
1053
|
-
|
1054
|
-
|
1055
|
-
and i.sii_state not in ["sent", "cancelled"]
|
1056
|
-
)
|
1057
|
-
)
|
1058
|
-
if not invoices._cancel_invoice_jobs():
|
1059
|
-
raise exceptions.UserError(
|
1060
|
-
_(
|
1061
|
-
"You can not communicate this invoice at this moment "
|
1062
|
-
"because there is a job running!"
|
1063
|
-
)
|
1064
|
-
)
|
1065
|
-
invoices._process_invoice_for_sii_send()
|
618
|
+
def _get_sii_jobs_field_name(self):
|
619
|
+
return "invoice_jobs_ids"
|
620
|
+
|
621
|
+
def _get_valid_document_states(self):
|
622
|
+
return SII_VALID_INVOICE_STATES
|
1066
623
|
|
1067
624
|
def _cancel_invoice_to_sii(self):
|
1068
625
|
for invoice in self.filtered(lambda i: i.state in ["cancel"]):
|
@@ -1123,7 +680,7 @@ class AccountMove(models.Model):
|
|
1123
680
|
and i.sii_state in ["sent", "sent_w_errors", "sent_modified"]
|
1124
681
|
)
|
1125
682
|
)
|
1126
|
-
if not invoices.
|
683
|
+
if not invoices._cancel_sii_jobs():
|
1127
684
|
raise exceptions.UserError(
|
1128
685
|
_(
|
1129
686
|
"You can not communicate the cancellation of this invoice "
|
@@ -1146,16 +703,8 @@ class AccountMove(models.Model):
|
|
1146
703
|
job = queue_obj.search([("uuid", "=", new_delay.uuid)], limit=1)
|
1147
704
|
invoice.sudo().invoice_jobs_ids |= job
|
1148
705
|
|
1149
|
-
def _cancel_invoice_jobs(self):
|
1150
|
-
for queue in self.sudo().mapped("invoice_jobs_ids"):
|
1151
|
-
if queue.state == "started":
|
1152
|
-
return False
|
1153
|
-
elif queue.state in ("pending", "enqueued", "failed"):
|
1154
|
-
queue.unlink()
|
1155
|
-
return True
|
1156
|
-
|
1157
706
|
def button_cancel(self):
|
1158
|
-
if not self.
|
707
|
+
if not self._cancel_sii_jobs():
|
1159
708
|
raise exceptions.UserError(
|
1160
709
|
_("You can not cancel this invoice because" " there is a job running!")
|
1161
710
|
)
|
@@ -1170,7 +719,7 @@ class AccountMove(models.Model):
|
|
1170
719
|
return res
|
1171
720
|
|
1172
721
|
def button_draft(self):
|
1173
|
-
if not self.
|
722
|
+
if not self._cancel_sii_jobs():
|
1174
723
|
raise exceptions.UserError(
|
1175
724
|
_(
|
1176
725
|
"You can not set to draft this invoice because"
|
@@ -1179,126 +728,17 @@ class AccountMove(models.Model):
|
|
1179
728
|
)
|
1180
729
|
return super().button_draft()
|
1181
730
|
|
1182
|
-
def
|
1183
|
-
|
1184
|
-
|
1185
|
-
|
1186
|
-
|
1187
|
-
|
1188
|
-
|
1189
|
-
|
1190
|
-
if partner_ident:
|
1191
|
-
res = int(partner_ident)
|
1192
|
-
elif self.fiscal_position_id.name == "Régimen Intracomunitario":
|
1193
|
-
res = 2
|
1194
|
-
elif self.fiscal_position_id.name == "Régimen Extracomunitario":
|
1195
|
-
res = 3
|
1196
|
-
else:
|
1197
|
-
res = 1
|
1198
|
-
return res
|
1199
|
-
|
1200
|
-
def _get_sii_identifier(self):
|
1201
|
-
"""Get the SII structure for a partner identifier depending on the
|
1202
|
-
conditions of the invoice.
|
1203
|
-
"""
|
1204
|
-
self.ensure_one()
|
1205
|
-
gen_type = self._get_sii_gen_type()
|
1206
|
-
(
|
1207
|
-
country_code,
|
1208
|
-
identifier_type,
|
1209
|
-
identifier,
|
1210
|
-
) = self._sii_get_partner()._parse_aeat_vat_info()
|
1211
|
-
# Limpiar alfanum
|
1212
|
-
if identifier:
|
1213
|
-
identifier = "".join(e for e in identifier if e.isalnum()).upper()
|
1214
|
-
else:
|
1215
|
-
identifier = "NO_DISPONIBLE"
|
1216
|
-
identifier_type = "06"
|
1217
|
-
if gen_type == 1:
|
1218
|
-
if "1117" in (self.sii_send_error or ""):
|
1219
|
-
return {
|
1220
|
-
"IDOtro": {
|
1221
|
-
"CodigoPais": country_code,
|
1222
|
-
"IDType": "07",
|
1223
|
-
"ID": identifier,
|
1224
|
-
}
|
1225
|
-
}
|
1226
|
-
else:
|
1227
|
-
if identifier_type == "":
|
1228
|
-
return {"NIF": identifier}
|
1229
|
-
return {
|
1230
|
-
"IDOtro": {
|
1231
|
-
"CodigoPais": country_code,
|
1232
|
-
"IDType": identifier_type,
|
1233
|
-
"ID": country_code + identifier
|
1234
|
-
if self.commercial_partner_id._map_aeat_country_code(
|
1235
|
-
country_code
|
1236
|
-
)
|
1237
|
-
in self.commercial_partner_id._get_aeat_europe_codes()
|
1238
|
-
else identifier,
|
1239
|
-
},
|
1240
|
-
}
|
1241
|
-
elif gen_type == 2:
|
1242
|
-
return {"IDOtro": {"IDType": "02", "ID": country_code + identifier}}
|
1243
|
-
elif gen_type == 3 and identifier_type:
|
1244
|
-
# Si usamos identificador tipo 02 en exportaciones, el envío falla con:
|
1245
|
-
# {'CodigoErrorRegistro': 1104,
|
1246
|
-
# 'DescripcionErrorRegistro': 'Valor del campo ID incorrecto'}
|
1247
|
-
if identifier_type == "02":
|
1248
|
-
identifier_type = "06"
|
1249
|
-
return {
|
1250
|
-
"IDOtro": {
|
1251
|
-
"CodigoPais": country_code,
|
1252
|
-
"IDType": identifier_type,
|
1253
|
-
"ID": identifier,
|
1254
|
-
},
|
1255
|
-
}
|
1256
|
-
elif gen_type == 3:
|
1257
|
-
return {"NIF": identifier}
|
1258
|
-
|
1259
|
-
def _get_sii_exempt_cause(self, applied_taxes):
|
1260
|
-
"""Código de la causa de exención según 3.6 y 3.7 de la FAQ del SII.
|
1261
|
-
|
1262
|
-
:param applied_taxes: Taxes that are exempt for filtering the lines.
|
1263
|
-
"""
|
1264
|
-
self.ensure_one()
|
1265
|
-
gen_type = self._get_sii_gen_type()
|
1266
|
-
if gen_type == 2:
|
1267
|
-
return "E5"
|
1268
|
-
else:
|
1269
|
-
exempt_cause = False
|
1270
|
-
product_exempt_causes = (
|
1271
|
-
self.mapped("invoice_line_ids")
|
1272
|
-
.filtered(
|
1273
|
-
lambda x: (
|
1274
|
-
any(tax in x.tax_ids for tax in applied_taxes)
|
1275
|
-
and x.product_id.sii_exempt_cause
|
1276
|
-
and x.product_id.sii_exempt_cause != "none"
|
1277
|
-
)
|
731
|
+
def _get_document_product_exempt(self, applied_taxes):
|
732
|
+
return set(
|
733
|
+
self.mapped("invoice_line_ids")
|
734
|
+
.filtered(
|
735
|
+
lambda x: (
|
736
|
+
any(tax in x.tax_ids for tax in applied_taxes)
|
737
|
+
and x.product_id.sii_exempt_cause
|
738
|
+
and x.product_id.sii_exempt_cause != "none"
|
1278
739
|
)
|
1279
|
-
.mapped("product_id.sii_exempt_cause")
|
1280
740
|
)
|
1281
|
-
|
1282
|
-
if len(product_exempt_causes) > 1:
|
1283
|
-
raise exceptions.UserError(
|
1284
|
-
_("Currently there's no support for multiple exempt " "causes.")
|
1285
|
-
)
|
1286
|
-
if product_exempt_causes:
|
1287
|
-
exempt_cause = product_exempt_causes.pop()
|
1288
|
-
elif (
|
1289
|
-
self.fiscal_position_id.sii_exempt_cause
|
1290
|
-
and self.fiscal_position_id.sii_exempt_cause != "none"
|
1291
|
-
):
|
1292
|
-
exempt_cause = self.fiscal_position_id.sii_exempt_cause
|
1293
|
-
if gen_type == 3 and exempt_cause not in ["E2", "E3"]:
|
1294
|
-
exempt_cause = "E2"
|
1295
|
-
return exempt_cause
|
1296
|
-
|
1297
|
-
def _get_no_taxable_cause(self):
|
1298
|
-
self.ensure_one()
|
1299
|
-
return (
|
1300
|
-
self.fiscal_position_id.sii_no_taxable_cause
|
1301
|
-
or "ImporteTAIReglasLocalizacion"
|
741
|
+
.mapped("product_id.sii_exempt_cause")
|
1302
742
|
)
|
1303
743
|
|
1304
744
|
def is_sii_invoice(self):
|
@@ -1310,10 +750,6 @@ class AccountMove(models.Model):
|
|
1310
750
|
"""
|
1311
751
|
self.ensure_one()
|
1312
752
|
|
1313
|
-
def _get_sii_country_code(self):
|
1314
|
-
self.ensure_one()
|
1315
|
-
return self._sii_get_partner()._parse_aeat_vat_info()[0]
|
1316
|
-
|
1317
753
|
@api.depends(
|
1318
754
|
"invoice_line_ids",
|
1319
755
|
"invoice_line_ids.name",
|
@@ -1386,8 +822,5 @@ class AccountMove(models.Model):
|
|
1386
822
|
)
|
1387
823
|
return res
|
1388
824
|
|
1389
|
-
def confirm_one_invoice(self):
|
1390
|
-
self.sudo()._send_invoice_to_sii()
|
1391
|
-
|
1392
825
|
def cancel_one_invoice(self):
|
1393
826
|
self.sudo()._cancel_invoice_to_sii()
|