odoo-addon-l10n-es-verifactu-oca 16.0.1.0.0.22__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.

Potentially problematic release.


This version of odoo-addon-l10n-es-verifactu-oca might be problematic. Click here for more details.

Files changed (68) hide show
  1. odoo/addons/l10n_es_verifactu_oca/README.rst +151 -0
  2. odoo/addons/l10n_es_verifactu_oca/__init__.py +2 -0
  3. odoo/addons/l10n_es_verifactu_oca/__manifest__.py +43 -0
  4. odoo/addons/l10n_es_verifactu_oca/data/account_fiscal_position_template_data.xml +149 -0
  5. odoo/addons/l10n_es_verifactu_oca/data/ir_config_parameter.xml +9 -0
  6. odoo/addons/l10n_es_verifactu_oca/data/ir_cron.xml +14 -0
  7. odoo/addons/l10n_es_verifactu_oca/data/mail_activity_data.xml +11 -0
  8. odoo/addons/l10n_es_verifactu_oca/data/neutralize.sql +2 -0
  9. odoo/addons/l10n_es_verifactu_oca/data/verifactu_map_data.xml +128 -0
  10. odoo/addons/l10n_es_verifactu_oca/data/verifactu_registration_key_data.xml +207 -0
  11. odoo/addons/l10n_es_verifactu_oca/data/verifactu_tax_agency_data.xml +19 -0
  12. odoo/addons/l10n_es_verifactu_oca/i18n/es.po +1640 -0
  13. odoo/addons/l10n_es_verifactu_oca/i18n/l10n_es_verifactu_oca.pot +1598 -0
  14. odoo/addons/l10n_es_verifactu_oca/models/__init__.py +15 -0
  15. odoo/addons/l10n_es_verifactu_oca/models/account_fiscal_position.py +34 -0
  16. odoo/addons/l10n_es_verifactu_oca/models/account_fiscal_position_template.py +18 -0
  17. odoo/addons/l10n_es_verifactu_oca/models/account_journal.py +64 -0
  18. odoo/addons/l10n_es_verifactu_oca/models/account_move.py +558 -0
  19. odoo/addons/l10n_es_verifactu_oca/models/aeat_tax_agency.py +30 -0
  20. odoo/addons/l10n_es_verifactu_oca/models/res_company.py +48 -0
  21. odoo/addons/l10n_es_verifactu_oca/models/res_partner.py +26 -0
  22. odoo/addons/l10n_es_verifactu_oca/models/verifactu_chaining.py +30 -0
  23. odoo/addons/l10n_es_verifactu_oca/models/verifactu_developer.py +16 -0
  24. odoo/addons/l10n_es_verifactu_oca/models/verifactu_invoice_entry.py +378 -0
  25. odoo/addons/l10n_es_verifactu_oca/models/verifactu_invoice_entry_response.py +122 -0
  26. odoo/addons/l10n_es_verifactu_oca/models/verifactu_invoice_entry_response_line.py +35 -0
  27. odoo/addons/l10n_es_verifactu_oca/models/verifactu_map.py +66 -0
  28. odoo/addons/l10n_es_verifactu_oca/models/verifactu_mixin.py +449 -0
  29. odoo/addons/l10n_es_verifactu_oca/models/verifactu_registration_key.py +24 -0
  30. odoo/addons/l10n_es_verifactu_oca/readme/CONFIGURE.rst +18 -0
  31. odoo/addons/l10n_es_verifactu_oca/readme/CONTRIBUTORS.rst +16 -0
  32. odoo/addons/l10n_es_verifactu_oca/readme/DESCRIPTION.rst +1 -0
  33. odoo/addons/l10n_es_verifactu_oca/readme/INSTALL.rst +4 -0
  34. odoo/addons/l10n_es_verifactu_oca/readme/ROADMAP.rst +15 -0
  35. odoo/addons/l10n_es_verifactu_oca/readme/USAGE.rst +1 -0
  36. odoo/addons/l10n_es_verifactu_oca/security/ir.model.access.csv +22 -0
  37. odoo/addons/l10n_es_verifactu_oca/security/verifactu_security.xml +6 -0
  38. odoo/addons/l10n_es_verifactu_oca/static/description/icon.png +0 -0
  39. odoo/addons/l10n_es_verifactu_oca/static/description/index.html +505 -0
  40. odoo/addons/l10n_es_verifactu_oca/tests/__init__.py +2 -0
  41. odoo/addons/l10n_es_verifactu_oca/tests/common.py +276 -0
  42. odoo/addons/l10n_es_verifactu_oca/tests/json/verifactu_mocked_response_1.json +35 -0
  43. odoo/addons/l10n_es_verifactu_oca/tests/json/verifactu_mocked_response_2.json +35 -0
  44. odoo/addons/l10n_es_verifactu_oca/tests/json/verifactu_out_invoice_s_iva10b_s_iva21s_dict.json +59 -0
  45. odoo/addons/l10n_es_verifactu_oca/tests/json/verifactu_out_invoice_s_iva21s_s_req52_dict.json +58 -0
  46. odoo/addons/l10n_es_verifactu_oca/tests/json/verifactu_out_refund_s_iva10b_s_iva10b_s_iva21s_dict.json +66 -0
  47. odoo/addons/l10n_es_verifactu_oca/tests/test_10n_es_verifactu.py +392 -0
  48. odoo/addons/l10n_es_verifactu_oca/tests/test_verifactu_invoice.py +348 -0
  49. odoo/addons/l10n_es_verifactu_oca/views/account_fiscal_position_view.xml +30 -0
  50. odoo/addons/l10n_es_verifactu_oca/views/account_journal_view.xml +28 -0
  51. odoo/addons/l10n_es_verifactu_oca/views/account_move_view.xml +219 -0
  52. odoo/addons/l10n_es_verifactu_oca/views/aeat_tax_agency_view.xml +31 -0
  53. odoo/addons/l10n_es_verifactu_oca/views/report_invoice.xml +26 -0
  54. odoo/addons/l10n_es_verifactu_oca/views/res_company_view.xml +50 -0
  55. odoo/addons/l10n_es_verifactu_oca/views/res_partner_view.xml +14 -0
  56. odoo/addons/l10n_es_verifactu_oca/views/verifactu_chaining_view.xml +47 -0
  57. odoo/addons/l10n_es_verifactu_oca/views/verifactu_developer_view.xml +48 -0
  58. odoo/addons/l10n_es_verifactu_oca/views/verifactu_invoice_entry_response_view.xml +149 -0
  59. odoo/addons/l10n_es_verifactu_oca/views/verifactu_invoice_entry_view.xml +124 -0
  60. odoo/addons/l10n_es_verifactu_oca/views/verifactu_map_lines_view.xml +20 -0
  61. odoo/addons/l10n_es_verifactu_oca/views/verifactu_map_view.xml +53 -0
  62. odoo/addons/l10n_es_verifactu_oca/views/verifactu_registration_keys_view.xml +42 -0
  63. odoo/addons/l10n_es_verifactu_oca/wizards/__init__.py +1 -0
  64. odoo/addons/l10n_es_verifactu_oca/wizards/account_move_reversal.py +16 -0
  65. odoo_addon_l10n_es_verifactu_oca-16.0.1.0.0.22.dist-info/METADATA +168 -0
  66. odoo_addon_l10n_es_verifactu_oca-16.0.1.0.0.22.dist-info/RECORD +68 -0
  67. odoo_addon_l10n_es_verifactu_oca-16.0.1.0.0.22.dist-info/WHEEL +5 -0
  68. odoo_addon_l10n_es_verifactu_oca-16.0.1.0.0.22.dist-info/top_level.txt +1 -0
