fix: se agrega funcionalidad de poder seleccionar resgistros en la vistas de partidas, coves, pedimento, edoc. Tambien se habilito la funcionalidad de poder eliminar los registros seleccionados. #12

Merged
jcedilloAS merged 1 commits from T2025-10-152-001 into main 2026-01-07 22:09:30 +00:00

View File

@@ -23,6 +23,7 @@ from django.http import HttpResponse
from rest_framework.decorators import action from rest_framework.decorators import action
from datetime import timedelta from datetime import timedelta
from django.utils import timezone from django.utils import timezone
from django.db.models import Q
from core.permissions import ( from core.permissions import (
IsSameOrganization, IsSameOrganization,
@@ -461,6 +462,602 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
@action(detail=False, methods=['post'], url_path='bulk-delete-partidas-vu')
def bulk_delete_partidas_vu(self, request):
"""
Endpoint para eliminar múltiples archivos xlm de partidas de vu de manera masiva.
Payload esperado:
{
"ids": ["uuid1", "uuid2", "uuid3", ...]
}
Respuesta exitosa:
{
"message": "Documentos eliminados exitosamente",
"deleted_count": 3,
"deleted_ids": ["uuid1", "uuid2", "uuid3"],
"space_freed_mb": 25.6
}
Respuesta con errores:
{
"message": "Algunos documentos no pudieron ser eliminados",
"deleted_count": 2,
"deleted_ids": ["uuid1", "uuid2"],
"failed_ids": ["uuid3"],
"errors": ["No se encontró el documento con ID uuid3"],
"space_freed_mb": 15.2
}
"""
# Obtener los IDs del payload
ids_vu = request.data.get('ids', [])
if not ids_vu:
return Response(
{"error": "Se requiere una lista de IDs para eliminar"},
status=status.HTTP_400_BAD_REQUEST
)
if not isinstance(ids_vu, list):
return Response(
{"error": "El campo 'ids' debe ser una lista"},
status=status.HTTP_400_BAD_REQUEST
)
# Obtener el queryset filtrado por organización
queryset = self.get_queryset()
from ..customs.models import Partida
partidas = Partida.objects.filter(id__in=ids_vu)
if not partidas.exists():
return Response(
{"error": "No se encontraron Partidas"},
status=status.HTTP_404_NOT_FOUND
)
ids = []
for partida in partidas:
pedimento_partida = partida.pedimento
pedimento_app = pedimento_partida.pedimento_app
pedimento_id= pedimento_partida.id
numero_partida = partida.numero_partida
documents = Document.objects.filter(
archivo__startswith=f'documents/vu_PT_{pedimento_app}_{numero_partida}',
pedimento_id=pedimento_id
).values_list('id', flat=True) # <-- solo los IDs
if documents.exists():
# agregar los IDs a la lista
ids.extend(documents)
if len(ids) <= 0:
return Response(
{"error": "No se encontraron docuemntos para eliminar"},
status=status.HTTP_404_NOT_FOUND
)
# Filtrar solo los documentos que existen y pertenecen a la organización del usuario
existing_documents = queryset.filter(id__in=ids)
existing_ids = list(existing_documents.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
total_space_freed = 0
errors = []
if existing_documents.exists():
try:
# Usar transacción atómica para consistencia
with transaction.atomic():
# Calcular el espacio total a liberar
total_space_freed = sum(doc.size for doc in existing_documents)
# Obtener la organización del usuario para actualizar el uso de almacenamiento
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
# Si es superusuario, puede eliminar documentos de cualquier organización
if request.user.is_superuser:
# Para superusuario, actualizar el uso de cada organización afectada
organizaciones_afectadas = {}
for doc in existing_documents:
if doc.organizacion.id not in organizaciones_afectadas:
organizaciones_afectadas[doc.organizacion.id] = {
'organizacion': doc.organizacion,
'espacio_liberado': 0
}
organizaciones_afectadas[doc.organizacion.id]['espacio_liberado'] += doc.size
# Actualizar uso de almacenamiento para cada organización
for org_data in organizaciones_afectadas.values():
try:
uso = UsoAlmacenamiento.objects.select_for_update().get(
organizacion=org_data['organizacion']
)
uso.espacio_utilizado -= org_data['espacio_liberado']
uso.save()
except UsoAlmacenamiento.DoesNotExist:
# Si no existe el registro, no hay nada que actualizar
pass
else:
# Para usuarios normales, solo documentos de su organización
try:
uso = UsoAlmacenamiento.objects.select_for_update().get(
organizacion=organizacion
)
uso.espacio_utilizado -= total_space_freed
uso.save()
except UsoAlmacenamiento.DoesNotExist:
# Si no existe el registro, no hay nada que actualizar
pass
# Eliminar los documentos
for doc in existing_documents:
archivos_eliminados = 0
try:
# Eliminar archivo físico
if doc.archivo and doc.archivo.storage.exists(doc.archivo.name):
doc.archivo.delete(save=False) # save=False para no intentar guardar el modelo
# Eliminar registro de la base de datos
doc.delete()
archivos_eliminados += 1
except Exception as e:
errors.append(f"No se pudo eliminar el documento {doc.id}: {str(e)}")
failed_ids.append(str(doc.id))
# deleted_count = existing_documents.count()
deleted_count = archivos_eliminados
# existing_documents.delete()
except Exception as e:
return Response(
{"error": f"Error al eliminar documentos: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
# Agregar errores para IDs no encontrados
if failed_ids:
errors = [f"No se encontró el documento con ID {id} o no pertenece a su organización" for id in failed_ids]
# Convertir bytes a MB para la respuesta
space_freed_mb = round(total_space_freed / (1024 * 1024), 2)
# Preparar respuesta
response_data = {
"deleted_count": deleted_count,
"deleted_ids": existing_ids_str,
"space_freed_mb": space_freed_mb
}
if failed_ids:
response_data.update({
"message": "Algunos documentos no pudieron ser eliminados",
"failed_ids": failed_ids,
"errors": errors
})
response_status = status.HTTP_207_MULTI_STATUS
else:
response_data["message"] = "Documentos eliminados exitosamente"
response_status = status.HTTP_200_OK
return Response(response_data, status=response_status)
@action(detail=False, methods=['post'], url_path='bulk-delete-coves-vu')
def bulk_delete_coves_vu(self, request):
"""
Endpoint para eliminar múltiples archivos xlm de coves de vu de manera masiva.
Payload esperado:
{
"ids": ["uuid1", "uuid2", "uuid3", ...]
}
Respuesta exitosa:
{
"message": "Documentos eliminados exitosamente",
"deleted_count": 3,
"deleted_ids": ["uuid1", "uuid2", "uuid3"],
"space_freed_mb": 25.6
}
Respuesta con errores:
{
"message": "Algunos documentos no pudieron ser eliminados",
"deleted_count": 2,
"deleted_ids": ["uuid1", "uuid2"],
"failed_ids": ["uuid3"],
"errors": ["No se encontró el documento con ID uuid3"],
"space_freed_mb": 15.2
}
"""
# Obtener los IDs del payload
ids_vu = request.data.get('ids', [])
if not ids_vu:
return Response(
{"error": "Se requiere una lista de IDs para eliminar"},
status=status.HTTP_400_BAD_REQUEST
)
if not isinstance(ids_vu, list):
return Response(
{"error": "El campo 'ids' debe ser una lista"},
status=status.HTTP_400_BAD_REQUEST
)
# Obtener el queryset filtrado por organización
queryset = self.get_queryset()
from ..customs.models import Cove
coves = Cove.objects.filter(id__in=ids_vu)
if not coves.exists():
return Response(
{"error": "No se encontraron COVEs"},
status=status.HTTP_404_NOT_FOUND
)
ids = []
for cove in coves:
pedimento_cove = cove.pedimento
pedimento_app = pedimento_cove.pedimento_app
pedimento_id=pedimento_cove.id
numero_cove = cove.numero_cove
documents = Document.objects.filter(
Q(archivo__startswith=f'documents/vu_COVE_{pedimento_app}_{numero_cove}') |
Q(archivo__startswith=f'documents/vu_AC_COVE_{pedimento_app}_{numero_cove}'),
pedimento_id=pedimento_id
).values_list('id', flat=True) # <-- solo los IDs
if documents.exists():
# agregar los IDs a la lista
ids.extend(documents)
if len(ids) <= 0:
return Response(
{"error": "No se encontraron docuemntos para eliminar"},
status=status.HTTP_404_NOT_FOUND
)
# Filtrar solo los documentos que existen y pertenecen a la organización del usuario
existing_documents = queryset.filter(id__in=ids)
existing_ids = list(existing_documents.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
total_space_freed = 0
errors = []
if existing_documents.exists():
try:
# Usar transacción atómica para consistencia
with transaction.atomic():
# Calcular el espacio total a liberar
total_space_freed = sum(doc.size for doc in existing_documents)
# Obtener la organización del usuario para actualizar el uso de almacenamiento
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
# Si es superusuario, puede eliminar documentos de cualquier organización
if request.user.is_superuser:
# Para superusuario, actualizar el uso de cada organización afectada
organizaciones_afectadas = {}
for doc in existing_documents:
if doc.organizacion.id not in organizaciones_afectadas:
organizaciones_afectadas[doc.organizacion.id] = {
'organizacion': doc.organizacion,
'espacio_liberado': 0
}
organizaciones_afectadas[doc.organizacion.id]['espacio_liberado'] += doc.size
# Actualizar uso de almacenamiento para cada organización
for org_data in organizaciones_afectadas.values():
try:
uso = UsoAlmacenamiento.objects.select_for_update().get(
organizacion=org_data['organizacion']
)
uso.espacio_utilizado -= org_data['espacio_liberado']
uso.save()
except UsoAlmacenamiento.DoesNotExist:
# Si no existe el registro, no hay nada que actualizar
pass
else:
# Para usuarios normales, solo documentos de su organización
try:
uso = UsoAlmacenamiento.objects.select_for_update().get(
organizacion=organizacion
)
uso.espacio_utilizado -= total_space_freed
uso.save()
except UsoAlmacenamiento.DoesNotExist:
# Si no existe el registro, no hay nada que actualizar
pass
# Eliminar los documentos
for doc in existing_documents:
archivos_eliminados = 0
try:
# Eliminar archivo físico
if doc.archivo and doc.archivo.storage.exists(doc.archivo.name):
doc.archivo.delete(save=False) # save=False para no intentar guardar el modelo
# Eliminar registro de la base de datos
doc.delete()
archivos_eliminados += 1
except Exception as e:
errors.append(f"No se pudo eliminar el documento {doc.id}: {str(e)}")
failed_ids.append(str(doc.id))
# deleted_count = existing_documents.count()
deleted_count = archivos_eliminados
# existing_documents.delete()
except Exception as e:
return Response(
{"error": f"Error al eliminar documentos: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
# Agregar errores para IDs no encontrados
if failed_ids:
errors = [f"No se encontró el documento con ID {id} o no pertenece a su organización" for id in failed_ids]
# Convertir bytes a MB para la respuesta
space_freed_mb = round(total_space_freed / (1024 * 1024), 2)
# Preparar respuesta
response_data = {
"deleted_count": deleted_count,
"deleted_ids": existing_ids_str,
"space_freed_mb": space_freed_mb
}
if failed_ids:
response_data.update({
"message": "Algunos documentos no pudieron ser eliminados",
"failed_ids": failed_ids,
"errors": errors
})
response_status = status.HTTP_207_MULTI_STATUS
else:
response_data["message"] = "Documentos eliminados exitosamente"
response_status = status.HTTP_200_OK
return Response(response_data, status=response_status)
@action(detail=False, methods=['post'], url_path='bulk-delete-edocs-vu')
def bulk_delete_edocs_vu(self, request):
"""
Endpoint para eliminar múltiples archivos xlm de edocs de vu de manera masiva.
Payload esperado:
{
"ids": ["uuid1", "uuid2", "uuid3", ...]
}
Respuesta exitosa:
{
"message": "Documentos eliminados exitosamente",
"deleted_count": 3,
"deleted_ids": ["uuid1", "uuid2", "uuid3"],
"space_freed_mb": 25.6
}
Respuesta con errores:
{
"message": "Algunos documentos no pudieron ser eliminados",
"deleted_count": 2,
"deleted_ids": ["uuid1", "uuid2"],
"failed_ids": ["uuid3"],
"errors": ["No se encontró el documento con ID uuid3"],
"space_freed_mb": 15.2
}
"""
# Obtener los IDs del payload
ids_vu = request.data.get('ids', [])
if not ids_vu:
return Response(
{"error": "Se requiere una lista de IDs para eliminar"},
status=status.HTTP_400_BAD_REQUEST
)
if not isinstance(ids_vu, list):
return Response(
{"error": "El campo 'ids' debe ser una lista"},
status=status.HTTP_400_BAD_REQUEST
)
# Obtener el queryset filtrado por organización
queryset = self.get_queryset()
from ..customs.models import EDocument
edocs = EDocument.objects.filter(id__in=ids_vu)
if not edocs.exists():
return Response(
{"error": "No se encontraron COVEs"},
status=status.HTTP_404_NOT_FOUND
)
ids = []
for edoc in edocs:
pedimento_edoc = edoc.pedimento
pedimento_id = pedimento_edoc.id
pedimento_app = pedimento_edoc.pedimento_app
numero_edocument = edoc.numero_edocument
documents = Document.objects.filter(
Q(archivo__startswith=f'documents/vu_ED_{pedimento_app}_{numero_edocument}') |
Q(archivo__startswith=f'documents/vu_AC_{pedimento_app}_{numero_edocument}'),
pedimento_id=pedimento_id
).values_list('id', flat=True) # <-- solo los IDs
if documents.exists():
# agregar los IDs a la lista
ids.extend(documents)
if len(ids) <= 0:
return Response(
{"error": "No se encontraron docuemntos para eliminar"},
status=status.HTTP_404_NOT_FOUND
)
# Filtrar solo los documentos que existen y pertenecen a la organización del usuario
existing_documents = queryset.filter(id__in=ids)
existing_ids = list(existing_documents.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
total_space_freed = 0
errors = []
if existing_documents.exists():
try:
# Usar transacción atómica para consistencia
with transaction.atomic():
# Calcular el espacio total a liberar
total_space_freed = sum(doc.size for doc in existing_documents)
# Obtener la organización del usuario para actualizar el uso de almacenamiento
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
# Si es superusuario, puede eliminar documentos de cualquier organización
if request.user.is_superuser:
# Para superusuario, actualizar el uso de cada organización afectada
organizaciones_afectadas = {}
for doc in existing_documents:
if doc.organizacion.id not in organizaciones_afectadas:
organizaciones_afectadas[doc.organizacion.id] = {
'organizacion': doc.organizacion,
'espacio_liberado': 0
}
organizaciones_afectadas[doc.organizacion.id]['espacio_liberado'] += doc.size
# Actualizar uso de almacenamiento para cada organización
for org_data in organizaciones_afectadas.values():
try:
uso = UsoAlmacenamiento.objects.select_for_update().get(
organizacion=org_data['organizacion']
)
uso.espacio_utilizado -= org_data['espacio_liberado']
uso.save()
except UsoAlmacenamiento.DoesNotExist:
# Si no existe el registro, no hay nada que actualizar
pass
else:
# Para usuarios normales, solo documentos de su organización
try:
uso = UsoAlmacenamiento.objects.select_for_update().get(
organizacion=organizacion
)
uso.espacio_utilizado -= total_space_freed
uso.save()
except UsoAlmacenamiento.DoesNotExist:
# Si no existe el registro, no hay nada que actualizar
pass
# Eliminar los documentos
archivos_eliminados = 0
for doc in existing_documents:
try:
# Eliminar archivo físico
if doc.archivo and doc.archivo.storage.exists(doc.archivo.name):
doc.archivo.delete(save=False) # save=False para no intentar guardar el modelo
# Eliminar registro de la base de datos
doc.delete()
archivos_eliminados += 1
except Exception as e:
errors.append(f"No se pudo eliminar el documento {doc.id}: {str(e)}")
failed_ids.append(str(doc.id))
# deleted_count = existing_documents.count()
deleted_count = archivos_eliminados
# existing_documents.delete()
except Exception as e:
return Response(
{"error": f"Error al eliminar documentos: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
# Agregar errores para IDs no encontrados
if failed_ids:
errors = [f"No se encontró el documento con ID {id} o no pertenece a su organización" for id in failed_ids]
# Convertir bytes a MB para la respuesta
space_freed_mb = round(total_space_freed / (1024 * 1024), 2)
# Preparar respuesta
response_data = {
"deleted_count": deleted_count,
"deleted_ids": existing_ids_str,
"space_freed_mb": space_freed_mb
}
if failed_ids:
response_data.update({
"message": "Algunos documentos no pudieron ser eliminados",
"failed_ids": failed_ids,
"errors": errors
})
response_status = status.HTTP_207_MULTI_STATUS
else:
response_data["message"] = "Documentos eliminados exitosamente"
response_status = status.HTTP_200_OK
return Response(response_data, status=response_status)
@action(detail=False, methods=['post'], url_path='bulk-upload', parser_classes=[MultiPartParser]) @action(detail=False, methods=['post'], url_path='bulk-upload', parser_classes=[MultiPartParser])
def bulk_upload(self, request): def bulk_upload(self, request):
""" """
@@ -666,6 +1263,295 @@ class DocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
@action(detail=False, methods=['post'], url_path='bulk-upload-vu', parser_classes=[MultiPartParser])
def bulk_upload_vu(self, request):
"""
Endpoint para subir múltiples documentos a un pedimento específico.
FormData esperado:
- pedimento_id: UUID del pedimento (requerido)
- files: Lista de archivos a subir (requerido)
Nota: Se usa automáticamente el tipo de documento "Documento General"
Respuesta exitosa:
{
"message": "Documentos subidos exitosamente",
"uploaded_count": 5,
"uploaded_documents": [
{
"id": "uuid1",
"filename": "documento1.pdf",
"size": 1024000,
"extension": "pdf"
},
...
],
"space_used_mb": 25.6,
"failed_files": [],
"errors": []
}
Respuesta con errores:
{
"message": "Algunos documentos no pudieron ser subidos",
"uploaded_count": 3,
"uploaded_documents": [...],
"space_used_mb": 15.2,
"failed_files": ["archivo4.pdf", "archivo5.doc"],
"errors": ["Archivo demasiado grande: archivo4.pdf", "Tipo de archivo no soportado: archivo5.doc"]
}
"""
# Validar datos requeridos
pedimento_id = request.data.get('pedimento_id')
if not pedimento_id:
return Response(
{"error": "Se requiere el campo 'pedimento_id'"},
status=status.HTTP_400_BAD_REQUEST
)
tab_seccion = request.data.get('tab_seccion')
if not tab_seccion:
return Response(
{"error": "Se requiere el campo 'tab_seccion'"},
status=status.HTTP_400_BAD_REQUEST
)
numero_documento = request.data.get('numero')
if not numero_documento:
return Response(
{"error": "Se requiere el campo 'numero'"},
status=status.HTTP_400_BAD_REQUEST
)
files = request.FILES.getlist('files')
if not files:
return Response(
{"error": "Se requiere al menos un archivo para subir"},
status=status.HTTP_400_BAD_REQUEST
)
# Validar usuario autenticado
if not request.user.is_authenticated:
return Response(
{"error": "Usuario no autenticado"},
status=status.HTTP_401_UNAUTHORIZED
)
# Obtener el pedimento primero para usar su organización
from api.customs.models import Pedimento
try:
pedimento = Pedimento.objects.get(id=pedimento_id)
except Pedimento.DoesNotExist:
return Response(
{"error": "Pedimento no encontrado"},
status=status.HTTP_404_NOT_FOUND
)
# Usar la organización del pedimento
organizacion = pedimento.organizacion
# Validar que el usuario tenga permisos para esta organización
if not request.user.is_superuser:
if not hasattr(request.user, 'organizacion') or request.user.organizacion != organizacion:
return Response(
{"error": "No tienes permisos para subir documentos a este pedimento"},
status=status.HTTP_403_FORBIDDEN
)
uploaded_documents = []
failed_files = []
errors = []
total_space_used = 0
try:
with transaction.atomic():
# Obtener uso de almacenamiento
uso = UsoAlmacenamiento.objects.select_for_update().get_or_create(
organizacion=organizacion,
defaults={'espacio_utilizado': 0}
)[0]
# Calcular límites
max_almacenamiento_bytes = organizacion.licencia.almacenamiento * 1024 ** 3
espacio_inicial = uso.espacio_utilizado
# Calcular el tamaño total de todos los archivos
total_files_size = sum(file.size for file in files)
nuevo_espacio_total = espacio_inicial + total_files_size
# Validar que hay espacio suficiente para todos los archivos
if nuevo_espacio_total > max_almacenamiento_bytes:
espacio_faltante = nuevo_espacio_total - max_almacenamiento_bytes
return Response({
"error": "Espacio de almacenamiento insuficiente para todos los archivos",
"detalle": {
"espacio_faltante_gb": round(espacio_faltante / (1024 ** 3), 2),
"espacio_utilizado_gb": round(espacio_inicial / (1024 ** 3), 2),
"limite_gb": organizacion.licencia.almacenamiento,
"archivos_gb": round(total_files_size / (1024 ** 3), 4),
"total_archivos": len(files)
},
"codigo": "bulk_storage_limit_exceeded"
}, status=status.HTTP_400_BAD_REQUEST)
# Procesar cada archivo
espacio_usado_temp = espacio_inicial
for file in files:
try:
nuevo_nombre = file.name
# Validaciones por archivo
if not file.name:
failed_files.append("archivo_sin_nombre")
errors.append("Archivo sin nombre detectado")
continue
# secciones = file.name.split('.')[-1].lower() if '.' in file.name else ''
filename = file.name
if '.' in filename:
base = '.'.join(filename.split('.')[:-1]) # todo excepto la última parte
secciones = filename.split('.')[-1] # la última “extensión” / flag
else:
base = filename
secciones = ""
file.name = base
# Obtener extensión del archivo
extension = file.name.split('.')[-1].lower() if '.' in file.name else ''
if tab_seccion == 'partida':
# Construir nombre nuevo
nuevo_nombre = f"vu_PT_{pedimento.pedimento_app}_{numero_documento}.{extension}"
# Usar tipo de documento por defecto siempre
document_type, created = DocumentType.objects.get_or_create(
nombre="Pedimento Partida",
defaults={'descripcion': "Tag para saber que el archivo guarda una partida"}
)
elif tab_seccion == 'cove':
if secciones == 'general':
nuevo_nombre = f"vu_COVE_{pedimento.pedimento_app}_{numero_documento}.{extension}"
# Usar tipo de documento por defecto siempre
document_type, created = DocumentType.objects.get_or_create(
nombre="Cove",
defaults={'descripcion': "Tag para saber que el archivo guarda un cove"}
)
elif secciones == 'acuse':
nuevo_nombre = f"vu_AC_COVE_{pedimento.pedimento_app}_{numero_documento}.{extension}"
# Usar tipo de documento por defecto siempre
document_type, created = DocumentType.objects.get_or_create(
nombre="Acuse Cove",
defaults={'descripcion': "Tag para saber que el archivo guarda un acuse de cove"}
)
else:
# Usar tipo de documento por defecto siempre
document_type, created = DocumentType.objects.get_or_create(
nombre="Documento General",
defaults={'descripcion': "Documento general sin tipo específico"}
)
elif tab_seccion == 'edoc':
if secciones == 'general':
nuevo_nombre = f"vu_ED_{pedimento.pedimento_app}_{numero_documento}.{extension}"
# Usar tipo de documento por defecto siempre
document_type, created = DocumentType.objects.get_or_create(
nombre="Pedimento EDocument",
defaults={'descripcion': "Tag para saber que el documento es un EDocument"}
)
elif secciones == 'acuse':
nuevo_nombre = f"vu_AC_{pedimento.pedimento_app}_{numero_documento}.{extension}"
# Usar tipo de documento por defecto siempre
document_type, created = DocumentType.objects.get_or_create(
nombre="Pedimento Acuse",
defaults={'descripcion': "Tag para saber que el documento es un Acuse"}
)
else:
# Usar tipo de documento por defecto siempre
document_type, created = DocumentType.objects.get_or_create(
nombre="Documento General",
defaults={'descripcion': "Documento general sin tipo específico"}
)
else:
failed_files.append("archivo_sin_seccion")
errors.append("Archivo sin seccion")
continue
# Renombrar archivo
file.name = nuevo_nombre
# Crear el documento
document = Document.objects.create(
organizacion=organizacion,
pedimento_id=pedimento_id,
document_type=document_type,
archivo=file,
size=file.size,
fuente_id=7,
extension=extension
)
# Actualizar espacio usado
espacio_usado_temp += file.size
total_space_used += file.size
uploaded_documents.append({
"id": str(document.id),
"filename": file.name,
"size": file.size,
"extension": extension,
"document_type": document_type.nombre
})
except Exception as e:
failed_files.append(file.name)
errors.append(f"Error al procesar {file.name}: {str(e)}")
continue
# Actualizar el uso de almacenamiento final
uso.espacio_utilizado = espacio_usado_temp
uso.save()
except Exception as e:
return Response(
{"error": f"Error durante el procesamiento masivo: {str(e)}"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
# Convertir bytes a MB para la respuesta
space_used_mb = round(total_space_used / (1024 * 1024), 2)
# Preparar respuesta
response_data = {
"uploaded_count": len(uploaded_documents),
"uploaded_documents": uploaded_documents,
"space_used_mb": space_used_mb,
"pedimento_id": str(pedimento_id),
"document_type": document_type.nombre
}
if failed_files:
response_data.update({
"message": "Algunos documentos no pudieron ser subidos",
"failed_files": failed_files,
"errors": errors
})
response_status = status.HTTP_207_MULTI_STATUS
else:
response_data["message"] = "Documentos subidos exitosamente"
response_status = status.HTTP_201_CREATED
return Response(response_data, status=response_status)
class ProtectedDocumentDownloadView(APIView, DocumentosFiltradosMixin): class ProtectedDocumentDownloadView(APIView, DocumentosFiltradosMixin):
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)] permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
serializer_class = DocumentSerializer serializer_class = DocumentSerializer
@@ -925,15 +1811,36 @@ class PedimentoDocumentViewSet(viewsets.ModelViewSet, DocumentosFiltradosMixin):
def get_queryset(self): def get_queryset(self):
queryset = self.get_queryset_filtrado_por_organizacion() queryset = self.get_queryset_filtrado_por_organizacion()
pedimento_id = self.request.query_params.get('pedimento')
# Obtener el pedimento primero para usar su organización
from api.customs.models import Pedimento
try:
pedimento = Pedimento.objects.get(id=pedimento_id)
except Pedimento.DoesNotExist:
return Response(
{"error": "Pedimento no encontrado"},
status=status.HTTP_404_NOT_FOUND
)
# Tipos de documento permitidos (fijos en código, Pedimento completo y remesas) # Tipos de documento permitidos (fijos en código, Pedimento completo y remesas)
TIPOS_PERMITIDOS = ['2', '3'] # <-- Ajusta aquí tus tipos TIPOS_PERMITIDOS = ['2', '3'] # <-- Ajusta aquí tus tipos
tipo_documento = self.request.query_params.get('document_type') tipo_documento = self.request.query_params.get('document_type')
if tipo_documento: if tipo_documento:
queryset = queryset.filter(document_type_id=tipo_documento) if tipo_documento == '2':
queryset = queryset.filter(archivo__startswith=f'documents/vu_PC_{pedimento.pedimento_app}.xml')
elif tipo_documento == '3':
queryset = queryset.filter(archivo__startswith=f'documents/vu_RM_{pedimento.pedimento_app}.xml')
else:
queryset = queryset.filter(archivo__startswith=f'documents/NOTFOUND_{pedimento.pedimento_app}.xml')
else: else:
# Filtrar por tipos permitidos # Filtrar por tipos permitidos
queryset = queryset.filter(document_type_id__in=TIPOS_PERMITIDOS) # queryset = queryset.filter(document_type_id__in=TIPOS_PERMITIDOS)
queryset = queryset.filter(
Q(archivo__startswith=f'documents/vu_PC_{pedimento.pedimento_app}.xml') |
Q(archivo__startswith=f'documents/vu_RM_{pedimento.pedimento_app}.xml')
)
buscar_archivo = self.request.query_params.get('archivo__icontains') buscar_archivo = self.request.query_params.get('archivo__icontains')
if buscar_archivo: if buscar_archivo: