feature/T2026-05-016-y-T2026-05-031 #28
@@ -157,7 +157,7 @@ class ViewPedimentoServicesUtilInformation(LoggingMixin, APIView, FiltroPorOrgan
|
||||
|
||||
# Si es importador de la organizacion, devuelve los servicios relacionados con sus pedimentos
|
||||
if self.request.user.is_authenticated and self.request.user.groups.filter(name='importador').exists() and self.request.user.is_importador and self.request.user.groups.filter(name='user').exists():
|
||||
return self.request.user.organizacion.procesamiento_pedimentos.filter(pedimento__contribuyente=self.request.user.rfc)
|
||||
return self.request.user.organizacion.procesamiento_pedimentos.filter(pedimento__contribuyente__in=self.request.user.rfc.all())
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -47,55 +47,31 @@ class PartidaSerializer(serializers.ModelSerializer):
|
||||
documentos = serializers.SerializerMethodField()
|
||||
|
||||
def get_documentos(self, obj):
|
||||
"""
|
||||
Busca documentos en la tabla `document` que coincidan EXACTAMENTE con:
|
||||
'documents/vu_PT_{pedimentoApp}_{numero}' al inicio del nombre del archivo.
|
||||
"""
|
||||
|
||||
if not obj or not getattr(obj, 'pedimento', None):
|
||||
return []
|
||||
|
||||
if not obj or not getattr(obj, 'numero_partida', None):
|
||||
return []
|
||||
|
||||
try:
|
||||
pedimentoApp = str(obj.pedimento.pedimento_app).strip()
|
||||
pedimento_app = str(obj.pedimento.pedimento_app).strip()
|
||||
numero = str(obj.numero_partida).strip()
|
||||
# Incluir pedimento_app en el patrón para evitar falsos positivos
|
||||
# entre partidas con números cortos (1 matchearía 10, 100, etc.)
|
||||
patron = f"vu_PT_{pedimento_app}_{numero}_"
|
||||
|
||||
# Construir el patrón exacto de búsqueda
|
||||
patron_exacto = f'documents/vu_PT_{pedimentoApp}_{numero}.xml'
|
||||
|
||||
# Buscar documentos que empiecen EXACTAMENTE con ese patrón
|
||||
# 17 = REQUEST partida, 18 = ERROR partida
|
||||
qs = Document.objects.filter(
|
||||
archivo=patron_exacto
|
||||
)
|
||||
pedimento=obj.pedimento,
|
||||
archivo__icontains=patron,
|
||||
).exclude(document_type_id__in=[17, 18])
|
||||
|
||||
# Opción 2: Si puede tener diferentes extensiones
|
||||
# patron_base = f'documents/vu_PT_{pedimentoApp}_{numero}'
|
||||
# qs = Document.objects.filter(
|
||||
# archivo__startswith=patron_base
|
||||
# ).filter(
|
||||
# archivo__in=[
|
||||
# f'{patron_base}.xml',
|
||||
# f'{patron_base}.pdf',
|
||||
# f'{patron_base}.zip'
|
||||
# ]
|
||||
# )
|
||||
|
||||
# Filtro adicional por pedimento si el modelo Document tiene este campo
|
||||
if hasattr(Document, 'pedimento'):
|
||||
qs = qs.filter(pedimento=obj.pedimento)
|
||||
|
||||
# Filtro por organización
|
||||
if hasattr(obj, 'organizacion') and obj.organizacion:
|
||||
qs = qs.filter(organizacion=obj.organizacion)
|
||||
|
||||
serializer = DocumentSerializer(qs, many=True, context=self.context)
|
||||
return serializer.data
|
||||
|
||||
#return []
|
||||
except Exception:
|
||||
# En caso de cualquier error (por ejemplo, importaciones circulares), devolver lista vacía
|
||||
return []
|
||||
class Meta:
|
||||
model = Partida
|
||||
@@ -208,10 +184,11 @@ class EDocumentSerializer(serializers.ModelSerializer):
|
||||
numero = str(obj.numero_edocument).strip()
|
||||
# id_pedimento = str(obj.pedimento_id).strip()
|
||||
|
||||
# excluir e documents de tipo request y de tipo error
|
||||
qs = Document.objects.filter(
|
||||
pedimento=obj.pedimento,
|
||||
archivo__icontains=numero,
|
||||
)
|
||||
).exclude(document_type_id__in=[21, 25])
|
||||
|
||||
# Filtro por organización si aplica
|
||||
if hasattr(obj, 'organizacion') and obj.organizacion:
|
||||
@@ -263,10 +240,15 @@ class CoveSerializer(serializers.ModelSerializer):
|
||||
try:
|
||||
numero = str(obj.numero_cove).strip()
|
||||
|
||||
# Excluir los tipo de documento 20, 24, 23 y 19
|
||||
# 20 = error solicitud cove
|
||||
# 24 = error solicitud acuse cove
|
||||
# 23 = request acuse cove
|
||||
# 19 = request cove
|
||||
qs = Document.objects.filter(
|
||||
pedimento=obj.pedimento,
|
||||
archivo__icontains=numero,
|
||||
)
|
||||
).exclude(document_type_id__in=[20, 24, 23, 19])
|
||||
|
||||
# Filtro por organización si aplica
|
||||
if hasattr(obj, 'organizacion') and obj.organizacion:
|
||||
|
||||
@@ -87,8 +87,11 @@ def trigger_celery_task_on_cove_create(sender, instance, created, **kwargs):
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.async_operations')
|
||||
logger.info(f"Cove creado: {instance.id}, creando procesamiento...")
|
||||
crear_procesamiento_cove.apply_async(args=[str(instance.pedimento.id)])
|
||||
crear_procesamiento_acuse_cove.apply_async(args=[str(instance.pedimento.id)])
|
||||
pedimento_id = str(instance.pedimento.id)
|
||||
def enqueue_cove_tasks():
|
||||
crear_procesamiento_cove.apply_async(args=[pedimento_id])
|
||||
crear_procesamiento_acuse_cove.apply_async(args=[pedimento_id])
|
||||
transaction.on_commit(enqueue_cove_tasks)
|
||||
|
||||
@receiver(post_save, sender=EDocument)
|
||||
def trigger_celery_task_on_edocument_create(sender, instance, created, **kwargs):
|
||||
@@ -96,5 +99,8 @@ def trigger_celery_task_on_edocument_create(sender, instance, created, **kwargs)
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.async_operations')
|
||||
logger.info(f"EDocument creado: {instance.id}, creando procesamiento...")
|
||||
crear_procesamiento_edocument.apply_async(args=[str(instance.pedimento.id)])
|
||||
crear_procesamiento_acuse.apply_async(args=[str(instance.pedimento.id)])
|
||||
pedimento_id = str(instance.pedimento.id)
|
||||
def enqueue_edocument_tasks():
|
||||
crear_procesamiento_edocument.apply_async(args=[pedimento_id])
|
||||
crear_procesamiento_acuse.apply_async(args=[pedimento_id])
|
||||
transaction.on_commit(enqueue_edocument_tasks)
|
||||
@@ -1,3 +1,4 @@
|
||||
from .microservice import *
|
||||
from .internal_services import *
|
||||
from .bulk_upload import *
|
||||
from .microservice_v2 import *
|
||||
|
||||
@@ -6,6 +6,8 @@ from api.customs.models import ProcesamientoPedimento, Pedimento, Cove, EDocumen
|
||||
from core.utils import xml_controller
|
||||
import requests
|
||||
from core.utils import xml_remesas_controller
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def obtener_pedimentos(organizacion_id):
|
||||
return Pedimento.objects.filter(organizacion_id=organizacion_id)
|
||||
@@ -35,23 +37,31 @@ def auditor_descargas(pedimento, servicio, related_name, variable, mensaje):
|
||||
pedimento_id = pedimento.id
|
||||
docs = getattr(pedimento, related_name).all()
|
||||
|
||||
print(f"pedimento: {pedimento}, servicio: {servicio}, related_name: {related_name}, variable: {variable}, mensaje: {mensaje}")
|
||||
logger.info(f"pedimento: {pedimento}, servicio: {servicio}, related_name: {related_name}, variable: {variable}, mensaje: {mensaje}")
|
||||
|
||||
# Si no hay documentos, marcar como completado
|
||||
if not docs.exists():
|
||||
proceso = modificar_estado_procesamiento(pedimento, servicio_id=servicio, nuevo_estado=3) # Estado "completado"
|
||||
print(f"✓ Pedimento {pedimento_id} no tiene {mensaje}s para procesar.")
|
||||
logger.info(f"✓ Pedimento {pedimento_id} no tiene {mensaje}s para procesar.")
|
||||
else:
|
||||
all_docs = all(getattr(doc, variable) for doc in docs)
|
||||
if all_docs:
|
||||
proceso = modificar_estado_procesamiento(pedimento, servicio_id=servicio, nuevo_estado=3) # Estado "completado"
|
||||
print(f"✓ Pedimento {pedimento_id} tiene todos sus {mensaje} descargados.")
|
||||
logger.info(f"✓ Pedimento {pedimento_id} tiene todos sus {mensaje} descargados.")
|
||||
else:
|
||||
proceso = modificar_estado_procesamiento(pedimento, servicio_id=servicio, nuevo_estado=4) # Estado "en progreso"
|
||||
print(f"✗ Pedimento {pedimento_id} NO tiene todos sus {mensaje} descargados.")
|
||||
logger.info(f"✗ Pedimento {pedimento_id} NO tiene todos sus {mensaje} descargados.")
|
||||
|
||||
if proceso:
|
||||
print(f"✓ Proceso de auditoría para pedimento {pedimento_id} completado.")
|
||||
logger.info(f"✓ Proceso de auditoría para pedimento {pedimento_id} completado.")
|
||||
else:
|
||||
print(f"✗ No se encontró proceso de auditoría para pedimento {pedimento_id}.")
|
||||
logger.info(f"✗ No se encontró proceso de auditoría para pedimento {pedimento_id}.")
|
||||
|
||||
## Auditar pedimentos
|
||||
|
||||
@@ -121,44 +131,66 @@ def auditar_procesamiento_remesa_por_pedimento(pedimento_id):
|
||||
|
||||
@shared_task
|
||||
def crear_partidas(organizacion_id):
|
||||
from api.customs.models import Partida
|
||||
|
||||
pedimentos = obtener_pedimentos(organizacion_id)
|
||||
total_pedimentos = pedimentos.count()
|
||||
pedimentos_procesados = 0
|
||||
total_partidas_agregadas = 0
|
||||
|
||||
print(f"Iniciando procesamiento de {total_pedimentos} pedimentos para organización {organizacion_id}")
|
||||
completados = []
|
||||
con_pendientes = []
|
||||
sin_datos = []
|
||||
errores = []
|
||||
|
||||
for pedimento in pedimentos:
|
||||
pedimentos_procesados += 1
|
||||
partidas_agregadas_pedimento = 0
|
||||
|
||||
# Validar que numero_partidas no sea None y sea mayor que 0
|
||||
if pedimento.numero_partidas is not None and pedimento.numero_partidas > 0:
|
||||
partidas_existentes = pedimento.partidas.count()
|
||||
if pedimento.numero_partidas > partidas_existentes:
|
||||
print(f"Procesando pedimento {pedimento.id} ({pedimentos_procesados}/{total_pedimentos}) - Partidas existentes: {partidas_existentes}, Requeridas: {pedimento.numero_partidas}")
|
||||
try:
|
||||
if not pedimento.numero_partidas or pedimento.numero_partidas <= 0:
|
||||
sin_datos.append({
|
||||
'pedimento_id': str(pedimento.id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'razon': f'numero_partidas inválido ({pedimento.numero_partidas})',
|
||||
})
|
||||
continue
|
||||
|
||||
for i in range(1, pedimento.numero_partidas + 1):
|
||||
from api.customs.models import Partida
|
||||
partida, created = Partida.objects.get_or_create(
|
||||
Partida.objects.get_or_create(
|
||||
pedimento=pedimento,
|
||||
numero_partida=i,
|
||||
organizacion_id=organizacion_id
|
||||
defaults={'organizacion_id': organizacion_id}
|
||||
)
|
||||
if created:
|
||||
partidas_agregadas_pedimento += 1
|
||||
total_partidas_agregadas += 1
|
||||
|
||||
print(f" → Partidas agregadas para pedimento {pedimento.id}: {partidas_agregadas_pedimento}")
|
||||
else:
|
||||
print(f"Pedimento {pedimento.id} ya tiene todas sus partidas ({partidas_existentes}/{pedimento.numero_partidas})")
|
||||
else:
|
||||
print(f"Pedimento {pedimento.id} omitido - numero_partidas: {pedimento.numero_partidas} (inválido)")
|
||||
partidas = list(pedimento.partidas.order_by('numero_partida'))
|
||||
no_descargadas = [p.numero_partida for p in partidas if not p.descargado]
|
||||
|
||||
print(f"\n=== RESUMEN ===")
|
||||
print(f"Pedimentos procesados: {pedimentos_procesados}")
|
||||
print(f"Total de partidas agregadas: {total_partidas_agregadas}")
|
||||
print(f"Procesamiento completado para organización {organizacion_id}")
|
||||
if not no_descargadas:
|
||||
completados.append(str(pedimento.id))
|
||||
else:
|
||||
con_pendientes.append({
|
||||
'pedimento_id': str(pedimento.id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'total_partidas': len(partidas),
|
||||
'descargadas': len(partidas) - len(no_descargadas),
|
||||
'no_descargadas': no_descargadas,
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
errores.append({
|
||||
'pedimento_id': str(pedimento.id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'error': str(e),
|
||||
})
|
||||
logger.error(f"Error creando partidas para pedimento {pedimento.id}: {e}")
|
||||
|
||||
return {
|
||||
'organizacion_id': str(organizacion_id),
|
||||
'total_pedimentos': total_pedimentos,
|
||||
'completados': len(completados),
|
||||
'con_pendientes': len(con_pendientes),
|
||||
'sin_datos': len(sin_datos),
|
||||
'con_errores': len(errores),
|
||||
'detalle_pendientes': con_pendientes,
|
||||
'detalle_sin_datos': sin_datos,
|
||||
'detalle_errores': errores,
|
||||
}
|
||||
|
||||
@shared_task
|
||||
def crear_partidas_por_pedimento(pedimento_id):
|
||||
@@ -169,6 +201,7 @@ def crear_partidas_por_pedimento(pedimento_id):
|
||||
return
|
||||
|
||||
print(f"Procesando pedimento individual {pedimento_id}...")
|
||||
logger.info(f"Procesando pedimento individual {pedimento_id}...")
|
||||
partidas_agregadas = 0
|
||||
|
||||
# Validar que numero_partidas no sea None y sea mayor que 0
|
||||
@@ -176,6 +209,7 @@ def crear_partidas_por_pedimento(pedimento_id):
|
||||
partidas_existentes = pedimento.partidas.count()
|
||||
if pedimento.numero_partidas > partidas_existentes:
|
||||
print(f"Pedimento {pedimento_id} - Partidas existentes: {partidas_existentes}, Requeridas: {pedimento.numero_partidas}")
|
||||
logger.info(f"Pedimento {pedimento_id} - Partidas existentes: {partidas_existentes}, Requeridas: {pedimento.numero_partidas}")
|
||||
|
||||
for i in range(1, pedimento.numero_partidas + 1):
|
||||
from api.customs.models import Partida
|
||||
@@ -188,62 +222,165 @@ def crear_partidas_por_pedimento(pedimento_id):
|
||||
partidas_agregadas += 1
|
||||
|
||||
print(f"✓ Partidas agregadas para pedimento {pedimento_id}: {partidas_agregadas}")
|
||||
logger.info(f"✓ Partidas agregadas para pedimento {pedimento_id}: {partidas_agregadas}")
|
||||
else:
|
||||
print(f"Pedimento {pedimento_id} ya tiene todas sus partidas ({partidas_existentes}/{pedimento.numero_partidas})")
|
||||
logger.info(f"Pedimento {pedimento_id} ya tiene todas sus partidas ({partidas_existentes}/{pedimento.numero_partidas})")
|
||||
else:
|
||||
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):
|
||||
"""
|
||||
Itera todos los pedimentos de una organización auditando el campo `variable`
|
||||
en la relación `related_name`. Retorna un resumen estructurado por pedimento.
|
||||
"""
|
||||
pedimentos = obtener_pedimentos(organizacion_id)
|
||||
total_pedimentos = pedimentos.count()
|
||||
|
||||
completados = []
|
||||
pendientes = []
|
||||
errores = []
|
||||
|
||||
for pedimento in pedimentos:
|
||||
try:
|
||||
docs = list(getattr(pedimento, related_name).all())
|
||||
total = len(docs)
|
||||
faltantes = [
|
||||
getattr(doc, 'numero_cove', None) or getattr(doc, 'numero_edocument', None)
|
||||
for doc in docs if not getattr(doc, variable)
|
||||
]
|
||||
|
||||
if total == 0 or len(faltantes) == 0:
|
||||
nuevo_estado = 3
|
||||
completados.append(str(pedimento.id))
|
||||
else:
|
||||
nuevo_estado = 4
|
||||
pendientes.append({
|
||||
'pedimento_id': str(pedimento.id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
f'faltantes_{label}': faltantes,
|
||||
'total': total,
|
||||
'descargados': total - len(faltantes),
|
||||
})
|
||||
|
||||
modificar_estado_procesamiento(pedimento, servicio_id=servicio, nuevo_estado=nuevo_estado)
|
||||
|
||||
except Exception as e:
|
||||
errores.append({
|
||||
'pedimento_id': str(pedimento.id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'error': str(e),
|
||||
})
|
||||
logger.error(f"Error auditando pedimento {pedimento.id} [{label}]: {e}")
|
||||
|
||||
return {
|
||||
'organizacion_id': str(organizacion_id),
|
||||
'auditoria': label,
|
||||
'total_pedimentos': total_pedimentos,
|
||||
'completados': len(completados),
|
||||
'con_pendientes': len(pendientes),
|
||||
'con_errores': len(errores),
|
||||
'detalle_pendientes': pendientes,
|
||||
'detalle_errores': errores,
|
||||
}
|
||||
|
||||
|
||||
# Auditar coves
|
||||
@shared_task
|
||||
def auditar_coves(organizacion_id):
|
||||
for pedimento in obtener_pedimentos(organizacion_id):
|
||||
auditor_descargas(
|
||||
pedimento,
|
||||
return _auditar_organizacion(
|
||||
organizacion_id,
|
||||
servicio=8,
|
||||
related_name='coves',
|
||||
variable='cove_descargado',
|
||||
mensaje='COVE'
|
||||
label='cove',
|
||||
)
|
||||
|
||||
@shared_task
|
||||
def auditar_acuse_cove(organizacion_id):
|
||||
for pedimento in obtener_pedimentos(organizacion_id):
|
||||
auditor_descargas(
|
||||
pedimento,
|
||||
return _auditar_organizacion(
|
||||
organizacion_id,
|
||||
servicio=9,
|
||||
related_name='coves',
|
||||
variable='acuse_cove_descargado',
|
||||
mensaje='acuse de COVE'
|
||||
label='acuse_cove',
|
||||
)
|
||||
|
||||
# Revisa si el pedimento completo todos sus acuse coves
|
||||
|
||||
# Auditar edocuments
|
||||
@shared_task
|
||||
def auditar_edocuments(organizacion_id):
|
||||
for pedimento in obtener_pedimentos(organizacion_id):
|
||||
auditor_descargas(
|
||||
pedimento,
|
||||
return _auditar_organizacion(
|
||||
organizacion_id,
|
||||
servicio=7,
|
||||
related_name='documentos',
|
||||
variable='edocument_descargado',
|
||||
mensaje='EDocument'
|
||||
label='edocument',
|
||||
)
|
||||
|
||||
@shared_task
|
||||
def auditar_acuse(organizacion_id):
|
||||
for pedimento in obtener_pedimentos(organizacion_id):
|
||||
auditor_descargas(
|
||||
pedimento,
|
||||
return _auditar_organizacion(
|
||||
organizacion_id,
|
||||
servicio=6,
|
||||
related_name='documentos',
|
||||
variable='acuse_descargado',
|
||||
mensaje='acuse'
|
||||
label='acuse',
|
||||
)
|
||||
|
||||
@shared_task
|
||||
def auditar_remesas(organizacion_id):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
pedimentos = obtener_pedimentos(organizacion_id)
|
||||
total_pedimentos = pedimentos.count()
|
||||
|
||||
completados = []
|
||||
pendientes = []
|
||||
errores = []
|
||||
|
||||
for pedimento in 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),
|
||||
'pedimento': pedimento.pedimento,
|
||||
})
|
||||
except Exception as e:
|
||||
errores.append({
|
||||
'pedimento_id': str(pedimento.id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'error': str(e),
|
||||
})
|
||||
logger.error(f"Error auditando remesa de pedimento {pedimento.id}: {e}")
|
||||
|
||||
return {
|
||||
'organizacion_id': str(organizacion_id),
|
||||
'auditoria': 'remesa',
|
||||
'total_pedimentos': total_pedimentos,
|
||||
'completados': len(completados),
|
||||
'con_pendientes': len(pendientes),
|
||||
'con_errores': len(errores),
|
||||
'detalle_pendientes': pendientes,
|
||||
'detalle_errores': errores,
|
||||
}
|
||||
|
||||
@shared_task
|
||||
def auditar_cove_por_pedimento(pedimento_id):
|
||||
try:
|
||||
print(f"auditar_cove_por_pedimento >>>> {pedimento_id}")
|
||||
logger.info(f"auditar_cove_por_pedimento >>>> {pedimento_id}")
|
||||
from api.customs.models import Pedimento
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
auditor_descargas(
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# auditoria_xml.py
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import datetime
|
||||
import logging
|
||||
logger = logging.getLogger('api.customs.auditoria_xml')
|
||||
|
||||
def extraer_info_pedimento_xml(xml_content):
|
||||
"""
|
||||
@@ -13,8 +15,10 @@ def extraer_info_pedimento_xml(xml_content):
|
||||
# Buscar el namespace (puede variar)
|
||||
namespaces = {
|
||||
'S': 'http://schemas.xmlsoap.org/soap/envelope/',
|
||||
's': 'http://schemas.xmlsoap.org/soap/envelope/',
|
||||
'ns2': 'http://www.ventanillaunica.gob.mx/pedimentos/ws/oxml/consultarpedimentocompleto',
|
||||
'ns3': 'http://www.ventanillaunica.gob.mx/common/ws/oxml/respuesta'
|
||||
'ns3': 'http://www.ventanillaunica.gob.mx/common/ws/oxml/respuesta',
|
||||
|
||||
}
|
||||
|
||||
resultado = {}
|
||||
@@ -181,10 +185,37 @@ def extraer_info_pedimento_xml(xml_content):
|
||||
if edocs_encontrados:
|
||||
resultado['edocuments_en_xml'] = edocs_encontrados
|
||||
|
||||
# Verificar si hay error en la respuesta
|
||||
# Verificar si hay error en la respuesta — 3 variantes según el servicio VUCEM:
|
||||
# 1) Remesas/pedimentos: <ns3:tieneError> en namespace oxml/respuesta
|
||||
# 2) eDocuments: <TieneError> en namespace tempuri.org, mensaje en <Errores>
|
||||
# 3) Acuses: <error> sin namespace dentro de responseConsultaAcuses
|
||||
tiene_error = root.find('.//ns3:tieneError', namespaces)
|
||||
if tiene_error is not None:
|
||||
resultado['tiene_error'] = tiene_error.text.lower() == 'true'
|
||||
if resultado['tiene_error']:
|
||||
mensaje = root.find('.//ns3:error/ns3:mensaje', namespaces)
|
||||
if mensaje is not None and mensaje.text:
|
||||
resultado['error_mensaje'] = mensaje.text.strip()
|
||||
else:
|
||||
# Variante eDocuments (tempuri.org)
|
||||
tiene_error_edoc = root.find('.//{http://tempuri.org/}TieneError')
|
||||
if tiene_error_edoc is not None:
|
||||
resultado['tiene_error'] = tiene_error_edoc.text.lower() == 'true'
|
||||
if resultado['tiene_error']:
|
||||
errores_elem = root.find('.//{http://tempuri.org/}Errores')
|
||||
if errores_elem is not None and errores_elem.text:
|
||||
resultado['error_mensaje'] = errores_elem.text.strip()
|
||||
else:
|
||||
# Variante acuses: <error> sin namespace
|
||||
error_acuses = root.find('.//error')
|
||||
if error_acuses is not None and error_acuses.text is not None:
|
||||
resultado['tiene_error'] = error_acuses.text.lower() == 'true'
|
||||
if resultado['tiene_error']:
|
||||
descripciones = root.findall('.//mensajeErrores/descripcion')
|
||||
if descripciones:
|
||||
resultado['error_mensaje'] = ' | '.join(
|
||||
d.text.strip() for d in descripciones if d.text
|
||||
)
|
||||
|
||||
return resultado
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from api.organization.models import Organizacion
|
||||
from celery import group
|
||||
from celery import shared_task, group
|
||||
from api.customs.models import *
|
||||
@@ -8,6 +9,11 @@ import requests
|
||||
from config.settings import SERVICE_API_URL_V2
|
||||
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:
|
||||
@@ -132,7 +138,7 @@ def procesar_edocs_pedimento(pedimento_id):
|
||||
}
|
||||
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/download/edoc/",
|
||||
f"{SERVICE_API_URL_V2}/services/download/all/edocs/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
@@ -277,10 +283,23 @@ def procesar_remesas(organizacion_id):
|
||||
pedimentos = Pedimento.objects.filter(organizacion_id=organizacion_id)
|
||||
|
||||
for pedimento in pedimentos:
|
||||
if not pedimento.documents.filter(document_type=3).exists(): # Tipo 3: Remesa
|
||||
# Convertir el pedimento a JSON usando el serializer
|
||||
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)
|
||||
credenciales = Vucem.objects.filter(id=CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first().vucem.id).first()
|
||||
|
||||
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)
|
||||
|
||||
@@ -289,15 +308,15 @@ def procesar_remesas(organizacion_id):
|
||||
"credencial": credenciales_dict
|
||||
}
|
||||
|
||||
|
||||
response = requests.post(
|
||||
f"{SERVICE_API_URL_V2}/services/remesas",
|
||||
f"{SERVICE_API_URL_V2}/services/remesas/",
|
||||
data=json.dumps(payload),
|
||||
headers={"Content-Type": "application/json"}
|
||||
)
|
||||
# Aquí puedes continuar con el resto de tu lógica
|
||||
logger.info(f"Servicio enviado para pedimento {pedimento.pedimento} — status {response.status_code}")
|
||||
|
||||
print(f"Servicio enviado para pedimento {pedimento.pedimento}")
|
||||
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):
|
||||
@@ -522,6 +541,34 @@ def ejecutar_todos_por_organizacion(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)
|
||||
|
||||
for org in active_orgs:
|
||||
process_organization_batch.apply_async(
|
||||
args=[org.id],
|
||||
queue='org_processing'
|
||||
)
|
||||
return f"Dispatched {active_orgs.count()} organizations"
|
||||
|
||||
|
||||
@@ -8,27 +8,24 @@ from drf_yasg import openapi
|
||||
from core.permissions import IsSuperUser, IsSameOrganizationDeveloper
|
||||
from .tasks.auditoria import (
|
||||
crear_partidas,
|
||||
crear_partidas_por_pedimento,
|
||||
auditar_procesamiento_remesa_por_pedimento,
|
||||
auditar_coves,
|
||||
auditar_acuse_cove,
|
||||
auditar_edocuments,
|
||||
auditar_acuse,
|
||||
auditar_cove_por_pedimento,
|
||||
auditar_acuse_cove_por_pedimento,
|
||||
auditar_edocument_por_pedimento,
|
||||
auditar_acuse_por_pedimento
|
||||
auditar_remesas,
|
||||
)
|
||||
from .tasks.internal_services import auditar_pedimentos
|
||||
from .tasks.microservice_v2 import procesar_pedimentos_completos
|
||||
from api.customs.models import Pedimento
|
||||
from api.organization.models import Organizacion
|
||||
from api.record.models import Document
|
||||
from .tasks.auditoria import auditar_pedimento_por_id
|
||||
from .tasks.auditoria_xml import extraer_info_pedimento_xml
|
||||
import tempfile
|
||||
import os
|
||||
from api.utils.storage_service import storage_service
|
||||
import logging
|
||||
import uuid
|
||||
logger = logging.getLogger('api.customs.views_auditor')
|
||||
|
||||
def get_document_content(documento):
|
||||
"""
|
||||
@@ -72,7 +69,7 @@ def get_document_path(documento):
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Crea partidas para todos los pedimentos de una organización",
|
||||
operation_description="Crea partidas faltantes para todos los pedimentos de una organización e informa cuáles están descargadas",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
@@ -81,7 +78,7 @@ def get_document_path(documento):
|
||||
required=['organizacion_id']
|
||||
),
|
||||
responses={
|
||||
200: openapi.Response('Tarea iniciada correctamente'),
|
||||
202: openapi.Response('Tarea iniciada — usar task_id para consultar resultado'),
|
||||
400: openapi.Response('Error en los parámetros'),
|
||||
403: openapi.Response('No tiene permisos suficientes')
|
||||
}
|
||||
@@ -89,37 +86,27 @@ def get_document_path(documento):
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
|
||||
def crear_partidas_organizacion(request):
|
||||
"""
|
||||
Crea partidas para todos los pedimentos de una organización específica.
|
||||
"""
|
||||
organizacion_id = request.data.get('organizacion_id')
|
||||
|
||||
if not organizacion_id:
|
||||
return Response(
|
||||
{'error': 'Debe proporcionar organizacion_id'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({'error': 'Debe proporcionar organizacion_id'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Validar permisos
|
||||
user = request.user
|
||||
if not user.is_superuser and str(user.organizacion.id) != organizacion_id:
|
||||
return Response(
|
||||
{'error': 'No tiene permisos para esta organización'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
return Response({'error': 'No tiene permisos para esta organización'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Ejecutar la tarea
|
||||
task = crear_partidas.delay(organizacion_id)
|
||||
message = f"Creación de partidas iniciada para la organización {organizacion_id}"
|
||||
|
||||
return Response({
|
||||
'message': message,
|
||||
'task_id': task.id
|
||||
}, status=status.HTTP_200_OK)
|
||||
'organizacion_id': organizacion_id,
|
||||
'auditoria': 'partidas',
|
||||
'task_id': task.id,
|
||||
'mensaje': f'Creación de partidas iniciada. Consulta el resultado en GET /api/tasks/status/{task.id}/',
|
||||
}, status=status.HTTP_202_ACCEPTED)
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Crea partidas para un pedimento específico",
|
||||
operation_description="Crea partidas faltantes para un pedimento e informa cuáles están descargadas",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
@@ -128,7 +115,7 @@ def crear_partidas_organizacion(request):
|
||||
required=['pedimento_id']
|
||||
),
|
||||
responses={
|
||||
200: openapi.Response('Tarea iniciada correctamente'),
|
||||
200: openapi.Response('Resultado de creación y estado de descarga de partidas'),
|
||||
400: openapi.Response('Error en los parámetros'),
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
404: openapi.Response('Pedimento no encontrado')
|
||||
@@ -137,39 +124,65 @@ def crear_partidas_organizacion(request):
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def crear_partidas_pedimento(request):
|
||||
"""
|
||||
Crea partidas para un pedimento específico.
|
||||
"""
|
||||
pedimento_id = request.data.get('pedimento_id')
|
||||
|
||||
if not pedimento_id:
|
||||
return Response(
|
||||
{'error': 'Debe proporcionar pedimento_id'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({'error': 'Debe proporcionar pedimento_id'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Validar permisos y existencia del pedimento
|
||||
try:
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
pedimento = Pedimento.objects.prefetch_related('partidas').select_related('organizacion').get(id=pedimento_id)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
user = request.user
|
||||
if not user.is_superuser and str(pedimento.organizacion.id) != str(user.organizacion.id):
|
||||
return Response(
|
||||
{'error': 'No tiene permisos para este pedimento'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response(
|
||||
{'error': 'Pedimento no encontrado'},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({'error': 'No tiene permisos para este pedimento'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Ejecutar la tarea
|
||||
task = crear_partidas_por_pedimento.delay(pedimento_id)
|
||||
message = f"Creación de partidas iniciada para el pedimento {pedimento_id}"
|
||||
if not pedimento.numero_partidas or pedimento.numero_partidas <= 0:
|
||||
return Response({
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'estado': 'sin_datos',
|
||||
'mensaje': f'El pedimento no tiene número de partidas definido (numero_partidas={pedimento.numero_partidas})',
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
# Crear partidas faltantes (get_or_create por número)
|
||||
from api.customs.models import Partida
|
||||
partidas_creadas = 0
|
||||
for i in range(1, pedimento.numero_partidas + 1):
|
||||
_, created = Partida.objects.get_or_create(
|
||||
pedimento=pedimento,
|
||||
numero_partida=i,
|
||||
defaults={'organizacion_id': pedimento.organizacion_id}
|
||||
)
|
||||
if created:
|
||||
partidas_creadas += 1
|
||||
|
||||
# Evaluar estado de descarga sobre el conjunto completo
|
||||
partidas = list(pedimento.partidas.order_by('numero_partida'))
|
||||
total = len(partidas)
|
||||
descargadas = [p.numero_partida for p in partidas if p.descargado]
|
||||
no_descargadas = [p.numero_partida for p in partidas if not p.descargado]
|
||||
|
||||
if not no_descargadas:
|
||||
estado = 'completado'
|
||||
mensaje = f'Todas las partidas están descargadas ({total}/{total})'
|
||||
else:
|
||||
estado = 'en_proceso'
|
||||
mensaje = f'{len(no_descargadas)} de {total} partidas pendientes de descarga'
|
||||
|
||||
return Response({
|
||||
'message': message,
|
||||
'task_id': task.id
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'estado': estado,
|
||||
'mensaje': mensaje,
|
||||
'resumen': {
|
||||
'total_partidas': total,
|
||||
'partidas_creadas_ahora': partidas_creadas,
|
||||
'descargadas': len(descargadas),
|
||||
'no_descargadas': len(no_descargadas),
|
||||
},
|
||||
'no_descargadas': no_descargadas,
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
@swagger_auto_schema(
|
||||
@@ -223,7 +236,7 @@ def auditar_pedimentos_endpoint(request):
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Audita el procesamiento de remesa para un pedimento específico",
|
||||
operation_description="Audita el estado de procesamiento de remesa de un pedimento específico",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
@@ -232,7 +245,7 @@ def auditar_pedimentos_endpoint(request):
|
||||
required=['pedimento_id']
|
||||
),
|
||||
responses={
|
||||
200: openapi.Response('Tarea de auditoría iniciada correctamente'),
|
||||
200: openapi.Response('Estado de procesamiento de remesa del pedimento'),
|
||||
400: openapi.Response('Error en los parámetros'),
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
404: openapi.Response('Pedimento no encontrado')
|
||||
@@ -241,247 +254,179 @@ def auditar_pedimentos_endpoint(request):
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
|
||||
def auditar_procesamiento_remesa_pedimento_endpoint(request):
|
||||
"""
|
||||
Inicia una tarea de auditoría de remesa para un pedimento específico.
|
||||
Verifica el estado del procesamiento de remesa y la creación de COVEs.
|
||||
"""
|
||||
pedimento_id = request.data.get('pedimento_id')
|
||||
|
||||
if not pedimento_id:
|
||||
return Response(
|
||||
{'error': 'Debe proporcionar pedimento_id'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
return Response({'error': 'Debe proporcionar pedimento_id'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# Validar permisos y existencia del pedimento
|
||||
try:
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
pedimento = Pedimento.objects.select_related('organizacion').prefetch_related('coves').get(id=pedimento_id)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
user = request.user
|
||||
if not user.is_superuser and str(pedimento.organizacion.id) != str(user.organizacion.id):
|
||||
return Response(
|
||||
{'error': 'No tiene permisos para este pedimento'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response(
|
||||
{'error': 'Pedimento no encontrado'},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
return Response({'error': 'No tiene permisos para este pedimento'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Ejecutar la tarea de auditoría
|
||||
task = auditar_procesamiento_remesa_por_pedimento.delay(pedimento_id)
|
||||
message = f"Auditoría de remesa iniciada para el pedimento {pedimento_id}"
|
||||
if not pedimento.remesas:
|
||||
return Response({
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'tiene_remesas': False,
|
||||
'estado': 'completado',
|
||||
'mensaje': 'El pedimento no tiene remesas para procesar',
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
tiene_documento_remesa = pedimento.documents.filter(document_type=3).exists()
|
||||
coves = list(pedimento.coves.all())
|
||||
total_coves = len(coves)
|
||||
|
||||
if not tiene_documento_remesa:
|
||||
estado = 'en_proceso'
|
||||
mensaje = 'Documento XML de remesa aún no descargado'
|
||||
elif total_coves == 0:
|
||||
estado = 'en_proceso'
|
||||
mensaje = 'Documento de remesa disponible pero no se han creado COVEs'
|
||||
else:
|
||||
estado = 'completado'
|
||||
mensaje = f'Remesa procesada — {total_coves} COVE(s) registrados'
|
||||
|
||||
return Response({
|
||||
'message': message,
|
||||
'task_id': task.id,
|
||||
'pedimento': {
|
||||
'id': str(pedimento.id),
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'tiene_remesas': pedimento.remesas
|
||||
}
|
||||
'tiene_remesas': True,
|
||||
'estado': estado,
|
||||
'mensaje': mensaje,
|
||||
'resumen': {
|
||||
'tiene_documento_remesa': tiene_documento_remesa,
|
||||
'total_coves_registrados': total_coves,
|
||||
},
|
||||
'coves': [c.numero_cove for c in coves],
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
def _lanzar_auditoria_organizacion(request, task_fn, label):
|
||||
"""Helper compartido para los 4 endpoints de auditoría masiva por organización."""
|
||||
organizacion_id = request.data.get('organizacion_id')
|
||||
if not organizacion_id:
|
||||
return Response({'error': 'Debe proporcionar organizacion_id'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
user = request.user
|
||||
if not user.is_superuser and str(user.organizacion.id) != organizacion_id:
|
||||
return Response({'error': 'No tiene permisos para esta organización'}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
task = task_fn.delay(organizacion_id)
|
||||
return Response({
|
||||
'organizacion_id': organizacion_id,
|
||||
'auditoria': label,
|
||||
'task_id': task.id,
|
||||
'mensaje': f'Auditoría de {label} iniciada. Consulta el resultado en GET /api/tasks/status/{task.id}/',
|
||||
}, status=status.HTTP_202_ACCEPTED)
|
||||
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Audita los COVEs de una organización",
|
||||
operation_description="Audita el estado de descarga de COVEs de todos los pedimentos de una organización",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'organizacion_id': openapi.Schema(type=openapi.TYPE_STRING, description='ID de la organización')
|
||||
},
|
||||
properties={'organizacion_id': openapi.Schema(type=openapi.TYPE_STRING)},
|
||||
required=['organizacion_id']
|
||||
),
|
||||
responses={
|
||||
200: openapi.Response('Tarea de auditoría iniciada correctamente'),
|
||||
202: openapi.Response('Tarea iniciada — usar task_id para consultar resultado'),
|
||||
400: openapi.Response('Error en los parámetros'),
|
||||
403: openapi.Response('No tiene permisos suficientes')
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
}
|
||||
)
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
|
||||
def auditar_coves_endpoint(request):
|
||||
"""
|
||||
Inicia una tarea de auditoría para los COVEs de una organización.
|
||||
Verifica la existencia y validez de los COVEs generados.
|
||||
"""
|
||||
organizacion_id = request.data.get('organizacion_id')
|
||||
|
||||
if not organizacion_id:
|
||||
return Response(
|
||||
{'error': 'Debe proporcionar organizacion_id'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Validar permisos
|
||||
user = request.user
|
||||
if not user.is_superuser and str(user.organizacion.id) != organizacion_id:
|
||||
return Response(
|
||||
{'error': 'No tiene permisos para esta organización'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
# Ejecutar la tarea de auditoría
|
||||
task = auditar_coves.delay(organizacion_id)
|
||||
message = f"Auditoría de COVEs iniciada para la organización {organizacion_id}"
|
||||
|
||||
return Response({
|
||||
'message': message,
|
||||
'task_id': task.id
|
||||
}, status=status.HTTP_200_OK)
|
||||
return _lanzar_auditoria_organizacion(request, auditar_coves, 'COVEs')
|
||||
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Audita los acuses de COVE de una organización",
|
||||
operation_description="Audita el estado de descarga de acuses de COVE de todos los pedimentos de una organización",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'organizacion_id': openapi.Schema(type=openapi.TYPE_STRING, description='ID de la organización')
|
||||
},
|
||||
properties={'organizacion_id': openapi.Schema(type=openapi.TYPE_STRING)},
|
||||
required=['organizacion_id']
|
||||
),
|
||||
responses={
|
||||
200: openapi.Response('Tarea de auditoría iniciada correctamente'),
|
||||
202: openapi.Response('Tarea iniciada — usar task_id para consultar resultado'),
|
||||
400: openapi.Response('Error en los parámetros'),
|
||||
403: openapi.Response('No tiene permisos suficientes')
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
}
|
||||
)
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
|
||||
def auditar_acuse_cove_endpoint(request):
|
||||
"""
|
||||
Inicia una tarea de auditoría para los acuses de COVE de una organización.
|
||||
Verifica la recepción y validez de los acuses de COVE.
|
||||
"""
|
||||
organizacion_id = request.data.get('organizacion_id')
|
||||
|
||||
if not organizacion_id:
|
||||
return Response(
|
||||
{'error': 'Debe proporcionar organizacion_id'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Validar permisos
|
||||
user = request.user
|
||||
if not user.is_superuser and str(user.organizacion.id) != organizacion_id:
|
||||
return Response(
|
||||
{'error': 'No tiene permisos para esta organización'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
# Ejecutar la tarea de auditoría
|
||||
task = auditar_acuse_cove.delay(organizacion_id)
|
||||
message = f"Auditoría de acuses de COVE iniciada para la organización {organizacion_id}"
|
||||
|
||||
return Response({
|
||||
'message': message,
|
||||
'task_id': task.id
|
||||
}, status=status.HTTP_200_OK)
|
||||
return _lanzar_auditoria_organizacion(request, auditar_acuse_cove, 'acuses de COVE')
|
||||
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Audita los EDocuments de una organización",
|
||||
operation_description="Audita el estado de descarga de EDocuments de todos los pedimentos de una organización",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'organizacion_id': openapi.Schema(type=openapi.TYPE_STRING, description='ID de la organización')
|
||||
},
|
||||
properties={'organizacion_id': openapi.Schema(type=openapi.TYPE_STRING)},
|
||||
required=['organizacion_id']
|
||||
),
|
||||
responses={
|
||||
200: openapi.Response('Tarea de auditoría iniciada correctamente'),
|
||||
202: openapi.Response('Tarea iniciada — usar task_id para consultar resultado'),
|
||||
400: openapi.Response('Error en los parámetros'),
|
||||
403: openapi.Response('No tiene permisos suficientes')
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
}
|
||||
)
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
|
||||
def auditar_edocuments_endpoint(request):
|
||||
"""
|
||||
Inicia una tarea de auditoría para los EDocuments de una organización.
|
||||
Verifica la existencia y validez de los EDocuments generados.
|
||||
"""
|
||||
organizacion_id = request.data.get('organizacion_id')
|
||||
|
||||
if not organizacion_id:
|
||||
return Response(
|
||||
{'error': 'Debe proporcionar organizacion_id'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Validar permisos
|
||||
user = request.user
|
||||
if not user.is_superuser and str(user.organizacion.id) != organizacion_id:
|
||||
return Response(
|
||||
{'error': 'No tiene permisos para esta organización'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
# Ejecutar la tarea de auditoría
|
||||
task = auditar_edocuments.delay(organizacion_id)
|
||||
message = f"Auditoría de EDocuments iniciada para la organización {organizacion_id}"
|
||||
|
||||
return Response({
|
||||
'message': message,
|
||||
'task_id': task.id
|
||||
}, status=status.HTTP_200_OK)
|
||||
return _lanzar_auditoria_organizacion(request, auditar_edocuments, 'EDocuments')
|
||||
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Audita los acuses de una organización",
|
||||
operation_description="Audita el estado de descarga de acuses de EDocument de todos los pedimentos de una organización",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'organizacion_id': openapi.Schema(type=openapi.TYPE_STRING, description='ID de la organización')
|
||||
},
|
||||
properties={'organizacion_id': openapi.Schema(type=openapi.TYPE_STRING)},
|
||||
required=['organizacion_id']
|
||||
),
|
||||
responses={
|
||||
200: openapi.Response('Tarea de auditoría iniciada correctamente'),
|
||||
202: openapi.Response('Tarea iniciada — usar task_id para consultar resultado'),
|
||||
400: openapi.Response('Error en los parámetros'),
|
||||
403: openapi.Response('No tiene permisos suficientes')
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
}
|
||||
)
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
|
||||
def auditar_acuse_endpoint(request):
|
||||
"""
|
||||
Inicia una tarea de auditoría para los acuses de una organización.
|
||||
Verifica la recepción y validez de los acuses.
|
||||
"""
|
||||
organizacion_id = request.data.get('organizacion_id')
|
||||
|
||||
if not organizacion_id:
|
||||
return Response(
|
||||
{'error': 'Debe proporcionar organizacion_id'},
|
||||
status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
# Validar permisos
|
||||
user = request.user
|
||||
if not user.is_superuser and str(user.organizacion.id) != organizacion_id:
|
||||
return Response(
|
||||
{'error': 'No tiene permisos para esta organización'},
|
||||
status=status.HTTP_403_FORBIDDEN
|
||||
)
|
||||
|
||||
# Ejecutar la tarea de auditoría
|
||||
task = auditar_acuse.delay(organizacion_id)
|
||||
message = f"Auditoría de acuses iniciada para la organización {organizacion_id}"
|
||||
|
||||
return Response({
|
||||
'message': message,
|
||||
'task_id': task.id
|
||||
}, status=status.HTTP_200_OK)
|
||||
return _lanzar_auditoria_organizacion(request, auditar_acuse, 'acuses de EDocument')
|
||||
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Audita el COVE de un pedimento específico",
|
||||
operation_description="Audita el estado de descarga de remesas de todos los pedimentos de una organización",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={'organizacion_id': openapi.Schema(type=openapi.TYPE_STRING)},
|
||||
required=['organizacion_id']
|
||||
),
|
||||
responses={
|
||||
202: openapi.Response('Tarea iniciada — usar task_id para consultar resultado'),
|
||||
400: openapi.Response('Error en los parámetros'),
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
}
|
||||
)
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated & (IsSuperUser | IsSameOrganizationDeveloper)])
|
||||
def auditar_remesas_endpoint(request):
|
||||
return _lanzar_auditoria_organizacion(request, auditar_remesas, 'remesas')
|
||||
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Audita el estado de descarga de COVEs de un pedimento específico",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
@@ -490,7 +435,7 @@ def auditar_acuse_endpoint(request):
|
||||
required=['pedimento_id']
|
||||
),
|
||||
responses={
|
||||
200: openapi.Response('Tarea de auditoría iniciada correctamente'),
|
||||
200: openapi.Response('Estado de descarga de COVEs del pedimento'),
|
||||
400: openapi.Response('Error en los parámetros'),
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
404: openapi.Response('Pedimento no encontrado')
|
||||
@@ -502,19 +447,48 @@ def auditar_cove_pedimento_endpoint(request):
|
||||
pedimento_id = request.data.get('pedimento_id')
|
||||
if not pedimento_id:
|
||||
return Response({'error': 'Debe proporcionar pedimento_id'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
pedimento = Pedimento.objects.select_related('organizacion').prefetch_related('coves').get(id=pedimento_id)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
user = request.user
|
||||
if not user.is_superuser and str(pedimento.organizacion.id) != str(user.organizacion.id):
|
||||
return Response({'error': 'No tiene permisos para este pedimento'}, status=status.HTTP_403_FORBIDDEN)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
|
||||
task = auditar_cove_por_pedimento.delay(pedimento_id)
|
||||
return Response({'message': f'Auditoría de COVE iniciada para el pedimento {pedimento_id}', 'task_id': task.id}, status=status.HTTP_200_OK)
|
||||
|
||||
coves = list(pedimento.coves.all())
|
||||
total = len(coves)
|
||||
descargados = sum(1 for c in coves if c.cove_descargado)
|
||||
pendientes = [c.numero_cove for c in coves if not c.cove_descargado]
|
||||
|
||||
if total == 0:
|
||||
nuevo_estado = 3
|
||||
mensaje = 'El pedimento no tiene COVEs registrados'
|
||||
elif descargados == total:
|
||||
nuevo_estado = 3
|
||||
mensaje = 'Todos los COVEs están descargados'
|
||||
else:
|
||||
nuevo_estado = 4
|
||||
mensaje = f'{total - descargados} de {total} COVEs pendientes de descarga'
|
||||
|
||||
from api.customs.tasks.auditoria import modificar_estado_procesamiento
|
||||
modificar_estado_procesamiento(pedimento, servicio_id=8, nuevo_estado=nuevo_estado)
|
||||
|
||||
return Response({
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'estado': 'completado' if nuevo_estado == 3 else 'en_proceso',
|
||||
'mensaje': mensaje,
|
||||
'resumen': {
|
||||
'total_coves': total,
|
||||
'coves_descargados': descargados,
|
||||
},
|
||||
'pendientes': pendientes,
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Audita el acuse de COVE de un pedimento específico",
|
||||
operation_description="Audita el estado de descarga de acuses de COVE de un pedimento específico",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
@@ -523,7 +497,7 @@ def auditar_cove_pedimento_endpoint(request):
|
||||
required=['pedimento_id']
|
||||
),
|
||||
responses={
|
||||
200: openapi.Response('Tarea de auditoría iniciada correctamente'),
|
||||
200: openapi.Response('Estado de descarga de acuses de COVE del pedimento'),
|
||||
400: openapi.Response('Error en los parámetros'),
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
404: openapi.Response('Pedimento no encontrado')
|
||||
@@ -535,19 +509,48 @@ def auditar_acuse_cove_pedimento_endpoint(request):
|
||||
pedimento_id = request.data.get('pedimento_id')
|
||||
if not pedimento_id:
|
||||
return Response({'error': 'Debe proporcionar pedimento_id'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
pedimento = Pedimento.objects.select_related('organizacion').prefetch_related('coves').get(id=pedimento_id)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
user = request.user
|
||||
if not user.is_superuser and str(pedimento.organizacion.id) != str(user.organizacion.id):
|
||||
return Response({'error': 'No tiene permisos para este pedimento'}, status=status.HTTP_403_FORBIDDEN)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
|
||||
task = auditar_acuse_cove_por_pedimento.delay(pedimento_id)
|
||||
return Response({'message': f'Auditoría de acuse de COVE iniciada para el pedimento {pedimento_id}', 'task_id': task.id}, status=status.HTTP_200_OK)
|
||||
|
||||
coves = list(pedimento.coves.all())
|
||||
total = len(coves)
|
||||
descargados = sum(1 for c in coves if c.acuse_cove_descargado)
|
||||
pendientes = [c.numero_cove for c in coves if not c.acuse_cove_descargado]
|
||||
|
||||
if total == 0:
|
||||
nuevo_estado = 3
|
||||
mensaje = 'El pedimento no tiene COVEs registrados, no hay acuses que auditar'
|
||||
elif descargados == total:
|
||||
nuevo_estado = 3
|
||||
mensaje = 'Todos los acuses de COVE están descargados'
|
||||
else:
|
||||
nuevo_estado = 4
|
||||
mensaje = f'{total - descargados} de {total} acuses de COVE pendientes de descarga'
|
||||
|
||||
from api.customs.tasks.auditoria import modificar_estado_procesamiento
|
||||
modificar_estado_procesamiento(pedimento, servicio_id=9, nuevo_estado=nuevo_estado)
|
||||
|
||||
return Response({
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'estado': 'completado' if nuevo_estado == 3 else 'en_proceso',
|
||||
'mensaje': mensaje,
|
||||
'resumen': {
|
||||
'total_coves': total,
|
||||
'acuses_descargados': descargados,
|
||||
},
|
||||
'pendientes': pendientes,
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Audita el EDocument de un pedimento específico",
|
||||
operation_description="Audita el estado de descarga de EDocuments de un pedimento específico",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
@@ -556,7 +559,7 @@ def auditar_acuse_cove_pedimento_endpoint(request):
|
||||
required=['pedimento_id']
|
||||
),
|
||||
responses={
|
||||
200: openapi.Response('Tarea de auditoría iniciada correctamente'),
|
||||
200: openapi.Response('Estado de descarga de EDocuments del pedimento'),
|
||||
400: openapi.Response('Error en los parámetros'),
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
404: openapi.Response('Pedimento no encontrado')
|
||||
@@ -568,19 +571,48 @@ def auditar_edocument_pedimento_endpoint(request):
|
||||
pedimento_id = request.data.get('pedimento_id')
|
||||
if not pedimento_id:
|
||||
return Response({'error': 'Debe proporcionar pedimento_id'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
pedimento = Pedimento.objects.select_related('organizacion').prefetch_related('documentos').get(id=pedimento_id)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
user = request.user
|
||||
if not user.is_superuser and str(pedimento.organizacion.id) != str(user.organizacion.id):
|
||||
return Response({'error': 'No tiene permisos para este pedimento'}, status=status.HTTP_403_FORBIDDEN)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
|
||||
task = auditar_edocument_por_pedimento.delay(pedimento_id)
|
||||
return Response({'message': f'Auditoría de EDocument iniciada para el pedimento {pedimento_id}', 'task_id': task.id}, status=status.HTTP_200_OK)
|
||||
|
||||
edocuments = list(pedimento.documentos.all())
|
||||
total = len(edocuments)
|
||||
descargados = sum(1 for d in edocuments if d.edocument_descargado)
|
||||
pendientes = [d.numero_edocument for d in edocuments if not d.edocument_descargado]
|
||||
|
||||
if total == 0:
|
||||
nuevo_estado = 3
|
||||
mensaje = 'El pedimento no tiene EDocuments registrados'
|
||||
elif descargados == total:
|
||||
nuevo_estado = 3
|
||||
mensaje = 'Todos los EDocuments están descargados'
|
||||
else:
|
||||
nuevo_estado = 4
|
||||
mensaje = f'{total - descargados} de {total} EDocuments pendientes de descarga'
|
||||
|
||||
from api.customs.tasks.auditoria import modificar_estado_procesamiento
|
||||
modificar_estado_procesamiento(pedimento, servicio_id=7, nuevo_estado=nuevo_estado)
|
||||
|
||||
return Response({
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'estado': 'completado' if nuevo_estado == 3 else 'en_proceso',
|
||||
'mensaje': mensaje,
|
||||
'resumen': {
|
||||
'total_edocuments': total,
|
||||
'edocuments_descargados': descargados,
|
||||
},
|
||||
'pendientes': pendientes,
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Audita el acuse de un pedimento específico",
|
||||
operation_description="Audita el estado de descarga de acuses de EDocument de un pedimento específico",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
@@ -589,28 +621,56 @@ def auditar_edocument_pedimento_endpoint(request):
|
||||
required=['pedimento_id']
|
||||
),
|
||||
responses={
|
||||
200: openapi.Response('Tarea de auditoría iniciada correctamente'),
|
||||
200: openapi.Response('Estado de descarga de acuses de EDocument del pedimento'),
|
||||
400: openapi.Response('Error en los parámetros'),
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
404: openapi.Response('Pedimento no encontrado')
|
||||
}
|
||||
)
|
||||
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated])
|
||||
def auditar_acuse_pedimento_endpoint(request):
|
||||
pedimento_id = request.data.get('pedimento_id')
|
||||
if not pedimento_id:
|
||||
return Response({'error': 'Debe proporcionar pedimento_id'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
pedimento = Pedimento.objects.select_related('organizacion').prefetch_related('documentos').get(id=pedimento_id)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
user = request.user
|
||||
if not user.is_superuser and str(pedimento.organizacion.id) != str(user.organizacion.id):
|
||||
return Response({'error': 'No tiene permisos para este pedimento'}, status=status.HTTP_403_FORBIDDEN)
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response({'error': 'Pedimento no encontrado'}, status=status.HTTP_404_NOT_FOUND)
|
||||
task = auditar_acuse_por_pedimento.delay(pedimento_id)
|
||||
return Response({'message': f'Auditoría de acuse iniciada para el pedimento {pedimento_id}', 'task_id': task.id}, status=status.HTTP_200_OK)
|
||||
|
||||
edocuments = list(pedimento.documentos.all())
|
||||
total = len(edocuments)
|
||||
descargados = sum(1 for d in edocuments if d.acuse_descargado)
|
||||
pendientes = [d.numero_edocument for d in edocuments if not d.acuse_descargado]
|
||||
|
||||
if total == 0:
|
||||
nuevo_estado = 3
|
||||
mensaje = 'El pedimento no tiene EDocuments registrados, no hay acuses que auditar'
|
||||
elif descargados == total:
|
||||
nuevo_estado = 3
|
||||
mensaje = 'Todos los acuses de EDocument están descargados'
|
||||
else:
|
||||
nuevo_estado = 4
|
||||
mensaje = f'{total - descargados} de {total} acuses de EDocument pendientes de descarga'
|
||||
|
||||
from api.customs.tasks.auditoria import modificar_estado_procesamiento
|
||||
modificar_estado_procesamiento(pedimento, servicio_id=6, nuevo_estado=nuevo_estado)
|
||||
|
||||
return Response({
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'estado': 'completado' if nuevo_estado == 3 else 'en_proceso',
|
||||
'mensaje': mensaje,
|
||||
'resumen': {
|
||||
'total_edocuments': total,
|
||||
'acuses_descargados': descargados,
|
||||
},
|
||||
'pendientes': pendientes,
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
### Procesamiento de pedimentos ###
|
||||
@swagger_auto_schema(
|
||||
@@ -1663,6 +1723,10 @@ def auditar_pedimento_endpoint(request):
|
||||
informacion_extraida = []
|
||||
|
||||
for documento in documentos_xml:
|
||||
|
||||
print(f"documento >>>> {documento}")
|
||||
logger.info(f"documento >>>> {documento}")
|
||||
|
||||
try:
|
||||
xml_info = {
|
||||
'documento_id': str(documento.id),
|
||||
@@ -1695,9 +1759,6 @@ def auditar_pedimento_endpoint(request):
|
||||
'error': f'Error procesando archivo: {str(e)}'
|
||||
})
|
||||
|
||||
# Ejecutar la tarea de auditoría completa
|
||||
task = auditar_pedimento_por_id.delay(pedimento_id)
|
||||
|
||||
response_data = {
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
@@ -1706,7 +1767,6 @@ def auditar_pedimento_endpoint(request):
|
||||
'xmls_analizados': xmls_analizados,
|
||||
'informacion_extraida': informacion_extraida,
|
||||
'auditoria_completa': True,
|
||||
'task_id': task.id,
|
||||
'mensaje': f'Auditoría completada para el pedimento {pedimento.pedimento}'
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
|
||||
from django.urls import reverse
|
||||
from django.test import TestCase
|
||||
from rest_framework.test import APITestCase, APIClient
|
||||
from rest_framework import status
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
from unittest.mock import patch, MagicMock
|
||||
from api.organization.models import Organizacion, UsoAlmacenamiento
|
||||
from api.cuser.models import CustomUser
|
||||
from api.customs.models import Pedimento
|
||||
from .models import Document
|
||||
from api.licence.models import Licencia
|
||||
from api.customs.views import is_same_document, get_clean_base_filename
|
||||
from .models import Document, DocumentType
|
||||
import io
|
||||
|
||||
class DocumentViewSetTests(APITestCase):
|
||||
@@ -95,3 +99,177 @@ class DocumentViewSetTests(APITestCase):
|
||||
url = reverse('descargar-documento', args=[doc.id])
|
||||
response = self.client.get(url)
|
||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests unitarios para las funciones helper de comparación de documentos
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class DocumentNameHelperTests(TestCase):
|
||||
"""Verifica que get_clean_base_filename e is_same_document manejan
|
||||
correctamente el sufijo UUID de 8 chars que añade storage_service."""
|
||||
|
||||
def test_strips_uuid_suffix(self):
|
||||
self.assertEqual(get_clean_base_filename('informe_a1b2c3d4.pdf'), 'informe')
|
||||
|
||||
def test_no_suffix_unchanged(self):
|
||||
self.assertEqual(get_clean_base_filename('informe.pdf'), 'informe')
|
||||
|
||||
def test_is_same_document_matches_stored_uuid_name(self):
|
||||
"""El archivo guardado tiene sufijo, el nuevo no — deben coincidir."""
|
||||
doc = MagicMock()
|
||||
doc.archivo.name = 'org_1/documents/24-01-3420-1234567/informe_a1b2c3d4.pdf'
|
||||
doc.extension = 'pdf'
|
||||
self.assertTrue(is_same_document(doc, 'informe.pdf'))
|
||||
|
||||
def test_is_same_document_different_name_no_match(self):
|
||||
doc = MagicMock()
|
||||
doc.archivo.name = 'org_1/documents/ped/informe_a1b2c3d4.pdf'
|
||||
doc.extension = 'pdf'
|
||||
self.assertFalse(is_same_document(doc, 'otro.pdf'))
|
||||
|
||||
def test_is_same_document_different_extension_no_match(self):
|
||||
doc = MagicMock()
|
||||
doc.archivo.name = 'org_1/documents/ped/informe_a1b2c3d4.pdf'
|
||||
doc.extension = 'pdf'
|
||||
self.assertFalse(is_same_document(doc, 'informe.xml'))
|
||||
|
||||
def test_both_clean_names_equal(self):
|
||||
"""Dos archivos con UUID distintos pero mismo nombre base deben coincidir."""
|
||||
doc = MagicMock()
|
||||
doc.archivo.name = 'org_1/documents/ped/pedimento_a1b2c3d4.xml'
|
||||
doc.extension = 'xml'
|
||||
self.assertTrue(is_same_document(doc, 'pedimento_b5c6d7e8.xml'))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tests de integración para bulk-upload (DocumentViewSet.bulk_upload)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class BulkUploadReplaceTests(APITestCase):
|
||||
"""Verifica que bulk-upload reemplaza documentos existentes en vez de duplicar
|
||||
y que no quedan archivos residuales en el storage."""
|
||||
|
||||
def setUp(self):
|
||||
self.licencia = Licencia.objects.create(nombre="Lic100GB", almacenamiento=100)
|
||||
self.org = Organizacion.objects.create(
|
||||
nombre="OrgBulkUpload",
|
||||
licencia=self.licencia,
|
||||
is_active=True,
|
||||
is_verified=True,
|
||||
)
|
||||
self.user = CustomUser.objects.create_user(
|
||||
username="bulkuploaduser", password="pass", organizacion=self.org
|
||||
)
|
||||
self.pedimento = Pedimento.objects.create(
|
||||
organizacion=self.org,
|
||||
pedimento="1234567",
|
||||
pedimento_app="24-01-3420-1234567",
|
||||
)
|
||||
self.doc_type = DocumentType.objects.get_or_create(nombre="Documento General")[0]
|
||||
self.url = reverse("Document-bulk-upload")
|
||||
self.client.force_authenticate(user=self.user)
|
||||
|
||||
def _post_file(self, filename, content=b"contenido de prueba"):
|
||||
archivo = SimpleUploadedFile(filename, content, content_type="application/pdf")
|
||||
return self.client.post(
|
||||
self.url,
|
||||
{"pedimento_id": str(self.pedimento.id), "files": [archivo]},
|
||||
format="multipart",
|
||||
)
|
||||
|
||||
@patch("api.record.views.storage_service")
|
||||
def test_new_file_creates_document(self, mock_st):
|
||||
"""Subir un archivo nuevo crea exactamente un Document."""
|
||||
mock_st.save_document.return_value = "org_1/documents/ped/informe_a1b2c3d4.pdf"
|
||||
|
||||
response = self._post_file("informe.pdf")
|
||||
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
self.assertEqual(Document.objects.filter(pedimento=self.pedimento).count(), 1)
|
||||
mock_st.delete_file.assert_not_called()
|
||||
|
||||
@patch("api.record.views.storage_service")
|
||||
def test_duplicate_replaces_not_creates(self, mock_st):
|
||||
"""Re-subir el mismo archivo debe actualizar el Document existente,
|
||||
no crear uno nuevo."""
|
||||
old_path = "org_1/documents/24-01-3420-1234567/informe_a1b2c3d4.pdf"
|
||||
old_doc = Document.objects.create(
|
||||
organizacion=self.org,
|
||||
pedimento=self.pedimento,
|
||||
document_type=self.doc_type,
|
||||
archivo=old_path,
|
||||
size=500,
|
||||
extension="pdf",
|
||||
)
|
||||
new_path = "org_1/documents/24-01-3420-1234567/informe_b5c6d7e8.pdf"
|
||||
mock_st.save_document.return_value = new_path
|
||||
mock_st.delete_file.return_value = True
|
||||
|
||||
response = self._post_file("informe.pdf", b"contenido actualizado")
|
||||
|
||||
self.assertIn(response.status_code, [status.HTTP_201_CREATED, status.HTTP_207_MULTI_STATUS])
|
||||
docs = Document.objects.filter(pedimento=self.pedimento)
|
||||
# Un único Document — sin duplicados
|
||||
self.assertEqual(docs.count(), 1)
|
||||
# Es el mismo registro (mismo UUID)
|
||||
self.assertEqual(docs.first().id, old_doc.id)
|
||||
# El campo archivo fue actualizado
|
||||
old_doc.refresh_from_db()
|
||||
self.assertEqual(old_doc.archivo.name, new_path)
|
||||
|
||||
@patch("api.record.views.storage_service")
|
||||
def test_replace_deletes_old_storage_file(self, mock_st):
|
||||
"""Al reemplazar, delete_file debe llamarse con la ruta del archivo viejo."""
|
||||
old_path = "org_1/documents/24-01-3420-1234567/informe_a1b2c3d4.pdf"
|
||||
Document.objects.create(
|
||||
organizacion=self.org,
|
||||
pedimento=self.pedimento,
|
||||
document_type=self.doc_type,
|
||||
archivo=old_path,
|
||||
size=500,
|
||||
extension="pdf",
|
||||
)
|
||||
mock_st.save_document.return_value = "org_1/documents/24-01-3420-1234567/informe_b5c6d7e8.pdf"
|
||||
mock_st.delete_file.return_value = True
|
||||
|
||||
self._post_file("informe.pdf")
|
||||
|
||||
mock_st.delete_file.assert_called_once_with(old_path)
|
||||
|
||||
@patch("api.record.views.storage_service")
|
||||
def test_different_filename_creates_new_document(self, mock_st):
|
||||
"""Archivo con nombre diferente debe crear un Document adicional."""
|
||||
Document.objects.create(
|
||||
organizacion=self.org,
|
||||
pedimento=self.pedimento,
|
||||
document_type=self.doc_type,
|
||||
archivo="org_1/documents/ped/informe_a1b2c3d4.pdf",
|
||||
size=500,
|
||||
extension="pdf",
|
||||
)
|
||||
mock_st.save_document.return_value = "org_1/documents/ped/otro_b5c6d7e8.pdf"
|
||||
|
||||
self._post_file("otro.pdf")
|
||||
|
||||
self.assertEqual(Document.objects.filter(pedimento=self.pedimento).count(), 2)
|
||||
mock_st.delete_file.assert_not_called()
|
||||
|
||||
@patch("api.record.views.storage_service")
|
||||
def test_multiple_files_no_cross_replacement(self, mock_st):
|
||||
"""Subir dos archivos distintos en la misma petición crea dos Documents."""
|
||||
mock_st.save_document.side_effect = [
|
||||
"org_1/documents/ped/a_a1b2c3d4.pdf",
|
||||
"org_1/documents/ped/b_a1b2c3d4.pdf",
|
||||
]
|
||||
archivos = [
|
||||
SimpleUploadedFile("a.pdf", b"contenido a", content_type="application/pdf"),
|
||||
SimpleUploadedFile("b.pdf", b"contenido b", content_type="application/pdf"),
|
||||
]
|
||||
self.client.post(
|
||||
self.url,
|
||||
{"pedimento_id": str(self.pedimento.id), "files": archivos},
|
||||
format="multipart",
|
||||
)
|
||||
self.assertEqual(Document.objects.filter(pedimento=self.pedimento).count(), 2)
|
||||
mock_st.delete_file.assert_not_called()
|
||||
|
||||
Reference in New Issue
Block a user