Se agregaron partidas y modelos

This commit is contained in:
2025-10-02 21:58:44 -06:00
parent 76909d0618
commit 466e12e623
30 changed files with 1090 additions and 8 deletions

View File

View File

@@ -0,0 +1,243 @@
import os
import gc
from django.core.management.base import BaseCommand
from django.conf import settings
from django.apps import apps
from django.db import models
from django.db import connection
class Command(BaseCommand):
help = 'Elimina archivos de media que no están referenciados en la base de datos'
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help='Muestra qué archivos se eliminarían sin eliminarlos',
)
parser.add_argument(
'--verbose',
action='store_true',
help='Muestra información detallada del proceso',
)
parser.add_argument(
'--batch-size',
type=int,
default=10000,
help='Tamaño del lote para procesar archivos (default: 10000)',
)
parser.add_argument(
'--limit',
type=int,
help='Limitar el número de archivos huérfanos a procesar',
)
def handle(self, *args, **options):
media_root = settings.MEDIA_ROOT
if not os.path.exists(media_root):
self.stdout.write(
self.style.ERROR(f'El directorio MEDIA_ROOT no existe: {media_root}')
)
return
if options['verbose']:
self.stdout.write(f'Analizando archivos en: {media_root}')
# Obtener archivos de BD de forma más eficiente
files_in_db = self._get_db_files_optimized(options)
if options['verbose']:
self.stdout.write(f'Archivos referenciados en BD: {len(files_in_db)}')
# Procesar archivos en lotes para evitar problemas de memoria
orphaned_files = self._find_orphaned_files_batch(media_root, files_in_db, options)
if not orphaned_files:
self.stdout.write(
self.style.SUCCESS('No se encontraron archivos huérfanos')
)
return
# Aplicar límite si se especifica
if options['limit'] and len(orphaned_files) > options['limit']:
orphaned_files = list(orphaned_files)[:options['limit']]
self.stdout.write(f'Limitando a {options["limit"]} archivos')
# Calcula el tamaño total de archivos huérfanos
total_size = self._calculate_total_size(orphaned_files, options)
size_mb = total_size / (1024 * 1024)
self.stdout.write(
f'Archivos huérfanos encontrados: {len(orphaned_files)} '
f'({size_mb:.2f} MB)'
)
if options['dry_run']:
self.stdout.write('\n--- MODO PRUEBA: Los siguientes archivos se eliminarían ---')
for i, file_path in enumerate(sorted(orphaned_files)):
if i >= 50: # Limitar salida en dry-run
remaining = len(orphaned_files) - 50
self.stdout.write(f' ... y {remaining} archivos más')
break
relative_path = os.path.relpath(file_path, media_root)
self.stdout.write(f' - {relative_path}')
else:
# Pide confirmación antes de eliminar
confirm = input(
f'\n¿Estás seguro de que quieres eliminar {len(orphaned_files)} archivos? (s/N): '
)
if confirm.lower() not in ['s', 'si', '', 'y', 'yes']:
self.stdout.write('Operación cancelada')
return
self._delete_files_batch(orphaned_files, media_root, options)
def _get_db_files_optimized(self, options):
"""Obtiene archivos de BD de forma optimizada"""
files_in_db = set()
media_root = settings.MEDIA_ROOT
for model in apps.get_models():
file_fields = [
field for field in model._meta.get_fields()
if isinstance(field, (models.FileField, models.ImageField))
]
if not file_fields:
continue
if options['verbose']:
self.stdout.write(f'Procesando modelo {model.__name__}...')
# Procesar en lotes para evitar cargar todo en memoria
batch_size = options['batch_size']
offset = 0
while True:
field_names = [field.name for field in file_fields]
queryset = model.objects.values_list(*field_names)[offset:offset + batch_size]
batch = list(queryset)
if not batch:
break
for row in batch:
for file_path in row:
if file_path:
full_path = os.path.join(media_root, str(file_path))
files_in_db.add(full_path)
offset += batch_size
if options['verbose'] and offset % (batch_size * 10) == 0:
self.stdout.write(f' Procesados {offset} registros...')
# Forzar liberación de memoria
gc.collect()
return files_in_db
def _find_orphaned_files_batch(self, media_root, files_in_db, options):
"""Encuentra archivos huérfanos procesando en lotes"""
orphaned_files = []
processed_count = 0
self.stdout.write('Buscando archivos huérfanos...')
for root, dirs, files in os.walk(media_root):
for file in files:
if file.startswith('.'):
continue
full_path = os.path.join(root, file)
if full_path not in files_in_db:
orphaned_files.append(full_path)
processed_count += 1
if options['verbose'] and processed_count % 50000 == 0:
self.stdout.write(f'Procesados {processed_count} archivos, encontrados {len(orphaned_files)} huérfanos...')
# Liberar memoria periódicamente
if processed_count % 100000 == 0:
gc.collect()
return orphaned_files
def _calculate_total_size(self, orphaned_files, options):
"""Calcula el tamaño total de archivos huérfanos"""
total_size = 0
count = 0
for file_path in orphaned_files:
try:
total_size += os.path.getsize(file_path)
count += 1
if options['verbose'] and count % 10000 == 0:
self.stdout.write(f'Calculando tamaño... {count}/{len(orphaned_files)}')
except OSError:
pass
return total_size
def _delete_files_batch(self, orphaned_files, media_root, options):
"""Elimina archivos en lotes"""
deleted_count = 0
deleted_size = 0
batch_size = 1000
total_files = len(orphaned_files)
for i in range(0, total_files, batch_size):
batch = orphaned_files[i:i + batch_size]
for file_path in batch:
try:
file_size = os.path.getsize(file_path)
os.remove(file_path)
deleted_count += 1
deleted_size += file_size
if options['verbose'] and deleted_count % 5000 == 0:
relative_path = os.path.relpath(file_path, media_root)
self.stdout.write(f'Progreso: {deleted_count}/{total_files} - Eliminado: {relative_path}')
except OSError as e:
relative_path = os.path.relpath(file_path, media_root)
self.stdout.write(
self.style.ERROR(f'Error eliminando {relative_path}: {e}')
)
# Mostrar progreso por lotes
progress = (i + batch_size) / total_files * 100
self.stdout.write(f'Progreso: {min(progress, 100):.1f}% ({deleted_count} eliminados)')
# Forzar liberación de memoria
gc.collect()
# Elimina directorios vacíos
self._remove_empty_dirs(media_root)
deleted_mb = deleted_size / (1024 * 1024)
self.stdout.write(
self.style.SUCCESS(
f'Eliminados {deleted_count} archivos huérfanos ({deleted_mb:.2f} MB)'
)
)
def _remove_empty_dirs(self, path):
"""Elimina directorios vacíos recursivamente"""
for root, dirs, files in os.walk(path, topdown=False):
for dir_name in dirs:
dir_path = os.path.join(root, dir_name)
try:
if not os.listdir(dir_path):
os.rmdir(dir_path)
except OSError:
pass

View File

@@ -0,0 +1,284 @@
import os
import gc
import time
from collections import defaultdict
from django.core.management.base import BaseCommand
from django.conf import settings
from django.apps import apps
from django.db import models, connection
class Command(BaseCommand):
help = 'Elimina archivos de media que no están referenciados en la base de datos (versión optimizada)'
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help='Muestra qué archivos se eliminarían sin eliminarlos',
)
parser.add_argument(
'--verbose',
action='store_true',
help='Muestra información detallada del proceso',
)
parser.add_argument(
'--limit',
type=int,
help='Limitar el número de archivos huérfanos a procesar',
)
parser.add_argument(
'--sample-size',
type=int,
default=10000,
help='Tamaño de muestra para análisis rápido (default: 10000)',
)
parser.add_argument(
'--quick-scan',
action='store_true',
help='Hacer un escaneo rápido con muestra limitada',
)
def handle(self, *args, **options):
media_root = settings.MEDIA_ROOT
if not os.path.exists(media_root):
self.stdout.write(
self.style.ERROR(f'El directorio MEDIA_ROOT no existe: {media_root}')
)
return
start_time = time.time()
self.stdout.write(f'Iniciando análisis en: {media_root}')
if options['quick_scan']:
self.stdout.write(f'Modo escaneo rápido - muestra de {options["sample_size"]} archivos')
orphaned_files = self._quick_scan(media_root, options)
else:
# Obtener archivos de BD usando SQL directo
files_in_db = self._get_db_files_sql(options)
if options['verbose']:
self.stdout.write(f'Archivos referenciados en BD: {len(files_in_db)}')
# Encontrar archivos huérfanos
orphaned_files = self._find_orphaned_files(media_root, files_in_db, options)
if not orphaned_files:
self.stdout.write(
self.style.SUCCESS('No se encontraron archivos huérfanos')
)
return
# Aplicar límite si se especifica
if options['limit'] and len(orphaned_files) > options['limit']:
orphaned_files = list(orphaned_files)[:options['limit']]
self.stdout.write(f'Limitando a {options["limit"]} archivos')
# Calcula el tamaño total
total_size = self._calculate_size_sample(orphaned_files, options)
size_mb = total_size / (1024 * 1024)
elapsed_time = time.time() - start_time
self.stdout.write(
f'Archivos huérfanos encontrados: {len(orphaned_files)} '
f'({size_mb:.2f} MB) - Tiempo: {elapsed_time:.2f}s'
)
if options['dry_run']:
self._show_dry_run_results(orphaned_files, media_root)
else:
self._delete_files(orphaned_files, media_root, options)
def _get_db_files_sql(self, options):
"""Obtiene archivos usando consultas SQL directas"""
files_in_db = set()
media_root = settings.MEDIA_ROOT
# Mapear modelos a sus campos de archivo
file_field_map = {}
for model in apps.get_models():
file_fields = [
field for field in model._meta.get_fields()
if isinstance(field, (models.FileField, models.ImageField))
]
if file_fields:
file_field_map[model] = file_fields
if options['verbose']:
self.stdout.write(f'Encontrados {len(file_field_map)} modelos con campos de archivo')
with connection.cursor() as cursor:
for model, fields in file_field_map.items():
if options['verbose']:
self.stdout.write(f'Procesando {model.__name__}...')
table_name = model._meta.db_table
field_names = [field.column for field in fields]
# Construir query SQL
field_selects = ', '.join(field_names)
where_conditions = []
for field in field_names:
where_conditions.append(f'{field} IS NOT NULL AND {field} != \'\'')
where_clause = ' OR '.join(where_conditions)
query = f"""
SELECT {field_selects}
FROM {table_name}
WHERE {where_clause}
"""
cursor.execute(query)
rows = cursor.fetchall()
for row in rows:
for file_path in row:
if file_path:
full_path = os.path.join(media_root, str(file_path))
files_in_db.add(full_path)
if options['verbose']:
self.stdout.write(f' {model.__name__}: {len(rows)} registros procesados')
return files_in_db
def _quick_scan(self, media_root, options):
"""Escaneo rápido con muestra limitada"""
sample_files = []
count = 0
target_size = options['sample_size']
self.stdout.write('Obteniendo muestra de archivos...')
for root, dirs, files in os.walk(media_root):
for file in files:
if file.startswith('.'):
continue
sample_files.append(os.path.join(root, file))
count += 1
if count >= target_size:
break
if count >= target_size:
break
self.stdout.write(f'Muestra obtenida: {len(sample_files)} archivos')
# Obtener archivos de BD para comparar
files_in_db = self._get_db_files_sql(options)
# Encontrar huérfanos en la muestra
orphaned_files = [f for f in sample_files if f not in files_in_db]
return orphaned_files
def _find_orphaned_files(self, media_root, files_in_db, options):
"""Encuentra archivos huérfanos optimizado"""
orphaned_files = []
processed_count = 0
self.stdout.write('Buscando archivos huérfanos...')
for root, dirs, files in os.walk(media_root):
for file in files:
if file.startswith('.'):
continue
full_path = os.path.join(root, file)
if full_path not in files_in_db:
orphaned_files.append(full_path)
processed_count += 1
if options['verbose'] and processed_count % 100000 == 0:
self.stdout.write(f'Procesados {processed_count} archivos, encontrados {len(orphaned_files)} huérfanos...')
# Liberar memoria cada 500k archivos
if processed_count % 500000 == 0:
gc.collect()
return orphaned_files
def _calculate_size_sample(self, orphaned_files, options):
"""Calcula tamaño usando muestra si hay muchos archivos"""
if len(orphaned_files) <= 1000:
# Si hay pocos archivos, calcular tamaño exacto
total_size = 0
for file_path in orphaned_files:
try:
total_size += os.path.getsize(file_path)
except OSError:
pass
return total_size
else:
# Si hay muchos archivos, usar muestra para estimar
sample_size = min(1000, len(orphaned_files))
sample_files = orphaned_files[:sample_size]
sample_total = 0
for file_path in sample_files:
try:
sample_total += os.path.getsize(file_path)
except OSError:
pass
# Extrapolar al total
avg_size = sample_total / sample_size if sample_size > 0 else 0
estimated_total = avg_size * len(orphaned_files)
self.stdout.write(f'Tamaño estimado basado en muestra de {sample_size} archivos')
return estimated_total
def _show_dry_run_results(self, orphaned_files, media_root):
"""Muestra resultados del dry run"""
self.stdout.write('\n--- MODO PRUEBA: Los siguientes archivos se eliminarían ---')
show_limit = 20
for i, file_path in enumerate(sorted(orphaned_files)):
if i >= show_limit:
remaining = len(orphaned_files) - show_limit
self.stdout.write(f' ... y {remaining} archivos más')
break
relative_path = os.path.relpath(file_path, media_root)
self.stdout.write(f' - {relative_path}')
def _delete_files(self, orphaned_files, media_root, options):
"""Elimina archivos con confirmación"""
confirm = input(
f'\n¿Estás seguro de que quieres eliminar {len(orphaned_files)} archivos? (s/N): '
)
if confirm.lower() not in ['s', 'si', '', 'y', 'yes']:
self.stdout.write('Operación cancelada')
return
deleted_count = 0
deleted_size = 0
for i, file_path in enumerate(orphaned_files):
try:
file_size = os.path.getsize(file_path)
os.remove(file_path)
deleted_count += 1
deleted_size += file_size
if options['verbose'] and deleted_count % 1000 == 0:
progress = (i + 1) / len(orphaned_files) * 100
self.stdout.write(f'Progreso: {progress:.1f}% ({deleted_count} eliminados)')
except OSError as e:
relative_path = os.path.relpath(file_path, media_root)
self.stdout.write(
self.style.ERROR(f'Error eliminando {relative_path}: {e}')
)
deleted_mb = deleted_size / (1024 * 1024)
self.stdout.write(
self.style.SUCCESS(
f'Eliminados {deleted_count} archivos huérfanos ({deleted_mb:.2f} MB)'
)
)

View File

@@ -0,0 +1,33 @@
# Generated by Django 5.2.3 on 2025-10-02 00:06
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('customs', '0010_alter_pedimento_contribuyente'),
]
operations = [
migrations.AddField(
model_name='cove',
name='acuse_cove_descargado',
field=models.BooleanField(default=False, help_text='Indica si el acuse de la cove ha sido descargado'),
),
migrations.AddField(
model_name='cove',
name='cove_descargado',
field=models.BooleanField(default=False, help_text='Indica si la cove ha sido descargada'),
),
migrations.AddField(
model_name='edocument',
name='acuse_descargado',
field=models.BooleanField(default=False, help_text='Indica si el acuse del e-documento ha sido descargado'),
),
migrations.AddField(
model_name='edocument',
name='edocument_descargado',
field=models.BooleanField(default=False, help_text='Indica si el e-documento ha sido descargado'),
),
]

View File

@@ -0,0 +1,31 @@
# Generated by Django 5.2.3 on 2025-10-03 01:18
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('customs', '0011_cove_acuse_cove_descargado_cove_cove_descargado_and_more'),
('organization', '0002_remove_organizacion_membretado_and_more'),
]
operations = [
migrations.CreateModel(
name='Partida',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('numero_partida', models.PositiveIntegerField(help_text='Número de la partida dentro del pedimento')),
('descargado', models.BooleanField(default=False, help_text='Indica si la partida ha sido descargada')),
('organizacion', models.ForeignKey(help_text='Organización a la que pertenece la partida', on_delete=django.db.models.deletion.CASCADE, related_name='partidas', to='organization.organizacion')),
('pedimento', models.ForeignKey(help_text='Pedimento asociado a la partida', on_delete=django.db.models.deletion.CASCADE, related_name='partidas', to='customs.pedimento')),
],
options={
'verbose_name': 'Partida',
'verbose_name_plural': 'Partidas',
'db_table': 'partida',
'ordering': ['pedimento', 'numero_partida'],
},
),
]

