fix/forzar-carga-acuses
This commit is contained in:
@@ -15,7 +15,7 @@ from .tasks.auditoria import (
|
||||
auditar_remesas,
|
||||
)
|
||||
from .tasks.internal_services import auditar_pedimentos
|
||||
from .tasks.microservice_v2 import procesar_pedimentos_completos
|
||||
from .tasks.microservice_v2 import procesar_pedimentos_completos, procesar_pedimento_completo_individual
|
||||
from api.customs.models import Pedimento
|
||||
from api.organization.models import Organizacion
|
||||
from api.record.models import Document
|
||||
@@ -25,6 +25,9 @@ import os
|
||||
from api.utils.storage_service import storage_service
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
_ERROR_DOCUMENT_TYPES = [10, 14, 16, 18, 20, 22, 24, 26]
|
||||
|
||||
logger = logging.getLogger('api.customs.views_auditor')
|
||||
|
||||
def get_document_content(documento):
|
||||
@@ -1680,98 +1683,155 @@ def auditor_obtener_respuesta_edocument_vu(request):
|
||||
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
|
||||
def auditar_pedimento_endpoint(request):
|
||||
"""
|
||||
Audita un pedimento específico verificando si existe su XML y extrayendo información.
|
||||
Audita el pedimento completo (PC): ¿está descargado? ¿se puede procesar?
|
||||
Incluye diagnóstico de campos y errores detectados por tipo de documento.
|
||||
"""
|
||||
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:
|
||||
# Validar permisos y existencia del pedimento
|
||||
pedimento = Pedimento.objects.get(id=pedimento_id)
|
||||
pedimento = Pedimento.objects.select_related(
|
||||
'organizacion', 'contribuyente'
|
||||
).get(id=pedimento_id)
|
||||
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
|
||||
)
|
||||
|
||||
# Buscar documentos XML del pedimento
|
||||
documentos_xml = Document.objects.filter(
|
||||
pedimento=pedimento,
|
||||
archivo__endswith='.xml',
|
||||
|
||||
# PC descargado (type 2)
|
||||
pc_descargado = pedimento.documents.filter(
|
||||
document_type_id=2,
|
||||
organizacion=pedimento.organizacion
|
||||
).exists()
|
||||
|
||||
# Fuente de carga
|
||||
fuente = 'datastage' if pedimento.consultar_vucem else 'manual'
|
||||
|
||||
# Diagnóstico de campos
|
||||
aduana = pedimento.aduana or ''
|
||||
patente = pedimento.patente or ''
|
||||
numero_pedimento = pedimento.pedimento or ''
|
||||
|
||||
aduana_valida = bool(aduana) and aduana.isdigit() and 2 <= len(aduana) <= 3
|
||||
patente_valida = bool(patente) and patente.isdigit() and len(patente) == 4
|
||||
pedimento_valido = bool(numero_pedimento) and numero_pedimento.isdigit() and len(numero_pedimento) >= 7
|
||||
numero_operacion_presente = bool(pedimento.numero_operacion)
|
||||
|
||||
from api.vucem.models import CredencialesImportador
|
||||
tiene_contribuyente = pedimento.contribuyente is not None
|
||||
tiene_credenciales = False
|
||||
credenciales_detalle = None
|
||||
if tiene_contribuyente:
|
||||
credencial = CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first()
|
||||
tiene_credenciales = bool(credencial and credencial.vucem)
|
||||
if credencial and not credencial.vucem:
|
||||
credenciales_detalle = 'Credencial encontrada pero sin cuenta VUCEM asociada'
|
||||
elif not credencial:
|
||||
credenciales_detalle = f'Sin credenciales VUCEM para RFC {pedimento.contribuyente.rfc}'
|
||||
|
||||
razones = []
|
||||
if not aduana_valida:
|
||||
razones.append(f'Aduana inválida o ausente (valor: "{aduana}")')
|
||||
if not patente_valida:
|
||||
razones.append(f'Patente inválida o ausente (valor: "{patente}")')
|
||||
if not pedimento_valido:
|
||||
razones.append(f'Número de pedimento inválido (valor: "{numero_pedimento}")')
|
||||
if not tiene_contribuyente:
|
||||
razones.append('Sin contribuyente asignado')
|
||||
elif not tiene_credenciales:
|
||||
razones.append(credenciales_detalle or 'Sin credenciales VUCEM')
|
||||
|
||||
puede_procesar = len(razones) == 0
|
||||
|
||||
datos = {
|
||||
'aduana': aduana or None,
|
||||
'patente': patente or None,
|
||||
'numero_pedimento': numero_pedimento or None,
|
||||
'numero_operacion': pedimento.numero_operacion,
|
||||
'contribuyente_rfc': pedimento.contribuyente.rfc if pedimento.contribuyente else None,
|
||||
'contribuyente_nombre': str(pedimento.contribuyente) if pedimento.contribuyente else None,
|
||||
}
|
||||
|
||||
validacion = {
|
||||
'aduana_valida': aduana_valida,
|
||||
'patente_valida': patente_valida,
|
||||
'pedimento_valido': pedimento_valido,
|
||||
'numero_operacion_presente': numero_operacion_presente,
|
||||
'tiene_contribuyente': tiene_contribuyente,
|
||||
'tiene_credenciales_vucem': tiene_credenciales,
|
||||
}
|
||||
|
||||
# Errores por tipo de documento
|
||||
docs_error = (
|
||||
Document.objects
|
||||
.filter(
|
||||
pedimento=pedimento,
|
||||
organizacion=pedimento.organizacion,
|
||||
document_type_id__in=_ERROR_DOCUMENT_TYPES,
|
||||
)
|
||||
.select_related('document_type')
|
||||
.order_by('document_type_id')
|
||||
)
|
||||
|
||||
if not documentos_xml.exists():
|
||||
return Response({
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'pedimento_app': pedimento.pedimento_app,
|
||||
'archivos_xml_encontrados': 0,
|
||||
'mensaje': 'No se encontraron archivos XML para este pedimento',
|
||||
'auditoria_completa': False
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
# Lista para almacenar información de cada XML
|
||||
xmls_analizados = []
|
||||
informacion_extraida = []
|
||||
|
||||
for documento in documentos_xml:
|
||||
errores_detectados = [
|
||||
{
|
||||
'documento_id': str(doc.id),
|
||||
'nombre_archivo': os.path.basename(str(doc.archivo)),
|
||||
'tipo_error': doc.document_type.descripcion if doc.document_type else 'Error desconocido',
|
||||
'tipo_id': doc.document_type_id,
|
||||
}
|
||||
for doc in docs_error
|
||||
]
|
||||
|
||||
print(f"documento >>>> {documento}")
|
||||
logger.info(f"documento >>>> {documento}")
|
||||
# XML del PC si existe
|
||||
informacion_xml = None
|
||||
doc_pc = pedimento.documents.filter(
|
||||
document_type_id=2,
|
||||
organizacion=pedimento.organizacion,
|
||||
archivo__endswith='.xml',
|
||||
).first()
|
||||
if doc_pc:
|
||||
xml_content = get_document_content(doc_pc)
|
||||
if xml_content:
|
||||
info_pedimento = extraer_info_pedimento_xml(xml_content)
|
||||
if info_pedimento:
|
||||
informacion_xml = info_pedimento
|
||||
actualizar_info_pedimento(pedimento, info_pedimento)
|
||||
|
||||
try:
|
||||
xml_info = {
|
||||
'documento_id': str(documento.id),
|
||||
'nombre_archivo': os.path.basename(str(documento.archivo)),
|
||||
'tamanio': documento.size,
|
||||
'extension': documento.extension,
|
||||
'tipo_documento': documento.document_type.descripcion if documento.document_type else 'Desconocido'
|
||||
}
|
||||
|
||||
xml_content = get_document_content(documento)
|
||||
|
||||
if xml_content is None:
|
||||
xml_info['error_lectura'] = 'No se pudo descargar el archivo'
|
||||
else:
|
||||
info_pedimento = extraer_info_pedimento_xml(xml_content)
|
||||
|
||||
if info_pedimento:
|
||||
xml_info['informacion_extraida'] = info_pedimento
|
||||
informacion_extraida.append(info_pedimento)
|
||||
hay_pendientes = not pc_descargado
|
||||
hay_errores = bool(errores_detectados)
|
||||
|
||||
# Actualizar el pedimento con la información encontrada si es necesario
|
||||
actualizar_info_pedimento(pedimento, info_pedimento)
|
||||
|
||||
xmls_analizados.append(xml_info)
|
||||
|
||||
except Exception as e:
|
||||
xmls_analizados.append({
|
||||
'documento_id': str(documento.id),
|
||||
'nombre_archivo': os.path.basename(str(documento.archivo)),
|
||||
'error': f'Error procesando archivo: {str(e)}'
|
||||
})
|
||||
|
||||
response_data = {
|
||||
if hay_errores:
|
||||
estado = 'CON_ERRORES'
|
||||
elif hay_pendientes:
|
||||
estado = 'PENDIENTE'
|
||||
else:
|
||||
estado = 'COMPLETO'
|
||||
|
||||
return Response({
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'pedimento_app': pedimento.pedimento_app,
|
||||
'archivos_xml_encontrados': len(xmls_analizados),
|
||||
'xmls_analizados': xmls_analizados,
|
||||
'informacion_extraida': informacion_extraida,
|
||||
'auditoria_completa': True,
|
||||
'mensaje': f'Auditoría completada para el pedimento {pedimento.pedimento}'
|
||||
}
|
||||
|
||||
return Response(response_data, status=status.HTTP_200_OK)
|
||||
|
||||
'estado': estado,
|
||||
'hay_pendientes': hay_pendientes,
|
||||
'hay_errores': hay_errores,
|
||||
'pc_descargado': pc_descargado,
|
||||
'puede_procesar': puede_procesar,
|
||||
'razones_no_puede_procesar': razones,
|
||||
'fuente': fuente,
|
||||
'datos': datos,
|
||||
'validacion': validacion,
|
||||
'errores_detectados': errores_detectados,
|
||||
'informacion_xml': informacion_xml,
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
except Pedimento.DoesNotExist:
|
||||
return Response(
|
||||
{'error': 'Pedimento no encontrado'},
|
||||
@@ -1783,6 +1843,164 @@ def auditar_pedimento_endpoint(request):
|
||||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||||
)
|
||||
|
||||
@swagger_auto_schema(
|
||||
method='post',
|
||||
operation_description="Procesa el pedimento completo (tipo 2) de un pedimento específico llamando al microservicio VUCEM.",
|
||||
request_body=openapi.Schema(
|
||||
type=openapi.TYPE_OBJECT,
|
||||
properties={
|
||||
'pedimento_id': openapi.Schema(type=openapi.TYPE_STRING, description='ID del pedimento'),
|
||||
},
|
||||
required=['pedimento_id']
|
||||
),
|
||||
responses={
|
||||
202: openapi.Response('Procesamiento encolado — usar task_id para consultar resultado'),
|
||||
200: openapi.Response('El pedimento ya tiene su documento completo descargado'),
|
||||
400: openapi.Response('Error en los parámetros o prerequisitos faltantes'),
|
||||
403: openapi.Response('No tiene permisos suficientes'),
|
||||
404: openapi.Response('Pedimento no encontrado'),
|
||||
}
|
||||
)
|
||||
@api_view(['POST'])
|
||||
@permission_classes([IsAuthenticated, require_permission('auditoria.process')])
|
||||
def procesar_pedimento_completo_endpoint(request):
|
||||
"""
|
||||
Diagnostica el pedimento completo y, si todo está en orden y aún no se ha
|
||||
descargado, encola la tarea de procesamiento.
|
||||
|
||||
Siempre devuelve diagnóstico completo: validación de campos, fuente, estado
|
||||
del PC y razones por las que no se puede procesar si aplica.
|
||||
"""
|
||||
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.select_related(
|
||||
'organizacion', 'contribuyente', 'tipo_operacion'
|
||||
).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)
|
||||
|
||||
# --- Diagnóstico de campos ---
|
||||
aduana = pedimento.aduana or ''
|
||||
patente = pedimento.patente or ''
|
||||
numero_pedimento = pedimento.pedimento or ''
|
||||
|
||||
aduana_valida = bool(aduana) and aduana.isdigit() and 2 <= len(aduana) <= 3
|
||||
patente_valida = bool(patente) and patente.isdigit() and len(patente) == 4
|
||||
pedimento_valido = bool(numero_pedimento) and numero_pedimento.isdigit() and len(numero_pedimento) >= 7
|
||||
numero_operacion_presente = bool(pedimento.numero_operacion)
|
||||
|
||||
# --- Fuente de carga ---
|
||||
# consultar_vucem=True indica que fue originado desde datastage
|
||||
fuente = 'datastage' if pedimento.consultar_vucem else 'manual'
|
||||
|
||||
# --- Estado del PC ---
|
||||
pc_descargado = pedimento.documents.filter(
|
||||
document_type_id=2,
|
||||
organizacion=pedimento.organizacion
|
||||
).exists()
|
||||
|
||||
# --- Credenciales VUCEM ---
|
||||
from api.vucem.models import CredencialesImportador
|
||||
tiene_contribuyente = pedimento.contribuyente is not None
|
||||
tiene_credenciales = False
|
||||
credenciales_detalle = None
|
||||
if tiene_contribuyente:
|
||||
credencial = CredencialesImportador.objects.filter(rfc=pedimento.contribuyente).first()
|
||||
tiene_credenciales = bool(credencial and credencial.vucem)
|
||||
if credencial and not credencial.vucem:
|
||||
credenciales_detalle = 'Credencial encontrada pero sin cuenta VUCEM asociada'
|
||||
elif not credencial:
|
||||
credenciales_detalle = f'Sin credenciales VUCEM para RFC {pedimento.contribuyente.rfc}'
|
||||
|
||||
# --- Puede procesar ---
|
||||
razones = []
|
||||
if not aduana_valida:
|
||||
razones.append(f'Aduana inválida o ausente (valor: "{aduana}")')
|
||||
if not patente_valida:
|
||||
razones.append(f'Patente inválida o ausente (valor: "{patente}")')
|
||||
if not pedimento_valido:
|
||||
razones.append(f'Número de pedimento inválido (valor: "{numero_pedimento}")')
|
||||
if not tiene_contribuyente:
|
||||
razones.append('Sin contribuyente asignado')
|
||||
elif not tiene_credenciales:
|
||||
razones.append(credenciales_detalle or 'Sin credenciales VUCEM')
|
||||
|
||||
puede_procesar = len(razones) == 0
|
||||
|
||||
datos = {
|
||||
'aduana': aduana or None,
|
||||
'patente': patente or None,
|
||||
'numero_pedimento': numero_pedimento or None,
|
||||
'numero_operacion': pedimento.numero_operacion,
|
||||
'regimen': pedimento.regimen,
|
||||
'clave_pedimento': pedimento.clave_pedimento,
|
||||
'fecha_pago': str(pedimento.fecha_pago) if pedimento.fecha_pago else None,
|
||||
'contribuyente_rfc': pedimento.contribuyente.rfc if pedimento.contribuyente else None,
|
||||
'contribuyente_nombre': str(pedimento.contribuyente) if pedimento.contribuyente else None,
|
||||
'remesas': pedimento.remesas,
|
||||
'numero_partidas': pedimento.numero_partidas,
|
||||
}
|
||||
|
||||
validacion = {
|
||||
'aduana_valida': aduana_valida,
|
||||
'patente_valida': patente_valida,
|
||||
'pedimento_valido': pedimento_valido,
|
||||
'numero_operacion_presente': numero_operacion_presente,
|
||||
'tiene_contribuyente': tiene_contribuyente,
|
||||
'tiene_credenciales_vucem': tiene_credenciales,
|
||||
'puede_procesar': puede_procesar,
|
||||
'razones_no_puede_procesar': razones,
|
||||
}
|
||||
|
||||
base_response = {
|
||||
'pedimento_id': str(pedimento_id),
|
||||
'pedimento': pedimento.pedimento,
|
||||
'pedimento_app': pedimento.pedimento_app,
|
||||
'fuente': fuente,
|
||||
'datos': datos,
|
||||
'validacion': validacion,
|
||||
'pc_descargado': pc_descargado,
|
||||
}
|
||||
|
||||
# Ya descargado — devolver diagnóstico sin encolar
|
||||
if pc_descargado:
|
||||
return Response({
|
||||
**base_response,
|
||||
'estado': 'ya_descargado',
|
||||
'mensaje': 'El pedimento completo ya fue descargado',
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
# No puede procesar — devolver diagnóstico con razones
|
||||
if not puede_procesar:
|
||||
return Response({
|
||||
**base_response,
|
||||
'estado': 'no_puede_procesar',
|
||||
'mensaje': 'El pedimento no cumple los requisitos para procesar',
|
||||
}, status=status.HTTP_200_OK)
|
||||
|
||||
# Todo en orden — encolar
|
||||
task = procesar_pedimento_completo_individual.delay(str(pedimento_id))
|
||||
logger.info(f"Procesamiento PC encolado: {pedimento.pedimento} (task={task.id})")
|
||||
|
||||
return Response({
|
||||
**base_response,
|
||||
'estado': 'encolado',
|
||||
'task_id': task.id,
|
||||
'mensaje': f'Procesamiento encolado para {pedimento.pedimento_app}',
|
||||
}, status=status.HTTP_202_ACCEPTED)
|
||||
|
||||
|
||||
def actualizar_info_pedimento(pedimento, info_xml):
|
||||
"""
|
||||
Actualiza la información del pedimento con los datos extraídos del XML.
|
||||
|
||||
Reference in New Issue
Block a user