Files
backend/api/customs/management/commands/backfill_document_links_legacy.py
marcos dcabfb8762 feat: backfill_document_links_legacy para docs legados de cove/edoc por numero (T2025-09-004)
Algunos documentos viejos quedaron con nomenclatura olvidada (otro pedimento_app
y/o prefijo, p.ej. vu_EDC_0201_800_..._04382515ZIFF5) que el matcher estricto no
liga. Como el numero_cove/numero_edocument es unico y esta en el nombre, este
comando los liga por ese numero (con frontera), sin exigir app ni prefijo.

Solo cove/edoc (llaves unicas); partida queda fuera (enteros cortos -> colisiones)
y nativos no tienen entidad. Correr despues de backfill_document_links.

Agrega core.document_links.numero_en_nombre. Fix: el DISTINCT de pedimento_id
limpia el ordering por defecto del modelo para no duplicar.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 13:26:06 -06:00

95 lines
4.3 KiB
Python

"""
Backfill LEGADO de la FK de cove/edocument por número único (T2025-09-004).
Para documentos viejos cuyo nombre quedó con una nomenclatura olvidada —otro
`pedimento_app` y/u otro prefijo (p.ej. `vu_EDC_0201_800_..._04382515ZIFF5_hex.pdf`
en un pedimento cuyo `pedimento_app` es `25-80-3452-5000586`)— el matcher estricto
de `backfill_document_links` no liga porque arma `vu_ed_{pedimento_app}_{numero}`.
Pero el **número de cove/edoc** (único y largo) sí está en el nombre y la entidad
existe.
Este comando, SOLO para cove y edocument (llaves únicas; partida queda fuera por ser
enteros cortos con colisiones, y los nativos no tienen entidad), liga la FK buscando
el `numero_cove`/`numero_edocument` como token en el nombre, sin exigir app ni prefijo.
Correr DESPUÉS de `backfill_document_links` (solo toca lo que quedó sin ligar).
Uso:
python manage.py backfill_document_links_legacy [--pedimento UUID] [--organizacion UUID] [--dry-run]
"""
from django.core.management.base import BaseCommand
from django.db import transaction
from api.customs.models import Cove, EDocument
from api.record.models import Document
from core.document_links import (
COVE_TYPES, EDOCUMENT_TYPES, SECCION_CAMPO, SECCION_LLAVE,
numero_en_nombre, seccion_de_tipo,
)
class Command(BaseCommand):
help = "Liga documentos legados de cove/edoc (FK nula) por su número único en el nombre, ignorando app/prefijo viejos. Correr después de backfill_document_links."
def add_arguments(self, parser):
parser.add_argument('--pedimento', type=str, default=None)
parser.add_argument('--organizacion', type=str, default=None)
parser.add_argument('--dry-run', action='store_true', help='Reporta sin escribir')
def handle(self, *args, **opts):
dry_run = opts['dry_run']
tipos = sorted(COVE_TYPES | EDOCUMENT_TYPES)
base = Document.objects.filter(
partida__isnull=True, cove__isnull=True, edocument__isnull=True,
document_type_id__in=tipos,
)
if opts['pedimento']:
base = base.filter(pedimento_id=opts['pedimento'])
if opts['organizacion']:
base = base.filter(pedimento__organizacion_id=opts['organizacion'])
if dry_run:
self.stdout.write(self.style.WARNING("== DRY-RUN: no se escribe nada =="))
# order_by() limpia el ordering por defecto del modelo (created_at), que si
# no se quita se cuela en el DISTINCT y duplica los pedimento_id.
ped_ids = list(base.order_by().values_list('pedimento_id', flat=True).distinct())
stats = {'pedimentos': 0, 'cove': 0, 'edocument': 0, 'ambiguos': 0, 'sin_match': 0}
for ped_id in ped_ids:
docs = list(base.filter(pedimento_id=ped_id))
entidades = {
'cove': list(Cove.objects.filter(pedimento_id=ped_id)),
'edocument': list(EDocument.objects.filter(pedimento_id=ped_id)),
}
lote = []
for doc in docs:
seccion = seccion_de_tipo(doc.document_type_id)
llave = SECCION_LLAVE[seccion]
matches = [
e for e in entidades[seccion]
if numero_en_nombre(doc.archivo.name, getattr(e, llave))
]
if len(matches) == 1:
setattr(doc, SECCION_CAMPO[seccion], matches[0])
lote.append(doc)
stats[seccion] += 1
elif len(matches) > 1:
stats['ambiguos'] += 1 # número ambiguo: se deja sin ligar
else:
stats['sin_match'] += 1 # ni por número aparece la entidad
if lote:
stats['pedimentos'] += 1
self.stdout.write(" %s: %d doc(s) ligados por número" % (str(ped_id)[:8], len(lote)))
if not dry_run:
with transaction.atomic():
Document.objects.bulk_update(lote, ['cove', 'edocument'], batch_size=500)
self.stdout.write("")
self.stdout.write(self.style.SUCCESS(
"Pedimentos: %d | ligados → cove=%d edocument=%d | ambiguos: %d | sin match ni por número: %d"
% (stats['pedimentos'], stats['cove'], stats['edocument'], stats['ambiguos'], stats['sin_match'])
))