View File

@@ -0,0 +1,17 @@
# Generated by Django 5.2.3 on 2025-10-03 02:12
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('customs', '0012_partida'),
]
operations = [
migrations.AlterUniqueTogether(
name='partida',
unique_together={('pedimento', 'numero_partida')},
),
]

View File

@@ -0,0 +1,20 @@
# Generated by Django 5.2.3 on 2025-10-03 03:20
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('customs', '0013_alter_partida_unique_together'),
]
operations = [
migrations.AddField(
model_name='partida',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, help_text='Fecha de creación del registro'),
preserve_default=False,
),
]

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.2.3 on 2025-10-03 03:21
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('customs', '0014_partida_created_at'),
]
operations = [
migrations.AddField(
model_name='partida',
name='updated_at',
field=models.DateTimeField(auto_now=True, help_text='Fecha de última actualización del registro'),
),
]

View File

@@ -61,6 +61,24 @@ class Pedimento(models.Model):
db_table = 'pedimento'
ordering = ['pedimento']
class Partida(models.Model):
pedimento = models.ForeignKey(Pedimento, on_delete=models.CASCADE, related_name='partidas', help_text="Pedimento asociado a la partida")
organizacion = models.ForeignKey('organization.Organizacion', on_delete=models.CASCADE, related_name='partidas', help_text="Organización a la que pertenece la partida")
numero_partida = models.PositiveIntegerField(help_text="Número de la partida dentro del pedimento")
descargado = models.BooleanField(default=False, help_text="Indica si la partida ha sido descargada")
created_at = models.DateTimeField(auto_now_add=True, help_text="Fecha de creación del registro")
updated_at = models.DateTimeField(auto_now=True, help_text="Fecha de última actualización del registro")
def __str__(self):
return f"Partida {self.numero_partida} del Pedimento {self.pedimento.pedimento}"
class Meta:
verbose_name = "Partida"
verbose_name_plural = "Partidas"
db_table = 'partida'
ordering = ['pedimento', 'numero_partida']
unique_together = ['pedimento', 'numero_partida'] # No puede existir el mismo número de partida para un pedimento
class EDocument(models.Model):
pedimento = models.ForeignKey(Pedimento, on_delete=models.CASCADE, related_name='documentos', help_text="Pedimento asociado al documento")
organizacion = models.ForeignKey('organization.Organizacion', on_delete=models.CASCADE, related_name='edocuments', help_text="Organización a la que pertenece el EDocument")
@@ -71,6 +89,8 @@ class EDocument(models.Model):
descripcion = models.CharField(max_length=200, blank=True, null=True, help_text="Descripción del documento")
created_at = models.DateTimeField(auto_now_add=True, help_text="Fecha de creación del documento")
updated_at = models.DateTimeField(auto_now=True, help_text="Fecha de última actualización del documento")
edocument_descargado = models.BooleanField(default=False, help_text="Indica si el e-documento ha sido descargado")
acuse_descargado = models.BooleanField(default=False, help_text="Indica si el acuse del e-documento ha sido descargado")
def __str__(self):
return f"{self.descripcion} - {self.pedimento.pedimento}"
@@ -87,6 +107,8 @@ class Cove(models.Model):
numero_cove = models.CharField(max_length=20, unique=True, help_text="Número único de la cove")
created_at = models.DateTimeField(auto_now_add=True, help_text="Fecha de creación de la cove")
updated_at = models.DateTimeField(auto_now=True, help_text="Fecha de última actualización de la cove")
cove_descargado = models.BooleanField(default=False, help_text="Indica si la cove ha sido descargada")
acuse_cove_descargado = models.BooleanField(default=False, help_text="Indica si el acuse de la cove ha sido descargado")
def __str__(self):
return f"{self.numero_cove} - {self.pedimento.pedimento}"

