# Avancement — Lot 2 (Espace Parents, Module 1, §4 du cahier des charges v4.1)

*Mis à jour après chaque fonctionnalité livrée, même principe que
`documentation/avancement-lot1.md` (Lot 1, entièrement traité). Points
hors scope consolidés dans `documentation/backlog-v2.md`.*

## §4.2 Fonctionnalités

| § | Fonctionnalité | État | Détail |
| --- | --- | --- | --- |
| **a)** | **Authentification & compte** | ✅ | Connexion, compte créé par Direction (Lot 1) ; mot de passe oublié livré — 2026-08-03 (self-service de *création* de compte reste hors scope, cf. backlog) |
| **b)** | **Suivi quotidien de l'enfant** | ✅ | `SuiviParentService::filDuJour()`, écran `fil_du_jour.html.twig` (navigation jour précédent/suivant) — 2026-08-03 |
| **c)** | **Photos et vidéos du jour** | ✅ | `SuiviParentService::photosVisibles()` (double vérification consentement), écran `photos_galerie.html.twig` — 2026-08-03 |
| **d)** | **Présence & planning** | ✅ | Historique des présences + déclaration/annulation d'absence (`DeclarationAbsence`) — 2026-08-03 ; consultation du planning prévu débloquée par `Contrat` (Lot 3, 2026-08-03) — voir `documentation/avancement-lot3.md` |
| **e)** | **Personnes autorisées (côté parent)** | ✅ | Proposer une permanente, déclarer une ponctuelle avec réauthentification, validation/refus Direction — 2026-08-03 |
| f) | Messagerie | ✅ | Livré au Lot 1 (§5.2.f), fil de discussion par enfant |
| **g)** | **Notifications** | ✅ | Scopé à 3 événements concrets (personne autorisée activée, incident signalé, nouvelle photo) — 2026-08-03, détail ci-dessous |
| h) | Facturation et services optionnels | ❌ | Bloqué sur le Lot 3/4 (`Facture`, `GrilleTarifaire` n'existent pas) |
| **i)** | **Documents & informations enfant (lecture)** | ⚠️ | Profil enfant en lecture seule fait (`profil_enfant.html.twig`) ; modification soumise à validation pas commencée |

## Détail du premier incrément (livré le 2026-08-03)

Réutilise presque entièrement les repositories déjà construits pour
l'équipe au Lot 1 (`RepasRepository::findDuJourPourEnfant` et consorts,
`PresenceService::personnesAutoriseesPour`, `PhotoService::estBloqueeParConsentement`)
— seule nouveauté réelle : `DeclarationAbsence` (déclarer/annuler une
absence, refuse d'annuler une absence déjà passée).

**Décision de sécurité notable** : le profil enfant et le tableau de bord
**n'affichent jamais** les alertes `RESTRICTION_FAMILIALE_JUDICIAIRE` au
parent (`SuiviParentService::alertesVisibles()`, filtre côté Service, pas
seulement côté template) — le cahier des charges (§6.2.b) dit ces alertes
"visibles pour l'équipe", pas pour les responsables légaux eux-mêmes, qui
peuvent être la personne visée par la restriction. Vérifié manuellement :
une alerte de restriction créée en base pour le test n'apparaît sur aucun
des deux écrans, alors que les alertes allergie/PAI s'affichent normalement.

Nouveau `App\Controller\EspaceParentController` sous `/parents/enfant/{enfantId}`
(même garde `ParentEnfantVoter::ACCEDER_ENFANT` que la messagerie du Lot 1).
`templates/parents/mes_enfants.html.twig` mis à jour pour lier chaque
enfant vers son tableau de bord.

Vérifié manuellement (serveur PHP local + curl, compte parent des
fixtures) : tableau de bord, fil du jour avec navigation, galerie photos
(seule la photo publiée ET consentie apparaît), historique des présences,
déclaration et annulation d'une absence, refus d'annuler une absence
passée, profil enfant avec filtre de restriction vérifié, et accès refusé
(403) sur les 5 routes pour l'enfant d'une autre famille.

## Détail de e) Personnes autorisées côté parent + g) Notifications (livré le 2026-08-03)

Aucune nouvelle entité : `PersonneAutorisee` (Lot 1) couvrait déjà
permanente/ponctuelle, statuts, période de validité — seul le Service
manquait côté parent. e) et g) se combinent naturellement : "personne
autorisée activée" (un événement de g) est littéralement la validation
d'une demande e).

