feature/pedimentos-correccion-partidas

This commit is contained in:
2026-05-28 07:10:39 -06:00
parent 94846fec8a
commit 709a5dedab
29 changed files with 1908 additions and 87 deletions

View File

@@ -6,9 +6,50 @@ from api.customs.models import ProcesamientoPedimento, Pedimento, Cove, EDocumen
from core.utils import xml_controller
import requests
from core.utils import xml_remesas_controller
from core.redis_events import publish_task_event
import logging
logger = logging.getLogger(__name__)
def _crear_notificacion_auditoria(user_id: str, task_id: str, label: str, resultado: dict):
"""Crea una Notificacion persistente cuando una tarea de auditoría masiva completa."""
try:
from api.notificaciones.models import Notificacion, TipoNotificacion
from api.cuser.models import CustomUser
tipo, _ = TipoNotificacion.objects.get_or_create(
tipo="auditoria_completada",
defaults={"descripcion": "Auditoría masiva completada"},
)
usuario = CustomUser.objects.filter(id=user_id).first()
if not usuario:
return
total = resultado.get('total_pedimentos', 0)
completados = resultado.get('completados', resultado.get('procesados', 0))
pendientes = resultado.get('con_pendientes', 0)
errores = resultado.get('con_errores', 0)
partes = [f"Auditoría de {label} completada — {completados}/{total} pedimentos"]
if pendientes:
partes.append(f"{pendientes} con pendientes")
if errores:
partes.append(f"{errores} con errores")
Notificacion.objects.create(
tipo=tipo,
dirigido=usuario,
mensaje=", ".join(partes),
datos={
"task_id": task_id,
"label": label,
"resultado": resultado,
},
)
except Exception as exc:
logger.error(f"[auditoria] Error creando notificación para tarea {task_id}: {exc}")
def obtener_pedimentos(organizacion_id):
return Pedimento.objects.filter(organizacion_id=organizacion_id)
@@ -129,19 +170,22 @@ def auditar_procesamiento_remesa_por_pedimento(pedimento_id):
'pedimento_id': str(pedimento_id)
}
@shared_task
def crear_partidas(organizacion_id):
@shared_task(bind=True)
def crear_partidas(self, organizacion_id, user_id=None):
from api.customs.models import Partida
task_id = self.request.id
pedimentos = obtener_pedimentos(organizacion_id)
total_pedimentos = pedimentos.count()
publish_task_event(task_id, "processing", f"Creando partidas: {total_pedimentos} pedimentos", progress=0)
completados = []
con_pendientes = []
sin_datos = []
errores = []
for pedimento in pedimentos:
for idx, pedimento in enumerate(pedimentos):
try:
if not pedimento.numero_partidas or pedimento.numero_partidas <= 0:
sin_datos.append({
@@ -180,8 +224,13 @@ def crear_partidas(organizacion_id):
})
logger.error(f"Error creando partidas para pedimento {pedimento.id}: {e}")
return {
if total_pedimentos > 0 and (idx + 1) % 10 == 0:
pct = int(((idx + 1) / total_pedimentos) * 100)
publish_task_event(task_id, "processing", f"Creando partidas: {idx + 1}/{total_pedimentos}", progress=pct)
resultado = {
'organizacion_id': str(organizacion_id),
'auditoria': 'partidas',
'total_pedimentos': total_pedimentos,
'completados': len(completados),
'con_pendientes': len(con_pendientes),
@@ -192,6 +241,12 @@ def crear_partidas(organizacion_id):
'detalle_errores': errores,
}
publish_task_event(task_id, "completed", "Creación de partidas completada", resultado=resultado, progress=100)
if user_id:
_crear_notificacion_auditoria(user_id, task_id, "Partidas", resultado)
return resultado
@shared_task
def crear_partidas_por_pedimento(pedimento_id):
try:
@@ -230,19 +285,23 @@ def crear_partidas_por_pedimento(pedimento_id):
print(f"Error: Pedimento {pedimento_id} tiene numero_partidas inválido: {pedimento.numero_partidas}")
logger.info(f"Error: Pedimento {pedimento_id} tiene numero_partidas inválido: {pedimento.numero_partidas}")
def _auditar_organizacion(organizacion_id, servicio, related_name, variable, label):
def _auditar_organizacion(organizacion_id, servicio, related_name, variable, label, task_id=None, user_id=None):
"""
Itera todos los pedimentos de una organización auditando el campo `variable`
en la relación `related_name`. Retorna un resumen estructurado por pedimento.
Publica eventos SSE en Redis si se proporciona task_id.
"""
pedimentos = obtener_pedimentos(organizacion_id)
total_pedimentos = pedimentos.count()
if task_id:
publish_task_event(task_id, "processing", f"Auditando {label}: {total_pedimentos} pedimentos", progress=0)
completados = []
pendientes = []
errores = []
for pedimento in pedimentos:
for idx, pedimento in enumerate(pedimentos):
try:
docs = list(getattr(pedimento, related_name).all())
total = len(docs)
@@ -266,6 +325,11 @@ def _auditar_organizacion(organizacion_id, servicio, related_name, variable, lab
modificar_estado_procesamiento(pedimento, servicio_id=servicio, nuevo_estado=nuevo_estado)
# Publicar progreso cada 10 pedimentos para no saturar Redis
if task_id and total_pedimentos > 0 and (idx + 1) % 10 == 0:
pct = int(((idx + 1) / total_pedimentos) * 100)
publish_task_event(task_id, "processing", f"Auditando {label}: {idx + 1}/{total_pedimentos}", progress=pct)
except Exception as e:
errores.append({
'pedimento_id': str(pedimento.id),
@@ -274,7 +338,7 @@ def _auditar_organizacion(organizacion_id, servicio, related_name, variable, lab
})
logger.error(f"Error auditando pedimento {pedimento.id} [{label}]: {e}")
return {
resultado = {
'organizacion_id': str(organizacion_id),
'auditoria': label,
'total_pedimentos': total_pedimentos,
@@ -285,73 +349,89 @@ def _auditar_organizacion(organizacion_id, servicio, related_name, variable, lab
'detalle_errores': errores,
}
if task_id:
publish_task_event(task_id, "completed", f"Auditoría de {label} completada", resultado=resultado, progress=100)
if user_id:
_crear_notificacion_auditoria(user_id, task_id, label, resultado)
@shared_task
def auditar_coves(organizacion_id):
return resultado
@shared_task(bind=True)
def auditar_coves(self, organizacion_id, user_id=None):
return _auditar_organizacion(
organizacion_id,
servicio=8,
related_name='coves',
variable='cove_descargado',
label='cove',
task_id=self.request.id,
user_id=user_id,
)
@shared_task
def auditar_acuse_cove(organizacion_id):
@shared_task(bind=True)
def auditar_acuse_cove(self, organizacion_id, user_id=None):
return _auditar_organizacion(
organizacion_id,
servicio=9,
related_name='coves',
variable='acuse_cove_descargado',
label='acuse_cove',
task_id=self.request.id,
user_id=user_id,
)
@shared_task
def auditar_edocuments(organizacion_id):
@shared_task(bind=True)
def auditar_edocuments(self, organizacion_id, user_id=None):
return _auditar_organizacion(
organizacion_id,
servicio=7,
related_name='documentos',
variable='edocument_descargado',
label='edocument',
task_id=self.request.id,
user_id=user_id,
)
@shared_task
def auditar_acuse(organizacion_id):
@shared_task(bind=True)
def auditar_acuse(self, organizacion_id, user_id=None):
return _auditar_organizacion(
organizacion_id,
servicio=6,
related_name='documentos',
variable='acuse_descargado',
label='acuse',
task_id=self.request.id,
user_id=user_id,
)
@shared_task
def auditar_remesas(organizacion_id):
@shared_task(bind=True)
def auditar_remesas(self, organizacion_id, user_id=None):
"""
Audita el estado de descarga de remesas para todos los pedimentos de una organización.
A diferencia de coves/edocuments, las remesas no tienen campo booleano propio —
se verifica la existencia de un documento de tipo 3 (Remesa) en el pedimento.
"""
task_id = self.request.id
pedimentos = obtener_pedimentos(organizacion_id)
total_pedimentos = pedimentos.count()
if task_id:
publish_task_event(task_id, "processing", f"Auditando remesas: {total_pedimentos} pedimentos", progress=0)
completados = []
pendientes = []
errores = []
for pedimento in pedimentos:
for idx, pedimento in enumerate(pedimentos):
try:
if not pedimento.remesas:
# El pedimento no declara remesas — no aplica, marcar como completado
modificar_estado_procesamiento(pedimento, servicio_id=5, nuevo_estado=3)
completados.append(str(pedimento.id))
elif pedimento.documents.filter(document_type=3).exists():
# Documento de remesa ya descargado
modificar_estado_procesamiento(pedimento, servicio_id=5, nuevo_estado=3)
completados.append(str(pedimento.id))
else:
# Tiene remesas declaradas pero el documento aún no existe
modificar_estado_procesamiento(pedimento, servicio_id=5, nuevo_estado=4)
pendientes.append({
'pedimento_id': str(pedimento.id),
@@ -365,7 +445,11 @@ def auditar_remesas(organizacion_id):
})
logger.error(f"Error auditando remesa de pedimento {pedimento.id}: {e}")
return {
if task_id and total_pedimentos > 0 and (idx + 1) % 10 == 0:
pct = int(((idx + 1) / total_pedimentos) * 100)
publish_task_event(task_id, "processing", f"Auditando remesas: {idx + 1}/{total_pedimentos}", progress=pct)
resultado = {
'organizacion_id': str(organizacion_id),
'auditoria': 'remesa',
'total_pedimentos': total_pedimentos,
@@ -376,6 +460,13 @@ def auditar_remesas(organizacion_id):
'detalle_errores': errores,
}
if task_id:
publish_task_event(task_id, "completed", "Auditoría de remesas completada", resultado=resultado, progress=100)
if user_id:
_crear_notificacion_auditoria(user_id, task_id, "Remesas", resultado)
return resultado
@shared_task
def auditar_cove_por_pedimento(pedimento_id):
try: