fix/de los tickets T2026-05-027, T2025-09-004 y T2025-09-056
This commit is contained in:
@@ -5,8 +5,10 @@ from api.customs.models import *
|
||||
from api.record.models import *
|
||||
from api.customs.serializers import PedimentoSerializer
|
||||
from api.vucem.models import *
|
||||
from django.db.models import F
|
||||
from django.utils import timezone
|
||||
import requests
|
||||
from config.settings import SERVICE_API_URL_V2
|
||||
from config.settings import SERVICE_API_URL_V2, MAX_INTENTOS_AUTO
|
||||
from datetime import datetime
|
||||
import json
|
||||
import logging
|
||||
@@ -77,16 +79,18 @@ def partida_to_dict(partida):
|
||||
|
||||
@shared_task
|
||||
def procesar_coves_pedimento(pedimento_id):
|
||||
# Flujo manual: incluye registros en 'error' y no aplica tope ni contador de intentos
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
if pedimento.coves.filter(cove_descargado=False).exists():
|
||||
estados_reprocesables = [EstadoDescarga.PENDIENTE, EstadoDescarga.ERROR]
|
||||
if pedimento.coves.filter(cove_estado__in=estados_reprocesables).exists():
|
||||
pedimento_dict = pedimento_to_dict(pedimento)
|
||||
credenciales = Vucem.objects.filter(
|
||||
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||
).first()
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
|
||||
payload = {
|
||||
"coves": [cove_to_dict(cove) for cove in pedimento.coves.filter(cove_descargado=False)],
|
||||
"coves": [cove_to_dict(cove) for cove in pedimento.coves.filter(cove_estado__in=estados_reprocesables)],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
@@ -106,8 +110,10 @@ def procesar_coves_pedimento(pedimento_id):
|
||||
|
||||
@shared_task
|
||||
def procesar_acuse_coves_pedimento(pedimento_id):
|
||||
# Flujo manual: incluye registros en 'error' y no aplica tope ni contador de intentos
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
if pedimento.coves.filter(acuse_cove_descargado=False).exists():
|
||||
estados_reprocesables = [EstadoDescarga.PENDIENTE, EstadoDescarga.ERROR]
|
||||
if pedimento.coves.filter(acuse_cove_estado__in=estados_reprocesables).exists():
|
||||
pedimento_dict = pedimento_to_dict(pedimento)
|
||||
credenciales = Vucem.objects.filter(
|
||||
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||
@@ -115,7 +121,7 @@ def procesar_acuse_coves_pedimento(pedimento_id):
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
payload = {
|
||||
"coves": [cove_to_dict(cove) for cove in pedimento.coves.filter(acuse_cove_descargado=False)],
|
||||
"coves": [cove_to_dict(cove) for cove in pedimento.coves.filter(acuse_cove_estado__in=estados_reprocesables)],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
@@ -135,8 +141,10 @@ def procesar_acuse_coves_pedimento(pedimento_id):
|
||||
|
||||
@shared_task
|
||||
def procesar_edocs_pedimento(pedimento_id):
|
||||
# Flujo manual: incluye registros en 'error' y no aplica tope ni contador de intentos
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
if pedimento.documentos.filter(edocument_descargado=False).exists():
|
||||
estados_reprocesables = [EstadoDescarga.PENDIENTE, EstadoDescarga.ERROR]
|
||||
if pedimento.documentos.filter(edocument_estado__in=estados_reprocesables).exists():
|
||||
pedimento_dict = pedimento_to_dict(pedimento)
|
||||
credenciales = Vucem.objects.filter(
|
||||
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||
@@ -144,7 +152,7 @@ def procesar_edocs_pedimento(pedimento_id):
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
payload = {
|
||||
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(edocument_descargado=False)],
|
||||
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(edocument_estado__in=estados_reprocesables)],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
@@ -164,8 +172,10 @@ def procesar_edocs_pedimento(pedimento_id):
|
||||
|
||||
@shared_task
|
||||
def procesar_acuses_pedimento(pedimento_id):
|
||||
# Flujo manual: incluye registros en 'error' y no aplica tope ni contador de intentos
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
if pedimento.documentos.filter(acuse_descargado=False).exists():
|
||||
estados_reprocesables = [EstadoDescarga.PENDIENTE, EstadoDescarga.ERROR]
|
||||
if pedimento.documentos.filter(acuse_estado__in=estados_reprocesables).exists():
|
||||
pedimento_dict = pedimento_to_dict(pedimento)
|
||||
credenciales = Vucem.objects.filter(
|
||||
id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id
|
||||
@@ -173,7 +183,7 @@ def procesar_acuses_pedimento(pedimento_id):
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
payload = {
|
||||
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(acuse_descargado=False)],
|
||||
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(acuse_estado__in=estados_reprocesables)],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
@@ -381,20 +391,31 @@ def procesar_coves(organizacion_id):
|
||||
coves__isnull=False
|
||||
).distinct()
|
||||
for pedimento in pedimentos:
|
||||
if pedimento.coves.filter(cove_descargado=False).exists(): # Tipo 3: Remesa
|
||||
# Compuerta del automático (T2026-05-027): solo 'pendiente' con intentos disponibles;
|
||||
# registros en 'error' o con tope agotado solo se relanzan de forma manual
|
||||
pendientes = pedimento.coves.filter(
|
||||
cove_estado=EstadoDescarga.PENDIENTE,
|
||||
cove_intentos__lt=MAX_INTENTOS_AUTO,
|
||||
)
|
||||
coves_batch = list(pendientes)
|
||||
if coves_batch:
|
||||
|
||||
# Convertir el pedimento a JSON usando el serializer
|
||||
pedimento_dict = pedimento_to_dict(pedimento)
|
||||
credenciales = Vucem.objects.filter(id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id).first()
|
||||
|
||||
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
|
||||
payload = {
|
||||
"coves": [cove_to_dict(cove) for cove in pedimento.coves.filter(cove_descargado=False)],
|
||||
"coves": [cove_to_dict(cove) for cove in coves_batch],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
|
||||
# Un ciclo de orquestación = un intento; los reintentos internos
|
||||
# del worker (Celery/SOAP) pertenecen a este mismo intento
|
||||
pendientes.update(cove_intentos=F('cove_intentos') + 1, ultimo_intento_at=timezone.now())
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/coves",
|
||||
@@ -416,20 +437,29 @@ def procesar_acuse_coves(organizacion_id):
|
||||
).distinct()
|
||||
|
||||
for pedimento in pedimentos:
|
||||
if pedimento.coves.filter(acuse_cove_descargado=False).exists(): # Tipo 3: Remesa
|
||||
# Compuerta del automático (T2026-05-027): solo 'pendiente' con intentos disponibles
|
||||
pendientes = pedimento.coves.filter(
|
||||
acuse_cove_estado=EstadoDescarga.PENDIENTE,
|
||||
acuse_cove_intentos__lt=MAX_INTENTOS_AUTO,
|
||||
)
|
||||
coves_batch = list(pendientes)
|
||||
if coves_batch:
|
||||
|
||||
# Convertir el pedimento a JSON usando el serializer
|
||||
pedimento_dict = pedimento_to_dict(pedimento)
|
||||
credenciales = Vucem.objects.filter(id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id).first()
|
||||
|
||||
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
|
||||
payload = {
|
||||
"coves": [cove_to_dict(cove) for cove in pedimento.coves.filter(acuse_cove_descargado=False)],
|
||||
"coves": [cove_to_dict(cove) for cove in coves_batch],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
|
||||
# Un ciclo de orquestación = un intento
|
||||
pendientes.update(acuse_cove_intentos=F('acuse_cove_intentos') + 1, ultimo_intento_at=timezone.now())
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/acuse/cove/",
|
||||
@@ -451,20 +481,29 @@ def procesar_acuses(organizacion_id):
|
||||
).distinct()
|
||||
|
||||
for pedimento in pedimentos:
|
||||
if pedimento.documentos.filter(acuse_descargado=False).exists(): # Tipo 3: Remesa
|
||||
# Compuerta del automático (T2026-05-027): solo 'pendiente' con intentos disponibles
|
||||
pendientes = pedimento.documentos.filter(
|
||||
acuse_estado=EstadoDescarga.PENDIENTE,
|
||||
acuse_intentos__lt=MAX_INTENTOS_AUTO,
|
||||
)
|
||||
edocs_batch = list(pendientes)
|
||||
if edocs_batch:
|
||||
|
||||
# Convertir el pedimento a JSON usando el serializer
|
||||
pedimento_dict = pedimento_to_dict(pedimento)
|
||||
credenciales = Vucem.objects.filter(id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id).first()
|
||||
|
||||
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
|
||||
payload = {
|
||||
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(acuse_descargado=False)],
|
||||
"edocs": [edoc_to_dict(edoc) for edoc in edocs_batch],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
# Un ciclo de orquestación = un intento
|
||||
pendientes.update(acuse_intentos=F('acuse_intentos') + 1, ultimo_intento_at=timezone.now())
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/all/acuse/pedimento/",
|
||||
@@ -486,20 +525,29 @@ def procesar_edocs(organizacion_id):
|
||||
).distinct()
|
||||
|
||||
for pedimento in pedimentos:
|
||||
if pedimento.documentos.filter(edocument_descargado=False).exists(): # Tipo 3: Remesa
|
||||
# Compuerta del automático (T2026-05-027): solo 'pendiente' con intentos disponibles
|
||||
pendientes = pedimento.documentos.filter(
|
||||
edocument_estado=EstadoDescarga.PENDIENTE,
|
||||
edocument_intentos__lt=MAX_INTENTOS_AUTO,
|
||||
)
|
||||
edocs_batch = list(pendientes)
|
||||
if edocs_batch:
|
||||
|
||||
# Convertir el pedimento a JSON usando el serializer
|
||||
pedimento_dict = pedimento_to_dict(pedimento)
|
||||
credenciales = Vucem.objects.filter(id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id).first()
|
||||
|
||||
|
||||
credenciales_dict = credenciales_to_dict(credenciales)
|
||||
|
||||
|
||||
payload = {
|
||||
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(edocument_descargado=False)],
|
||||
"edocs": [edoc_to_dict(edoc) for edoc in edocs_batch],
|
||||
"pedimento": pedimento_dict,
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
# Un ciclo de orquestación = un intento
|
||||
pendientes.update(edocument_intentos=F('edocument_intentos') + 1, ultimo_intento_at=timezone.now())
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/download/all/edocs/",
|
||||
@@ -660,3 +708,59 @@ def process_all_organizations():
|
||||
)
|
||||
return f"Dispatched {active_orgs.count()} organizations"
|
||||
|
||||
@shared_task
|
||||
def reintentar_descargas_pendientes():
|
||||
"""
|
||||
Reintento recurrente de descargas VUCEM (T2026-05-027): transiciona a 'error'
|
||||
los registros que agotaron MAX_INTENTOS_AUTO y relanza los pendientes por
|
||||
organización. El incremento del contador vive en las tareas procesar_*
|
||||
(puerta común de todos los flujos automáticos), por lo que aquí solo se orquesta.
|
||||
"""
|
||||
ahora = timezone.now()
|
||||
mensaje_tope = (
|
||||
f"Se agotaron {MAX_INTENTOS_AUTO} intentos automáticos de descarga; "
|
||||
f"requiere reproceso manual"
|
||||
)
|
||||
|
||||
# 1) Transicionar a 'error' lo que agotó el tope automático.
|
||||
# update() no pasa por save(): sincronizar también el booleano legado y updated_at.
|
||||
edocs_err = EDocument.objects.filter(
|
||||
edocument_estado=EstadoDescarga.PENDIENTE,
|
||||
edocument_intentos__gte=MAX_INTENTOS_AUTO,
|
||||
).update(edocument_estado=EstadoDescarga.ERROR, edocument_descargado=False,
|
||||
ultimo_error=mensaje_tope, updated_at=ahora)
|
||||
acuses_err = EDocument.objects.filter(
|
||||
acuse_estado=EstadoDescarga.PENDIENTE,
|
||||
acuse_intentos__gte=MAX_INTENTOS_AUTO,
|
||||
).update(acuse_estado=EstadoDescarga.ERROR, acuse_descargado=False,
|
||||
ultimo_error=mensaje_tope, updated_at=ahora)
|
||||
coves_err = Cove.objects.filter(
|
||||
cove_estado=EstadoDescarga.PENDIENTE,
|
||||
cove_intentos__gte=MAX_INTENTOS_AUTO,
|
||||
).update(cove_estado=EstadoDescarga.ERROR, cove_descargado=False,
|
||||
ultimo_error=mensaje_tope, updated_at=ahora)
|
||||
acuse_coves_err = Cove.objects.filter(
|
||||
acuse_cove_estado=EstadoDescarga.PENDIENTE,
|
||||
acuse_cove_intentos__gte=MAX_INTENTOS_AUTO,
|
||||
).update(acuse_cove_estado=EstadoDescarga.ERROR, acuse_cove_descargado=False,
|
||||
ultimo_error=mensaje_tope, updated_at=ahora)
|
||||
|
||||
if edocs_err or acuses_err or coves_err or acuse_coves_err:
|
||||
logger.info(
|
||||
f"Tope de intentos agotado -> error: edocs={edocs_err}, acuses={acuses_err}, "
|
||||
f"coves={coves_err}, acuse_coves={acuse_coves_err}"
|
||||
)
|
||||
|
||||
# 2) Relanzar por organización (procesar_* aplica la compuerta e incrementa el contador)
|
||||
active_orgs = Organizacion.objects.filter(
|
||||
is_active=True,
|
||||
is_verified=True,
|
||||
apply_auto_download=True,
|
||||
)
|
||||
for org in active_orgs:
|
||||
process_organization_batch.apply_async(
|
||||
args=[str(org.id)],
|
||||
queue='org_processing'
|
||||
)
|
||||
return f"Reintentos despachados para {active_orgs.count()} organizaciones"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user