From 9a8827bb6fc542ee2b5f1c0f505094cb48ac16d8 Mon Sep 17 00:00:00 2001 From: Kevin Rosales Date: Wed, 15 Oct 2025 19:29:11 -0600 Subject: [PATCH] customs crear/update pedimento --- api/customs/views.py | 395 ++----------------------------------------- 1 file changed, 11 insertions(+), 384 deletions(-) diff --git a/api/customs/views.py b/api/customs/views.py index 348fb5b..debbf5a 100644 --- a/api/customs/views.py +++ b/api/customs/views.py @@ -38,23 +38,6 @@ from api.customs.serializers import ( from api.logger.mixins import LoggingMixin from mixins.filtrado_organizacion import OrganizacionFiltradaMixin, ProcesosPorOrganizacionMixin import requests -import os -import re -import zipfile -import tempfile -import shutil -from datetime import datetime -from django.core.files.base import ContentFile -from django.db import transaction -from rest_framework.parsers import MultiPartParser, FormParser -from api.record.models import Document, DocumentType - -# Importar rarfile de manera opcional -try: - import rarfile - RAR_SUPPORT = True -except ImportError: - RAR_SUPPORT = False from .tasks.microservice_v2 import * @@ -194,24 +177,24 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada # Verificar si la respuesta fue exitosa if response.status_code == 200: - print(f"✅ Servicio FastAPI ejecutado exitosamente: {response.status_code}") - print(f"📄 Respuesta: {response.json()}") + print(f"Servicio FastAPI ejecutado exitosamente: {response.status_code}") + print(f"Respuesta: {response.json()}") elif response.status_code == 201: - print(f"✅ Recurso creado exitosamente en FastAPI: {response.status_code}") - print(f"📄 Respuesta: {response.json()}") + print(f"Recurso creado exitosamente en FastAPI: {response.status_code}") + print(f"Respuesta: {response.json()}") else: - print(f"⚠️ Servicio FastAPI respondió con error: {response.status_code}") - print(f"📄 Respuesta: {response.text}") + print(f"Servicio FastAPI respondió con error: {response.status_code}") + print(f"Respuesta: {response.text}") except requests.exceptions.ConnectionError as e: - print(f"❌ No se pudo conectar al servicio FastAPI: {e}") - print(f"🔧 Verifica que el servicio FastAPI esté corriendo en {SERVICE_API_URL}") + print(f"No se pudo conectar al servicio FastAPI: {e}") + print(f"Verifica que el servicio FastAPI esté corriendo en {SERVICE_API_URL}") except requests.exceptions.Timeout as e: - print(f"⏰ Timeout al conectar con el servicio FastAPI: {e}") + print(f"Timeout al conectar con el servicio FastAPI: {e}") except requests.exceptions.RequestException as e: - print(f"🚨 Error de request al servicio FastAPI: {e}") + print(f"Error de request al servicio FastAPI: {e}") except Exception as e: - print(f"💥 Error inesperado al llamar al servicio FastAPI: {e}") + print(f"Error inesperado al llamar al servicio FastAPI: {e}") def perform_update(self, serializer): """ @@ -341,362 +324,6 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada return Response(response_data, status=response_status) - @action(detail=False, methods=['post'], url_path='bulk-create', parser_classes=[MultiPartParser, FormParser]) - def bulk_create(self, request): - """ - Endpoint para crear múltiples pedimentos de manera masiva desde archivos. - - FormData esperado: - - contribuyente: string (nombre del contribuyente) - - archivos: files (pueden ser múltiples archivos: zip, rar o individuales) - - Nomenclatura esperada de archivos: anio-aduana-patente-pedimento - - anio: 2 dígitos (ej: 24) - - aduana: 2 o 3 dígitos (ej: 01, 123) - - patente: 4 dígitos (ej: 3420) - - pedimento: 7 dígitos (ej: 1234567) - - Ejemplo: 24-01-3420-1234567 - - Nota: Cada archivo ZIP/RAR se procesa independientemente en su propio subdirectorio. - - Respuesta exitosa: - { - "message": "Pedimentos creados exitosamente", - "created_count": 5, - "created_pedimentos": [...], - "documents_created": 15, - "processed_files": 3, - "summary": "Procesados 3 archivo(s): 5 pedimento(s) creado(s), 15 documento(s) asociado(s)", - "failed_files": [], - "errors": [] - } - """ - print(request.data) - # Validar datos requeridos - contribuyente = request.data.get('contribuyente') - archivos = request.FILES.getlist('archivos') - - if not contribuyente: - return Response( - {"error": "Se requiere el campo 'contribuyente'"}, - status=status.HTTP_400_BAD_REQUEST - ) - - if not archivos: - return Response( - {"error": "Se requiere al menos un archivo"}, - status=status.HTTP_400_BAD_REQUEST - ) - - # Validar organización del usuario - if not request.user.is_authenticated or not hasattr(request.user, 'organizacion'): - return Response( - {"error": "Usuario no autenticado o sin organización"}, - status=status.HTTP_400_BAD_REQUEST - ) - - organizacion = request.user.organizacion - - # Regex para validar nomenclatura: anio-aduana-patente-pedimento - nomenclatura_pattern = re.compile(r'^(\d{2})-(\d{2,3})-(\d{4})-(\d{7})$') - - created_pedimentos = [] - failed_files = [] - errors = [] - documents_created = 0 - temp_dir = None - - # Obtener DocumentType ANTES de la transacción atómica - print("Intentando obtener o crear DocumentType...") - try: - # Primero intentar obtener si ya existe - try: - document_type = DocumentType.objects.get(nombre="Pedimento") - print(f"DocumentType obtenido existente: {document_type.nombre} (ID: {document_type.id})") - except DocumentType.DoesNotExist: - # Si no existe, crear uno nuevo - document_type = DocumentType.objects.create( - nombre="Pedimento", - descripcion="Documento de pedimento" - ) - print(f"DocumentType creado nuevo: {document_type.nombre} (ID: {document_type.id})") - - except Exception as e: - print(f"Error al obtener/crear DocumentType: {str(e)}") - # Como fallback, intentar obtener cualquier DocumentType existente - try: - document_type = DocumentType.objects.first() - if document_type: - print(f"Usando DocumentType existente como fallback: {document_type.nombre} (ID: {document_type.id})") - else: - print("No hay DocumentType disponible") - return Response( - {"error": "No se pudo configurar el tipo de documento y no hay tipos existentes"}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR - ) - except Exception as fallback_error: - print(f"Error en fallback: {str(fallback_error)}") - return Response( - {"error": f"Error crítico al configurar tipo de documento: {str(e)}"}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR - ) - - try: - print("Iniciando transacción atómica...") - with transaction.atomic(): - # Crear directorio temporal - temp_dir = tempfile.mkdtemp() - print(f"Directorio temporal creado: {temp_dir}") - - # Procesar cada archivo enviado - for idx, archivo in enumerate(archivos): - archivo_name = archivo.name.lower() - print(f"Procesando archivo {idx + 1}/{len(archivos)}: {archivo_name}") - - # Crear subdirectorio para cada archivo usando el nombre del archivo sin extensión - archivo_name_sin_extension = os.path.splitext(archivo.name)[0] - sub_dir = os.path.join(temp_dir, archivo_name_sin_extension) - os.makedirs(sub_dir, exist_ok=True) - print(f"Subdirectorio creado: {sub_dir}") - - if archivo_name.endswith('.zip'): - # Manejar archivo ZIP - print("Es un archivo ZIP") - try: - with zipfile.ZipFile(archivo, 'r') as zip_ref: - zip_ref.extractall(sub_dir) - print("Archivo ZIP extraído exitosamente") - except zipfile.BadZipFile as e: - return Response( - {"error": f"Archivo ZIP corrupto o inválido: {archivo.name} - {str(e)}"}, - status=status.HTTP_400_BAD_REQUEST - ) - except Exception as e: - return Response( - {"error": f"Error al extraer ZIP {archivo.name}: {str(e)}"}, - status=status.HTTP_400_BAD_REQUEST - ) - elif archivo_name.endswith('.rar'): - # Manejar archivo RAR - if not RAR_SUPPORT: - return Response( - {"error": "Soporte para archivos RAR no disponible. Instalar rarfile: pip install rarfile"}, - status=status.HTTP_400_BAD_REQUEST - ) - try: - with rarfile.RarFile(archivo, 'r') as rar_ref: - rar_ref.extractall(sub_dir) - print(f"Archivo RAR {archivo.name} extraído en sub_dir") - except rarfile.Error as e: - return Response( - {"error": f"Error al extraer archivo RAR {archivo.name}: {str(e)}"}, - status=status.HTTP_400_BAD_REQUEST - ) - else: - # Asumir que es un archivo individual - # Crear el archivo en el subdirectorio - archivo_path = os.path.join(sub_dir, archivo.name) - with open(archivo_path, 'wb') as f: - for chunk in archivo.chunks(): - f.write(chunk) - print(f"Archivo individual {archivo.name} guardado en sub_dir:", archivo_path) - - # Recorrer todos los archivos extraídos o el directorio - print("Iniciando recorrido de archivos...") - for root, dirs, files in os.walk(temp_dir): - print(f"Revisando directorio: {root}") - print(f"Archivos encontrados: {files}") - - for file_name in files: - print(f"Procesando archivo: {file_name}") - file_path = os.path.join(root, file_name) - - # Obtener la ruta relativa para determinar la estructura de carpetas - relative_path = os.path.relpath(file_path, temp_dir) - print(f"Ruta relativa: {relative_path}") - - # Determinar si el archivo está en una carpeta que sigue la nomenclatura - folder_name = None - if os.path.dirname(relative_path): - # El archivo está dentro de una carpeta - folder_parts = relative_path.split(os.sep) - folder_name = folder_parts[0] # Primera carpeta (nombre del archivo ZIP/RAR sin extensión) - else: - # El archivo está en la raíz, usar el nombre del archivo sin extensión - folder_name = os.path.splitext(file_name)[0] - - print(f"Folder name para validación: {folder_name}") - - # Validar nomenclatura - match = nomenclatura_pattern.match(folder_name) - if not match: - print(f"Nomenclatura inválida: {folder_name}") - # Determinar el archivo original basado en el subdirectorio - archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar') - failed_files.append({ - "file": relative_path, - "archivo_original": archivo_original, - "error": f"Nomenclatura inválida: {folder_name}. Esperado: anio-aduana-patente-pedimento" - }) - continue - - print(f"Nomenclatura válida: {folder_name}") - anio, aduana, patente, pedimento_num = match.groups() - print(f"Extraído - Año: {anio}, Aduana: {aduana}, Patente: {patente}, Pedimento: {pedimento_num}") - - # Crear fecha_pago basada en el año - try: - # Convertir año de 2 dígitos a 4 dígitos - anio_completo = 2000 + int(anio) if int(anio) < 50 else 1900 + int(anio) - fecha_pago = datetime(anio_completo, 1, 1).date() - print(f"Fecha de pago calculada: {fecha_pago}") - except ValueError: - archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar') - failed_files.append({ - "file": relative_path, - "archivo_original": archivo_original, - "error": f"Año inválido: {anio}" - }) - continue - - # Generar pedimento_app - pedimento_app = f"{anio}-{aduana.zfill(2)}-{patente}-{pedimento_num}" - print(f"Pedimento_app generado: {pedimento_app}") - print(f"Buscando pedimento existente con pedimento_app: {pedimento_app} y organización ID: {organizacion.id}") - # Verificar si el pedimento ya existe - existing_pedimento = Pedimento.objects.filter( - pedimento_app=pedimento_app, - organizacion=organizacion - ).first() - - print(f"Pedimento existente: {existing_pedimento is not None}") - - if not existing_pedimento: - print("📝 Pedimento no existe, creando nuevo...") - # Crear nuevo pedimento - try: - print("🔄 Iniciando creación de pedimento...") - - # Obtener o crear el importador - print(f"🏢 Buscando/creando importador con RFC: {contribuyente}") - importador, created = Importador.objects.get_or_create( - rfc=contribuyente, - defaults={ - 'nombre': f"Importador {contribuyente}", - 'organizacion': organizacion - } - ) - if created: - print(f"✅ Importador creado: {importador.rfc} - {importador.nombre}") - else: - print(f"♻️ Importador existente: {importador.rfc} - {importador.nombre}") - - pedimento = Pedimento.objects.create( - organizacion=organizacion, - contribuyente=importador, - pedimento=int(pedimento_num), - aduana=int(aduana), - patente=int(patente), - fecha_pago=fecha_pago, - pedimento_app=pedimento_app, - agente_aduanal=f"Agente {patente}", # Valor por defecto - clave_pedimento="A1" # Valor por defecto - ) - - print(f"✅ Pedimento creado exitosamente: ID {pedimento.id}, pedimento_app: {pedimento_app}") - - created_pedimentos.append({ - "id": str(pedimento.id), - "pedimento_app": pedimento_app, - "contribuyente": importador.rfc, - "contribuyente_nombre": importador.nombre - }) - - except Exception as e: - print(f"❌ Error al crear pedimento: {str(e)}") - archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar') - failed_files.append({ - "file": relative_path, - "archivo_original": archivo_original, - "error": f"Error al crear pedimento: {str(e)}" - }) - continue - else: - print(f"♻️ Usando pedimento existente: ID {existing_pedimento.id}") - # Usar pedimento existente - pedimento = existing_pedimento - - print(f"🔄 Iniciando creación de documento para pedimento ID: {pedimento.id}") - # Crear documento asociado al pedimento - try: - print("📖 Leyendo archivo desde directorio temporal...") - # Leer el archivo desde el directorio temporal - with open(file_path, 'rb') as f: - file_content = f.read() - - print(f"📄 Archivo leído: {len(file_content)} bytes") - # Crear ContentFile que Django puede manejar correctamente - django_file = ContentFile(file_content, name=file_name) - - print(f"Creando documento para archivo: {file_name}") - # Crear documento - Django automáticamente guardará el archivo en media/documents/ - document = Document.objects.create( - organizacion=organizacion, - pedimento_id=pedimento.id, - document_type=document_type, - archivo=django_file, - size=len(file_content), - extension=os.path.splitext(file_name)[1].lower().lstrip('.') - ) - print(f"Documento creado exitosamente: {document.id}") - - documents_created += 1 - print(f"📊 Total documentos creados hasta ahora: {documents_created}") - - except Exception as e: - print(f"❌ Error al crear documento: {str(e)}") - archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar') - failed_files.append({ - "file": relative_path, - "archivo_original": archivo_original, - "error": f"Error al crear documento: {str(e)}" - }) - continue - - print(f"🏁 Procesamiento completado. Archivos procesados en este directorio.") - except Exception as e: - return Response( - {"error": f"Error durante el procesamiento: {str(e)}"}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR - ) - finally: - # Limpiar directorio temporal - if temp_dir and os.path.exists(temp_dir): - shutil.rmtree(temp_dir) - - # Preparar respuesta - response_data = { - "created_count": len(created_pedimentos), - "created_pedimentos": created_pedimentos, - "documents_created": documents_created, - "failed_files": failed_files, - "processed_files": len(archivos), - "summary": f"Procesados {len(archivos)} archivo(s): {len(created_pedimentos)} pedimento(s) creado(s), {documents_created} documento(s) asociado(s)" - } - - if failed_files: - response_data.update({ - "message": "Procesamiento completado con algunos errores", - "errors": [item["error"] for item in failed_files] - }) - response_status = status.HTTP_207_MULTI_STATUS - else: - response_data["message"] = "Pedimentos creados exitosamente" - response_status = status.HTTP_201_CREATED - - return Response(response_data, status=response_status) - my_tags = ['Pedimentos'] class PartidaViewSet(viewsets.ModelViewSet):