Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 32 additions & 42 deletions l10n_es_aeat_sii_invoice_summary/models/account_move.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# © 2017 FactorLibre - Hugo Santos <hugo.santos@factorlibre.com>
# © 2018 FactorLibre - Victor Rodrigo <victor.rodrigo@factorlibre.com>
# © 2022 ProcessControl - David Ramia <david.ramia@processcontrol.es>
# Copyright 2026 Tecnativa - Carlos Dauden
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html).
from odoo import _, exceptions, fields, models
from odoo import _, fields, models


class AccountMove(models.Model):
Expand All @@ -12,51 +13,40 @@ class AccountMove(models.Model):
sii_invoice_summary_start = fields.Char("SII Invoice Summary: First Invoice")
sii_invoice_summary_end = fields.Char("SII Invoice Summary: Last Invoice")

def _is_aeat_summary_invoice(self):
if (
self.is_invoice_summary
and self.is_sale_document()
and self.sii_invoice_summary_start != self.sii_invoice_summary_end
):
return True
return False

def _is_aeat_simplified_invoice(self):
# En el SII, una factura resumen (F4) debe considerarse, a efectos de estructura
# y validaciones, equivalente a una factura simplificada (F2), compartiendo
# todas sus restricciones y campos obligatorios, con la única diferencia de
# requerir la clave TipoFactura = F4 y el campo adicional
# NumSerieFacturaEmisorResumenFin
return self.is_invoice_summary or super()._is_aeat_simplified_invoice()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Esto debería llamar a _is_aeat_summary_invoice, ¿no? De todas formas, este método puede que se use en otros lados donde no interese igualarlas. Habría que pasar un contexto donde se quiere obtener esto y solo entonces igualarlo.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, no se debe llamar a _is_aeat_summary_invoice porque, tal como está explicado en el comentario, el objetivo es que la estructura sea la misma de la factura simplificada independientemente del resto de condiciones que evalúa ese método.

No, no es necesario pasar un contexto ya que no vamos a añadir código innecesario para un caso que no se va a dar.
Si alguna vez se da se deberá evaluar si se quiere mantener este enfoque o hay que cambiarlo, y en caso de querer mantenerlo una solución podría ser añadir ese contexto.
Si se te ocurre un caso real y concreto también podemos hacer ahora ese análisis, pero no porque puede que se use, ya que las posibilidades teóricas y las ocurrencias son infinitas, pero nuestro trabajo es aplicar el sentido común en contacto con la realidad.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lo del contexto sí que lo considero necesario, ya que si tienes un método _is_aeat_simplified_invoice, lo que esperas como programador es que ese método devuelva cuándo la factura es simplificada, no cuando es simplificada y cuando es una factura resumen, con lo que aunque ahora mismo no parece que afecte a ninguna otra cosa (que eso no lo sabes, porque otros han podido utilizar ese método en sus customizaciones), puede que en el futuro a nosotros mismos nos vaya eso en contra.

Otra posible vía es la de tener en el módulo padre un método nuevo _is_sii_simplified_structure o algo así, que sea el que se evalúa para enviar esa estructura básica, y entonces sea el que se sobreescribe aquí.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

¿En qué caso real una factura resumen no tiene el mismo trato que una factura simplificada?

Podemos no hacer absolutamente nada y que sea el usuario el que se asegure de que ambas cosas están marcadas para las facturas resumen, pero lo que se ha intentado es simplificar el código y cubrir el mayor número de casos sin que afecte a nadie que esté usando el módulo sin marcar las facturas resumen como simplificadas.

Antes de hacer el PR me he tomado mi tiempo analizando donde se utiliza cada método e intentando comprender cual es el comportamiento esperado.
En todas las llamadas al método _is_aeat_simplified_invoice (SII, POS, VeriFactu, etc.) el comportamiento deseado es que las facturas resumen tengan la misma estructura que las simplificadas, por lo que añadir un contexto, un nuevo método o lo que sea va en contra del objetivo de simplificación, ya que habría que modificar todos esos módulos cuando lo que se busca es precisamente lo contrario.

Si te parece, en lugar de teorizar sin mirar nada, puedes hacer un pequeño análisis para estudiar el caso concreto y evitas hacer perder más tiempo a las personas que ya lo han realizado.
Una vez hecho ese análisis puedes tomarte la libertad de cerrar el PR y abrir uno nuevo si lo consideras oportuno.

La realidad actual es que las facturas no se envían al SII correctamente y el código que hay es un despropósito (al que por cierto no se le pusieron problemas para fusionar).

Este PR tiene 2 commits en los que se resuelve el problema principal con dos enfoques distintos, siendo el último el óptimo, pero el primero es más conservador por si no se llegaba a entender la segunda opción.


def _get_sii_invoice_type(self):
invoice_type = super()._get_sii_invoice_type()
if self._is_aeat_summary_invoice() and invoice_type == "F2":
invoice_type = "F4"
return invoice_type

