Feature #1452
openDésactiver / suspendre un CGP : cascade soft delete (cgp, profil, compte, liaisons clients) et réactivation
0%
Description
### Problème
Il n'existe aucun moyen de **suspendre un CGP** proprement. L'action « supprimer » disponible aujourd'hui dans la liste des CGP est incomplète : elle marque uniquement la ligne `cgp` comme supprimée et laisse tout le reste actif.
Concrètement, après un « delete » actuel :
- le **profil** du CGP reste actif (`profile.deleted_at` non renseigné) ;
- son **compte utilisateur** reste actif (`user_account.deleted_at` non renseigné) ;
- ses **liaisons clients** restent actives (`cgp_beneficiary.deleted_at` non renseigné) — le CGP continue donc d'apparaître comme rattaché à ses bénéficiaires ;
- ses **jetons d'authentification en cours** ne sont pas révoqués.
Le besoin métier : pouvoir **désactiver / suspendre un CGP** (départ, fin de partenariat, litige, suspension temporaire) en coupant réellement son accès et en détachant ses clients, **sans perdre l'historique** — et pouvoir le **réactiver** ensuite.
### Contexte technique (état actuel)
Chaîne existante :
- Front : `components/cgp/CgpTableComponent.tsx` (`useDeleteCgp`) → route Next `app/api/cgp/[id]/route.ts` → `deleteCgp()` (`api/cgp/api/cgpApi.ts:35`).
- API : `DELETE /cgp/{cgp_id}` (`internal/rest/server/cgp.go:39` et `:316`), **réservé au rôle Admin** → `cgpCommand.DeleteCGP` (`internal/core/cgp_command.go:283`) → `DBCGP.DeleteCGP` (`internal/db/db_cgp.go:120`).
- `DBCGP.DeleteCGP` exécute **un seul UPDATE** : `UPDATE cgp SET deleted_at = ..., updated_at = ... WHERE id = ...`. Rien d'autre n'est propagé.
Les briques nécessaires **existent déjà** et ne sont simplement pas appelées :
- `dbProfile.DeleteProfile(profileID)` — `internal/db/db_profile.go:231` (soft delete du profil) ;
- soft delete du compte utilisateur — `internal/db/db_user_account.go:294` (`UPDATE user_account SET deleted_at = ...`) ;
- `DeleteCGPBeneficiary(beneficiaryID, cgpID)` — `internal/db/db_beneficiary.go:532` (soft delete d'une liaison `cgp_beneficiary`).
- Le CGP porte déjà `profile_id` et `user_account_id` (`types.CGP`), les cibles du cascade sont donc directement accessibles.
Points d'attention identifiés à la lecture du code :
- **Le login CGP passe par le profil.** `getUserAccountForCGP` (`internal/core/authentification.go:751`) appelle `dbProfile.GetByEmailAndRole`, dont la requête filtre `deleted_at IS NULL` (`db_profile.go:142`) : soft-delete du profil ⇒ connexion CGP bloquée. **En revanche**, la voie générique `GetUserAccountByEmail` (`db_user_account.go:103`) **ne filtre pas** `deleted_at` — il faut donc vérifier explicitement que *toutes* les voies d'authentification (dont `/login`, `forgot_password_cgp`, `validate_security_code_cgp`) rejettent un compte suspendu, et non pas seulement `/logincgp`.
- **Les jetons déjà émis** (`user_account_auth_token`) doivent être révoqués, sinon un CGP suspendu reste connecté jusqu'à expiration.
- **Atomicité** : l'opération touche 4 tables (`cgp`, `profile`, `user_account`, `cgp_beneficiary`) via des `Exec` indépendants. Elle doit être encapsulée dans une **transaction** pour éviter un état partiellement suspendu.
- **Bug annexe à corriger au passage** — `internal/types/cgp.go` : les tags JSON de `CGP` sont **inversés** (`DeletedAt *time.Time \`json:"test_deleted_at"\`` et `TestDeletedAt time.Time \`json:"deleted_at"\``). Tout consommateur qui lit `deleted_at` sur un CGP reçoit aujourd'hui le mauvais champ, ce qui rendra le statut « suspendu » faux côté front.
### Comportement proposé
**Périmètre du cascade (décidé) : on délie, on ne supprime pas les clients.** Les bénéficiaires, leurs mandats et leurs avoirs restent **intacts et actifs** ; seule la liaison au CGP est soft-deletée. Les clients deviennent « sans CGP » et peuvent être réattribués.
**Suspendre un CGP** (Admin uniquement), en une transaction :
1. `cgp.deleted_at = now()`
2. `profile.deleted_at = now()` (profil de rôle `cgp` rattaché)
3. `user_account.deleted_at = now()`
4. `cgp_beneficiary.deleted_at = now()` pour **toutes** les liaisons du CGP
5. Révocation des jetons d'authentification actifs du compte
**Réactiver un CGP suspendu** (Admin uniquement) : opération symétrique remettant `deleted_at` à `NULL` sur `cgp`, `profile` et `user_account`. Les liaisons clients **ne sont pas restaurées automatiquement** (un client a pu être réattribué entre-temps) : l'admin réattribue explicitement les bénéficiaires souhaités.
**UI** : l'action actuelle « supprimer » de la liste des CGP devient une action **« Désactiver »** avec confirmation explicite rappelant les conséquences (perte d'accès + N clients détachés). Les CGP désactivés restent consultables via un filtre / onglet dédié, avec leur date de désactivation et une action « Réactiver ».
## Acceptance criteria
- [ ] Un Admin peut **désactiver** un CGP depuis la liste des CGP ; l'action est précédée d'une confirmation indiquant le nombre de clients qui seront détachés.
- [ ] Après désactivation : `cgp.deleted_at`, `profile.deleted_at` et `user_account.deleted_at` sont renseignés, et **toutes** les lignes `cgp_beneficiary` du CGP ont `deleted_at` renseigné.
- [ ] Les **bénéficiaires, mandats/wallets et valorisations restent actifs et inchangés** — aucun `deleted_at` posé sur `beneficiary` ou `wallet`. Les clients concernés apparaissent simplement sans CGP.
- [ ] Le CGP désactivé **ne peut plus se connecter**, par aucune voie d'authentification (`/logincgp`, `/login`, réinitialisation de mot de passe, validation de code de sécurité) — vérifié par test.
- [ ] Les **jetons d'accès et de rafraîchissement déjà émis** pour ce compte sont révoqués : une session ouverte au moment de la désactivation ne permet plus d'appeler l'API.
- [ ] L'opération est **transactionnelle** : en cas d'échec sur l'une des étapes, aucune modification n'est persistée (pas d'état partiellement suspendu).
- [ ] Un CGP désactivé **disparaît des listes et KPI actifs** (liste CGP par défaut, `/bff/cgps`, agrégats) et n'est plus proposé comme cible de rattachement d'un nouveau client.
- [ ] Les CGP désactivés restent **consultables** via un filtre dédié, avec leur date de désactivation.
- [ ] Un Admin peut **réactiver** un CGP désactivé : `deleted_at` repasse à `NULL` sur `cgp`, `profile` et `user_account`, et le CGP peut de nouveau se connecter.
- [ ] La réactivation **ne restaure pas** les liaisons clients : le CGP réactivé repart sans bénéficiaire, à réattribuer explicitement.
- [ ] L'ensemble des actions (désactivation / réactivation) est **réservé au rôle Admin** et **tracé** (audit / event log) avec l'auteur et l'horodatage.
- [ ] Les tags JSON inversés de `types.CGP` (`deleted_at` / `test_deleted_at`) sont corrigés, et le front expose un statut de CGP fiable.
- [ ] Aucune régression sur les CGP actifs : listes, rattachements, commissions et rétrocessions inchangés.
## Classification
- feature
## Complexity
- 6/10 — pas d'algorithme nouveau, mais un cascade transactionnel sur 4 tables, un impact direct sur l'authentification (révocation de session + toutes les voies de login), un chemin de réactivation, et une correction de contrat JSON. La couverture de test conditionne la confiance.