Story CP-011 : Changement de Tenant Backend + Interface Sélecteur de Tenant¶
Module : control-plane Slice : Slice 11 from architecture Brand context : Papillon HR Suite Priority : 11 Depends on : CP-003 Estimated complexity : M
Objectif¶
Permettre le flux de travail du cabinet comptable (comptable externe) : un utilisateur appartenant à plusieurs tenants peut basculer entre eux sans se réauthentifier. Ce slice ajoute le composant UI de sélection de tenant, le mécanisme d'échange de token et le flux de changement principal. Les miniatures du tableau de bord et l'optimisation des performances sont dans CP-028.
Périmètre Backend¶
Entités & Records¶
Aucune nouvelle entité — utilise UserTenantMembership de CP-003.
DTOs (extension de CP-003) :
public record SwitchTenantRequest(UUID targetTenantId) {}
public record SwitchTenantResponse(
String accessToken, String refreshToken,
UUID tenantId, String tenantName
) {}
Couche Service¶
TenantSwitchService.java :
- switchTenant(UUID userId, UUID targetTenantId) → SwitchTenantResponse :
1. Valider que l'utilisateur a une adhésion active dans le tenant cible
2. Demander un nouveau JWT à Keycloak avec le claim tenant_id cible
3. Invalider l'ancien JWT (si Keycloak supporte la révocation de token)
4. Retourner les nouveaux tokens access + refresh
5. Le changement se complète en < 2 secondes
Endpoints API¶
Propriété de cette story (CP-011) :
| Méthode | Chemin | Corps de requête | Réponse | Auth |
|---|---|---|---|---|
| GET | /api/v1/auth/my-tenants | — | MyTenantsResponse | Tout utilisateur authentifié |
| POST | /api/v1/auth/switch-tenant | SwitchTenantRequest | SwitchTenantResponse | Tout utilisateur authentifié |
Considérations multi-tenant¶
- L'échange de token remplace un contexte tenant par un autre
- Tous les caches en mémoire (permissions, statut des modules) doivent être invalidés au changement
- Les données IndexedDB de l'ancien tenant doivent être effacées au changement
- Les données du nouveau tenant sont préchargées après le changement
Périmètre Frontend¶
Pages & Routes¶
- Pas de page dédiée. Le sélecteur de tenant est accessible depuis :
- Le menu avatar utilisateur dans la barre supérieure → "Mes entreprises"
Composants¶
TenantSwitcher.tsx— Bottom sheet plein écran (mobile) / dropdown (bureau)TenantSwitcherCard.tsx— Carte tenant avec logo, nom, rôle, badge courantTenantSwitchOverlay.tsx— Chargement plein écran pendant le changement : "Chargement de {name}…"
États UI (TOUS REQUIS)¶
| État | Comportement | Texte en Français |
|---|---|---|
| Loading | Spinner pendant le chargement de la liste | — |
| Tenant unique | Sélecteur non affiché (uniquement pour les utilisateurs multi-tenant) | — |
| Multi-tenant | Liste des tenants avec le tenant courant mis en évidence | "Mes entreprises" |
| Changement en cours | Overlay plein écran avec indicateur pulsé | "Chargement de {tenantName}…" |
| Erreur de changement | Toast | "Impossible de changer d'entreprise. Veuillez réessayer." |
| Succès | Redirection vers le tableau de bord du nouveau tenant | — |
Comportement responsive¶
- 360px (mobile) : Bottom sheet plein écran affichant la liste des tenants. Chaque carte : emplacement logo, nom d'entreprise, rôle de l'utilisateur, coche sur le tenant courant.
- 1024px+ (bureau) : Dropdown depuis le menu avatar utilisateur. Même contenu, disposition compacte.
Interactions¶
- L'utilisateur appuie sur l'avatar → le menu affiche l'option "Mes entreprises"
- Appui → la liste des tenants apparaît
- Appui sur un tenant différent → overlay de chargement plein écran : "Chargement de {name}…"
- L'échange de token s'effectue
- Toutes les données en cache sont effacées → nouvelles données tenant préchargées → redirection vers le Tableau de bord
- Le contexte du tenant précédent est entièrement effacé de la mémoire
Micro-textes français :
| Clé | Texte Français |
|---|---|
tenant_switcher.title |
"Mes entreprises" |
tenant_switcher.current |
"Entreprise active" |
tenant_switcher.switching |
"Chargement de {name}…" |
tenant_switcher.hint |
"Tapez sur une entreprise pour changer de contexte." |
Critères d'acceptation¶
AC-089: Tenant switch with token exchange
Given I belong to tenant A (current) and tenant B
When I select tenant B from the switcher
Then a new JWT is issued for tenant B
And I land on tenant B's dashboard
And all tenant A data is cleared from cache/IndexedDB
AC-090: Switcher only for multi-tenant users
Given I belong to only 1 tenant
When I open the user menu
Then "Mes entreprises" option does not appear
AC-091: Switcher shows current tenant
Given I belong to 3 tenants and am currently in "Acme SARL"
When I open the tenant switcher
Then "Acme SARL" has a "✓ Entreprise active" badge
Règles OHADA & Réglementaires¶
- Le cabinet comptable est une structure commerciale courante dans les zones OHADA — les comptables externes gèrent plusieurs entreprises clientes. La fonctionnalité de changement de tenant multi-tenant sert directement ce profil utilisateur.
- L'échange de token ne doit pas fuiter de données entre les tenants — tous les caches sont effacés au changement.
Standards & Conventions¶
docs/standards/react-typescript-guidelines.md— §Bottom sheet, §États overlay/chargement, §Gestion du cachedocs/standards/offline-sync-guidelines.md— §Effacement IndexedDB lors du changement de contexte
Exigences de test¶
Tests unitaires¶
- TenantSwitchService : changement valide, adhésion invalide → rejeté
- TenantSwitcher : s'affiche uniquement pour les utilisateurs multi-tenant
- TenantSwitcherCard : rendu du badge de tenant courant
Tests d'intégration¶
- Échange de token : le nouveau JWT contient le tenant_id correct
- Changement refusé : l'utilisateur n'est pas membre du tenant cible → 403
- Cache/IndexedDB effacé après le changement
Ce que le QA validera¶
- Le sélecteur s'affiche uniquement pour les utilisateurs multi-tenant
- Le tenant courant est mis en évidence
- L'overlay de changement s'affiche et se ferme correctement
- Toutes les données reflètent le nouveau tenant après le changement (liste d'employés, paramètres, etc.)
Hors périmètre¶
- Miniatures de tableau de bord tenant — CP-028
- Affichage du rôle par tenant dans le sélecteur — CP-028
- Optimisation des performances de changement (< 2s) — CP-028
- Configuration du tenant principal (changer quel tenant est le "home") — V2
- Recherche dans la liste de tenants (pour les utilisateurs avec de nombreux tenants) — V2
- Reporting inter-tenant (vue agrégée entre tenants) — V2
- Changement de tenant hors ligne — Impossible (nécessite un échange de token)
Définition de Done¶
- [ ] Tous les endpoints backend implémentés et retournant les réponses correctes
- [ ] Tous les composants frontend s'affichent correctement à 360px, 768px, 1024px
- [ ] Les 6 états UI implémentés (loading, tenant unique, multi-tenant, changement en cours, erreur, succès)
- [ ] Les micro-textes français correspondent exactement à la spec design
- [ ] Tous les critères d'acceptation passent manuellement
- [ ] Tests unitaires écrits et passants
- [ ] Tests d'intégration écrits et passants
- [ ] Isolation multi-tenant vérifiée (aucune fuite de données inter-tenant)
- [ ] Pas de
anyTypeScript — tous les types explicites - [ ] Pas de types Java bruts — tous les génériques explicites
- [ ] Les réponses API respectent le format d'enveloppe standard
- [ ] Entrées d'audit créées pour les événements de changement de tenant