# Décisions à prendre

*Points relevés pendant le développement qui ne sont pas des tâches de
code en attente, mais de vraies questions ouvertes — trancher ces
questions débloque le code correspondant, listé dans
`documentation/backlog-v2.md`. Contrairement au backlog (qui explique
pourquoi un point a été laissé de côté), ce document liste ce qu'il
faut décider pour pouvoir l'attaquer.*

## Résolu — Module 3, f) Gestion RH & données (2026-08-07)

Les six sous-points du §6.2.f sont maintenant tous traités : archivage,
liste des dossiers, journal (livrés le 2026-08-04, cf.
`documentation/avancement-lot3.md`), et les quatre restants tranchés
avec le client le 2026-08-07 :

- **Fusion de doublons → prévention à la source, livrée.** Plutôt qu'un
  écran de fusion a posteriori, `InscriptionService::admettre()` et
  `FicheEnfantAdminService::creerEtAjouterResponsable()` cherchent
  désormais une correspondance existante (même numéro de mobile ou
  même email — le mobile est jugé fiable en Côte d'Ivoire, une ligne
  fixe partagée y est rare) avant de créer un nouveau
  `ResponsableLegal`. Réutilisation automatique si trouvée, mise à jour
  de la fiche avec les valeurs les plus récentes ("on garde toujours la
  valeur de la fiche récente"). Détail dans `documentation/backlog-v2.md`.
- **Import initial Excel → confirmé usage unique** (migration
  ponctuelle d'une crèche qui reprend ses données, pas un import
  récurrent). Pas de code à ce stade : une commande CLI (comme
  `app:export:reversibilite`) suffira le jour d'une vraie migration,
  format de fichier à définir à ce moment-là.
- **Export complet → abandonné** (portée jugée trop floue par le
  client pour être attaquée en l'état).
- **Modèles de documents → abandonné** (une seule ligne du cahier des
  charges sans élaboration, pas d'usage concret identifié).
- **Suivi des notifications échouées → arbitré et livré.** Décision
  client : tracer les échecs uniquement pour les notifications jugées
  importantes — sécurité/accès enfant (incident signalé, personne
  autorisée activée/refusée), relance de facture impayée, messagerie
  équipe↔parent. Photos/vidéos et propositions entre parents restent en
  best-effort silencieux (niveau `warning`, comme aujourd'hui). Détail
  dans `documentation/backlog-v2.md`.

**Module 3, f) entièrement clos.**

## Résolu — Module 4, b) Génération des factures (2026-08-04)

Bibliothèque PDF : **Dompdf**, tranché avec l'utilisateur. `Contrat` a
gagné `modeFacturation` (`ModeFacturation`) et `ligneTarifaire`
(`?GrilleTarifaire`). Détail complet dans
`documentation/avancement-lot4.md`.

## Module 7 — Exigences d'exploitation (§10, évalué le 2026-08-05)

Modules 5 (Pilotage/Reporting) et 6 (Communication générale) sont
livrés (cf. `documentation/avancement-lot5.md`,
`documentation/avancement-lot6.md`) — tous les modules fonctionnels
(§4 à §9) sont couverts. §10 est le dernier bloc du cahier des
charges, mais contrairement aux modules précédents ce n'est
**presque entièrement pas du code applicatif** : c'est un tableau
d'exigences chiffrées pour évaluer/comparer un prestataire
d'hébergement/exploitation (le document le dit explicitement : "pour
qu'un prestataire puisse être évalué et comparé sur des bases
communes"), et la quasi-totalité de ses lignes sont marquées "à
définir" dans le cahier des charges lui-même.

Évaluation ligne par ligne :

| Ligne §10 | Nature | Verdict |
| --- | --- | --- |
| Disponibilité visée, RPO, RTO, durée de conservation des sauvegardes, fréquence des sauvegardes | Infra/hébergeur | Hors scope de ce dépôt (pas de code applicatif à écrire) |
| Temps de réponse des écrans | Perf/SLA, pas une fonctionnalité | Hors scope tant qu'aucun objectif chiffré n'est fixé ; rien n'indique un problème de perf actuel (volumétrie mono-site faible) |
| Navigateurs / appareils pris en charge | QA/tests, pas une fonctionnalité | Hors scope (pas de framework CSS/JS, §2 — déjà le choix le plus compatible) |
| Volumétrie prévue | Donnée métier à chiffrer avec le client | Hors scope code |
| **Taille maximale des fichiers uploadés** | Code applicatif | **Déjà fait** — 8 Mo max + détection MIME via `finfo` (whitelist pdf/jpg/png), dupliqué dans `DocumentAdministratifService`, `MouvementFondateurService`, `PhotoService`, `DepenseService` |
| **Durée de conservation des journaux techniques** (+ §2.4 pour les autres catégories : suivi quotidien, photos, dossier administratif, factures, messagerie) | Code applicatif, arbitré et livré (2026-08-05) | **Fait** — voir détail ci-dessous. Journal d'audit exclu (voir arbitrage), factures exclues (aucune durée chiffrée). |
| **Réversibilité en fin de contrat (export complet CSV/JSON)** | Code applicatif, mais bloqué | Cf. section "Export complet" ci-dessus (Module 3, f) — déjà identifié comme bloqué sur 3 portées possibles différentes ; les exports CSV déjà livrés (Module 4 i, Module 5 statistiques) couvrent un usage métier ponctuel, pas cette exigence contractuelle de portée globale |
| Environnements distincts (dev/recette/prod) | Config/déploiement | Déjà largement couvert par les mécanismes Symfony standards (`.env`/`.env.local`, `when@test` dans `security.yaml`) ; le reste (recette séparée, CI/CD) est de l'infra, pas du code métier |
| Procédure de restauration documentée et testée, politique de mise à jour des dépendances, support utilisateur, formation, documentation utilisateur par rôle, checklist de recette | Process/organisationnel | Hors scope de ce dépôt — livrables documentaires/contractuels, pas du code |
| **Supervision applicative (alertes en cas de panne)** | Code applicatif, non bloqué | **Fait** (2026-08-05) : `GET /healthz` (`src/Controller/HealthController.php`), non authentifié (`PUBLIC_ACCESS`, un outil de monitoring n'a pas de session applicative), vérifie la connexion base de données via `Doctrine\DBAL\Connection::executeQuery('SELECT 1')`, retourne `{"status":"ok"}` (200) ou `{"status":"error"}` (503), aucune information sensible dans la réponse. |

### Purge/rétention automatique — arbitré et livré (2026-08-05)

Deux arbitrages pris avec l'utilisateur avant de coder :
1. **Durées** : reprendre telles quelles les durées proposées au §2.4
   (6 mois pour le suivi quotidien, 6 mois — borne haute de "3 à 6
   mois" — pour les photos, 1 an pour le dossier administratif et la
   messagerie), plutôt que d'attendre une validation juridique ou de
   ne coder qu'un mode rapport permanent.
2. **Journal d'audit (`JournalAudit`) exclu de la purge** : conçu dès
   le départ comme non-supprimable pour l'intégrité de la piste
   d'audit — le §2.4 demandait une suppression après ~1 an, mais
   l'invariant existant a été jugé prioritaire (cohérent avec le motif
   même de sa conservation, "détection d'incidents"). Les **factures
   restent également exclues** (aucune durée chiffrée au §2.4, "durée
   légale locale" non définie).

**`app:purge:donnees --force`** (`src/Command/PurgeDonneesCommand.php`,
premier fichier de `src/Command/`) : purge `Presence`/`Repas`/
`Activite`/`Incident` (6 mois après `Enfant.dateSortie`), `Photo` (6
mois, seulement si **tous** les enfants tagués sont partis — sinon la
photo reste utile à un enfant encore présent), `DocumentAdministratif`
(1 an), `Message` (1 an). Mode rapport seul par défaut (sûr), `--force`
pour supprimer réellement. `src/Service/PurgeService.php` porte la
logique métier ; nouvelles méthodes `compterPourEnfants()`/
`supprimerPourEnfants()` (DQL `DELETE` en masse, nouveau pattern dans
ce repo) sur les repositories du suivi quotidien et de la messagerie ;
réutilise `PhotoService::supprimer()`/`DocumentAdministratifService::supprimer()`
déjà existants pour la suppression des fichiers physiques.

Vérifié en E2E réel (pas seulement en tests unitaires) : un enfant
archivé avec une date de sortie à 8 mois, ayant 1 présence, 1 incident
et une photo taguée uniquement avec lui → rapport correct (1/1/1),
aucune suppression tant que `--force` n'est pas passé (compte inchangé
en base), puis suppression réelle confirmée (lignes **et** fichiers
physiques, y compris la miniature) après `--force` ; un autre enfant
encore actif avec des données du même type n'a pas été affecté.
`php bin/phpunit` : 239 tests, 571 assertions, tous verts.
`make:migration` : aucun changement de schéma détecté (attendu).

**Planification (cron/systemd timer) volontairement hors scope** —
infra/exploitation (§10), pas du code applicatif.

### Export de réversibilité — arbitré et livré (2026-08-05)

Portée déjà arbitrée avec l'utilisateur : export complet de toute la
base, pas un export ciblé (dossier d'un enfant, portabilité RGPD-like,
ou liste CSV opérationnelle — ces deux autres usages, identifiés dans
la section "Export complet" du Module 3 f) ci-dessus, restent non
traités et hors scope de cet incrément).

**`app:export:reversibilite <répertoire-sortie>`**
(`src/Command/ExportReversibiliteCommand.php`,
`src/Service/ExportReversibiliteService.php`) : parcourt
génériquement les métadonnées Doctrine de **toutes les entités** (44
confirmées dans `src/Entity/`) via `EntityManager::getMetadataFactory()`
plutôt que d'écrire un exporteur par entité — premier usage de
l'introspection de métadonnées dans ce repo, justifié par la
complétude automatique garantie quand le schéma évolue. Un fichier
JSON par entité, associations *ToOne exportées comme identifiant,
associations *ToMany (collections) omises (toujours le côté inverse
d'une FK déjà exportée ailleurs). Champs sensibles exclus en dur :
`User.password`, `User.secretDoubleAuthentification`. Fichiers
physiques (`Photo`/`DocumentAdministratif`/`MouvementFondateur`/
`Depense`, tous sous `var/photos/`) copiés tels quels via
`Symfony\Component\Filesystem\Filesystem::mirror()`. Refuse d'écraser
un répertoire de sortie existant non vide. `manifest.json` récapitule
date d'export, nombre de lignes par entité, nombre de fichiers.

Vérifié en E2E réel (pas de test automatisé sur le parcours complet —
aucun précédent de test fonctionnel dans ce repo, mocker 44
`ClassMetadata` n'aurait pas de sens ; seule la normalisation de
valeurs — dates ISO 8601, enums — est testée unitairement) : export
complet sur la base de développement → 44 fichiers JSON générés, un
par entité, comptes exacts vérifiés contre `SELECT COUNT(*)` pour
plusieurs entités (Enfant 3, Facture 4, JournalAudit 64) ; `User.json`
confirmé sans `password` ni `secretDoubleAuthentification` ;
`Facture.json` confirmé avec `enfant_id`/`contrat_id` mais aucune
collection imbriquée ; `Sondage.json` confirmé sans `options`/`votes`
imbriqués (déjà représentés par `SondageOption.sondage_id`/
`SondageVote.sondage_id`) ; `fichiers/` confirmé identique à
`var/photos/` (12 fichiers, diff vide) ; second appel sur le même
répertoire non vide → refusé proprement.

**Révision (2026-08-05, robustesse mémoire)** : la première version
chargeait chaque table entièrement en mémoire (`findAll()` +
`json_encode()` d'un bloc) — un risque réel pour `JournalAudit`, seule
entité append-only à vie de l'app (exclue de la purge), qui grossira
sans limite sur plusieurs années. Corrigé pour écrire **en flux** :
curseur Doctrine (`Query::toIterable()`, pas d'hydratation complète de
la table) + écriture ligne par ligne dans le fichier JSON +
`EntityManager::clear()` tous les 500 enregistrements (vide la carte
d'identité pour ne jamais accumuler l'historique de la session).
Mémoire bornée à un lot, indépendante du nombre de lignes de la table.
Re-vérifié après coup : mêmes 44 fichiers, mêmes comptes, JSON toujours
valide (parsé avec succès par un parseur externe).

**Limite connue et assumée** : ceci élimine le coût d'hydratation ORM
+ tableau PHP complet + `json_encode()` global, qui est la part
dominante du risque mémoire ici. Le tampon bas niveau du driver MySQL/
PDO (requêtes "buffered" par défaut) n'a pas été changé — un
streaming réseau complètement constant nécessiterait de configurer des
requêtes non bufferisées au niveau de la connexion, un changement
global plus risqué (affecterait toute requête concurrente sur la même
connexion) qui dépasse le cadre de cette seule commande. Documenté
dans `documentation/ameliorations-performance.md` si un besoin réel de
streaming réseau bout-à-bout se confirme.

**Conclusion** : tous les points de code identifiés pour le Module 7
sont livrés (taille des fichiers, santé applicative, purge/rétention,
export de réversibilité). Le reste du module (disponibilité,
sauvegardes, RPO/RTO, environnements, support, formation, documentation
utilisateur, checklist de recette) est structurellement hors scope de
ce dépôt (infra/hébergeur, SLA contractuel, organisationnel). **Module
7 clos.**
