Project

General

Profile

Actions

Feature #1452

open
RA

Désactiver / suspendre un CGP : cascade soft delete (cgp, profil, compte, liaisons clients) et réactivation

Feature #1452: Désactiver / suspendre un CGP : cascade soft delete (cgp, profil, compte, liaisons clients) et réactivation

Added by Redmine Admin 15 days ago. Updated 15 days ago.

Status:
Submitted
Priority:
Normal
Assignee:
-
Start date:
07/21/2026
Due date:
% Done:

0%

Estimated time:
spec_ref:
build_status:
build_number:
ci_run_url:
scan_status:
scan_report_url:
deploy_status:
preprod_url:
deployed_at:
branch:
pr_url:
security_key:
severity:
paused:

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.

RA Updated by Redmine Admin 15 days ago Actions #1

  • Description updated (diff)
Actions

Also available in: PDF Atom