View File

@@ -5,7 +5,8 @@ from api.customs.models import (
ProcesamientoPedimento,
EDocument,
Cove,
Importador
Importador,
Partida
)
from django.db import models
from api.record.models import Document # Asegúrate de importar el modelo Documento
@@ -41,7 +42,10 @@ class PedimentoSerializer(serializers.ModelSerializer):
rep['documentos_peso_total'] = self.get_documentos_peso_total(instance)
return rep
class PartidaSerializer(serializers.ModelSerializer):
class Meta:
model = Partida
fields = '__all__'
class TipoOperacionSerializer(serializers.ModelSerializer):
class Meta:

View File

@@ -71,8 +71,39 @@ def auditar_acuse_coves(organizacion_id):
pass
@shared_task
def auditar_acuse_edocuments(organizacion_id):
# crear servicio individual para cada acuse de edocument faltante en microservicios
pass
pedimentos = obtener_pedimentos(organizacion_id)
for pedimento in pedimentos:
acuses_descargados = pedimento.documents.filter(document_type=4)
edocs = pedimento.documentos.all()
@shared_task
def crear_partidas(organizacion_id):
pedimentos = obtener_pedimentos(organizacion_id)
for pedimento in pedimentos:
crear_partidas_por_pedimento(pedimento.id)
@shared_task
def crear_partidas_por_pedimento(pedimento_id):
try:
pedimento = Pedimento.objects.get(id=pedimento_id)
except Pedimento.DoesNotExist:
return
if pedimento.numero_partidas > pedimento.partidas.count():
for i in range(1, pedimento.numero_partidas + 1):
from api.customs.models import Partida
Partida.objects.get_or_create(
pedimento=pedimento,
numero_partida=i,
organizacion_id=pedimento.organizacion_id
)

