odoo-addon-l10n-es-aeat-sii-oca 16.0.2.5.3__py3-none-any.whl → 17.0.1.0.0.3__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 +73 -86
- odoo/addons/l10n_es_aeat_sii_oca/__manifest__.py +7 -6
- odoo/addons/l10n_es_aeat_sii_oca/data/aeat_sii_map_data.xml +203 -187
- odoo/addons/l10n_es_aeat_sii_oca/data/aeat_sii_tax_agency_data.xml +0 -25
- odoo/addons/l10n_es_aeat_sii_oca/data/ir_config_parameter_data.xml +9 -0
- odoo/addons/l10n_es_aeat_sii_oca/data/ir_cron.xml +12 -0
- odoo/addons/l10n_es_aeat_sii_oca/data/l10n.es.aeat.map.tax.line.tax.csv +138 -0
- odoo/addons/l10n_es_aeat_sii_oca/hooks.py +3 -6
- odoo/addons/l10n_es_aeat_sii_oca/i18n/bg.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/ca.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/cs.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/de.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/es.po +3 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/es_CO.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/es_CR.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/eu.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/fr.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/gl.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/hr.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/l10n_es_aeat_sii_oca.pot +92 -82
- odoo/addons/l10n_es_aeat_sii_oca/i18n/nl.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/pl.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/pt.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/pt_BR.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/ru.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/sl.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/sv.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/tr.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/i18n/vi.po +0 -33
- odoo/addons/l10n_es_aeat_sii_oca/models/__init__.py +0 -1
- odoo/addons/l10n_es_aeat_sii_oca/models/account_move.py +142 -49
- odoo/addons/l10n_es_aeat_sii_oca/models/aeat_sii_map.py +3 -1
- odoo/addons/l10n_es_aeat_sii_oca/models/aeat_sii_mapping_registration_keys.py +1 -1
- odoo/addons/l10n_es_aeat_sii_oca/models/res_company.py +8 -9
- odoo/addons/l10n_es_aeat_sii_oca/models/sii_mixin.py +60 -58
- odoo/addons/l10n_es_aeat_sii_oca/readme/CONFIGURE.md +20 -0
- odoo/addons/l10n_es_aeat_sii_oca/readme/CONTRIBUTORS.md +24 -0
- odoo/addons/l10n_es_aeat_sii_oca/readme/DESCRIPTION.md +2 -0
- odoo/addons/l10n_es_aeat_sii_oca/readme/INSTALL.md +6 -0
- odoo/addons/l10n_es_aeat_sii_oca/readme/ROADMAP.md +12 -0
- odoo/addons/l10n_es_aeat_sii_oca/readme/{USAGE.rst → USAGE.md} +2 -2
- odoo/addons/l10n_es_aeat_sii_oca/security/ir.model.access.csv +0 -1
- odoo/addons/l10n_es_aeat_sii_oca/static/description/index.html +33 -46
- odoo/addons/l10n_es_aeat_sii_oca/tests/json/sii_in_invoice_p_iva21_ibc_group_dict.json +24 -0
- odoo/addons/l10n_es_aeat_sii_oca/tests/test_l10n_es_aeat_sii.py +71 -50
- odoo/addons/l10n_es_aeat_sii_oca/views/account_fiscal_position_view.xml +6 -16
- odoo/addons/l10n_es_aeat_sii_oca/views/account_journal_view.xml +2 -4
- odoo/addons/l10n_es_aeat_sii_oca/views/account_move_views.xml +39 -70
- odoo/addons/l10n_es_aeat_sii_oca/views/aeat_sii_map_view.xml +1 -1
- odoo/addons/l10n_es_aeat_sii_oca/views/res_company_view.xml +6 -10
- odoo/addons/l10n_es_aeat_sii_oca/wizards/account_move_reversal.py +9 -8
- odoo/addons/l10n_es_aeat_sii_oca/wizards/account_move_reversal_views.xml +4 -2
- odoo/addons/l10n_es_aeat_sii_oca/wizards/account_move_send_sii.py +15 -19
- odoo/addons/l10n_es_aeat_sii_oca/wizards/account_move_send_sii_views.xml +38 -5
- {odoo_addon_l10n_es_aeat_sii_oca-16.0.2.5.3.dist-info → odoo_addon_l10n_es_aeat_sii_oca-17.0.1.0.0.3.dist-info}/METADATA +84 -96
- {odoo_addon_l10n_es_aeat_sii_oca-16.0.2.5.3.dist-info → odoo_addon_l10n_es_aeat_sii_oca-17.0.1.0.0.3.dist-info}/RECORD +58 -59
- {odoo_addon_l10n_es_aeat_sii_oca-16.0.2.5.3.dist-info → odoo_addon_l10n_es_aeat_sii_oca-17.0.1.0.0.3.dist-info}/WHEEL +1 -1
- odoo_addon_l10n_es_aeat_sii_oca-17.0.1.0.0.3.dist-info/top_level.txt +1 -0
- odoo/addons/l10n_es_aeat_sii_oca/data/aeat_sii_queue_job.xml +0 -17
- odoo/addons/l10n_es_aeat_sii_oca/migrations/16.0.1.4.0/post-migration.py +0 -18
- odoo/addons/l10n_es_aeat_sii_oca/models/queue_job.py +0 -18
- odoo/addons/l10n_es_aeat_sii_oca/readme/CONFIGURE.rst +0 -39
- odoo/addons/l10n_es_aeat_sii_oca/readme/CONTRIBUTORS.rst +0 -25
- odoo/addons/l10n_es_aeat_sii_oca/readme/DESCRIPTION.rst +0 -2
- odoo/addons/l10n_es_aeat_sii_oca/readme/INSTALL.rst +0 -8
- odoo/addons/l10n_es_aeat_sii_oca/readme/ROADMAP.rst +0 -10
- odoo/addons/l10n_es_aeat_sii_oca/security/aeat_sii.xml +0 -17
- odoo/addons/l10n_es_aeat_sii_oca/views/queue_job_views.xml +0 -38
- odoo_addon_l10n_es_aeat_sii_oca-16.0.2.5.3.dist-info/top_level.txt +0 -1
@@ -23,18 +23,6 @@ SII_VALID_INVOICE_STATES = ["posted"]
|
|
23
23
|
_logger = logging.getLogger(__name__)
|
24
24
|
|
25
25
|
|
26
|
-
try:
|
27
|
-
from odoo.addons.queue_job.job import job
|
28
|
-
except ImportError:
|
29
|
-
_logger.debug("Can not `import queue_job`.")
|
30
|
-
import functools
|
31
|
-
|
32
|
-
def empty_decorator_factory(*argv, **kwargs):
|
33
|
-
return functools.partial
|
34
|
-
|
35
|
-
job = empty_decorator_factory
|
36
|
-
|
37
|
-
|
38
26
|
class AccountMove(models.Model):
|
39
27
|
_name = "account.move"
|
40
28
|
_inherit = ["account.move", "sii.mixin"]
|
@@ -95,14 +83,7 @@ class AccountMove(models.Model):
|
|
95
83
|
"The invoice number should start with LC, QZC, QRC, A01 or A02.",
|
96
84
|
copy=False,
|
97
85
|
)
|
98
|
-
|
99
|
-
comodel_name="queue.job",
|
100
|
-
column1="invoice_id",
|
101
|
-
column2="job_id",
|
102
|
-
relation="account_move_queue_job_rel",
|
103
|
-
string="Connector Jobs",
|
104
|
-
copy=False,
|
105
|
-
)
|
86
|
+
sii_dua_invoice = fields.Boolean(compute="_compute_dua_invoice")
|
106
87
|
|
107
88
|
@api.depends("move_type")
|
108
89
|
def _compute_sii_refund_type(self):
|
@@ -124,6 +105,21 @@ class AccountMove(models.Model):
|
|
124
105
|
def _compute_macrodata(self):
|
125
106
|
return super()._compute_macrodata()
|
126
107
|
|
108
|
+
@api.depends("company_id", "fiscal_position_id", "invoice_line_ids.tax_ids")
|
109
|
+
def _compute_dua_invoice(self):
|
110
|
+
for invoice in self:
|
111
|
+
taxes = self.env["account.tax"]
|
112
|
+
for template in [
|
113
|
+
"account_tax_template_p_iva4_ibc_group",
|
114
|
+
"account_tax_template_p_iva10_ibc_group",
|
115
|
+
"account_tax_template_p_iva21_ibc_group",
|
116
|
+
]:
|
117
|
+
tax_id = invoice.company_id._get_tax_id_from_xmlid(template)
|
118
|
+
taxes |= self.env["account.tax"].browse(tax_id)
|
119
|
+
invoice.sii_dua_invoice = invoice.line_ids.filtered(
|
120
|
+
lambda x, taxes=taxes: any([tax in taxes for tax in x.tax_ids])
|
121
|
+
)
|
122
|
+
|
127
123
|
def _aeat_get_partner(self):
|
128
124
|
return self.commercial_partner_id
|
129
125
|
|
@@ -528,18 +524,27 @@ class AccountMove(models.Model):
|
|
528
524
|
{"NombreRazon": partner.name[0:120]}
|
529
525
|
)
|
530
526
|
else:
|
531
|
-
|
527
|
+
invoice_type = self._get_sii_invoice_type()
|
528
|
+
company_name = partner.name[0:120]
|
529
|
+
if self.sii_dua_invoice:
|
530
|
+
company_name = self.company_id.name
|
531
|
+
if not self.sii_lc_operation:
|
532
|
+
invoice_type = "F5"
|
533
|
+
|
532
534
|
inv_dict["FacturaRecibida"] = {
|
533
535
|
# TODO: Incluir los 5 tipos de facturas rectificativas
|
534
|
-
"TipoFactura":
|
536
|
+
"TipoFactura": invoice_type,
|
535
537
|
"ClaveRegimenEspecialOTrascendencia": self.sii_registration_key.code,
|
536
538
|
"DescripcionOperacion": self.sii_description,
|
537
539
|
"DesgloseFactura": desglose_factura,
|
538
|
-
"Contraparte": {"NombreRazon":
|
540
|
+
"Contraparte": {"NombreRazon": company_name},
|
539
541
|
"FechaRegContable": reg_date,
|
540
|
-
"ImporteTotal": amount_total,
|
541
542
|
"CuotaDeducible": tax_amount,
|
542
543
|
}
|
544
|
+
if not self.sii_dua_invoice:
|
545
|
+
inv_dict["FacturaRecibida"]["ImporteTotal"] = (
|
546
|
+
-self.amount_total_signed - not_in_amount_total
|
547
|
+
)
|
543
548
|
if self.sii_macrodata:
|
544
549
|
inv_dict["FacturaRecibida"].update(Macrodato="S")
|
545
550
|
if self.sii_registration_key_additional1:
|
@@ -571,6 +576,13 @@ class AccountMove(models.Model):
|
|
571
576
|
),
|
572
577
|
"CuotaRectificada": refund_tax_amount,
|
573
578
|
}
|
579
|
+
|
580
|
+
if self.sii_dua_invoice:
|
581
|
+
inv_dict["FacturaRecibida"].pop("FechaOperacion", None)
|
582
|
+
nif = self.company_id.partner_id._parse_aeat_vat_info()[2]
|
583
|
+
inv_dict["FacturaRecibida"]["IDEmisorFactura"] = {"NIF": nif}
|
584
|
+
inv_dict["IDFactura"]["IDEmisorFactura"] = {"NIF": nif}
|
585
|
+
inv_dict["FacturaRecibida"]["Contraparte"]["NIF"] = nif
|
574
586
|
return inv_dict
|
575
587
|
|
576
588
|
def _get_cancel_sii_invoice_dict(self):
|
@@ -620,9 +632,7 @@ class AccountMove(models.Model):
|
|
620
632
|
"context": self.env.context,
|
621
633
|
}
|
622
634
|
|
623
|
-
|
624
|
-
return "invoice_jobs_ids"
|
625
|
-
|
635
|
+
@api.model
|
626
636
|
def _get_valid_document_states(self):
|
627
637
|
return SII_VALID_INVOICE_STATES
|
628
638
|
|
@@ -652,6 +662,7 @@ class AccountMove(models.Model):
|
|
652
662
|
"aeat_state": "cancelled",
|
653
663
|
"sii_csv": res["CSV"],
|
654
664
|
"aeat_send_failed": False,
|
665
|
+
"sii_needs_cancel": False,
|
655
666
|
}
|
656
667
|
)
|
657
668
|
res_line = res["RespuestaLinea"][0]
|
@@ -685,33 +696,32 @@ class AccountMove(models.Model):
|
|
685
696
|
and i.aeat_state in ["sent", "sent_w_errors", "sent_modified"]
|
686
697
|
)
|
687
698
|
)
|
688
|
-
if not invoices.
|
699
|
+
if not invoices._cancel_send_to_sii():
|
689
700
|
raise exceptions.UserError(
|
690
701
|
_(
|
691
702
|
"You can not communicate the cancellation of this invoice "
|
692
|
-
"at this moment
|
703
|
+
"at this moment. Please, try again later."
|
693
704
|
)
|
694
705
|
)
|
695
|
-
queue_obj = self.env["queue.job"]
|
696
706
|
for invoice in invoices:
|
697
707
|
company = invoice.company_id
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
708
|
+
sii_sending_time = company._get_sii_sending_time()
|
709
|
+
invoice.write({"sii_send_date": sii_sending_time, "sii_needs_cancel": True})
|
710
|
+
# Create trigger if any company needs to send doc to SII now
|
711
|
+
# so the sending to SII cron is executed as soon as possible
|
712
|
+
if invoices.company_id.filtered(
|
713
|
+
lambda company: company.send_mode == "auto"
|
714
|
+
or (company.send_mode == "delayed" and company.delay_time == 0.0)
|
715
|
+
):
|
716
|
+
sii_send_cron = self.env.ref("l10n_es_aeat_sii_oca.invoice_send_to_sii")
|
717
|
+
self.env["ir.cron.trigger"].sudo().create(
|
718
|
+
{"cron_id": sii_send_cron.id, "call_at": fields.Datetime.now()}
|
707
719
|
)
|
708
|
-
job = queue_obj.search([("uuid", "=", new_delay.uuid)], limit=1)
|
709
|
-
invoice.sudo().invoice_jobs_ids |= job
|
710
720
|
|
711
721
|
def button_cancel(self):
|
712
|
-
if not self.
|
722
|
+
if not self._cancel_send_to_sii():
|
713
723
|
raise exceptions.UserError(
|
714
|
-
_("You
|
724
|
+
_("You cannot cancel this invoice. Please, try again later.")
|
715
725
|
)
|
716
726
|
res = super().button_cancel()
|
717
727
|
for invoice in self.filtered(lambda x: x.sii_enabled):
|
@@ -724,11 +734,11 @@ class AccountMove(models.Model):
|
|
724
734
|
return res
|
725
735
|
|
726
736
|
def button_draft(self):
|
727
|
-
if not self.
|
737
|
+
if not self._cancel_send_to_sii():
|
728
738
|
raise exceptions.UserError(
|
729
739
|
_(
|
730
740
|
"You can not set to draft this invoice because"
|
731
|
-
"
|
741
|
+
" the SII trigger could not be cancelled."
|
732
742
|
)
|
733
743
|
)
|
734
744
|
return super().button_draft()
|
@@ -795,19 +805,31 @@ class AccountMove(models.Model):
|
|
795
805
|
"move_type",
|
796
806
|
"fiscal_position_id",
|
797
807
|
"fiscal_position_id.aeat_active",
|
808
|
+
"invoice_line_ids",
|
798
809
|
)
|
799
810
|
def _compute_sii_enabled(self):
|
800
811
|
"""Compute if the invoice is enabled for the SII"""
|
801
812
|
for invoice in self:
|
813
|
+
dua_sii_exempt_taxes = invoice._get_dua_sii_exempt_taxes()
|
802
814
|
if (
|
803
815
|
invoice.company_id.sii_enabled
|
804
816
|
and invoice.journal_id.sii_enabled
|
805
817
|
and invoice.is_invoice()
|
806
818
|
):
|
807
819
|
invoice.sii_enabled = (
|
808
|
-
|
809
|
-
|
810
|
-
|
820
|
+
(
|
821
|
+
invoice.fiscal_position_id
|
822
|
+
and invoice.fiscal_position_id.aeat_active
|
823
|
+
)
|
824
|
+
or not invoice.fiscal_position_id
|
825
|
+
) and (
|
826
|
+
not dua_sii_exempt_taxes
|
827
|
+
or not invoice.invoice_line_ids.filtered(
|
828
|
+
lambda x, dua_taxes=dua_sii_exempt_taxes: any(
|
829
|
+
[tax.id in dua_taxes for tax in x.tax_ids]
|
830
|
+
)
|
831
|
+
)
|
832
|
+
)
|
811
833
|
else:
|
812
834
|
invoice.sii_enabled = False
|
813
835
|
|
@@ -832,7 +854,7 @@ class AccountMove(models.Model):
|
|
832
854
|
# OVERRIDE
|
833
855
|
if not default_values_list:
|
834
856
|
default_values_list = [{} for move in self]
|
835
|
-
for move, default_values in zip(self, default_values_list):
|
857
|
+
for move, default_values in zip(self, default_values_list, strict=False):
|
836
858
|
if move.sii_enabled:
|
837
859
|
extra_dict = {}
|
838
860
|
sii_refund_type = self.env.context.get("sii_refund_type", False)
|
@@ -853,3 +875,74 @@ class AccountMove(models.Model):
|
|
853
875
|
|
854
876
|
def cancel_one_invoice(self):
|
855
877
|
self.sudo()._cancel_invoice_to_sii()
|
878
|
+
|
879
|
+
@api.model
|
880
|
+
def _get_sii_batch(self):
|
881
|
+
try:
|
882
|
+
return int(
|
883
|
+
self.env["ir.config_parameter"]
|
884
|
+
.sudo()
|
885
|
+
.get_param("l10n_es_aeat_sii_oca.sii_batch", "50")
|
886
|
+
)
|
887
|
+
except ValueError as e:
|
888
|
+
raise exceptions.UserError(
|
889
|
+
_(
|
890
|
+
"The value in l10n_es_aeat_sii_oca.sii_batch system"
|
891
|
+
" parameter must be an integer. Please, check the "
|
892
|
+
"value of the parameter."
|
893
|
+
)
|
894
|
+
) from e
|
895
|
+
|
896
|
+
@api.model
|
897
|
+
def _send_to_sii_valid(self):
|
898
|
+
remaining_documents = self.env["account.move"]
|
899
|
+
documents = all_documents = self.search(
|
900
|
+
[
|
901
|
+
("state", "in", self._get_valid_document_states()),
|
902
|
+
(
|
903
|
+
"aeat_state",
|
904
|
+
"not in",
|
905
|
+
["sent", "cancelled"],
|
906
|
+
),
|
907
|
+
("sii_send_date", "<=", fields.Datetime.now()),
|
908
|
+
]
|
909
|
+
)
|
910
|
+
if documents:
|
911
|
+
batch = self._get_sii_batch()
|
912
|
+
documents = all_documents[:batch]
|
913
|
+
remaining_documents = all_documents - documents
|
914
|
+
documents.confirm_one_document()
|
915
|
+
return remaining_documents
|
916
|
+
|
917
|
+
@api.model
|
918
|
+
def _send_to_sii_cancel(self):
|
919
|
+
remaining_cancel_documents = self.env["account.move"]
|
920
|
+
cancel_documents = all_cancel_documents = self.search(
|
921
|
+
[
|
922
|
+
("state", "in", ["cancel"]),
|
923
|
+
(
|
924
|
+
"aeat_state",
|
925
|
+
"in",
|
926
|
+
["sent", "sent_w_errors", "sent_modified"],
|
927
|
+
),
|
928
|
+
("sii_needs_cancel", "=", True),
|
929
|
+
("sii_send_date", "<=", fields.Datetime.now()),
|
930
|
+
]
|
931
|
+
)
|
932
|
+
if cancel_documents:
|
933
|
+
batch = self._get_sii_batch()
|
934
|
+
cancel_documents = all_cancel_documents[:batch]
|
935
|
+
remaining_cancel_documents = all_cancel_documents - cancel_documents
|
936
|
+
cancel_documents.cancel_one_invoice()
|
937
|
+
return remaining_cancel_documents
|
938
|
+
|
939
|
+
@api.model
|
940
|
+
def _send_to_sii(self):
|
941
|
+
remaining_documents = self._send_to_sii_valid()
|
942
|
+
remaining_cancel_documents = self._send_to_sii_cancel()
|
943
|
+
# Manage remaining invoices
|
944
|
+
if remaining_documents or remaining_cancel_documents:
|
945
|
+
sii_send_cron = self.env.ref("l10n_es_aeat_sii_oca.invoice_send_to_sii")
|
946
|
+
self.env["ir.cron.trigger"].sudo().create(
|
947
|
+
{"cron_id": sii_send_cron.id, "call_at": fields.Datetime.now()}
|
948
|
+
)
|
@@ -59,7 +59,9 @@ class AeatSiiMapLines(models.Model):
|
|
59
59
|
|
60
60
|
code = fields.Char(required=True)
|
61
61
|
name = fields.Char()
|
62
|
-
|
62
|
+
tax_xmlid_ids = fields.Many2many(
|
63
|
+
comodel_name="l10n.es.aeat.map.tax.line.tax", string="Taxes templates"
|
64
|
+
)
|
63
65
|
sii_map_id = fields.Many2one(
|
64
66
|
comodel_name="aeat.sii.map", string="Aeat SII Map", ondelete="cascade"
|
65
67
|
)
|
@@ -55,17 +55,13 @@ class ResCompany(models.Model):
|
|
55
55
|
help="By default, the invoice is sent/queued in validation process. "
|
56
56
|
"With manual method, there's a button to send the invoice.",
|
57
57
|
)
|
58
|
-
use_connector = fields.Boolean(
|
59
|
-
help="Check it to use connector instead of sending the invoice "
|
60
|
-
"directly when it's validated",
|
61
|
-
)
|
62
58
|
send_mode = fields.Selection(
|
63
59
|
selection=[
|
64
60
|
("auto", "On validate"),
|
65
61
|
("fixed", "At fixed time"),
|
66
62
|
("delayed", "With delay"),
|
67
63
|
],
|
68
|
-
default="
|
64
|
+
default="delayed",
|
69
65
|
)
|
70
66
|
sent_time = fields.Float()
|
71
67
|
delay_time = fields.Float()
|
@@ -77,7 +73,7 @@ class ResCompany(models.Model):
|
|
77
73
|
default="monthly",
|
78
74
|
)
|
79
75
|
|
80
|
-
def
|
76
|
+
def _get_sii_sending_time(self):
|
81
77
|
if self.send_mode == "fixed":
|
82
78
|
tz = self.env.context.get("tz", self.env.user.partner_id.tz)
|
83
79
|
offset = datetime.now(pytz.timezone(tz)).strftime("%z") if tz else "+00"
|
@@ -90,7 +86,10 @@ class ResCompany(models.Model):
|
|
90
86
|
now += timedelta(days=1)
|
91
87
|
now = now.replace(hour=hour, minute=minute)
|
92
88
|
return now
|
93
|
-
elif self.send_mode == "delayed":
|
94
|
-
return datetime.now() + timedelta(seconds=self.delay_time * 3600)
|
95
89
|
else:
|
96
|
-
|
90
|
+
delay_time = (
|
91
|
+
0.0
|
92
|
+
if self.send_mode == "auto" or not self.delay_time
|
93
|
+
else self.delay_time
|
94
|
+
)
|
95
|
+
return datetime.now() + timedelta(hours=delay_time)
|
@@ -96,6 +96,8 @@ class SiiMixin(models.AbstractModel):
|
|
96
96
|
"greater o equal to 100 000 000,00 euros.",
|
97
97
|
compute="_compute_macrodata",
|
98
98
|
)
|
99
|
+
sii_send_date = fields.Datetime(string="SII Send Date", index=True)
|
100
|
+
sii_needs_cancel = fields.Boolean(readonly=True)
|
99
101
|
|
100
102
|
def _compute_sii_refund_type(self):
|
101
103
|
self.sii_refund_type = False
|
@@ -139,13 +141,6 @@ class SiiMixin(models.AbstractModel):
|
|
139
141
|
|
140
142
|
@api.depends("sii_registration_key")
|
141
143
|
def _compute_sii_registration_key_code(self):
|
142
|
-
"""
|
143
|
-
Para evitar tiempos de instalación largos en BBDD grandes, es necesario que
|
144
|
-
sólo dependa de sii_registration_key, ya que en caso de añadirlo odoo buscará
|
145
|
-
todos los movimientos y cuando escribamos el key, aunque sea un campo no almacenado
|
146
|
-
A partir de v16.0 este cambio ya no es necesario, ya que el sistema ya revisa que el
|
147
|
-
campo sea almacenado o que este visualizandose (en caché)
|
148
|
-
"""
|
149
144
|
for record in self:
|
150
145
|
record.sii_registration_key_code = record.sii_registration_key.code
|
151
146
|
|
@@ -206,8 +201,24 @@ class SiiMixin(models.AbstractModel):
|
|
206
201
|
],
|
207
202
|
limit=1,
|
208
203
|
)
|
209
|
-
tax_templates = sii_map.map_lines.filtered(
|
210
|
-
|
204
|
+
tax_templates = sii_map.map_lines.filtered(
|
205
|
+
lambda x: x.code in codes
|
206
|
+
).tax_xmlid_ids
|
207
|
+
taxes = self.env["account.tax"]
|
208
|
+
for template in tax_templates:
|
209
|
+
tax_id = self.company_id._get_tax_id_from_xmlid(template.name)
|
210
|
+
taxes |= self.env["account.tax"].browse(tax_id)
|
211
|
+
return taxes
|
212
|
+
|
213
|
+
def _get_dua_sii_exempt_taxes(self):
|
214
|
+
self.ensure_one()
|
215
|
+
taxes = []
|
216
|
+
dua_exempt_tax = self.company_id._get_tax_id_from_xmlid(
|
217
|
+
"account_tax_template_p_dua_exempt"
|
218
|
+
)
|
219
|
+
if dua_exempt_tax:
|
220
|
+
taxes.append(dua_exempt_tax)
|
221
|
+
return taxes
|
211
222
|
|
212
223
|
def _get_aeat_header(self, tipo_comunicacion=False, cancellation=False):
|
213
224
|
"""Builds SII send header
|
@@ -233,61 +244,52 @@ class SiiMixin(models.AbstractModel):
|
|
233
244
|
header.update({"TipoComunicacion": tipo_comunicacion})
|
234
245
|
return header
|
235
246
|
|
236
|
-
def
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
if queue.state == "started":
|
242
|
-
return False
|
243
|
-
elif queue.state in ("pending", "enqueued", "failed"):
|
244
|
-
queue.unlink()
|
247
|
+
def _cancel_send_to_sii(self):
|
248
|
+
try:
|
249
|
+
self.sudo().write({"sii_send_date": False})
|
250
|
+
except Exception:
|
251
|
+
return False
|
245
252
|
return True
|
246
253
|
|
247
254
|
def send_sii(self):
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
)
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
continue
|
269
|
-
document._process_sii_send()
|
255
|
+
documents = self.filtered(
|
256
|
+
lambda document: (
|
257
|
+
document.sii_enabled
|
258
|
+
and document.state in self._get_valid_document_states()
|
259
|
+
and document.aeat_state not in ["sent", "cancelled"]
|
260
|
+
)
|
261
|
+
)
|
262
|
+
if not documents._cancel_send_to_sii():
|
263
|
+
raise UserError(
|
264
|
+
_(
|
265
|
+
"You can not communicate this document at this moment. "
|
266
|
+
"Please, try again later."
|
267
|
+
)
|
268
|
+
)
|
269
|
+
if documents:
|
270
|
+
documents.with_context(bypass_sii_send=True)._process_sii_send()
|
271
|
+
sii_send_cron = self.env.ref("l10n_es_aeat_sii_oca.invoice_send_to_sii")
|
272
|
+
self.env["ir.cron.trigger"].sudo().create(
|
273
|
+
{"cron_id": sii_send_cron.id, "call_at": fields.Datetime.now()}
|
274
|
+
)
|
270
275
|
|
271
276
|
def _process_sii_send(self):
|
272
277
|
"""Process document sending to the SII. Adds general checks from
|
273
|
-
configuration parameters and document availability for SII.
|
274
|
-
document is to be sent the decides the send method: direct send or
|
275
|
-
via connector depending on 'Use connector' configuration"""
|
276
|
-
queue_obj = self.env["queue.job"].sudo()
|
278
|
+
configuration parameters and document availability for SII."""
|
277
279
|
for record in self:
|
278
280
|
company = record.company_id
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
281
|
+
sii_sending_time = company._get_sii_sending_time()
|
282
|
+
record.sii_send_date = sii_sending_time
|
283
|
+
# Create trigger if any company needs to send doc to SII now
|
284
|
+
# so the sending to SII cron is executed as soon as possible
|
285
|
+
if self.company_id.filtered(
|
286
|
+
lambda company: company.send_mode == "auto"
|
287
|
+
or (company.send_mode == "delayed" and company.delay_time == 0.0)
|
288
|
+
) and not self.env.context.get("bypass_sii_send", False):
|
289
|
+
sii_send_cron = self.env.ref("l10n_es_aeat_sii_oca.invoice_send_to_sii")
|
290
|
+
self.env["ir.cron.trigger"].sudo().create(
|
291
|
+
{"cron_id": sii_send_cron.id, "call_at": fields.Datetime.now()}
|
292
|
+
)
|
291
293
|
|
292
294
|
def _bind_service(self, client, port_name, address=None):
|
293
295
|
self.ensure_one()
|
@@ -811,7 +813,7 @@ class SiiMixin(models.AbstractModel):
|
|
811
813
|
):
|
812
814
|
doc_vals[
|
813
815
|
"sii_account_registration_date"
|
814
|
-
] =
|
816
|
+
] = document._get_account_registration_date()
|
815
817
|
doc_vals["sii_return"] = res
|
816
818
|
send_error = False
|
817
819
|
if res_line["CodigoErrorRegistro"]:
|
@@ -831,12 +833,12 @@ class SiiMixin(models.AbstractModel):
|
|
831
833
|
"aeat_send_error": repr(fault)[:60],
|
832
834
|
"sii_return": repr(fault),
|
833
835
|
"aeat_content_sent": json.dumps(inv_dict, indent=4),
|
836
|
+
"sii_send_date": False,
|
834
837
|
}
|
835
838
|
)
|
836
839
|
document.write(doc_vals)
|
837
840
|
new_cr.commit()
|
838
841
|
new_cr.close()
|
839
|
-
raise ValidationError(fault) from fault
|
840
842
|
|
841
843
|
def confirm_one_document(self):
|
842
844
|
self.sudo()._send_document_to_sii()
|
@@ -0,0 +1,20 @@
|
|
1
|
+
Para configurar este módulo es necesario:
|
2
|
+
|
3
|
+
1. En la compañia se almacenan las URLs del servicio SOAP de hacienda.
|
4
|
+
Estas URLs pueden cambiar según comunidades
|
5
|
+
2. Los certificados deben alojarse en una carpeta accesible por la
|
6
|
+
instalación de Odoo.
|
7
|
+
3. Preparar el certificado. El certificado enviado por la FMNT es en
|
8
|
+
formato p12, este certificado no se puede usar directamente con
|
9
|
+
Zeep. Se tiene que extraer la clave pública y la clave privada.
|
10
|
+
|
11
|
+
El parámetro del sistema l10n_es_aeat_sii_oca.sii_batch almacena el
|
12
|
+
número de facturas máximo que se enviarán al SII cada vez que se realice
|
13
|
+
un envío. El valor por defecto es 50.
|
14
|
+
|
15
|
+
En Linux se pueden usar los siguientes comandos:
|
16
|
+
|
17
|
+
- Clave pública: "openssl pkcs12 -in Certificado.p12 -nokeys -out
|
18
|
+
publicCert.crt -nodes"
|
19
|
+
- Clave privada: "openssl pkcs12 -in Certifcado.p12 -nocerts -out
|
20
|
+
privateKey.pem -nodes"
|
@@ -0,0 +1,24 @@
|
|
1
|
+
- Ignacio Ibeas \<<ignacio@acysos.com>\>
|
2
|
+
- Rubén Cerdà \<<ruben.cerda.roig@diagram.es>\>
|
3
|
+
- Ramon Guiu \<<ramon.guiu@minorisa.net>\>
|
4
|
+
- Pablo Fuentes \<<pablo@studio73.es>\>
|
5
|
+
- Jordi Tolsà \<<jordi@studio73.es>\>
|
6
|
+
- Ismael Calvo \<<ismael.calvo@factorlibre.es>\>
|
7
|
+
- Omar Castiñeira - Comunitea S.L. \<<omar@comunitea.com>\>
|
8
|
+
- Juanjo Algaz \<<jalgaz@gmail.com>\>, Planeta Huerto
|
9
|
+
\<<juanjoalgaz@planetahuerto.es>\>
|
10
|
+
- Javi Melendez \<<javimelex@gmail.com>\>
|
11
|
+
- Santi Argüeso - Comunitea S.L. \<<santi@comunitea.com>\>
|
12
|
+
- Angel Moya - PESOL \<<angel.moya@pesol.es>\>
|
13
|
+
- Eric Antonés - NuoBiT Solutions, S.L. \<<eantones@nuobit.com>\>
|
14
|
+
- [Sygel](https://www.sygel.es):
|
15
|
+
- Valentin Vinagre
|
16
|
+
- Manuel Regidor
|
17
|
+
- [Tecnativa](https://www.tecnativa.com):
|
18
|
+
- Pedro M. Baeza
|
19
|
+
- João Marques
|
20
|
+
- Lois Rilo Antelo \<<lois.rilo@forgeflow.com>\>
|
21
|
+
- Eduardo de Miguel (<edu@moduon.team>)
|
22
|
+
- Jose Zambudio \<<jose@aurestic.es>\>
|
23
|
+
- [Factor Libre](https://factorlibre.com):
|
24
|
+
- Luis J. Salvatierra \<<luis.salvatierra@factorlibre.com>\>
|
@@ -0,0 +1,12 @@
|
|
1
|
+
- Comunicación de cobros y pagos.
|
2
|
+
- Determinadas facturas intracomunitarias (Articulo 66 RIVA).
|
3
|
+
- Asistente para consultar los documentos comunicados.
|
4
|
+
- Libro de bienes de inversión (Libro anual se crea un módulo aparte).
|
5
|
+
- Regímenes especiales de seguros, de agencias de viaje o de bienes
|
6
|
+
usados.
|
7
|
+
- Devolución de IVA de viajeros.
|
8
|
+
- Facturas rectificativas por sustitución.
|
9
|
+
- Sistema de control de número de reintentos en caso de error en el envío.
|
10
|
+
- Soportar facturas de canje de facturas simplificadas por facturas
|
11
|
+
completas. Ver <https://github.com/OCA/l10n-spain/issues/1171> para
|
12
|
+
más información.
|
@@ -1,2 +1,2 @@
|
|
1
|
-
Cuando se valida una factura automáticamente envia la comunicación al
|
2
|
-
de AEAT.
|
1
|
+
Cuando se valida una factura automáticamente envia la comunicación al
|
2
|
+
servidor de AEAT.
|
@@ -6,5 +6,4 @@ access_model_aeat_sii_map_lines_aeat,aeat.sii.map.lines aeat,model_aeat_sii_map_
|
|
6
6
|
access_model_aeat_sii_mapping_registration_keys_admin,aeat.sii.mapping.registration.keys admin,model_aeat_sii_mapping_registration_keys,base.group_system,1,1,1,1
|
7
7
|
access_model_aeat_sii_mapping_registration_keys_aeat,aeat.sii.mapping.registration.keys aeat,model_aeat_sii_mapping_registration_keys,l10n_es_aeat.group_account_aeat,1,0,0,0
|
8
8
|
access_model_aeat_sii_mapping_registration_keys_aeat_account,aeat.sii.mapping.registration.keys aeat,model_aeat_sii_mapping_registration_keys,account.group_account_invoice,1,0,0,0
|
9
|
-
access_queue_job,access_queue_job aeat,queue_job.model_queue_job,l10n_es_aeat.group_account_aeat,1,0,0,0
|
10
9
|
access_wizard_send_sii,access_wizard_send_sii,model_wizard_send_sii,l10n_es_aeat.group_account_aeat,1,1,1,1
|