118 lines
4.0 KiB
Python
118 lines
4.0 KiB
Python
"""
|
|
Corrige el mismatch de case entre el campo `archivo` en BD y los nombres
|
|
reales de los objetos en MinIO.
|
|
|
|
Causa habitual: transferencia de archivos de producción a local lowercaseó
|
|
los filenames, pero la BD conserva los nombres originales con mayúsculas.
|
|
|
|
Estrategia: para cada Document cuyo `archivo` no exista en MinIO con el
|
|
nombre exacto, intenta el filename en minúsculas. Si lo encuentra, actualiza
|
|
el campo en BD. Los archivos que ya coinciden no se tocan.
|
|
|
|
Uso:
|
|
python manage.py fix_archivo_case --pedimento <UUID> --dry-run
|
|
python manage.py fix_archivo_case --pedimento <UUID>
|
|
python manage.py fix_archivo_case --organizacion <UUID> --dry-run
|
|
python manage.py fix_archivo_case --organizacion <UUID>
|
|
"""
|
|
import posixpath
|
|
|
|
from django.core.management.base import BaseCommand, CommandError
|
|
|
|
from api.customs.models import Pedimento
|
|
from api.record.models import Document
|
|
from api.utils.minio_client import minio_client
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = "Corrige mismatch de case entre campo archivo en BD y MinIO."
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument(
|
|
"--pedimento", metavar="UUID",
|
|
help="UUID del pedimento a corregir.",
|
|
)
|
|
parser.add_argument(
|
|
"--organizacion", metavar="UUID",
|
|
help="UUID de la organización.",
|
|
)
|
|
parser.add_argument(
|
|
"--dry-run", action="store_true",
|
|
help="Solo diagnóstico, sin aplicar cambios.",
|
|
)
|
|
|
|
def handle(self, *args, **options):
|
|
ped_id = options.get("pedimento")
|
|
org_id = options.get("organizacion")
|
|
dry_run = options["dry_run"]
|
|
|
|
if dry_run:
|
|
self.stdout.write(self.style.WARNING(
|
|
"=== MODO PRUEBA (--dry-run): Sin cambios en BD ===\n"
|
|
))
|
|
|
|
qs = Document.objects.all()
|
|
if ped_id:
|
|
try:
|
|
ped = Pedimento.objects.get(id=ped_id)
|
|
except Pedimento.DoesNotExist:
|
|
raise CommandError(f"Pedimento {ped_id!r} no encontrado.")
|
|
qs = qs.filter(pedimento=ped)
|
|
self.stdout.write(f"Pedimento: {ped.pedimento_app}\n")
|
|
elif org_id:
|
|
qs = qs.filter(organizacion_id=org_id)
|
|
|
|
total = qs.count()
|
|
self.stdout.write(f"Documentos a revisar: {total}\n")
|
|
|
|
ok = mismatch = not_found = 0
|
|
|
|
for doc in qs.iterator(chunk_size=500):
|
|
name = doc.archivo.name if doc.archivo else None
|
|
if not name:
|
|
continue
|
|
|
|
if minio_client.file_exists(name):
|
|
ok += 1
|
|
continue
|
|
|
|
lower_name = self._lower_filename(name)
|
|
if lower_name == name:
|
|
not_found += 1
|
|
continue
|
|
|
|
if minio_client.file_exists(lower_name):
|
|
mismatch += 1
|
|
self.stdout.write(
|
|
f" {'[DRY]' if dry_run else '[FIX]'} doc {doc.id}:\n"
|
|
f" BD : {name}\n"
|
|
f" MinIO : {lower_name}\n"
|
|
)
|
|
if not dry_run:
|
|
doc.archivo.name = lower_name
|
|
doc.save(update_fields=["archivo"])
|
|
else:
|
|
not_found += 1
|
|
|
|
self.stdout.write(
|
|
f"\n{'─' * 60}\nRESUMEN\n"
|
|
f" Coinciden exacto : {ok}\n"
|
|
f" Mismatch de case : {mismatch}\n"
|
|
f" No encontrados : {not_found}\n"
|
|
)
|
|
|
|
if dry_run and mismatch:
|
|
self.stdout.write(self.style.WARNING(
|
|
"\nEjecuta sin --dry-run para aplicar los cambios."
|
|
))
|
|
elif not dry_run and mismatch:
|
|
self.stdout.write(self.style.SUCCESS(
|
|
f"\n{mismatch} registros actualizados en BD."
|
|
))
|
|
|
|
def _lower_filename(self, name):
|
|
"""Lowercase solo el filename, preserva el path del directorio."""
|
|
dir_part = posixpath.dirname(name)
|
|
filename = posixpath.basename(name)
|
|
return posixpath.join(dir_part, filename.lower())
|