**e)** — `PersonneAutoriseeService` : `proposerPermanente()` (statut
`EN_ATTENTE`, bloque si restriction active), `validerPermanente()`/
`refuserPermanente()` (Direction uniquement, motif obligatoire pour le
refus, tracés dans `JournalAudit` — pas de nouveau champ d'entité pour la
trace de refus), `declarerAutorisationPonctuelleParent()` avec
**réauthentification** (mot de passe revérifié en Service via
`UserPasswordHasherInterface`, pas juste au Contrôleur — même discipline
que l'habilitation médicaments du Lot 1) et fin plafonnée à 23:59:59 du
jour choisi quel que soit ce qui est saisi (§12 "ne reste jamais active
par oubli" — vérifié manuellement avec une fin demandée 2 jours plus tard,
correctement ramenée au jour même).

Écran parent : `EspaceParentController` (`/parents/enfant/{id}/autorisations`,
même contrôleur que présences/absences — §4.3 liste "Planning, absences &
autorisations de sortie" comme un seul écran). Écran équipe : ajout d'une
file de validation Direction sur l'écran présences existant
(`PresenceController`, pas un nouvel écran séparé).

**g)** — nouveau `NotificationParentMailer` (même contrat best-effort que
`NotificationMessageMailer` du Lot 1), scopé à 3 événements concrets
plutôt que le "nouveau contenu" générique du cahier des charges :
personne autorisée activée, incident signalé (`IncidentService::declarer()`),
nouvelle photo publiée (`PhotoService::publier()`). "Message reçu" était
déjà couvert depuis le Lot 1.

Vérifié manuellement (serveur PHP local + curl, 3 rôles) : proposition
parent → trace `JournalAudit` → validation Direction → la personne
apparaît dans les listes de pointage de départ ; refus avec motif tracé ;
ponctuelle avec mauvais mot de passe refusée, bon mot de passe acceptée et
plafonnée en fin de journée ; frontières d'accès (éducateur non-Direction
sur valider/refuser : 403 ; parent sur l'enfant d'une autre famille : 403).

## Détail de a) Mot de passe oublié (livré le 2026-08-03)

Générique à **tous les rôles** (parent, éducateur, Direction...), pas
seulement `ROLE_PARENT` : restreindre par rôle n'aurait rien apporté. Le
self-service de *création* de compte (invitation par email) reste hors
scope — seule la réinitialisation d'un mot de passe existant est couverte
(cf. `documentation/backlog-v2.md`).

Nouvelle entité `DemandeReinitialisationMotDePasse` (`user`, `tokenHache`
— SHA-256 du jeton, jamais le jeton en clair en base, même principe que
les mots de passe —, `dateExpiration` à +1h, `utilisee`). Nouveau
`ReinitialisationMotDePasseService` : `demander(string $email)` ne révèle
jamais si l'email correspond à un compte existant (anti-énumération —
même redirection dans tous les cas, vérifié manuellement email connu vs.
inconnu) ; `reinitialiser(string $token, string $nouveauMotDePasse)` lève
`TokenReinitialisationInvalideException` pour un jeton inconnu, déjà
utilisé, ou expiré. Email envoyé via `MailerInterface` injecté
directement (un seul type d'email, pas besoin d'un mailer dédié séparé) ;
échec d'envoi loggé en `error` (plus grave qu'un best-effort de
notification, ça bloque vraiment l'utilisateur) sans jamais remonter au
Contrôleur.

Nouveau `ReinitialisationMotDePasseController` (routes publiques,
`security.yaml` mis à jour avec 2 règles `PUBLIC_ACCESS` avant le filet
deny-par-défaut) ; lien "Mot de passe oublié ?" ajouté à l'écran de
connexion.

Vérifié manuellement (serveur PHP local + curl) : comme le jeton en clair
n'est jamais stocké, un jeton connu a été fabriqué et son hash SHA-256
inséré directement en base pour simuler "l'email reçu" — flux complet
(formulaire → changement de mot de passe → connexion avec le nouveau mot
de passe) fonctionnel ; réutilisation du même jeton refusée ; jeton
expiré refusé (attention : la première tentative de test a révélé un
écart d'horloge de ~2h entre `NOW()` MySQL et l'horloge PHP sur cette
machine — sans impact applicatif puisque l'écriture et la lecture de
`dateExpiration` passent toutes les deux exclusivement par PHP/Doctrine,
mais la fabrication manuelle du jeton expiré devait utiliser l'horloge
PHP, pas celle de MySQL) ; jeton inconnu refusé ; confirmation de mot de
passe différente refusée ; mot de passe trop court refusé.

## Prochaine étape

Reste du Lot 2 : **h) facturation** (bloqué sur le Lot 3/4, `Facture`/
`GrilleTarifaire` n'existent pas encore). Le Lot 2 est sinon
fonctionnellement complet : consultation, personnes autorisées,
notifications, authentification/mot de passe oublié, et depuis le premier
incrément du Lot 3 (2026-08-03), consultation du planning prévu au
contrat — cf. `documentation/avancement-lot3.md`.