View File

@@ -185,8 +185,7 @@ def auditar_pedimento(organizacion_id):
pedimento.remesas = xml_data.get('remesas')
pedimento.tipo_operacion__id = xml_data.get('tipo_operacion')
pedimento.fecha_pago = xml_data.get('fecha_pago')
pedimento.pedimento_app = xml_data.get('fecha_pago')[2:4] + "-" + pedimento.aduana[:2] + "-" + pedimento.patente + "-" + pedimento.pedimento
pedimento.save()
pedimento.pedimento_app = xml_data.get('fecha_pago')[2:4] + "-" + pedimento.aduana[:2] + "-" + pedimento.patente + "-" + pedimento.pedimentodd
for edoc in xml_data.get('edocuments', []):
EDocument.objects.get_or_create(

View File

@@ -9,7 +9,8 @@ from .views import (
ViewSetProcesamientoPedimento,
ViewSetEDocument,
ViewSetCove,
ImportadorViewSet
ImportadorViewSet,
PartidaViewSet
)
# from .views import YourViewSet # Import your viewsets here
@@ -26,6 +27,7 @@ router.register(r'procesamientopedimentos', ViewSetProcesamientoPedimento, basen
router.register(r'edocuments', ViewSetEDocument, basename='EDocument')
router.register(r'coves', ViewSetCove, basename='Cove')
router.register(r'importadores', ImportadorViewSet, basename='Importador')
router.register(r'partidas', PartidaViewSet, basename='Partida')
# Import your viewsets here

View File

@@ -21,7 +21,8 @@ from api.customs.models import (
ProcesamientoPedimento,
EDocument,
Cove,
Importador
Importador,
Partida
)
from api.customs.serializers import (
PedimentoSerializer,
@@ -29,7 +30,8 @@ from api.customs.serializers import (
ProcesamientoPedimentoSerializer,
EDocumentSerializer,
CoveSerializer,
ImportadorSerializer
ImportadorSerializer,
PartidaSerializer
)
from api.logger.mixins import LoggingMixin
@@ -202,6 +204,36 @@ class ViewSetPedimento(LoggingMixin, viewsets.ModelViewSet, OrganizacionFiltrada
my_tags = ['Pedimentos']
class PartidaViewSet(viewsets.ModelViewSet):
"""
ViewSet for Partida model.
Permite filtrar por:
- pedimento: UUID del pedimento (query parameter principal)
- pedimento__id: UUID del pedimento (alternativo)
Ejemplo: GET /api/partidas/?pedimento=6782d22e-5e97-4efc-87c9-bd8497c8ac7e
"""
permission_classes = [IsAuthenticated & (IsSameOrganization | IsSameOrganizationAndAdmin | IsSameOrganizationDeveloper | IsSuperUser)]
queryset = Partida.objects.all()
serializer_class = PartidaSerializer
pagination_class = CustomPagination
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
filterset_fields = {
'pedimento': ['exact'], # Filtro directo por UUID del pedimento
'pedimento__id': ['exact'], # Filtro alternativo
'numero_partida': ['exact', 'gte', 'lte'], # Filtros por número de partida
'descargado': ['exact'], # Filtro por estado de descarga
'created_at': ['exact', 'gte', 'lte'], # Filtros por fecha de creación
'updated_at': ['exact', 'gte', 'lte'] # Filtros por fecha de actualización
}
search_fields = ['pedimento__pedimento', 'pedimento__pedimento_app']
ordering_fields = ['numero_partida', 'pedimento__pedimento', 'id', 'created_at', 'updated_at']
ordering = ['numero_partida'] # Ordenar por número de partida por defecto
my_tags = ['Partidas']
class ViewSetTipoOperacion(LoggingMixin, viewsets.ModelViewSet):
"""
ViewSet for TipoOperacion model.