# PTA V7.15 — Les affectations sont bornées par le contrat de l’agent ## Le problème traité La fiche agent portait `date_debut_contrat` et `date_fin_contrat` depuis l’origine — saisies, contrôlées l’une par rapport à l’autre, affichées dans les écrans et les PDF. Mais **aucun contrôle de planification ne les lisait** : rien n’empêchait de planifier un agent trois mois avant son arrivée, ou un contrat temporaire l’année suivant son départ. Les conséquences n’étaient pas cosmétiques : ces heures entraient dans les cumuls annuels, dans le quota, dans les contrôles de couverture — un lieu pouvait passer pour couvert par un agent qui n’était pas encore embauché. ## Ce qui change ### 1. Toute écriture de créneau est bornée La règle est posée sur **tous** les chemins qui créent ou déplacent une affectation : | Chemin | Ce qui est contrôlé | | --- | --- | | Enregistrement d’un brouillon | Chaque date saisie | | Modification d’un créneau (fiche agent) | La nouvelle date | | Déplacement / duplication d’un créneau | La date cible | | Copie d’une semaine entière | Les cinq jours ouvrés de la semaine cible | | Application d’une semaine type | Les cinq jours ouvrés de la semaine visée | | Duplication sur une période | Les deux bornes de la période | Le refus est un **422** portant `code: HORS_CONTRAT`, la liste des dates refusées, et une phrase qui nomme la période réellement couverte : > Marc Louis n’est pas sous contrat à cette période : son contrat court à partir du 04/05/2026. > Date(s) refusée(s) : 02/03/2026. La règle vaut pour **tous les motifs**, congés et absences compris : hors contrat, il n’y a rien à décompter. Une borne absente **ouvre**, elle ne ferme jamais : pas de date de fin signifie contrat à durée indéterminée ; pas de date de début (fiche ancienne) signifie qu’aucune borne basse n’est opposée, plutôt que d’inventer une date. ### 2. La validation révèle les affectations déjà hors contrat Les gardes ci-dessus empêchent d’en créer de nouvelles ; elles ne disent rien de celles qui existaient avant. Un contrôle bloquant `HORS_CONTRAT` rejoint donc les quatre contrôles de validation existants, à la validation d’une semaine comme à celle d’une année entière : > L’affectation #824 du 2026-05-11 est en dehors des dates de contrat de l’agent. La comparaison est faite en SQL sur la fiche agent jointe : c’est l’état du contrat **au moment du contrôle** qui fait foi. ### 3. Le contrat ne peut plus glisser sous des affectations existantes Contrôle miroir, sans lequel le premier ne servirait à rien : modifier les dates de contrat d’un agent est refusé si cela laisserait des affectations au-dehors. Le message dit combien et sur quelle période, pour qu’on sache quoi retirer : > Ces dates de contrat laisseraient 6 affectation(s) en dehors du contrat, entre le 11/05/2026 et le > 14/08/2026. Supprimez-les d’abord, ou élargissez les dates. ## Vérifications effectuées Onze cas exécutés sur la pile, tous conformes : saisie avant le début de contrat (refusée), dans le contrat (acceptée), sur la semaine à cheval contenant le premier jour (acceptée), copie de semaine hors contrat, saisie après la date de fin, déplacement et modification de créneau vers une date hors contrat, raccourcissement de contrat sous des affectations existantes, contrôle de validation sur une donnée devenue illégitime, application d’une semaine type sur une semaine puis sur une période hors contrat. ## Fichiers | Fichier | Rôle | | --- | --- | | `app/Models/AgentModel.php` | `contractRange()`, `contractCoversDate()`, `datesOutsideContract()`, `contractViolationMessage()`, `entriesOutsideContract()` | | `app/Controllers/PlanningController.php` | Garde sur les quatre chemins d’écriture, contrôle de validation | | `app/Controllers/WeekTemplateController.php` | Garde sur l’application à une semaine et sur une période | | `app/Controllers/AnnualOverviewController.php` | Même contrôle à la validation annuelle | | `app/Controllers/AgentController.php` | Refus d’un contrat qui orphelinerait des affectations | | `app/Models/PlanningModel.php` | `entriesOutsideContract()` pour le contrôle de validation | Aucune migration : les colonnes existent depuis le socle, seule leur exploitation change. ## Point d’attention La garde est **dupliquée** dans `PlanningController` et `WeekTemplateController`, faute de base commune entre contrôleurs — comme l’est déjà `parseWeek()`. Ce qui n’est pas dupliqué, c’est le message : il est construit par `AgentModel::contractViolationMessage()`, pour que l’utilisateur lise la même phrase quel que soit l’écran. **Toute nouvelle voie d’écriture de créneaux doit poser la même garde** — le contrôle de validation n’est qu’un filet, pas un substitut.