# Séances formateur : pointage, journal et suivi (spécification)

Date : 2026-09-24. Statut : validé en conversation, **révisé le 2026-09-24** après relecture critique (simplifications validées par le porteur de projet, voir § 10).

## 1. Objectif

Donner au formateur un **pointage de séance** simple (arrivée, puis clôture) qui sert en même temps de **cahier de séance** (ce qu'il prévoit, ce qu'il a fait) et d'**appel** de la classe. En retour :

- l'admin et le gestionnaire suivent l'activité réelle des formateurs (heures, régularité, bilans, retards) ;
- les rapports de fin de formation sont enrichis (heures dispensées, journal des séances).

Critères de réussite :

1. Un formateur ouvre sa séance en un clic depuis son tableau de bord et la clôture en moins d'une minute, y compris sur téléphone.
2. L'admin voit en direct qui est en séance, et sur un mois : heures par formateur, jours sans séance, clôtures tardives.
3. Le rapport PDF d'une formation contient la synthèse des heures et le journal des séances.

Hors périmètre (décidé) : pauses/reprises, plusieurs séances de la même formation le même jour, géolocalisation, validation des heures par un tiers.

## 2. Décisions actées avec le porteur de projet

| Sujet | Décision |
|---|---|
| Pointage | Ouverture (heure d'arrivée automatique + prévision) puis clôture (durée proposée, corrigeable + appel + bilan) |
| Pauses / reprises | Aucune |
| Oublis | Rattrapage jusqu'à 7 jours, marqué « a posteriori » ; séance non clôturée le jour même = « clôture tardive » |
| Appel | Intégré à la clôture, même mécanisme que « Marquer présent » (page Présence). Toute correction ou rattrapage d'appel passe par la page Présence |
| Leçons | Celles planifiées ce jour-là dans le Programme (pas de choix manuel) |
| Présents | Toujours lus en direct dans `presences` (jamais figés) |
| Suivi | Une seule page « Séances » : le formateur y voit les siennes, le gestionnaire son centre, l'admin tout ; partenaire = total d'heures agrégé uniquement |
| Correction | Durée et bilan. Formateur : le jour même de la clôture ; ensuite admin seulement, tracé |

## 3. Données

### 3.1 Table `seances`

| Colonne | Type | Notes |
|---|---|---|
| `id` | bigint | |
| `formateur_id` | FK `users` | le formateur qui pointe |
| `formation_id` | FK `formations` | une formation qu'il enseigne |
| `centre_id` | FK `centres` | centre du formateur au moment de l'ouverture (instantané) |
| `date` | date | jour de la séance |
| `ouverte_le` | datetime nullable | heure d'arrivée (automatique) ; `null` pour une séance rattrapée |
| `prevision` | text nullable | « ce que je prévois aujourd'hui », max 2000 |
| `cloturee_le` | datetime nullable | `null` = séance en cours ; pour un rattrapage, moment de la saisie |
| `duree_minutes` | smallint nullable | renseignée à la clôture, 1 à 720 |
| `bilan` | text nullable | ce qui a été fait, observations, max 4000 |
| `nb_inscrits` | smallint nullable | instantané des inscriptions actives à la clôture (dénominateur de l'assiduité) |
| `saisie_a_posteriori` | boolean | créée par le rattrapage |
| `cloture_tardive` | boolean | clôturée un autre jour que `date` |
| `corrigee_par_id` | FK `users` nullable | dernier admin ayant corrigé |
| `corrigee_le` | datetime nullable | |
| timestamps, softDeletes | | pas de suppression physique (règle du projet) |

Contrainte d'unicité : `(formateur_id, formation_id, date)`.

### 3.2 Données dérivées (jamais stockées sur la séance)

- **Leçons de la séance** : les `lecons_du_jour` planifiées à `date` pour `formation_id` dans `centre_id`. Le Programme est la seule référence : si le formateur a fait autre chose, il déplace la date dans le Programme ou l'écrit dans son bilan. Cohérent avec l'appel, qui valide ces mêmes leçons aux apprenants présents.
- **Présents** : les `presences` à `true` du jour pour les inscriptions de la formation dans le centre. Toujours à jour, y compris après une correction depuis la page Présence. Limite connue : la présence est par jour et par formation ; deux formateurs de la même formation, même centre, même jour, affichent les mêmes présents.

### 3.3 Invariants

- `date` n'est jamais dans le futur.
- **Une seule séance ouverte à la fois** par formateur (`cloturee_le` null). Ouvrir une nouvelle séance alors qu'une autre est ouverte est refusé avec un message qui invite à la clôturer.
- La formation doit être l'une de celles du formateur (`formateur_formations`).
- Si la formation a une période (`date_debut`/`date_fin`), la date de la séance doit être dedans (règle `DansLaPeriodeDeLaFormation`).

## 4. Parcours du formateur

### 4.1 Carte « Ma séance du jour » (tableau de bord formateur, en tête)

États :

1. **Aucune séance en cours** : bouton « Commencer la séance ». Si une séance d'un jour précédent est restée ouverte, la carte affiche « Séance du JJ/MM non clôturée » avec le bouton « Clôturer ».
2. **En cours** : « En cours depuis HH:MM » + durée écoulée (mise à jour chaque minute côté client), rappel de la prévision, bouton « Clôturer la séance ».
3. **Clôturée(s) aujourd'hui** : résumé par séance (« 3 h 15 · 12/15 présents »), bouton « Modifier » tant que c'est permis, et « Commencer une autre séance » s'il enseigne d'autres formations pas encore pointées aujourd'hui.

Sous la carte : « Saisir une séance passée » et « Mes séances ». Les messages de confirmation s'affichent en tête du tableau de bord.

### 4.2 Ouvrir (modale « Commencer la séance »)

- Formation : `Select`, remplacé par le titre si une seule formation reste possible aujourd'hui.
- « Au programme aujourd'hui » : les leçons planifiées ce jour (lecture seule), ou « Aucune leçon planifiée aujourd'hui ».
- Prévision : zone de texte libre.
- Validation → `ouverte_le = now()`, `date = today`.

### 4.3 Clôturer (modale « Clôturer la séance »)

- **Durée** : proposée = minutes écoulées depuis `ouverte_le`, arrondie au quart d'heure (15 min à 12 h) ; saisie en heures + minutes. En clôture tardive, pas de proposition : saisie obligatoire.
- **Appel** : apprenants à inscription active de la formation dans le centre ; cochés d'office ceux déjà présents à la date de la séance ; bouton « Tous présents ». Une phrase rappelle que les apprenants cochés sont marqués présents et que les leçons du jour leur sont validées. L'appel est rechargé à l'ouverture de la modale. À l'enregistrement, le client envoie `presents` (cochés) et `absents` (affichés présents puis décochés) : un coché qui n'était pas présent → `ValiderPresenceParFormateur::execute(..., true)` ; un `absent` qui était présent → `execute(..., false)`. **Une absence n'est jamais déduite d'un oubli** : un apprenant qui coche lui-même pendant la séance n'est jamais effacé (correction issue de la relecture finale).
- **Bilan** : zone de texte libre.
- Validation → `cloturee_le = now()`, `duree_minutes`, `bilan`, `nb_inscrits`, `cloture_tardive = (jour de la clôture ≠ date)`.

### 4.4 Modifier une séance clôturée

Durée et bilan uniquement, dans la modale de détail de la séance. Formateur : le jour même de la clôture, ensuite bouton masqué et refus serveur (403). Les présences se corrigent sur la page Présence.

### 4.5 Rattrapage (« Saisir une séance passée »)

Formation (si plusieurs), jour (de J-7 à J-1, `DatePicker` borné), durée, bilan. `ouverte_le = null`, `saisie_a_posteriori = true`. Après l'enregistrement, l'app ouvre la page Présence sur cette formation et ce jour, avec le message « Séance du JJ/MM enregistrée. Faites maintenant l'appel de ce jour-là. »

## 5. Page « Séances » (formateur, gestionnaire, admin)

Une seule page, route `seances.index`, contenu adapté au rôle.

- **Navigation par mois** (◀ Octobre 2026 ▶), jamais au-delà du mois courant.
- **Formateur** : ses séances. Tuiles heures + séances du mois ; liste.
- **Gestionnaire (son centre) et admin (tous centres)** :
  - filtres centre (admin) et formateur ;
  - tuiles heures, séances, « en séance maintenant » (rafraîchie toutes les 30 s sur le mois courant, avec les noms), « à surveiller » (clôtures tardives, saisies a posteriori, séances d'un jour passé encore ouvertes) ;
  - tableau **Par formateur** : heures, séances, assiduité moyenne (moyenne de présents / inscrits), **jours sans séance** (couples formation × jour du mois, jusqu'à la veille puisque la journée en cours n'est pas terminée, où une leçon était planifiée dans son centre pour une de ses formations sans séance de sa part pour cette formation).
- **Liste des séances** : date, formateur (suivi), formation, arrivée, durée, présents/inscrits, badges d'état (`En cours`, `Clôturée`, `Tardive`, `A posteriori`, `Corrigée`). « Voir » → modale de détail (prévision, leçons, bilan, noms des présents), avec le formulaire de correction quand il est permis.

Tableau de bord partenaire/admin : tuile « Heures dispensées en {mois} » (du 1er du mois de la date consultée jusqu'à cette date), et une ligne « Heures ce mois » sur chaque carte de centre. Aucun texte libre exposé.

## 6. Rapport de formation enrichi

Dans « Rapports », quand une formation est choisie : boutons « Rapport de formation (PDF) » et « Journal (CSV) » ; centre et période optionnels (période par défaut = période de la formation si fixée, sinon tout).

PDF `pdf/rapport-formation.blade.php` :

1. En-tête : formation, centre(s), période, formateur(s).
2. Synthèse : heures dispensées, nombre de séances, leçons traitées en séance / leçons prévues à ce jour, assiduité moyenne, progression moyenne.
3. Journal des séances : date, formateur, durée, présents/inscrits, leçons, bilan (tronqué à 600 caractères).
4. Apprenants : reprise du tableau du rapport actuel (progression, assiduité).

CSV : une ligne par séance, neutralisation `EscapesCsvFormulas` sur les textes. Chaque export est journalisé dans `rapports_generes`.

## 7. Autorisations (`SeancePolicy`)

| Action | Admin | Gestionnaire | Formateur | Secrétaire | Partenaire |
|---|---|---|---|---|---|
| Ouvrir / clôturer / rattraper | non | non | ses formations, son centre | non | non |
| Corriger (durée, bilan) | oui, toute séance clôturée | non | la sienne, le jour de la clôture | non | non |
| Page Séances, détail | tout | son centre | les siennes | non | non |
| Rapport de formation | oui | son centre | ses formations | non | non |
| Heures agrégées (dashboard) | oui | non | non | non | oui |

Tout identifiant venant de l'URL est revérifié par la Policy. `before()` laisse passer l'admin sauf pour ouvrir, clôturer et corriger (vérifiés dans les méthodes : un admin ne pointe jamais, et ne corrige qu'une séance clôturée).

## 8. Découpage technique

- Migration `create_seances_table`.
- Modèle `Seance` (casts, relations, scope `visibleTo($user)`, `formaterDuree()`), relation `User::seances()`.
- Actions : `Seances\OuvrirSeance`, `Seances\FaireAppel`, `Seances\CloturerSeance`, `Seances\SaisirSeancePassee`, `Seances\SerialiserSeances` (format commun + présents et leçons dérivés en requêtes groupées), `Seances\DonneesCarteSeance`.
- Contrôleur `Staff\SeanceController` (store, cloturer, update, rattrapage, index) ; extension de `RapportController` ; `DashboardController::formateur()` ; `Partenaire\DashboardController`.
- Form Requests dédiées, `SeancePolicy`.
- React : `Staff/Dashboard/Formateur.jsx` (carte + messages), `Staff/Seances/Index.jsx`, partiels de `Staff/Seances/Partials/`, boutons dans `Staff/Rapports/Index.jsx`, entrée « Séances » dans `LIENS_PAR_ROLE` (admin, gestionnaire, formateur). `FlashStatus` ajouté à `Staff/Presences/Index.jsx` (message du rattrapage et de « Marquer présent », jamais affiché jusqu'ici).

## 9. Tests (PHPUnit)

- Ouverture : heure enregistrée, refus si une séance est déjà ouverte, refus même formation le même jour, refus formation non enseignée, refus hors période, admin et gestionnaire refusés.
- Clôture : durée, `nb_inscrits`, appel qui valide/annule les présences via `ValiderPresenceParFormateur`, clôture tardive (appel à la date de la séance), bornes de durée, identifiants étrangers ignorés.
- Correction : formateur le jour même oui, le lendemain 403 ; admin toujours, tracé ; séance ouverte non corrigeable ; gestionnaire 403.
- Rattrapage : accepté de J-7 à J-1, refusé au-delà et aujourd'hui, marqué a posteriori, redirection vers Présence, jour déjà saisi refusé, hors période refusé.
- Page Séances : formateur limité aux siennes, gestionnaire limité à son centre même avec un filtre forcé, admin tout ; présents lus en direct (corrigés depuis Présence) ; jours sans séance ; en séance maintenant ; secrétaire et partenaire 403.
- Carte : états, séance de la veille restée ouverte, leçons du jour.
- Rapport : synthèse et journal, CSV neutralisé, journalisation, cloisonnement.

## 10. Révision du 2026-09-24 (relecture critique)

Retirés ou fusionnés par rapport à la première version, pour supprimer des incohérences et alléger les écrans :

- Table `seance_modules` et choix manuel des leçons : deux vérités (l'appel valide de toute façon les leçons planifiées du jour aux présents), rapport pouvant afficher « 3 réalisées sur 2 planifiées ».
- `nb_presents` figé : pouvait contredire la liste des noms après une correction depuis la page Présence.
- Appel dans la modification et le rattrapage (et son mode « ne jamais retirer ») : la page Présence fait déjà ce travail.
- Deux pages (« Mes séances » + « Suivi des formateurs ») fusionnées en une, selon la règle du projet « un contrôleur par ressource, la portée gérée par Policy + `visibleTo()` ».
- Période libre du suivi (deux calendriers, raccourcis, filtre formation) remplacée par une navigation par mois.
- Heures d'arrivée et de départ du rattrapage : non vérifiables après coup, remplacées par la durée.
