767 lines
32 KiB
Python
767 lines
32 KiB
Python
from api.organization.models import Organizacion
|
|
from celery import group
|
|
from celery import shared_task, group
|
|
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, MAX_INTENTOS_AUTO
|
|
from datetime import datetime
|
|
import json
|
|
import logging
|
|
import uuid
|
|
# este solo fue para pruebas personales, lo dejo por si en un futuro lo requiero
|
|
TEST_ORG_ID = uuid.UUID('defc7848-4f39-4d67-9dba-5bb445248d23')
|
|
logger = logging.getLogger('api.customs.microservice_v2')
|
|
|
|
def credenciales_to_dict(credenciales):
|
|
if not credenciales:
|
|
return {}
|
|
|
|
key_value = None
|
|
if credenciales.key:
|
|
if hasattr(credenciales.key, 'url'):
|
|
key_value = credenciales.key.url
|
|
else:
|
|
key_value = str(credenciales.key)
|
|
|
|
cer_value = None
|
|
if credenciales.cer:
|
|
if hasattr(credenciales.cer, 'url'):
|
|
cer_value = credenciales.cer.url
|
|
else:
|
|
cer_value = str(credenciales.cer)
|
|
|
|
return {
|
|
"id": str(credenciales.id),
|
|
"user": credenciales.usuario,
|
|
"password": credenciales.password,
|
|
"efirma": credenciales.efirma,
|
|
"key": key_value,
|
|
"cer": cer_value,
|
|
"is_active": credenciales.is_active,
|
|
"organizacion": str(credenciales.organizacion.id) if credenciales.organizacion else None,
|
|
}
|
|
|
|
def pedimento_to_dict(pedimento):
|
|
return {
|
|
"id": str(pedimento.id),
|
|
"pedimento": str(pedimento.pedimento),
|
|
"pedimento_app": str(pedimento.pedimento_app),
|
|
"aduana": str(pedimento.aduana),
|
|
"patente": str(pedimento.patente),
|
|
"organizacion": str(pedimento.organizacion.id), # nunca None
|
|
"regimen": str(pedimento.regimen or ""),
|
|
"clave_pedimento": str(pedimento.clave_pedimento or ""),
|
|
"numero_operacion": str(pedimento.numero_operacion or "")
|
|
}
|
|
|
|
def cove_to_dict(cove):
|
|
return {
|
|
"id": cove.id,
|
|
"cove": str(cove.numero_cove),
|
|
}
|
|
|
|
def edoc_to_dict(edoc):
|
|
return {
|
|
"id": edoc.id,
|
|
"numero_edocument": str(edoc.numero_edocument),
|
|
}
|
|
|
|
def partida_to_dict(partida):
|
|
return {
|
|
"id": partida.id,
|
|
"numero": partida.numero_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)
|
|
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_estado__in=estados_reprocesables)],
|
|
"pedimento": pedimento_dict,
|
|
"credencial": credenciales_dict
|
|
}
|
|
|
|
try:
|
|
response = requests.post(
|
|
f"{SERVICE_API_URL_V2}/services/all/coves",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
logging.info(f"COVEs encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(f"Error encolando COVEs para pedimento {pedimento.pedimento}: {e}")
|
|
raise
|
|
|
|
@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)
|
|
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
|
|
).first()
|
|
credenciales_dict = credenciales_to_dict(credenciales)
|
|
|
|
payload = {
|
|
"coves": [cove_to_dict(cove) for cove in pedimento.coves.filter(acuse_cove_estado__in=estados_reprocesables)],
|
|
"pedimento": pedimento_dict,
|
|
"credencial": credenciales_dict
|
|
}
|
|
|
|
try:
|
|
response = requests.post(
|
|
f"{SERVICE_API_URL_V2}/services/all/acuse/cove/",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
logging.info(f"Acuses de COVEs encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(f"Error encolando acuses de COVEs para pedimento {pedimento.pedimento}: {e}")
|
|
raise
|
|
|
|
@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)
|
|
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
|
|
).first()
|
|
credenciales_dict = credenciales_to_dict(credenciales)
|
|
|
|
payload = {
|
|
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(edocument_estado__in=estados_reprocesables)],
|
|
"pedimento": pedimento_dict,
|
|
"credencial": credenciales_dict
|
|
}
|
|
|
|
try:
|
|
response = requests.post(
|
|
f"{SERVICE_API_URL_V2}/services/download/all/edocs/",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
logging.info(f"E-documents encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(f"Error encolando E-documents para pedimento {pedimento.pedimento}: {e}")
|
|
raise
|
|
|
|
@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)
|
|
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
|
|
).first()
|
|
credenciales_dict = credenciales_to_dict(credenciales)
|
|
|
|
payload = {
|
|
"edocs": [edoc_to_dict(edoc) for edoc in pedimento.documentos.filter(acuse_estado__in=estados_reprocesables)],
|
|
"pedimento": pedimento_dict,
|
|
"credencial": credenciales_dict
|
|
}
|
|
|
|
try:
|
|
response = requests.post(
|
|
f"{SERVICE_API_URL_V2}/services/all/acuse/pedimento/",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
logging.info(f"Acuses encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(f"Error encolando acuses para pedimento {pedimento.pedimento}: {e}")
|
|
raise
|
|
|
|
@shared_task
|
|
def procesar_partidas_pedimento(pedimento_id):
|
|
pedimento = Pedimento.objects.get(id=pedimento_id)
|
|
if pedimento.partidas.filter(descargado=False).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)
|
|
|
|
partidas_pendientes = list(pedimento.partidas.filter(descargado=False))
|
|
payload = {
|
|
"partidas": [partida_to_dict(p) for p in partidas_pendientes],
|
|
"pedimento": pedimento_dict,
|
|
"credencial": credenciales_dict
|
|
}
|
|
|
|
try:
|
|
response = requests.post(
|
|
f"{SERVICE_API_URL_V2}/services/all/partidas/",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
result = response.json()
|
|
logging.info(
|
|
f"Partidas encoladas para pedimento {pedimento.pedimento}: "
|
|
f"{result.get('total', 0)} de {len(partidas_pendientes)}"
|
|
)
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(
|
|
f"Error encolando partidas para pedimento {pedimento.pedimento}: {e}"
|
|
)
|
|
raise
|
|
|
|
@shared_task
|
|
def procesar_remesas_pedimento(pedimento_id):
|
|
pedimento = Pedimento.objects.get(id=pedimento_id)
|
|
if not pedimento.documents.filter(document_type=3).exists(): # Tipo 3: Remesa
|
|
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 = {
|
|
"pedimento": pedimento_dict,
|
|
"credencial": credenciales_dict
|
|
}
|
|
|
|
try:
|
|
response = requests.post(
|
|
f"{SERVICE_API_URL_V2}/services/remesas",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
logging.info(f"Remesa encolada para pedimento {pedimento.pedimento}")
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(f"Error encolando remesa para pedimento {pedimento.pedimento}: {e}")
|
|
raise
|
|
|
|
@shared_task
|
|
def procesar_pedimento_completo_individual(pedimento_id, force=False):
|
|
pedimento = Pedimento.objects.get(id=pedimento_id)
|
|
if force or not pedimento.documents.filter(document_type=2).exists(): # Tipo 2: Pedimento Completo
|
|
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 = {
|
|
"pedimento": pedimento_dict,
|
|
"credencial": credenciales_dict
|
|
}
|
|
try:
|
|
response = requests.post(
|
|
f"{SERVICE_API_URL_V2}/services/pedimento_completo",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
logging.info(f"Pedimento completo encolado: {pedimento.pedimento}")
|
|
return response
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(f"Error encolando pedimento completo {pedimento.pedimento}: {e}")
|
|
raise
|
|
|
|
@shared_task
|
|
def procesar_pedimentos_completos(organizacion_id):
|
|
pedimentos = Pedimento.objects.filter(organizacion_id=organizacion_id)
|
|
respuestas = []
|
|
for pedimento in pedimentos:
|
|
|
|
if not pedimento.contribuyente:
|
|
print(f"Pedimento {pedimento.pedimento} no tiene contribuyente")
|
|
continue
|
|
|
|
credencial_importador = CredencialesImportador.objects.filter(
|
|
rfc=pedimento.contribuyente
|
|
).first()
|
|
|
|
if not credencial_importador:
|
|
print(f"No credencial para RFC {pedimento.contribuyente.rfc}")
|
|
continue
|
|
|
|
if not pedimento.documents.filter(document_type=2).exists(): # Tipo 2: Pedimento Completo
|
|
# 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 = Vucem.objects.filter(id=credencial_importador.vucem.id).first()
|
|
|
|
if not credenciales:
|
|
print(f"No se encontraron credenciales para el pedimento {pedimento.pedimento_app}")
|
|
continue
|
|
|
|
credenciales_dict = credenciales_to_dict(credenciales)
|
|
payload = {
|
|
"pedimento": pedimento_dict,
|
|
"credencial": credenciales_dict
|
|
}
|
|
|
|
url = f"{SERVICE_API_URL_V2}/services/pedimento_completo"
|
|
dataJson = json.dumps(payload)
|
|
|
|
try:
|
|
response = requests.post(
|
|
url,
|
|
data=dataJson,
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
logging.info(f"Pedimento completo encolado: {pedimento.pedimento}")
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(f"Error encolando pedimento completo {pedimento.pedimento}: {e}")
|
|
continue
|
|
|
|
@shared_task
|
|
def procesar_remesas(organizacion_id):
|
|
pedimentos = Pedimento.objects.filter(organizacion_id=organizacion_id)
|
|
|
|
for pedimento in pedimentos:
|
|
logger.info(f"pedimento >>>> {pedimento}")
|
|
try:
|
|
# if pedimento.documents.filter(document_type=3).exists(): # Remesa ya descargada
|
|
# logger.info(f"Pedimento {pedimento.pedimento} ya tiene remesa descargada, omitiendo.")
|
|
# continue
|
|
|
|
pedimento_dict = pedimento_to_dict(pedimento)
|
|
|
|
credencial_importador = CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first()
|
|
if not credencial_importador:
|
|
logger.warning(f"Sin credenciales para RFC {pedimento.contribuyente} (pedimento {pedimento.pedimento}), omitiendo.")
|
|
continue
|
|
|
|
credenciales = Vucem.objects.filter(id=credencial_importador.vucem.id).first()
|
|
if not credenciales:
|
|
logger.warning(f"Credencial Vucem no encontrada para pedimento {pedimento.pedimento}, omitiendo.")
|
|
continue
|
|
|
|
credenciales_dict = credenciales_to_dict(credenciales)
|
|
|
|
payload = {
|
|
"pedimento": pedimento_dict,
|
|
"credencial": credenciales_dict
|
|
}
|
|
|
|
response = requests.post(
|
|
f"{SERVICE_API_URL_V2}/services/remesas/",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
logger.info(f"Remesa encolada para pedimento {pedimento.pedimento} — status {response.status_code}")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error procesando remesa para pedimento {pedimento.pedimento}: {e}", exc_info=True)
|
|
|
|
@shared_task
|
|
def procesar_coves(organizacion_id):
|
|
pedimentos = Pedimento.objects.filter(
|
|
organizacion_id=organizacion_id,
|
|
coves__isnull=False
|
|
).distinct()
|
|
for pedimento in pedimentos:
|
|
# 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 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",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
logging.info(f"COVEs encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(f"Error encolando COVEs para pedimento {pedimento.pedimento}: {e}")
|
|
continue
|
|
|
|
@shared_task
|
|
def procesar_acuse_coves(organizacion_id):
|
|
pedimentos = Pedimento.objects.filter(
|
|
organizacion_id=organizacion_id,
|
|
coves__isnull=False
|
|
).distinct()
|
|
|
|
for pedimento in pedimentos:
|
|
# 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 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/",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
logging.info(f"Acuses de COVEs encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(f"Error encolando acuses de COVEs para pedimento {pedimento.pedimento}: {e}")
|
|
continue
|
|
|
|
@shared_task
|
|
def procesar_acuses(organizacion_id):
|
|
pedimentos = Pedimento.objects.filter(
|
|
organizacion_id=organizacion_id,
|
|
documentos__isnull=False
|
|
).distinct()
|
|
|
|
for pedimento in pedimentos:
|
|
# 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 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/",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
logging.info(f"Acuses encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(f"Error encolando acuses para pedimento {pedimento.pedimento}: {e}")
|
|
continue
|
|
|
|
@shared_task
|
|
def procesar_edocs(organizacion_id):
|
|
pedimentos = Pedimento.objects.filter(
|
|
organizacion_id=organizacion_id,
|
|
documentos__isnull=False
|
|
).distinct()
|
|
|
|
for pedimento in pedimentos:
|
|
# 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 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/",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
logging.info(f"E-documents encolados para pedimento {pedimento.pedimento}: {response.json().get('total', '?')}")
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(f"Error encolando E-documents para pedimento {pedimento.pedimento}: {e}")
|
|
continue
|
|
|
|
@shared_task
|
|
def procesar_partidas(organizacion_id):
|
|
pedimentos = Pedimento.objects.filter(
|
|
organizacion_id=organizacion_id,
|
|
partidas__isnull=False
|
|
).distinct()
|
|
|
|
for pedimento in pedimentos:
|
|
partidas_pendientes = list(pedimento.partidas.filter(descargado=False))
|
|
if not partidas_pendientes:
|
|
continue
|
|
|
|
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 = {
|
|
"partidas": [partida_to_dict(p) for p in partidas_pendientes],
|
|
"pedimento": pedimento_dict,
|
|
"credencial": credenciales_dict
|
|
}
|
|
|
|
try:
|
|
response = requests.post(
|
|
f"{SERVICE_API_URL_V2}/services/all/partidas/",
|
|
data=json.dumps(payload),
|
|
headers={"Content-Type": "application/json"},
|
|
timeout=60
|
|
)
|
|
response.raise_for_status()
|
|
result = response.json()
|
|
logging.info(
|
|
f"Partidas encoladas para pedimento {pedimento.pedimento}: "
|
|
f"{result.get('total', 0)} de {len(partidas_pendientes)}"
|
|
)
|
|
except requests.exceptions.RequestException as e:
|
|
logging.error(
|
|
f"Error encolando partidas para pedimento {pedimento.pedimento}: {e}"
|
|
)
|
|
continue
|
|
|
|
@shared_task
|
|
def documentos_con_errores(organizacion_id):
|
|
documentos = Document.objects.filter(organizacion_id=organizacion_id)
|
|
|
|
for doc in documentos:
|
|
if doc.document_type is None or doc.size is None or doc.archivo is None:
|
|
print(f"Documento con error: {doc.id} en organización {organizacion_id}")
|
|
# Aquí puedes agregar lógica adicional para manejar documentos con errores
|
|
# como enviar notificaciones, registrar en un log, etc.
|
|
|
|
@shared_task
|
|
def procesar_procesamiento_pedimento(organizacion_id):
|
|
# print("Creando procesamientos de pedimentos para organización:", organizacion_id)
|
|
|
|
pedimentos = Pedimento.objects.filter(organizacion_id=organizacion_id)
|
|
# pedimentos = Pedimento.objects.filter(id='1c061182-ac68-45b0-b3d7-35bf2264982b')
|
|
if not pedimentos.exists():
|
|
print("No se encontraron pedimentos para la organización:", organizacion_id)
|
|
return
|
|
for pedimento in pedimentos:
|
|
if not pedimento.documents.filter(document_type=2).exists(): # Tipo 2: Pedimento Completo
|
|
|
|
procesamiento_pedimento = ProcesamientoPedimento.objects.filter(
|
|
pedimento_id=pedimento.id,
|
|
servicio_id=3, # servicio 3: Pedimento Completo
|
|
)
|
|
|
|
if not procesamiento_pedimento.exists():
|
|
ProcesamientoPedimento.objects.create(
|
|
pedimento_id=pedimento.id
|
|
, organizacion_id=pedimento.organizacion_id
|
|
, estado_id =1
|
|
, servicio_id=3
|
|
, tipo_procesamiento_id=2) # servicio 3: Pedimento Completo
|
|
|
|
# print("Procesamiento creado para pedimento:", pedimento.pedimento_app)
|
|
|
|
procesar_pedimentos_completos.delay(organizacion_id)
|
|
|
|
def ejecutar_por_organizacion_y_procesamiento(organizacion_id, procesamiento):
|
|
if procesamiento == 'coves':
|
|
procesar_coves.delay(organizacion_id)
|
|
elif procesamiento == 'edocs':
|
|
procesar_edocs.delay(organizacion_id)
|
|
elif procesamiento == 'acuses':
|
|
procesar_acuses.delay(organizacion_id)
|
|
elif procesamiento == 'acuse_coves':
|
|
procesar_acuse_coves.delay(organizacion_id)
|
|
elif procesamiento == 'partidas':
|
|
procesar_partidas.delay(organizacion_id)
|
|
elif procesamiento == 'pedimentos_completos':
|
|
procesar_pedimentos_completos.delay(organizacion_id)
|
|
elif procesamiento == 'remesas':
|
|
procesar_remesas.delay(organizacion_id)
|
|
elif procesamiento == 'procesamiento_pedimento':
|
|
procesar_procesamiento_pedimento.delay(organizacion_id)
|
|
else:
|
|
# Procesamiento no reconocido
|
|
# print(f"Procesamiento no reconocido: {procesamiento}")
|
|
pass
|
|
|
|
def ejecutar_todos_por_organizacion(organizacion_id):
|
|
procesar_coves.delay(organizacion_id)
|
|
procesar_edocs.delay(organizacion_id)
|
|
procesar_acuses.delay(organizacion_id)
|
|
procesar_acuse_coves.delay(organizacion_id)
|
|
procesar_partidas.delay(organizacion_id)
|
|
procesar_pedimentos_completos.delay(organizacion_id)
|
|
procesar_remesas.delay(organizacion_id)
|
|
|
|
def ejecutar_basicos_organizacion(organizacion_id):
|
|
# solo coves y e documents, si es necesario ya en un futuro se agregan los de partidas, pedimento completo y esas madres
|
|
procesar_coves.delay(organizacion_id)
|
|
procesar_acuse_coves.delay(organizacion_id)
|
|
procesar_edocs.delay(organizacion_id)
|
|
procesar_acuses.delay(organizacion_id)
|
|
# procesar_partidas.delay(organizacion_id)
|
|
# procesar_pedimentos_completos.delay(organizacion_id)
|
|
# procesar_remesas.delay(organizacion_id)
|
|
|
|
@shared_task
|
|
def process_organization_batch(org_id):
|
|
"""
|
|
Procesa todos los tipos de documentos pendientes para una organización.
|
|
"""
|
|
ejecutar_basicos_organizacion(org_id)
|
|
|
|
@shared_task
|
|
def process_all_organizations():
|
|
"""
|
|
Envía una tarea por organización activa a la cola org_processing.
|
|
"""
|
|
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"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"
|
|
|