def _get_aeat_invoice_dict_out(self, cancel=False):
inv_dict = super()._get_aeat_invoice_dict_out(cancel=cancel)
if self.is_invoice_summary and self.is_sale_document():
tipo_factura = "F4"
if self.sii_invoice_summary_start:
if self.sii_invoice_summary_start == self.sii_invoice_summary_end:
tipo_factura = "F2" if self.move_type == "out_invoice" else "R5"
else:
inv_dict["IDFactura"][
"NumSerieFacturaEmisor"
] = self.sii_invoice_summary_start
inv_dict["IDFactura"][
"NumSerieFacturaEmisorResumenFin"
] = self.sii_invoice_summary_end
if "FacturaExpedida" in inv_dict:
if "TipoFactura" in inv_dict["FacturaExpedida"]:
inv_dict["FacturaExpedida"]["TipoFactura"] = tipo_factura
if "Contraparte" in inv_dict["FacturaExpedida"]:
del inv_dict["FacturaExpedida"]["Contraparte"]
if (
"TipoRectificativa" in inv_dict["FacturaExpedida"]
and tipo_factura == "F4"
):
del inv_dict["FacturaExpedida"]["TipoRectificativa"]

if inv_dict.get("FacturaExpedida", {}).get("TipoFactura", "") == "F4":
inv_dict["IDFactura"][
"NumSerieFacturaEmisor"
] = self.sii_invoice_summary_start
inv_dict["IDFactura"][
"NumSerieFacturaEmisorResumenFin"
] = self.sii_invoice_summary_end
return inv_dict

def _aeat_check_exceptions(self):
Comment thread
pedrobaeza marked this conversation as resolved.
"""Inheritable method for exceptions control when sending SII invoices."""
res = False
try:
res = super()._aeat_check_exceptions()
except exceptions.UserError as e:
if (
e.args[0] == _("The partner has not a VAT configured.")
and self.is_invoice_summary
):
pass
else:
raise

if self.is_invoice_summary and self.is_purchase_document():
raise exceptions.UserError(_("You can't make a supplier summary invoice."))
return res

def write(self, vals):
"""Cannot let change sii_invoice_summary fields
values in a SII registered supplier invoice"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
"IDEmisorFactura": {
"NIF": "U2687761C"
},
"NumSerieFacturaEmisor": "1",
"FechaExpedicionFacturaEmisor": "01-01-2020",
"NumSerieFacturaEmisorResumenFin": "10"
"NumSerieFacturaEmisor": "TEST001",
"FechaExpedicionFacturaEmisor": "01-01-2020"
},
"PeriodoLiquidacion": {
"Ejercicio": 2020,
"Periodo": "01"
},
"FacturaExpedida": {
"TipoFactura": "F4",
"TipoFactura": "R5",
"ClaveRegimenEspecialOTrascendencia": "01",
"DescripcionOperacion": "/",
"TipoDesglose": {
Expand All @@ -36,6 +35,7 @@
}
}
},
"TipoRectificativa": "I",
"ImporteTotal": -300.0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
"IDEmisorFactura": {
"NIF": "U2687761C"
},
"NumSerieFacturaEmisor": "1",
"FechaExpedicionFacturaEmisor": "01-01-2020",
"NumSerieFacturaEmisorResumenFin": "10"
"NumSerieFacturaEmisor": "TEST001",
"FechaExpedicionFacturaEmisor": "01-01-2020"
},
"PeriodoLiquidacion": {
"Ejercicio": 2020,
"Periodo": "01"
},
"FacturaExpedida": {
"TipoFactura": "F4",
"TipoFactura": "R5",
"ClaveRegimenEspecialOTrascendencia": "01",
"DescripcionOperacion": "/",
"TipoDesglose": {
Expand Down Expand Up @@ -51,6 +50,7 @@
}
}
},
"TipoRectificativa": "I",
"ImporteTotal": -462.0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
"IDEmisorFactura": {
"NIF": "U2687761C"
},
"NumSerieFacturaEmisor": "1",
"FechaExpedicionFacturaEmisor": "01-01-2020",
"NumSerieFacturaEmisorResumenFin": "10"
"NumSerieFacturaEmisor": "TEST001",
"FechaExpedicionFacturaEmisor": "01-01-2020"
},
"PeriodoLiquidacion": {
"Ejercicio": 2020,
"Periodo": "01"
},
"FacturaExpedida": {
"TipoFactura": "F4",
"TipoFactura": "R5",
"ClaveRegimenEspecialOTrascendencia": "01",
"DescripcionOperacion": "/",
"TipoDesglose": {
Expand All @@ -36,6 +35,7 @@
}
}
},
"TipoRectificativa": "I",
"ImporteTotal": -300.0
}
}