Skip to main content

Fusió de coreògrafs duplicats

Procediment per detectar i fusionar coreògrafs duplicats al catàleg. Replica el patró de fusió de balls i fusió de cançons.

Atenció: la fusió és irreversible un cop confirmada. Sempre que sigui possible, fes una còpia de seguretat de la base de dades abans d'una sessió intensiva de neteja.

Casos d'ús​

  • Mateix coreògraf importat des de diferents fonts (ETL linedance.cat, YouTube, alta manual) amb noms lleugerament diferents (Maggie Gallagher vs M. Gallagher).
  • Solos creats inadvertidament a partir de membres d'un grup (típic després d'imports massius).
  • Variants ortogràfiques (John Doe vs Jhon Doe).

Permisos​

Tots els endpoints i pàgines requereixen rol ADMIN.

Detectar duplicats​

  1. Vés a /choreographers i fes clic al botó Detectar duplicats (només visible per a admins). També pots accedir directament a /admin/choreographers/duplicates.
  2. Fes clic a Escanejar duplicats. La cerca utilitza dues estratègies:
    • Nom exacte (case-insensitive, dins el mateix isGroup) — confiança 80%.
    • Similitud trigram via pg_trgm (llindar 0.6) — confiança variable calculada a partir de la similitud.
  3. La pantalla mostra grups ordenats per confiança. Cada grup indica:
    • Tipus (Solista o Grup)
    • Conflicte de propietari si dos coreògrafs estan reclamats per usuaris diferents — això bloqueja la fusió fins que un dels owners revoqui la seva reclamació.

Validacions abans de fusionar​

El backend rebutja la fusió si es compleix qualsevol d'aquestes condicions:

#CondicióPer què
D1Solo+Grup mesclatsTrenca la semàntica isGroup.
D2Dos CLAIMED amb owners diferentsDecisió no es pot prendre silenciosament; cal revocar primer.
D4Cicle a choreographer_members (un grup conté un altre del set)Provocaria self-membership.
D6Algun candidat té sol·licituds d'ownership pendentsResol-les abans (cancel·la o aprova).
D7Survivor i merged tenen Persons vinculades a Users registrats diferentsConflict d'identitat: dos comptes d'usuari diferents declaren ser la mateixa persona. L'admin ha de resoldre-ho manualment.
D8Hi ha una FK desconeguda cap a people no registrada al codiXarxa de seguretat — evita pèrdua de dades silenciosa quan l'esquema evoluciona.

Executar la fusió (UI)​

L'assistent té 3 passos:

Pas 0 — Tria del survivor​

Selecciona el coreògraf que sobreviurà. Els altres seran eliminats; les seves dades es traslladaran al survivor.

Cada candidat mostra badges amb informació clau (nombre de balls, membres, grups, links externs, Person, owner).

Pas 1 — Comparar i triar dades​

  • Camps escalars (name, country, bio): per defecte preserven els del survivor. Pots triar manualment de quin coreògraf agafar cada camp.
  • Fusió de Person (només si dos solos tenen Person diferents): per defecte manté el del survivor; pots copiar camps específics (email, avatar, etc.) del Person eliminat. Si el Person eliminat té altres rols (TEACHER, DJ…), no s'esborra de la BD; només es desvincula del rol CHOREOGRAPHER.
  • Aliases: marca quins noms dels coreògrafs eliminats vols guardar com a noms històrics del survivor (es registren a resource_name_history).
  • Avisos: la pàgina mostra els enllaços externs que es descartaran per duplicat (UNIQUE constraint a (platform, url)).

Pas 2 — Confirmació​

Resum final amb avís d'irreversibilitat. El botó vermell Confirmar fusió executa l'operació.

Què passa al backend​

L'execució es fa dins una transacció amb pessimistic lock sobre tots els coreògrafs implicats. Per a cada coreògraf eliminat:

  1. dance_choreographer: les associacions s'mouen al survivor; si el survivor ja té el ball, la fila duplicada s'esborra (el rol del survivor preval).
  2. choreographer_members: les memberships (com a grup i com a membre) s'mouen i es deduplicquen.
  3. resource_external_links: s'mouen al survivor; en cas de col·lisió (platform, url), l'enllaç del survivor preval i el del merged es descarta.
  4. resource_name_history: les entrades s'mouen + s'afegeix una entrada sintètica amb reason = "Fusionat des de #N".
  5. ownership_requests resoltes: s'mouen al survivor (les PENDING ja s'han bloquejat a la validació).
  6. Person merge complet (si survivor i merged tenen Persons diferents):
    • Mou person_roles del Person merged → Person survivor (UNION + dedup per (person_id, role_type))
    • Mou event_people del Person merged → Person survivor (UNION + dedup per (event_id, person_id))
    • Mou users.person_id només si el merged té User i el survivor no
    • Esborra el Person merged. CASCADE neteja els person_roles i event_people que no s'han pogut moure (eren duplicats amb el survivor i s'han descartat per UNIQUE constraint)
  7. listingStatus + owner: si el survivor era INFORMATIVE i un eliminat era CLAIMED, el survivor es promou.
  8. Eliminació: el coreògraf eliminat s'esborra (CASCADE neteja qualsevol resta).
  9. Audit: una entrada nova a choreographer_merge_history registra qui, quan, què, i si algun dels eliminats estava reclamat.

Audit trail​

La taula choreographer_merge_history (V95) preserva:

  • survivor_id — coreògraf que va sobreviure.
  • merged_ids[], merged_names[] — coreògrafs eliminats.
  • merged_was_claimed[] — flag per saber si algun era CLAIMED al moment.
  • merge_details (JSONB) — reservat per a detalls de fields chosen, etc.
  • merged_by, merged_at.

Rollback​

No hi ha rollback automàtic. Si cal recuperar dades:

  1. Restaura una còpia de seguretat de la BD anterior a la fusió.
  2. Consulta choreographer_merge_history per saber quins IDs es van fusionar.

Mantenir el Person merge segur al llarg del temps​

El servei té una whitelist anomenada KNOWN_PEOPLE_FKS amb totes les FKs conegudes que apunten a people:

private static final Set<String> KNOWN_PEOPLE_FKS = Set.of(
"choreographers.person_id",
"users.person_id",
"person_roles.person_id",
"event_people.person_id"
);

Cada executeMerge() consulta information_schema.table_constraints i compara amb aquesta llista. Si troba una FK desconeguda → bloqueja la fusió amb un missatge clar dient què cal actualitzar.

A més, el test ChoreographerMergePeopleFkConsistencyIT verifica al CI que la llista del codi coincideix amb la realitat de la BD. Si algú afegeix una nova FK a people (per exemple class_attendees.person_id), el test falla amb instruccions per actualitzar la whitelist i decidir la semàntica de fusió de la nova taula.

Què fer si el test falla:

  1. Identificar la nova FK i la seva delete_rule
  2. Decidir si:
    • Cal moure-la com person_roles / event_people (UNION + dedup)?
    • O cal bloquejar la fusió quan estigui present?
  3. Afegir-la a KNOWN_PEOPLE_FKS
  4. Implementar la query de mou al ChoreographerRepository
  5. Cridar-la al bloc Person merge de ChoreographerMergeService.executeMerge
  6. Actualitzar EXPECTED_FKS al test

Endpoints API​

GET  /api/admin/choreographers/duplicates
POST /api/admin/choreographers/merge/preview { "choreographerIds": [1, 2] }
POST /api/admin/choreographers/merge/execute { ... ChoreographerMergeConfirmRequest ... }

Tots requereixen ROLE_ADMIN.