@@ -0,0 +1,35 @@
1
+ # Copyright 2025 ForgeFlow S.L.
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+
4
+ from odoo import fields, models
5
+
6
+ from ..models.verifactu_invoice_entry import VERIFACTU_SEND_STATES
7
+
8
+
9
+ class VerifactuInvoiceEntryResponseLine(models.Model):
10
+ _name = "verifactu.invoice.entry.response.line"
11
+ _description = "VERI*FACTU send log"
12
+ _order = "id desc"
13
+
14
+ entry_id = fields.Many2one(comodel_name="verifactu.invoice.entry", required=True)
15
+ entry_response_id = fields.Many2one("verifactu.invoice.entry.response")
16
+ model = fields.Char(readonly=True)
17
+ document_id = fields.Many2oneReference(
18
+ string="Document", model_field="model", readonly=True, index=True
19
+ )
20
+ response = fields.Text()
21
+ send_state = fields.Selection(
22
+ selection=VERIFACTU_SEND_STATES,
23
+ string="VERI*FACTU send state",
24
+ default="not_sent",
25
+ readonly=True,
26
+ copy=False,
27
+ help="Indicates the state of this document in relation with the "
28
+ "presentation to VERI*FACTU.",
29
+ )
30
+ verifactu_csv = fields.Text(related="entry_response_id.verifactu_csv")
31
+ error_code = fields.Char()
32
+
33
+ @property
34
+ def document(self):
35
+ return self.env[self.model].browse(self.document_id).exists()
@@ -0,0 +1,66 @@
1
+ # Copyright 2024 Aures TIC - Almudena de La Puente <almudena@aurestic.es>
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+ from odoo import _, api, exceptions, fields, models
4
+
5
+
6
+ class AeatVerifactuMap(models.Model):
7
+ _name = "verifactu.map"
8
+ _description = "VERI*FACTU mapping"
9
+
10
+ name = fields.Char(string="Model", required=True)
11
+ date_from = fields.Date()
12
+ date_to = fields.Date()
13
+ map_lines = fields.One2many(
14
+ comodel_name="verifactu.map.line",
15
+ inverse_name="verifactu_map_id",
16
+ string="Lines",
17
+ )
18
+
19
+ @api.constrains("date_from", "date_to")
20
+ def _unique_date_range(self):
21
+ for record in self:
22
+ record._unique_date_range_one()
23
+
24
+ def _unique_date_range_one(self):
25
+ # Based in l10n_es_aeat module
26
+ domain = [("id", "!=", self.id)]
27
+ if self.date_from and self.date_to:
28
+ domain += [
29
+ "|",
30
+ "&",
31
+ ("date_from", "<=", self.date_to),
32
+ ("date_from", ">=", self.date_from),
33
+ "|",
34
+ "&",
35
+ ("date_to", "<=", self.date_to),
36
+ ("date_to", ">=", self.date_from),
37
+ "|",
38
+ "&",
39
+ ("date_from", "=", False),
40
+ ("date_to", ">=", self.date_from),
41
+ "|",
42
+ "&",
43
+ ("date_to", "=", False),
44
+ ("date_from", "<=", self.date_to),
45
+ ]
46
+ elif self.date_from:
47
+ domain += [("date_to", ">=", self.date_from)]
48
+ elif self.date_to:
49
+ domain += [("date_from", "<=", self.date_to)]
50
+ date_lst = self.search(domain)
51
+ if date_lst:
52
+ raise exceptions.UserError(
53
+ _("Error! The dates of the record overlap with an existing " "record.")
54
+ )
55
+
56
+
57
+ class AeatVerifactuMapLines(models.Model):
58
+ _name = "verifactu.map.line"
59
+ _description = "VERI*FACTU mapping line"
60
+
61
+ code = fields.Char(required=True)
62
+ name = fields.Char()
63
+ taxes = fields.Many2many(comodel_name="account.tax.template")
64
+ verifactu_map_id = fields.Many2one(
65
+ comodel_name="verifactu.map", string="Parent mapping", ondelete="cascade"
66
+ )
@@ -0,0 +1,449 @@
1
+ # Copyright 2024 Aures TIC - Jose Zambudio
2
+ # Copyright 2024 Aures TIC - Almudena de La Puente
3
+ # Copyright 2025 Tecnativa - Pedro M. Baeza
4
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
5
+
6
+ import base64
7
+ import io
8
+ import json
9
+ from hashlib import sha256
10
+ from urllib.parse import urlencode
11
+
12
+ import psycopg2
13
+ import qrcode
14
+
15
+ from odoo import _, api, fields, models
16
+ from odoo.exceptions import UserError
17
+ from odoo.tools.float_utils import float_compare
18
+
19
+ from odoo.addons.l10n_es_aeat.models.aeat_mixin import round_by_keys
20
+
21
+ VERIFACTU_VERSION = 1.0
22
+ VERIFACTU_DATE_FORMAT = "%d-%m-%Y"
23
+ VERIFACTU_MACRODATA_LIMIT = 100000000.0
24
+
25
+
26
+ class VerifactuMixin(models.AbstractModel):
27
+ _name = "verifactu.mixin"
28
+ _inherit = "aeat.mixin"
29
+ _description = "VERI*FACTU mixin"
30
+
31
+ verifactu_enabled = fields.Boolean(
32
+ string="VERI*FACTU enabled",
33
+ compute="_compute_verifactu_enabled",
34
+ search="_search_verifactu_enabled",
35
+ )
36
+ verifactu_hash_string = fields.Char(
37
+ string="VERI*FACTU hash string", copy=False, tracking=True
38
+ )
39
+ verifactu_hash = fields.Char(string="VERI*FACTU hash", copy=False, tracking=True)
40
+ verifactu_refund_type = fields.Selection(
41
+ string="VERI*FACTU refund type",
42
+ selection=[
43
+ # ('S', 'By substitution'), - TODO: no está soportado por el momento
44
+ ("I", "By differences"),
45
+ ],
46
+ compute="_compute_verifactu_refund_type",
47
+ store=True,
48
+ readonly=False,
49
+ )
50
+ verifactu_description = fields.Text(string="VERI*FACTU description", copy=False)
51
+ verifactu_macrodata = fields.Boolean(
52
+ string="VERI*FACTU macrodata?",
53
+ help="Check to confirm that the document has an absolute amount "
54
+ "greater o equal to 100 000 000,00 euros.",
55
+ compute="_compute_verifactu_macrodata",
56
+ )
57
+ verifactu_csv = fields.Char(string="VERI*FACTU CSV", copy=False, readonly=True)
58
+ verifactu_return = fields.Text(
59
+ string="VERI*FACTU return", copy=False, readonly=True
60
+ )
61
+ verifactu_registration_date = fields.Datetime(
62
+ string="VERI*FACTU registration date", copy=False
63
+ )
64
+ verifactu_registration_key = fields.Many2one(
65
+ string="VERI*FACTU registration key",
66
+ comodel_name="verifactu.registration.key",
67
+ compute="_compute_verifactu_registration_key",
68
+ store=True,
69
+ readonly=False,
70
+ )
71
+ verifactu_tax_key = fields.Selection(
72
+ string="VERI*FACTU tax key",
73
+ selection="_get_verifactu_tax_keys",
74
+ compute="_compute_verifactu_tax_key",
75
+ store=True,
76
+ readonly=False,
77
+ )
78
+ verifactu_registration_key_code = fields.Char(
79
+ string="VERI*FACTU key code", compute="_compute_verifactu_registration_key_code"
80
+ )
81
+ verifactu_qr_url = fields.Char(
82
+ string="VERI*FACTU URL", compute="_compute_verifactu_qr_url"
83
+ )
84
+ verifactu_qr = fields.Binary(
85
+ string="VERI*FACTU QR", compute="_compute_verifactu_qr"
86
+ )
87
+ verifactu_send_date = fields.Datetime(
88
+ string="VERI*FACTU send date", index=True, copy=False
89
+ )
90
+ verifactu_invoice_entry_ids = fields.One2many(
91
+ comodel_name="verifactu.invoice.entry",
92
+ inverse_name="document_id",
93
+ domain=lambda doc: [("model", "=", doc._name)],
94
+ string="VERI*FACTU invoice entries",
95
+ readonly=True,
96
+ copy=False,
97
+ )
98
+ verifactu_response_line_ids = fields.One2many(
99
+ comodel_name="verifactu.invoice.entry.response.line",
100
+ inverse_name="document_id",
101
+ domain=lambda doc: [("model", "=", doc._name)],
102
+ string="VERI*FACTU response lines",
103
+ readonly=True,
104
+ copy=False,
105
+ )
106
+ last_verifactu_invoice_entry_id = fields.Many2one(
107
+ comodel_name="verifactu.invoice.entry",
108
+ string="VERI*FACTU last invoice entry",
109
+ readonly=True,
110
+ copy=False,
111
+ )
112
+ last_verifactu_response_line_id = fields.Many2one(
113
+ comodel_name="verifactu.invoice.entry.response.line",
114
+ string="VERI*FACTU response line",
115
+ readonly=True,
116
+ copy=False,
117
+ )
118
+
119
+ @api.model
120
+ def _get_verifactu_reference_models(self):
121
+ """This method is used to define the models that can be used as
122
+ previous documents in the VERI*FACTU mixin.
123
+ """
124
+ return ["account.move"]
125
+
126
+ def _compute_verifactu_enabled(self):
127
+ raise NotImplementedError
128
+
129
+ def _compute_verifactu_macrodata(self):
130
+ for document in self:
131
+ document.verifactu_macrodata = (
132
+ float_compare(
133
+ abs(document._get_verifactu_amount_total()),
134
+ VERIFACTU_MACRODATA_LIMIT,
135
+ precision_digits=2,
136
+ )
137
+ >= 0
138
+ )
139
+
140
+ def _compute_verifactu_qr_url(self):
141
+ """Returns the URL to be used in the QR code. A sample URL would be (urlencoded):
142
+ https://prewww2.aeat.es/wlpl/TIKECONT/ValidarQR?nif=89890001K&numserie=12345678%26G33&fecha=01-01-2024&importe=241.4
143
+ """ # noqa: B950
144
+ for record in self:
145
+ # FIXME: Not be hard-coded
146
+ agency = self.env.ref("l10n_es_aeat.aeat_tax_agency_spain")
147
+ if record.company_id.verifactu_test:
148
+ qr_base_url = agency.verifactu_qr_base_url_test_address
149
+ else:
150
+ qr_base_url = agency.verifactu_qr_base_url
151
+ qr_values = record._get_verifactu_qr_values()
152
+ # Check all values are ASCII between 32 and 126
153
+ for value in qr_values.values():
154
+ try:
155
+ str(value).encode("ascii")
156
+ except UnicodeEncodeError as uee:
157
+ raise UserError(
158
+ _("QR URL value '{}' is not ASCII").format(value)
159
+ ) from uee
160
+ # Build QR URL
161
+ qr_url = urlencode(qr_values, encoding="utf-8")
162
+ record.verifactu_qr_url = f"{qr_base_url}?{qr_url}"
163
+
164
+ def _compute_verifactu_qr(self):
165
+ for record in self:
166
+ if record.state != "posted" or not record.verifactu_enabled:
167
+ record.verifactu_qr = False
168
+ continue
169
+ qr = qrcode.QRCode(
170
+ border=0, error_correction=qrcode.constants.ERROR_CORRECT_M
171
+ )
172
+ qr.add_data(record.verifactu_qr_url)
173
+ qr.make()
174
+ img = qr.make_image()
175
+ with io.BytesIO() as temp:
176
+ img.save(temp, format="PNG")
177
+ record.verifactu_qr = base64.b64encode(temp.getvalue())
178
+
179
+ def _compute_verifactu_registration_key(self):
180
+ raise NotImplementedError()
181
+
182
+ def _compute_verifactu_tax_key(self):
183
+ raise NotImplementedError()
184
+
185
+ @api.depends("verifactu_registration_key")
186
+ def _compute_verifactu_registration_key_code(self):
187
+ for record in self:
188
+ record.verifactu_registration_key_code = (
189
+ record.verifactu_registration_key.code
190
+ )
191
+
192
+ @api.model
193
+ def _search_verifactu_enabled(self, operator, value):
194
+ if operator not in ("=", "!="):
195
+ raise ValueError(_("Unsupported search operator"))
196
+ return [("company_id.verifactu_enabled", operator, value)]
197
+
198
+ def _get_verifactu_qr_values(self):
199
+ raise NotImplementedError
200
+
201
+ @api.model
202
+ def _get_verifactu_tax_keys(self):
203
+ return self.env["account.fiscal.position"]._get_verifactu_tax_keys()
204
+
205
+ def _connect_verifactu_params_aeat(self, mapping_key):
206
+ self.ensure_one()
207
+ agency = self.company_id.tax_agency_id
208
+ if not agency:
209
+ # We use spanish agency by default to keep old behavior with
210
+ # ir.config parameters. In the future it might be good to reinforce
211
+ # to explicitly set a tax agency in the company by raising an error
212
+ # here.
213
+ agency = self.env.ref("l10n_es_aeat.aeat_tax_agency_spain")
214
+ return agency._connect_params_verifactu(self.company_id)
215
+
216
+ def _get_verifactu_invoice_dict(self):
217
+ self.ensure_one()
218
+ inv_dict = {}
219
+ mapping_key = self._get_mapping_key()
220
+ if mapping_key in ["out_invoice", "out_refund"]:
221
+ inv_dict = self._get_verifactu_invoice_dict_out()
222
+ else:
223
+ raise NotImplementedError
224
+ round_by_keys(
225
+ inv_dict,
226
+ [
227
+ "BaseImponibleOimporteNoSujeto",
228
+ "CuotaRepercutida",
229
+ "TipoRecargoEquivalencia",
230
+ "CuotaRecargoEquivalencia",
231
+ "CuotaTotal",
232
+ "ImporteTotal",
233
+ "BaseRectificada",
234
+ "CuotaRectificada",
235
+ ],
236
+ )
237
+ return inv_dict
238
+
239
+ def _get_verifactu_developer_dict(self):
240
+ """Datos del desarrollador del sistema informático."""
241
+ if not self.company_id.verifactu_developer_id:
242
+ raise UserError(
243
+ _("Please, configure the VERI*FACTU developer in your company")
244
+ )
245
+ developer = self.company_id.verifactu_developer_id
246
+ chaining = self._get_verifactu_chaining()
247
+ verifactu_companies = (
248
+ self.env["res.company"]
249
+ .sudo()
250
+ .search_count([("verifactu_enabled", "=", True)])
251
+ )
252
+ return {
253
+ "NombreRazon": developer.name,
254
+ "NIF": developer.vat,
255
+ "NombreSistemaInformatico": developer.sif_name,
256
+ "IdSistemaInformatico": chaining.sif_id,
257
+ "Version": developer.version,
258
+ "NumeroInstalacion": chaining.installation_number,
259
+ "TipoUsoPosibleSoloVerifactu": "S",
260
+ "TipoUsoPosibleMultiOT": "S",
261
+ "IndicadorMultiplesOT": "S" if verifactu_companies > 1 else "N",
262
+ "IDOtro": {
263
+ "IDType": "",
264
+ "ID": "",
265
+ },
266
+ }
267
+
268
+ def _get_verifactu_chaining_invoice_dict(self):
269
+ raise NotImplementedError
270
+
271
+ def _aeat_check_exceptions(self):
272
+ """Inheritable method for exceptions control when sending VERI*FACTU invoices."""
273
+ res = super()._aeat_check_exceptions()
274
+ if self.company_id.verifactu_enabled and not self.verifactu_enabled:
275
+ raise UserError(_("This invoice is not VERI*FACTU enabled."))
276
+ return res
277
+
278
+ def _get_verifactu_date(self, date):
279
+ datetimeobject = fields.Date.to_date(date)
280
+ return datetimeobject.strftime(VERIFACTU_DATE_FORMAT)
281
+
282
+ def _get_verifactu_hash_string(self):
283
+ raise NotImplementedError
284
+
285
+ def _get_verifactu_chaining(self):
286
+ return NotImplementedError
287
+
288
+ def _generate_verifactu_chaining(self, entry_type=False):
289
+ """Generate VERI*FACTU invoice entry for company-wide chaining."""
290
+ self.ensure_one()
291
+ chaining = self._get_verifactu_chaining()
292
+ chaining.flush_recordset(["last_verifactu_invoice_entry_id"])
293
+ try:
294
+ with self.env.cr.savepoint():
295
+ self.env.cr.execute(
296
+ f"SELECT last_verifactu_invoice_entry_id FROM {chaining._table}"
297
+ " WHERE id = %s FOR UPDATE NOWAIT",
298
+ [chaining.id],
299
+ )
300
+ result = self.env.cr.fetchone()
301
+ previous_invoice_entry_id = result[0] if result and result[0] else False
302
+ invoice_vals = {
303
+ "verifactu_chaining_id": chaining.id,
304
+ "model": self._name,
305
+ "document_id": self.id,
306
+ "document_name": self.name,
307
+ "previous_invoice_entry_id": previous_invoice_entry_id,
308
+ "company_id": self.company_id.id,
309
+ "document_hash": "",
310
+ }
311
+ if entry_type:
312
+ invoice_vals["entry_type"] = entry_type
313
+ invoice_entry = self.env["verifactu.invoice.entry"].create(invoice_vals)
314
+ self.last_verifactu_invoice_entry_id = invoice_entry
315
+ verifactu_hash_values = self._get_verifactu_hash_string()
316
+ self.verifactu_hash_string = verifactu_hash_values
317
+ hash_string = sha256(verifactu_hash_values.encode("utf-8"))
318
+ self.verifactu_hash = hash_string.hexdigest().upper()
319
+ # Generate JSON data for AEAT
320
+ aeat_json_data = ""
321
+ try:
322
+ inv_dict = self._get_verifactu_invoice_dict()
323
+ aeat_json_data = json.dumps(inv_dict, indent=4)
324
+ except Exception:
325
+ # If JSON generation fails, store empty string
326
+ aeat_json_data = ""
327
+ invoice_entry.document_hash = hash_string.hexdigest().upper()
328
+ invoice_entry.aeat_json_data = aeat_json_data
329
+ self.env.cr.execute(
330
+ f"UPDATE {chaining._table} "
331
+ "SET last_verifactu_invoice_entry_id = %s WHERE id = %s",
332
+ [invoice_entry.id, chaining.id],
333
+ )
334
+ chaining.invalidate_recordset(["last_verifactu_invoice_entry_id"])
335
+ except psycopg2.OperationalError as err:
336
+ if err.pgcode == "55P03": # could not obtain the lock
337
+ raise UserError(
338
+ _(
339
+ "Could not obtain last document sent to VERI*FACTU for "
340
+ "chaining %s.",
341
+ chaining.name,
342
+ )
343
+ ) from err
344
+ raise
345
+
346
+ def _get_verifactu_document_type(self):
347
+ raise NotImplementedError()
348
+
349
+ def _get_verifactu_description(self):
350
+ raise NotImplementedError()
351
+
352
+ def _get_verifactu_taxes_and_total(self):
353
+ raise NotImplementedError
354
+
355
+ def _get_verifactu_version(self):
356
+ return VERIFACTU_VERSION
357
+
358
+ def _get_verifactu_receiver_dict(self):
359
+ raise NotImplementedError
360
+
361
+ def _compute_verifactu_refund_type(self):
362
+ self.verifactu_refund_type = False
363
+
364
+ def _get_verifactu_accepted_tax_agencies(self):
365
+ return ["l10n_es_aeat.aeat_tax_agency_spain"]
366
+
367
+ def _check_verifactu_configuration(self, suffixes=None):
368
+ prefix = _("The invoice %s cannot be sent to VERI*FACTU because:")
369
+ if not suffixes:
370
+ suffixes = []
371
+ if not self._get_verifactu_chaining():
372
+ suffixes.append(
373
+ _("- Your company does not have a VERI*FACTU chaining configured.")
374
+ )
375
+ if not self.company_id.tax_agency_id:
376
+ suffixes.append(_("- Your company does not have a tax agency configured."))
377
+ if (
378
+ self.company_id.tax_agency_id.get_external_id
379
+ in self._get_verifactu_accepted_tax_agencies()
380
+ ):
381
+ suffixes.append(_("- Your company's tax agency is not supported."))
382
+ if not self.company_id.verifactu_developer_id:
383
+ suffixes.append(
384
+ _("- Your company does not have a VERI*FACTU developer configured.")
385
+ )
386
+ if not self.company_id.country_code or self.company_id.country_code != "ES":
387
+ suffixes.append(_("Your company is not registered in Spain."))
388
+ if suffixes:
389
+ raise UserError((prefix + "\n".join(suffixes)) % self[self._rec_name])
390
+
391
+ @api.model
392
+ def _get_verifactu_map(self, date):
393
+ return (
394
+ self.env["verifactu.map"]
395
+ .sudo()
396
+ .with_context(active_test=False)
397
+ .search(
398
+ [
399
+ "|",
400
+ ("date_from", "<=", date),
401
+ ("date_from", "=", False),
402
+ "|",
403
+ ("date_to", ">=", date),
404
+ ("date_to", "=", False),
405
+ ],
406
+ limit=1,
407
+ )
408
+ )
409
+
410
+ @api.model
411
+ def _get_verifactu_taxes_map(self, codes, date):
412
+ """Return the codes that correspond to verifactu map line codes.
413
+
414
+ :param codes: List of code strings to get the mapping.
415
+ :param date: Date to map
416
+ :return: Recordset with the corresponding codes
417
+ """
418
+ verifactu_map = self._get_verifactu_map(date)
419
+ tax_templates = verifactu_map.map_lines.filtered(
420
+ lambda x: x.code in codes
421
+ ).taxes
422
+ return self.company_id.get_taxes_from_templates(tax_templates)
423
+
424
+ def _raise_exception_verifactu(self, field_name):
425
+ raise UserError(
426
+ _(
427
+ "You cannot change the %s of document "
428
+ "already registered at VERI*FACTU. You must cancel the "
429
+ "document and create a new one with the correct value."
430
+ )
431
+ % field_name
432
+ )
433
+
434
+ @api.model
435
+ def _get_verifactu_batch(self):
436
+ try:
437
+ return int(
438
+ self.env["ir.config_parameter"]
439
+ .sudo()
440
+ .get_param("l10n_es_verifactu_oca.verifactu_batch", "1000")
441
+ )
442
+ except ValueError as e:
443
+ raise UserError(
444
+ _(
445
+ "The value in l10n_es_verifactu_oca.verifactu_batch "
446
+ "system parameter must be an integer. Please, check the "
447
+ "value of the parameter."
448
+ )
449
+ ) from e
@@ -0,0 +1,24 @@
1
+ # Copyright 2024 Aures TIC - Almudena de La Puente
2
+ # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
3
+
4
+ from odoo import api, fields, models
5
+
6
+
7
+ class AeatVerifactuMappingRegistrationKeys(models.Model):
8
+ _name = "verifactu.registration.key"
9
+ _description = "VERI*FACTU registration key"
10
+
11
+ code = fields.Char(required=True, size=2)
12
+ name = fields.Char(required=True)
13
+ verifactu_tax_key = fields.Selection(
14
+ selection="_get_verifactu_tax_keys",
15
+ string="VERI*FACTU tax key",
16
+ required=True,
17
+ )
18
+
19
+ def name_get(self):
20
+ return [(x.id, f"[{x.code}]-{x.name}") for x in self]
21
+
22
+ @api.model
23
+ def _get_verifactu_tax_keys(self):
24
+ return self.env["account.fiscal.position"]._get_verifactu_tax_keys()
@@ -0,0 +1,18 @@
1
+ Para configurar este módulo es necesario:
2
+
3
+ #. Acceder a Facturación/Contabilidad -> Configuración -> AEAT -> Agencia Tributaria, podrás consultar las URLs del servicio SOAP de Hacienda.
4
+ Estas URLs pueden cambiar según comunidades
5
+ #. El certificado enviado por la FMNT es en formato p12, este certificado no se puede usar directamente con Zeep.
6
+ Accede a Facturación/Contabilidad -> Configuración -> AEAT -> Certificados AEAT, y allí podrás:
7
+ Subir el certificado p12 y extraer las claves públicas y privadas con el botón "Obtener claves"
8
+ #. Debes tener en cuenta que los certificados se alojan en una carpeta accesible por la instalación de Odoo.
9
+ #. Completar los datos de desarrollador y del encadenamiento a nivel de compañía en la pestaña de VERI*FACTU.
10
+
11
+ En caso de que la obtención de claves no funcione y uses Linux, cuentas con los siguientes comandos para tratar de solucionarlo:
12
+
13
+ - Clave pública: "openssl pkcs12 -in Certificado.p12 -nokeys -out publicCert.crt -nodes"
14
+ - Clave privada: "openssl pkcs12 -in Certificado.p12 -nocerts -out privateKey.pem -nodes"
15
+
16
+ #. Establecer en las posiciones fiscales la clave de impuestos y la clave de registro VERI*FACTU.
17
+ #. Para aplicar las claves ejecute el asistente de actualización del módulo account_chart_update.
18
+
@@ -0,0 +1,16 @@
1
+ * Aures TIC:
2
+ * Jose Zambudio
3
+ * Almudena de La Puente
4
+ * Anna Martínez
5
+ * ForgeFlow S.L.:
6
+ * Laura Cazorla
7
+ * Andreu Orensanz
8
+ * Jordi Ballester
9
+ * Ozono multimedia:
10
+ * Iván Antón
11
+ * SDi:
12
+ * Fernando La Chica
13
+ * Process control:
14
+ * Jorge Luis López
15
+ * Tecnativa:
16
+ * Pedro M. Baeza
@@ -0,0 +1 @@
1
+ Módulo para la presentación inmediata de la facturación.
@@ -0,0 +1,4 @@
1
+ Para instalar esté módulo necesita:
2
+
3
+ #. Libreria Python Zeep, se puede instalar con el comando 'pip install zeep'
4
+ #. Libreria Python Requests, se puede instalar con el comando 'pip install requests'
@@ -0,0 +1,15 @@
1
+ * Refactorización SII-VERI*FACTU en l10n_es_aeat de los métodos que sean comunes.
2
+ * Envío separado de la confirmación de la factura (cron.trigger/queque.job)
3
+ * Control de errores del sistema, generar avisos. (caída de aeat, errores de conexión, etc.)
4
+ * Declaración responsable. https://sede.agenciatributaria.gob.es/Sede/iva/sistemas-informaticos-facturacion-verifactu/preguntas-frecuentes/certificacion-sistemas-informaticos-declaracion-responsable.html?faqId=a15d77fe52572910VgnVCM100000dc381e0aRCRD
5
+ * Posibilidad de consultar el estado de las facturas enviadas.
6
+ * Operaciones exentas y causas de exención.
7
+ * Crear un selection con todos los valores posibles de codigos de error, para poder guardarlo y agrupar las facturas por ese código.
8
+ * Contemplar el tiempo de espera entre envíos de registros cuando AEAT devuelve un tiempo superior a 60 segundos.
9
+
10
+ CASOS NO CUBIERTOS:
11
+ 1 - Modificación de facturas enviadas (AEAT recomienda generar rectificativa).
12
+ Según AEAT: Si los errores detectados tras la emisión NO están contemplados en el ROF, pero afectan a campos del registro de facturación (RF) generado al emitir la factura (que, digamos, “no se ven” en la factura impresa, es decir, son campos “internos”, como ciertas codificaciones tributarias), se debe corregir la factura original (esos datos “internos” de la misma) y se debe generar un RF de alta de
13
+ subsanación de esa factura donde conste ya la nueva información que proceda. Estos casos deberían ser MUY POCO FRECUENTES.
14
+ 2 - Anulación de facturas enviadas (AEAT recomienda generar rectificativa).
15
+ Según AEAT: "Si se considera que "toda la factura" en sí misma está mal o no debería haberse emitido, siempre que para solucionarlo no deba emplearse algún procedimiento (de rectificativa u otro) previsto en el ROF, se podrá "anular" generando para ello un RF de anulación. Estos casos deberían ser MUY POCO FRECUENTES."
@@ -0,0 +1 @@
1
+ Cuando se valida una factura, automáticamente genera el registro de envío para verifactu. Cada minuto se enviarán todos aquellos registros pendientes de enviar mediante un cron.
@@ -0,0 +1,22 @@
1
+ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
2
+ access_model_verifactu_map_admin,verifactu.map admin,model_verifactu_map,base.group_system,1,1,1,1
3
+ access_model_verifactu_map_aeat,verifactu.map aeat,model_verifactu_map,l10n_es_aeat.group_account_aeat,1,0,0,0
4
+ access_model_verifactu_map_lines_admin,verifactu.map.line admin,model_verifactu_map_line,base.group_system,1,1,1,1
5
+ access_model_verifactu_map_lines_aeat,verifactu.map.line aeat,model_verifactu_map_line,l10n_es_aeat.group_account_aeat,1,0,0,0
6
+ access_model_verifactu_registration_keys_admin,verifactu.registration.key admin,model_verifactu_registration_key,base.group_system,1,1,1,1
7
+ access_model_verifactu_registration_keys_aeat,verifactu.registration.key aeat,model_verifactu_registration_key,l10n_es_aeat.group_account_aeat,1,0,0,0
8
+ access_model_verifactu_registration_keys_aeat_account,verifactu.registration.key aeat,model_verifactu_registration_key,account.group_account_invoice,1,0,0,0
9
+ access_model_verifactu_developer_aeat,verifactu.developer aeat,model_verifactu_developer,l10n_es_aeat.group_account_aeat,1,1,1,1
10
+ access_model_verifactu_developer_aeat_account,verifactu.developer aeat,model_verifactu_developer,account.group_account_invoice,1,0,0,0
11
+ access_model_verifactu_invoice_admin,verifactu.invoice.entry admin,model_verifactu_invoice_entry,base.group_system,1,1,1,1
12
+ access_model_verifactu_invoice_aeat,verifactu.invoice.entry aeat,model_verifactu_invoice_entry,l10n_es_aeat.group_account_aeat,1,1,1,0
13
+ access_model_verifactu_invoice_aeat_account,verifactu.invoice.entry aeat,model_verifactu_invoice_entry,account.group_account_invoice,1,1,1,0
14
+ access_model_verifactu_invoice_entry_response_invoice_user,verifactu.invoice.entry.response invoice,model_verifactu_invoice_entry_response,account.group_account_invoice,1,0,0,0
15
+ access_model_verifactu_invoice_entry_response_system,verifactu.invoice_entry.response system,model_verifactu_invoice_entry_response,base.group_system,1,1,1,1
16
+ access_model_verifactu_invoice_entry_response_line_invoice_user,verifactu.invoice_entry.response.line invoice user,model_verifactu_invoice_entry_response_line,account.group_account_invoice,1,0,0,0
17
+ access_model_verifactu_invoice_entry_response_line_system,verifactu.invoice_entry.response.line system,model_verifactu_invoice_entry_response_line,base.group_system,1,1,1,1
18
+ access_model_verifactu_invoice_entry_response_line_responsible,verifactu.invoice_entry.response verifactu,model_verifactu_invoice_entry_response,group_verifactu_responsible,1,1,0,0
19
+ access_model_verifactu_chaining_admin,verifactu.chaining.admin,model_verifactu_chaining,base.group_system,1,1,1,1
20
+ access_model_verifactu_chaining_aeat,verifactu.chaining.aeat,model_verifactu_chaining,l10n_es_aeat.group_account_aeat,1,0,0,0
21
+ access_model_verifactu_chaining_responsible,verifactu.chaining.responsible,model_verifactu_chaining,group_verifactu_responsible,1,1,0,0
22
+ access_model_verifactu_chaining_user,verifactu.chaining.user,model_verifactu_chaining,account.group_account_invoice,1,0,0,0
@@ -0,0 +1,6 @@
1
+ <odoo>
2
+ <record id="group_verifactu_responsible" model="res.groups">
3
+ <field name="name">VERI*FACTU Responsible</field>
4
+ <field name="category_id" ref="base.module_category_accounting" />
5
+ </record>
6
+ </odoo>