feat: comando dedup_documents para duplicados legados (T2025-09-004)
Limpia documentos duplicados (misma sub-entidad + mismo document_type) creados ANTES del fix de reemplazo del microservicio (jun-2026). Conserva el mas reciente con archivo valido en storage, borra el resto (archivo MinIO si no lo referencia otra fila + fila + ajuste de cuota). --dry-run, conteo previo, idempotente; solo docs ligados a entidad (partida/cove/edocument). La creacion ya reemplaza desde jun-2026: verificado 0 duplicados posteriores al fix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -780,3 +780,52 @@ class DocumentFKResolutionTests(TestCase):
|
||||
call_command("backfill_document_links", pedimento=str(self.pedimento.id), stdout=StringIO())
|
||||
d_pt.refresh_from_db()
|
||||
self.assertEqual(d_pt.partida_id, self.partida.id)
|
||||
|
||||
def _tres_copias_edoc(self):
|
||||
"""3 copias del mismo edoc (type 5) con created_at d1<d2<d3 y archivos distintos."""
|
||||
from django.utils import timezone
|
||||
import datetime
|
||||
d1 = self._doc(f"vu_ED_{self.APP}_EDOC001_aaa.pdf", 5)
|
||||
d2 = self._doc(f"vu_ED_{self.APP}_EDOC001_bbb.pdf", 5)
|
||||
d3 = self._doc(f"vu_ED_{self.APP}_EDOC001_ccc.pdf", 5)
|
||||
base = timezone.now()
|
||||
Document.objects.filter(id=d1.id).update(created_at=base - datetime.timedelta(days=2))
|
||||
Document.objects.filter(id=d2.id).update(created_at=base - datetime.timedelta(days=1))
|
||||
Document.objects.filter(id=d3.id).update(created_at=base)
|
||||
return d1, d2, d3
|
||||
|
||||
def test_dedup_conserva_mas_reciente_y_es_idempotente(self):
|
||||
from unittest.mock import patch
|
||||
d1, d2, d3 = self._tres_copias_edoc()
|
||||
self.assertEqual(Document.objects.filter(edocument=self.edoc, document_type_id=5).count(), 3)
|
||||
with patch('api.customs.management.commands.dedup_documents.storage_service') as st:
|
||||
st.file_exists.return_value = True
|
||||
call_command('dedup_documents', pedimento=str(self.pedimento.id), stdout=StringIO())
|
||||
self.assertEqual(st.delete_file.call_count, 2) # borró los 2 viejos de MinIO
|
||||
restantes = list(Document.objects.filter(edocument=self.edoc, document_type_id=5))
|
||||
self.assertEqual(len(restantes), 1)
|
||||
self.assertEqual(restantes[0].id, d3.id) # conservó el más reciente
|
||||
# idempotente: re-correr no borra nada
|
||||
with patch('api.customs.management.commands.dedup_documents.storage_service') as st2:
|
||||
st2.file_exists.return_value = True
|
||||
call_command('dedup_documents', pedimento=str(self.pedimento.id), stdout=StringIO())
|
||||
self.assertEqual(st2.delete_file.call_count, 0)
|
||||
|
||||
def test_dedup_dry_run_no_borra(self):
|
||||
from unittest.mock import patch
|
||||
self._tres_copias_edoc()
|
||||
with patch('api.customs.management.commands.dedup_documents.storage_service') as st:
|
||||
call_command('dedup_documents', pedimento=str(self.pedimento.id), dry_run=True, stdout=StringIO())
|
||||
self.assertEqual(st.delete_file.call_count, 0)
|
||||
self.assertEqual(Document.objects.filter(edocument=self.edoc, document_type_id=5).count(), 3)
|
||||
|
||||
def test_dedup_conserva_el_que_tiene_archivo_en_storage(self):
|
||||
from unittest.mock import patch
|
||||
d1, d2, d3 = self._tres_copias_edoc() # d3 el más reciente
|
||||
# En storage solo existe el de d1 (el más viejo); los más nuevos no.
|
||||
with patch('api.customs.management.commands.dedup_documents.storage_service') as st:
|
||||
st.file_exists.side_effect = lambda nombre: nombre.endswith('_aaa.pdf')
|
||||
call_command('dedup_documents', pedimento=str(self.pedimento.id), stdout=StringIO())
|
||||
restantes = list(Document.objects.filter(edocument=self.edoc, document_type_id=5))
|
||||
self.assertEqual(len(restantes), 1)
|
||||
self.assertEqual(restantes[0].id, d1.id) # conservó el único con archivo válido
|
||||
|
||||
Reference in New Issue
Block a user