# Cahier de recette — Lot L4 « Rôles, collaborateurs & pilotage »

> **Statut au 10/08 : passe API déroulée, passe écrans à faire.**
>
> La partie exerçable sans session de navigateur a été jouée sur staging le 10/08 — périmètres,
> filtres, contrats de réponse — en interrogeant l'API sous jetons temporaires, révoqués depuis.
> **Deux défauts trouvés et corrigés**, plus deux constats consignés (voir « Résultats » ci-dessous).
>
> **Reste à faire : tout ce qui demande un navigateur et une session** — S4 (mails), S5, S10
> (navigation), et la lecture visuelle des écrans. Ces scénarios demandent qu'Enguerran ouvre
> lui-même chaque profil, comme pour `RECETTE_L3.md`.

## Résultats de la passe API — 10/08/2026

### ✅ Validés sur staging

| Scénario | Vérifié |
|---|---|
| **S1.4** | Les cinq rôles managériaux lisent les référentiels (correctif #220) ; un apprenant reçoit 403 |
| **S1.5** | Un `sub_manager` ne peut pas créer de société — la lecture ouverte n'a pas ouvert l'écriture |
| **S2.1** | Libellés conformes : `manager` → **Manager RH**, `sub_manager` → **Manager** |
| **S3.3** | Récursivité exacte sur une chaîne à trois niveaux : 1 direct / 8 en hiérarchie, 2 / 7, 3 / 3 |
| **S6.3** | « Tous les collaborateurs » : 200 pour Manager RH et admin, **403** pour manager général, directeur et manager |
| **S8.1** | Deux funnels d'unités distinctes (`learners` / `enrollments`) |
| **S8.2** | Somme des lignes du tableau = funnel des inscriptions, **jamais** celui des personnes |
| **S8.3** | Complétion et taux de démarrage distincts, cohorte cohérente, aucun taux > 100 % |
| **S8.4** | Le filtre équipe **change réellement** les chiffres : 49 apprenants sans filtre, 3 avec |
| **S8.5** | **Un filtre ne peut pas élargir** : un manager visant l'équipe d'un autre obtient 0 |
| **S8.6** | `is_global` vrai pour l'admin, faux pour un manager (correctif #219) |
| **S8.8** | Date de mise en service de la mesure et nombre de comptes exclus rendus |
| **S9.3** | Timeline bornée à 60 jours ; `full_history=1` lève la borne |
| **S9.4/9.5** | Horizon **24 mois** pour le Manager RH, **illimité** pour l'admin, `horizon_applies_to = activity_only` |
| **S9.8** | Cockpit d'un apprenant hors périmètre : **403** |

### 🔴 Deux défauts trouvés et corrigés

**1. Six services d'API ignoraient le mot de passe périmé** — [FRONT#50](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-FRONT/issues/50), corrigé.
Le middleware `check.password.age` refuse en 403 tout appel d'un compte dont le mot de passe
dépasse 60 jours. Huit services lisaient le drapeau `require_password_reset` ; **les six créés
en L3 et L4 l'avaient oublié**. Trois comptes staging dépassaient le seuil : pour eux, tous les
écrans du lot répondaient sans rien expliquer — indicateurs « non chargés », listes vides, et
pour le cockpit un message de hors-périmètre **faux**. Serait allé en production, où les comptes
réels ont par construction des mots de passe anciens.

**2. Le cockpit tombait en 500 sur toute formation portant un quiz** — PR #230, corrigé.
`quizzes.title` n'existe pas, la colonne s'appelle `name`. Découvert sur la **première** paire
apprenant × formation testée. Aucun test ne l'avait vu : les tests inséraient des tentatives de
quiz sans rattacher de quiz à la formation, et la méthode sort tôt dans ce cas — la jointure
fautive n'était jamais exécutée. Un test qui insère une tentative sans quiz rattaché ne teste
pas les quiz, **il teste la sortie anticipée**, et il passe au vert en donnant l'illusion inverse.

### 📋 Deux constats consignés, non bloquants

- **#229** — le référentiel déclare le Manager RH « hiérarchique », cinq endroits le contredisent
  en dur. Le correctif évident (`scope => 'global'`) **casserait** l'horizon de 24 mois et la
  règle de rattachement : le champ `scope` confond trois questions distinctes. À traiter en L4.5.
- **#228** — le message de mot de passe périmé est en anglais en dur, hors des fichiers de
  traduction. C'est le seul texte censé lever la confusion de l'utilisateur bloqué.

## Résultats de la passe UI — 10/08/2026, profil manager d'équipe

Déroulée dans le navigateur d'Enguerran, connecté en manager d'équipe.

### ✅ Validés à l'écran

| Scénario | Vérifié |
|---|---|
| **S6.1** | « Mes collaborateurs » — 4 rattachés directs, filtres Société et Profil **remplis** (correctif #220 confirmé en conditions réelles) |
| **S6.2** | « Mes équipes » rend la hiérarchie et ajoute la colonne **Manager de rattachement**, propre à cet écran |
| **S6.4** | Tri d'un compteur : la ligne à 3 passe en dernier, l'indicateur de tri s'affiche. Filtre « ≥ 1 » sur En retard → « Aucune donnée trouvée » et compteur à 0 |
| **S6.5** | Export XLSX de la **vue courante** : recherche filtrant à 1 ligne → fichier de **2 lignes** (entête + 1 donnée), entêtes en français |
| **S6.6** | Le nom ouvre le cockpit |
| **S7.1/7.2** | Niveau 1 : décompte par statut, et « Affectée par : **Inconnu** » — les inscriptions antérieures à #54 n'ont pas d'auteur, et on ne l'invente pas |
| **S8.1→8.8** | Dashboard conforme à la maquette : deux funnels avec leurs unités, avertissement qu'ils ne s'additionnent pas, deux taux nommés séparément, « Sur votre périmètre. 4 apprenants comptés », bascule Formations/Tutos, mention de mise en service du tracking |
| **S8.9** | Tri du tableau par module : réordonne correctement et **zéro requête réseau** — vérifié en vidant le journal réseau avant le clic |
| **S9.1→9.5** | Cockpit : les deux niveaux, les 8 KPI, timeline 60 jours, bouton « Afficher tout l'historique » qui bascule en « historique complet », et **les deux règles de #182 écrites à l'écran** avec « 24 mois glissants » pour ce rôle |

### 🔴 Deux défauts trouvés

**1. Les cinq écrans de pilotage n'ont aucune barre de navigation** — [FRONT#53](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-FRONT/issues/53).
Ce sont des culs-de-sac : ni passage d'un écran à l'autre, ni déconnexion, sauf par l'URL. Le
correctif direct a été tenté deux fois et échoue : `CollaboratorsScreen` est imbriqué dans
`CreateCourse`, qui importe aussi les *pages* `MesEquipes` et `TousLesCollaborateurs` comme
onglets. Aucun fichier n'est donc un endroit sûr — seule la **route** l'est. Solution en trois
étapes détaillée dans l'issue.

**2. Le cockpit n'affiche pas le nom de l'apprenant** — [FRONT#55](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-FRONT/issues/55).
La maquette prévoit « ‹ Retour — Sophie Bernard » ; l'écran dit « Cockpit apprenant ». Deux
dossiers différents sont donc visuellement identiques, sur un écran où l'on décide de
quelqu'un. Le nom est déjà dans la réponse de l'API ; la ligne de contexte (profil · société ·
manager) demande, elle, un ajout côté back.

### 📋 Un constat

[FRONT#54](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-FRONT/issues/54) — les raccourcis
« Mes cours / Historique / Mes favoris » s'affichent dans l'espace Pilotage. La condition porte
sur le **rôle** (`userRole.includes("learner")`) alors qu'elle devrait porter sur **l'espace
actif** : la plupart des encadrants sont aussi apprenants, donc elle est vraie en permanence.
Si tout reste visible partout, la bascule Pilotage ⇄ Apprentissage ne sépare plus rien.

### Reste à faire — demande un autre profil

- **admin** : S1.1 à S1.3 (référentiels), S3.4 et **S3.5** (réaffectation — le scénario le plus
  important du lot, seul geste dont un test vert ne prouve rien à cause de MySQL), S5 (écran
  Utilisateurs, marquage des comptes exclus) ;
- **Manager RH** : S6.3, l'accès à « Tous les collaborateurs » ;
- **une boîte mail** : tout S4.
- **bloqué par FRONT#53** : la visibilité conditionnelle de l'onglet « Mes équipes » (S6.2,
  second volet) et S10 en entier — il n'y a pas de navigation à exercer.

### Jeu de recette laissé sur staging

Neuf comptes `@recette-l4.test` forment une chaîne hiérarchique à trois niveaux —
Gaelle (manager général) › Damien (directeur) › Sophie et Marc (managers) › cinq apprenants.
Les comptes de test existants n'avaient presque personne de rattaché, ce qui rendait S3.3, S6.1
et S6.2 inexerçables. Supprimables d'une requête sur le domaine.

**Périmètre livré — 18 issues.**

| Thème | Issues |
|---|---|
| Référentiels et rattachement | #51, #52, #160 |
| Rôles | #53, #188 |
| Hiérarchie | #58 |
| Invitations et onboarding | #54, #55, #183 |
| Écrans de listes | #56, #59, #62, #63, #57 |
| Pilotage | #65, #61, #163 |
| Navigation | #68 |

**Back** — PR #219 (filtres du dashboard), #220 (référentiels lisibles par les managers),
plus les PR de chaque issue.
**Front** — PR [#42](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-FRONT/pull/42) (dashboard),
[#43](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-FRONT/pull/43) (cockpit), et les
précédentes du lot.

**État des suites au 09/08** — back : 869 verts, 5 échecs préexistants (`cta_url`, hors
périmètre). Front : 897 verts, 102 échecs préexistants. Aucune régression introduite par
le lot, vérifiée en comparant les **noms** des tests en échec avant et après, la suite
front étant flottante à ±1.

---

## Préalable — mettre L4 sur staging

Rien de ce qui suit n'est jouable tant que `development` n'est pas déployé.

1. Déployer `development` (back et front) sur staging, en suivant
   [`12_PROCEDURE_DEPLOIEMENT.md`](../12_PROCEDURE_DEPLOIEMENT.md).
2. Jouer les migrations — L4 en ajoute (`excluded_from_stats` sur `users`, référentiels).
3. **Vérifier `APP_LOCALE=fr`** (issue #111). Le `.env` local est à `en` : si staging en
   hérite, toute l'API répond en anglais et la recette mesurera une traduction absente là
   où il n'y a qu'un réglage.
4. Vérifier que le rôle `manager_rh` existe bien après migration (#53, option 1).

---

## S1 · Référentiels sociétés et profils métier (#51)

- ⬜ **S1.1** L'admin crée une société, la modifie, la désactive. Elle apparaît aussitôt
  dans les filtres des écrans de collaborateurs.
- ⬜ **S1.2** Idem pour un profil métier. **Le référentiel est livré vide** : c'est le
  moment de le remplir avec de vraies valeurs et de vérifier qu'elles se propagent.
- ⬜ **S1.3** Supprimer une société **rattachée** à des utilisateurs propose l'écran de
  réaffectation avant suppression — pas une suppression en cascade, pas un refus sec.
- ⬜ **S1.4** **Les filtres Société et Profil métier sont remplis pour un manager non-admin.**
  C'est le correctif #220 : ils étaient vides pour tout le monde sauf l'administrateur, et
  un filtre vide ne ressemble pas à une panne — il ressemble à « il n'y a rien à filtrer ».
  À vérifier **avec un compte sub_manager**, pas avec le compte admin.
- ⬜ **S1.5** Ce même compte sub_manager ne peut **pas** créer ni supprimer une société : la
  lecture a été ouverte, pas l'écriture.

## S2 · Rôles et Manager RH (#53, #188)

- ⬜ **S2.1** Le rôle `manager` s'affiche partout comme **« Manager RH »**, et `sub_manager`
  comme **« Manager »**. Un seul référentiel : vérifier la cohérence entre l'écran admin,
  les listes, les filtres et les exports.
- ⬜ **S2.2** Les **deux personnes réelles** en rôle `manager` (arbitrage du 09/08) sont bien
  Manager RH et voient toute la plateforme.
- ⬜ **S2.3** Le rôle apparaît dans **tous** les périmètres du tableau de bord — c'est
  l'objet de #188, où quatre endroits énuméraient les rôles chacun à sa façon.
- ⬜ **S2.4** Un rôle **ajouté au référentiel** apparaît dans les facettes sans modification
  d'écran.

## S3 · Rattachement et hiérarchie (#52, #58, #160)

- ⬜ **S3.1** Le rattachement se saisit **sur la fiche du collaborateur** (« mon manager »),
  plus sur celle du manager.
- ⬜ **S3.2** Un collaborateur ne peut pas être son propre manager, ni créer un cycle.
- ⬜ **S3.3** La hiérarchie est **récursive** : un manager général voit les équipes de ses
  directeurs, pas seulement ses rattachés directs.
- ⬜ **S3.4** Supprimer un manager propose la **réaffectation de ses apprenants** (#160).
  ⚠️ Ce geste passe par un `UPDATE` que MySQL refusait dans sa première écriture (erreur
  1093) : **à jouer sur staging, pas seulement en local sur SQLite.** C'est le seul point
  du lot dont un test vert ne prouve rien.
- ⬜ **S3.5** L'alerte « apprenants sans manager » s'affiche en permanence sur l'écran admin
  et tombe à zéro après réaffectation.

## S4 · Invitations et première connexion (#54, #55, #183)

- ⬜ **S4.1** Une invitation part **en un seul mail par utilisateur**.
- ⬜ **S4.2** **Le mail arrive et son lien fonctionne.** ⚠️ Un `@if` collé à un mot avait
  fait échouer toute création de compte en 500, invisible sous `Mail::fake()`. À ouvrir
  pour de vrai.
- ⬜ **S4.3** Le lien du mail pointe vers le bon domaine — dépend de `FRONT_URL`, désormais
  lu via `config('app.front_url')`.
- ⬜ **S4.4** Première connexion : l'onboarding s'affiche une fois, pas à chaque connexion.
- ⬜ **S4.5** Le **délai de démarrage** se règle dans Paramètres et la modification est
  visible **immédiatement**, sans redémarrage.

## S5 · Écran admin Utilisateurs (#56)

- ⬜ **S5.1** Colonnes société, profil métier, manager de rattachement, avec leurs facettes.
- ⬜ **S5.2** **Comptes exclus des statistiques** : l'admin coche les 4 comptes de test. Le
  compte **garde son accès** et **reste visible** dans la liste — c'est un filtre de mesure,
  pas une désactivation.
- ⬜ **S5.3** Le modal de modification enregistre correctement rôle et rattachement.
  ⚠️ Point connu : il porte encore l'ancien `MultiSelect` — à regarder de près.

## S6 · Les trois écrans de collaborateurs (#59, #62, #63)

- ⬜ **S6.1** « Mes collaborateurs » = rattachés **directs**.
- ⬜ **S6.2** « Mes équipes » = descendance **directe et indirecte**, et l'onglet n'apparaît
  que s'il existe plus d'un niveau — c'est une **pertinence**, pas un droit.
- ⬜ **S6.3** « Tous les collaborateurs » n'est accessible qu'au Manager RH et à l'admin.
  Tenter l'URL avec un autre rôle rend **403**.
- ⬜ **S6.4** Les sept compteurs sont **triables**, « en retard » et « abandonnées »
  filtrables en « au moins N ».
- ⬜ **S6.5** L'export XLSX rend **la vue courante**, filtres compris.
- ⬜ **S6.6** **Le nom de chaque ligne ouvre le cockpit** de la personne (lien ajouté avec
  #61 : sans lui, le cockpit n'avait aucune porte d'entrée).

## S7 · Formations suivies par utilisateur (#57)

- ⬜ **S7.1** Le bloc affiche le décompte par statut, puis le détail.
- ⬜ **S7.2** Un auteur d'affectation inconnu s'affiche **comme inconnu** — les inscriptions
  antérieures à #54 n'ont pas été remplies rétroactivement, et écrire « par l'administrateur »
  serait inventer.
- ⬜ **S7.3** Consulter quelqu'un **hors périmètre** donne un message de périmètre, **pas une
  liste vide** qui laisserait croire à une absence de formations.

## S8 · Dashboard de pilotage (#65, #163)

- ⬜ **S8.1** **Les deux funnels affichent leur unité** — personnes d'un côté, couples
  apprenant × formation de l'autre — et l'avertissement entre les deux est lisible.
- ⬜ **S8.2** La **somme des lignes du tableau** par module égale le funnel des inscriptions,
  **jamais** celui des personnes. À vérifier avec une addition réelle.
- ⬜ **S8.3** **Complétion = terminés / démarrés**, distincte du **taux de démarrage**.
  Aucun taux ne dépasse 100 %, y compris pour un apprenant ayant démarré avant la période
  et terminé pendant.
- ⬜ **S8.4** **Le filtre société change réellement les chiffres.** ⚠️ Point le plus
  important de cette section : `company_id` était ignoré en silence avant #219, et un
  filtre qui ne filtre pas n'affiche aucune erreur — il affiche les mêmes nombres.
- ⬜ **S8.5** Le filtre équipe **ne peut pas élargir** : un manager qui vise l'équipe d'un
  autre obtient zéro, jamais les apprenants de cet autre.
- ⬜ **S8.6** « Sur votre périmètre » pour un manager, « ensemble de la plateforme » pour un
  admin (#219 : ce test était constamment faux).
- ⬜ **S8.7** Bascule **Formations / Tutos** dans l'écran, sans changer de page.
- ⬜ **S8.8** Mention « données disponibles depuis » et **nombre de comptes exclus** affichés.
- ⬜ **S8.9** Trier une colonne du tableau **ne recharge pas** les indicateurs.
- ⬜ **S8.10** « Utilisateurs actifs » ne recopie plus le total (#163).
- ⬜ **S8.11** Couper le réseau : l'écran **dit** que les indicateurs n'ont pas pu être
  chargés, il n'affiche pas des zéros — un zéro se lit comme un désengagement.

## S9 · Cockpit apprenant × formation (#61, #182)

- ⬜ **S9.1** Cliquer une formation ouvre son cockpit ; en changer **ne fait pas perdre**
  la personne.
- ⬜ **S9.2** Les huit indicateurs sont cohérents avec la réalité d'un compte de test.
- ⬜ **S9.3** Timeline bornée à **60 jours**, et le bouton lève la borne.
- ⬜ **S9.4** **Les deux règles de rétention sont écrites à l'écran** : dossier de formation
  sans limite d'ancienneté, activité sur l'horizon du rôle.
- ⬜ **S9.5** Le nombre de mois affiché **dépend du rôle** : 24 mois pour un manager, « tout
  l'historique disponible » pour un admin.
- ⬜ **S9.6** ⚠️ **Le cœur de #182 :** sur un apprenant dont l'activité est ancienne, le
  **dossier de formation reste complet** — quiz, achèvements, certificats — alors que la
  mesure d'activité, elle, est plafonnée. Le vérifier demande un compte à l'historique long ;
  c'est le scénario le plus difficile à monter et le plus important du lot.
- ⬜ **S9.7** Horizon **modifiable** dans Paramètres, effet immédiat.
- ⬜ **S9.8** Un apprenant hors périmètre : message de périmètre, pas liste vide.

## S10 · Navigation pilotage (#68)

- ⬜ **S10.1** La navigation est réduite aux écrans de pilotage, sans écran « Rôle ».
- ⬜ **S10.2** La bascule **Pilotage / Apprentissage** s'allume correctement sur **tous** les
  écrans `/pilotage/*` — elle les ignorait, et « Apprentissage » s'allumait sur
  « Mes collaborateurs ».
- ⬜ **S10.3** « Pilotage » mène au **dashboard unique**, quel que soit le rôle managérial.
- ⬜ **S10.4** Se déconnecter depuis un écran de pilotage fonctionne.

---

## Ce que les tests ne peuvent pas voir

Les suites automatisées sont vertes, et cela ne remplace pas cette recette. Quatre raisons,
toutes vécues sur ce lot :

1. **Les tests tournent sur SQLite, la production sur MySQL.** Un `UPDATE … WHERE NOT EXISTS
   (SELECT … FROM la_même_table)` passait dix tests et se faisait refuser par MySQL
   (erreur 1093). Voir S3.4.
2. **`Mail::fake()` n'affiche pas un gabarit.** Un `@if` collé à un mot faisait échouer toute
   création de compte en 500 sans qu'aucun test ne s'en aperçoive. Voir S4.2.
3. **Un test vérifie ce qu'on lui demande de vérifier.** Les filtres du dashboard étaient
   ignorés en silence et aucun test ne l'a signalé — parce qu'aucun ne le demandait. C'est en
   câblant l'écran que ça s'est vu. Voir S8.4.
4. **Un contrôle de rôle absent ne se voit qu'avec le bon compte.** Les référentiels étaient
   illisibles pour tout manager non-admin, mais la recette faite avec un compte admin ne
   l'aurait jamais montré. Voir S1.4 — **à jouer avec un sub_manager.**

## Ordre conseillé

S1 → S2 → S3 (données et rôles d'abord : tout le reste en dépend) → S5 → S6 → S7 → S8 → S9
→ S10, en terminant par S4 qui demande une vraie boîte mail.

## Convention

Une issue reste **ouverte** tant que son scénario n'est pas validé sur staging. Chaque écart
constaté devient un commentaire sur l'issue concernée — et, s'il a un impact fort, entre dans
son critère d'acceptation, pas seulement en commentaire.
