feature/implementacion de gestor de informacion y archivos minIO

This commit is contained in:
Dulce
2026-04-22 11:10:05 -06:00
parent 69d07f2713
commit 39504e196c
23 changed files with 2272 additions and 391 deletions

View File

@@ -61,6 +61,7 @@ except ImportError:
# Importar tarea de procesamiento de pedimento (Celery)
from api.customs.tasks.microservice import procesar_pedimento_completo_individual
from api.utils.storage_service import storage_service
def get_available_extractors():
"""
@@ -395,31 +396,8 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
@action(detail=False, methods=['post'], url_path='bulk-delete')
def bulk_delete(self, request):
"""
Endpoint para eliminar múltiples pedimentos de manera masiva.
import traceback
Payload esperado:
{
"ids": ["uuid1", "uuid2", "uuid3", ...]
}
Respuesta exitosa:
{
"message": "Pedimentos eliminados exitosamente",
"deleted_count": 3,
"deleted_ids": ["uuid1", "uuid2", "uuid3"]
}
Respuesta con errores:
{
"message": "Algunos pedimentos no pudieron ser eliminados",
"deleted_count": 2,
"deleted_ids": ["uuid1", "uuid2"],
"failed_ids": ["uuid3"],
"errors": ["No se encontró el pedimento con ID uuid3"]
}
"""
# Obtener los IDs del payload
ids = request.data.get('ids', [])
if not ids:
@@ -434,18 +412,11 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
status=status.HTTP_400_BAD_REQUEST
)
# Obtener el queryset filtrado por organización
queryset = self.get_queryset()
# Filtrar solo los pedimentos que existen y pertenecen a la organización del usuario
existing_pedimentos = queryset.filter(id__in=ids)
existing_ids = list(existing_pedimentos.values_list('id', flat=True))
# Convertir UUIDs a strings para comparación
existing_ids_str = [str(id) for id in existing_ids]
requested_ids_str = [str(id) for id in ids]
# Identificar IDs que no existen o no pertenecen a la organización
failed_ids = [id for id in requested_ids_str if id not in existing_ids_str]
deleted_count = 0
@@ -453,20 +424,28 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
if existing_pedimentos.exists():
try:
# Eliminar los pedimentos encontrados
for pedimento in existing_pedimentos:
documentos = Document.objects.filter(pedimento_id=pedimento.id)
for doc in documentos:
if doc.archivo:
ruta = str(doc.archivo)
try:
storage_service.delete_file(ruta)
except Exception as e:
traceback.print_exc()
documentos.delete()
deleted_count = existing_pedimentos.count()
existing_pedimentos.delete()
except Exception as e:
traceback.print_exc()
return Response(
{"error": f"Error al eliminar pedimentos: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
# Agregar errores para IDs no encontrados
if failed_ids:
errors = [f"No se encontró el pedimento con ID {id} o no pertenece a su organización" for id in failed_ids]
# Preparar respuesta
response_data = {
"deleted_count": deleted_count,
"deleted_ids": existing_ids_str
@@ -793,14 +772,14 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
print(f"Procesando documento: {file_name}")
try:
# Leer el archivo desde el directorio temporal
# Leer el archivo para extraer info del XML
with open(file_path, 'rb') as f:
file_content = f.read()
from api.utils.helpers import extraer_info_pedimento_xml
# Extraer info del pedimento desde XML si es aplicable
if file_name.lower().endswith('.xml'):
try:
from api.utils.helpers import extraer_info_pedimento_xml
xml_info = extraer_info_pedimento_xml(file_content)
if xml_info:
if 'numero_operacion' in xml_info:
@@ -812,11 +791,12 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
print(f"Información extraída del XML: {xml_info}")
except Exception as e:
print(f"No se pudo extraer información del XML {file_name}: {str(e)}")
# Obtener información del archivo
extension = os.path.splitext(file_name)[1].lower().lstrip('.')
file_size = os.path.getsize(file_path)
# Buscar si ya existe un documento con el mismo nombre para este pedimento
# Buscar si ya existe un documento con el mismo nombre
existing_documents = Document.objects.filter(
pedimento_id=pedimento.id,
organizacion=organizacion
@@ -829,25 +809,30 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
print(f"✅ Encontrado documento existente: ID {doc.id}")
break
# Crear ContentFile
django_file = ContentFile(file_content, name=file_name)
if existing_document:
# Opcional: Eliminar el archivo físico anterior
try:
if existing_document.archivo and os.path.exists(existing_document.archivo.path):
os.remove(existing_document.archivo.path)
except (ValueError, OSError) as e:
print(f"No se pudo eliminar archivo físico anterior: {str(e)}")
# Eliminar archivo anterior si existe
if existing_document.archivo:
storage_service.delete_file(existing_document.archivo)
# Actualizar el documento existente
existing_document.archivo = django_file
existing_document.size = len(file_content)
existing_document.extension = extension
existing_document.updated_at = timezone.now() # Si tienes este campo
existing_document.save()
documents_created += 1
print(f"📄 Documento actualizado: {file_name}")
# Guardar nuevo archivo usando la ruta del archivo temporal
ruta = storage_service.save_document_from_path(
file_path=file_path,
file_name=file_name,
organizacion_id=organizacion.id,
pedimento_app=pedimento_app,
metadata={
'pedimento_id': str(pedimento.id),
'document_id': str(existing_document.id),
'source': 'bulk_create_update'
}
)
if ruta:
existing_document.archivo = ruta
existing_document.size = file_size
existing_document.extension = extension
existing_document.save()
documents_created += 1
else:
# Crear nuevo documento
@@ -856,16 +841,32 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
pedimento_id=pedimento.id,
document_type=document_type,
fuente_id=4,
archivo=django_file,
size=len(file_content),
size=file_size,
extension=extension
)
documents_created += 1
print(f"📄 Nuevo documento creado: {file_name}")
# Guardar archivo usando la ruta del archivo temporal
ruta = storage_service.save_document_from_path(
file_path=file_path,
file_name=file_name,
organizacion_id=organizacion.id,
pedimento_app=pedimento_app,
metadata={
'pedimento_id': str(pedimento.id),
'document_id': str(document.id),
'source': 'bulk_create'
}
)
if ruta:
document.archivo = ruta
document.save()
documents_created += 1
else:
document.delete()
except Exception as e:
print(f"❌ Error al procesar documento {file_name}: {str(e)}")
# Continuar con otros documentos
print(f"🏁 Procesamiento completado. Archivos procesados en este directorio.")
@@ -1359,8 +1360,7 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
# 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
# Leer el archivo desde el directorio temporal (solo para XML/nomenclatura especial)
with open(file_path, 'rb') as f:
file_content = f.read()
@@ -1372,54 +1372,66 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
# Patrón: 7 dígitos, punto, 3 dígitos (ej: M8988852.300)
patron_nomenclatura = re.compile(r'^[m|M]\d{7}\.\d{3}$', re.IGNORECASE)
# Separar nombre base y extensión
nombre_base, extension = os.path.splitext(file_name)
if patron_nomenclatura.match(file_name_lower):
tiene_nomenclatura_especial = True
# Procesar el archivo con el método auxiliar
info_extraida = procesar_archivo_m_con_nomenclatura(file_content, pedimento )
# Procesar el archivo con el método auxiliar
info_extraida = procesar_archivo_m_con_nomenclatura(file_content, pedimento)
if info_extraida.get('tiene_nomenclatura_especial', False):
# Agregar información de procesamiento a los datos de respuesta
if 'procesamiento_archivos' not in locals():
procesamiento_archivos = []
if 'procesamiento_archivos' not in locals():
procesamiento_archivos = []
procesamiento_archivos.append({
'archivo': file_name,
'nomenclatura_especial': True,
'registros_encontrados': info_extraida.get('registros_encontrados', []),
'actualizaciones': info_extraida.get('actualizaciones_aplicadas', [])
})
procesamiento_archivos.append({
'archivo': file_name,
'nomenclatura_especial': True,
'registros_encontrados': info_extraida.get('registros_encontrados', []),
'actualizaciones': info_extraida.get('actualizaciones_aplicadas', [])
})
# print(f"📄 Archivo leído: {len(file_content)} bytes")
# Crear ContentFile que Django puede manejar correctamente
django_file = ContentFile(file_content, name=file_name)
extension = os.path.splitext(file_name)[1].lower().lstrip('.')
file_size = os.path.getsize(file_path)
fuente, created = Fuente.objects.get_or_create(
nombre="APP-EFC",
descripcion='Transmitido por la app de escritorio'
fuente, created = Fuente.objects.get_or_create(
nombre="APP-EFC",
descripcion='Transmitido por la app de escritorio'
)
# 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,
fuente_id=fuente.id,
archivo=django_file,
size=len(file_content),
extension=os.path.splitext(file_name)[1].lower().lstrip('.')
size=file_size,
extension=extension
)
# print(f"Documento creado exitosamente: {document.id}")
documents_created += 1
# print(f"📊 Total documentos creados hasta ahora: {documents_created}")
ruta = storage_service.save_document_from_path(
file_path=file_path,
file_name=file_name,
organizacion_id=organizacion.id,
pedimento_app=pedimento_app,
metadata={
'pedimento_id': str(pedimento.id),
'document_id': str(document.id),
'source': 'efc_app_desk',
'tiene_nomenclatura_especial': str(tiene_nomenclatura_especial)
}
)
if ruta:
document.archivo = ruta
document.save()
documents_created += 1
else:
document.delete()
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": "Error al guardar archivo en storage"
})
continue
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,
@@ -1817,32 +1829,18 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
# Crear documento asociado al pedimento
try:
# Leer el archivo desde el directorio temporal
with open(file_path, 'rb') as f:
file_content = f.read()
extension = os.path.splitext(file_name)[1].lower().lstrip('.')
file_size = os.path.getsize(file_path)
# Verificar si el archivo tiene la nomenclatura especial M8988852.300
file_name_lower = file_name.lower()
tiene_nomenclatura_especial = False
info_extraida = {}
# Patrón: 7 dígitos, punto, 3 dígitos (ej: M8988852.300)
patron_nomenclatura = re.compile(r'^[m|M]\d{7}\.\d{3}$', re.IGNORECASE)
# Separar nombre base y extensión
nombre_base, extension = os.path.splitext(file_name)
if patron_nomenclatura.match(file_name_lower):
tiene_nomenclatura_especial = True
# Procesar el archivo con el método auxiliar
with open(file_path, 'rb') as f:
file_content = f.read()
info_extraida = procesar_archivo_m_con_nomenclatura(file_content, pedimento)
if info_extraida.get('tiene_nomenclatura_especial', False):
# Agregar información de procesamiento a los datos de respuesta
if 'procesamiento_archivos' not in locals():
procesamiento_archivos = []
procesamiento_archivos.append({
'archivo': file_name,
'nomenclatura_especial': True,
@@ -1850,20 +1848,15 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
'actualizaciones': info_extraida.get('actualizaciones_aplicadas', [])
})
# Crear ContentFile que Django puede manejar correctamente
django_file = ContentFile(file_content, name=file_name)
fuente, created = Fuente.objects.get_or_create(
fuente, _ = Fuente.objects.get_or_create(
nombre="APP-EFC",
descripcion='Transmitido por la app de escritorio'
)
# Buscar si ya existe un documento con el mismo nombre para este pedimento
existing_documents = Document.objects.filter(
pedimento_id=pedimento.id,
organizacion=organizacion
)
existing_document = None
for doc in existing_documents:
if is_same_document(doc, file_name):
@@ -1871,37 +1864,49 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
break
if existing_document:
# Opcional: Eliminar el archivo físico anterior
try:
if existing_document.archivo and os.path.exists(existing_document.archivo.path):
os.remove(existing_document.archivo.path)
except (ValueError, OSError) as e:
pass
# Actualizar el documento existente con el nuevo archivo y datos
existing_document.archivo = django_file
existing_document.size = len(file_content)
existing_document.extension = extension
existing_document.updated_at = timezone.now() # Si tienes este campo
existing_document.save()
documents_created += 1
if existing_document.archivo:
storage_service.delete_file(existing_document.archivo)
ruta = storage_service.save_document_from_path(
file_path=file_path,
file_name=file_name,
organizacion_id=organizacion.id,
pedimento_app=pedimento_app
)
if ruta:
existing_document.archivo = ruta
existing_document.size = file_size
existing_document.extension = extension
existing_document.save()
documents_created += 1
else:
# 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,
fuente_id=fuente.id,
archivo=django_file,
size=len(file_content),
extension=os.path.splitext(file_name)[1].lower().lstrip('.')
size=file_size,
extension=extension
)
documents_created += 1
ruta = storage_service.save_document_from_path(
file_path=file_path,
file_name=file_name,
organizacion_id=organizacion.id,
pedimento_app=pedimento_app
)
if ruta:
document.archivo = ruta
document.save()
documents_created += 1
else:
document.delete()
raise Exception("Error al guardar archivo")
except Exception as e:
archivo_original = folder_name + ('.zip' if any(f.endswith('.zip') for f in [a.name for a in archivos]) else '.rar')
archivo_original = folder_name + '.zip'
failed_records.append({
"file": relative_path,
"archivo_original": archivo_original,