# PTA V7.27 — Le Service Enfance distribue les jetons des agents et des responsables ## Le problème traité Depuis la V7.11, tout compte d’accès passait par la DSI : création, jeton perdu, départ d’un remplaçant. Or c’est le **Service Enfance** qui sait qu’un agent est arrivé, qu’un responsable a laissé son jeton chez lui ou qu’un contrat s’arrête. Le détour par la DSI ajoutait un délai — parfois plusieurs jours — sans ajouter le moindre contrôle : la DSI ne pouvait rien vérifier de ce qu’on lui demandait, elle exécutait. ## Ce qui change L’onglet **Administration** du Service Enfance gagne une section « Jetons d’accès des agents et des responsables », sous les horaires de couverture. On y fait, pour ces deux rôles, ce que la page DSI permet : - **créer** un compte pour un agent ou un lieu qui n’en a pas encore ; - **régénérer** un jeton perdu ou compromis — l’ancien cesse aussitôt de fonctionner ; - **désactiver** puis réactiver un compte (départ temporaire) ; - **supprimer** un compte définitivement ; - **générer en lot** les jetons de toutes les identités actives qui n’en ont pas. Comme sur la page DSI, un jeton produit n’est lisible **qu’une fois**, dans le panneau « Jetons à transmettre » : la base n’en garde qu’une empreinte SHA-256. Perdu, il ne se retrouve pas — il se régénère. ## Ce qui ne change pas : la frontière de privilège La délégation s’arrête à **deux rôles : AGENT et RESPONSABLE_STRUCTURE**. Les comptes **SERVICE_ENFANCE** et **DSI** n’apparaissent pas dans cette section, ne sont pas créables, et toute action qui en désigne un est refusée en **403**. Ce n’est pas une commodité d’affichage : - un compte Service Enfance créé par le Service Enfance serait une **auto-promotion** — le rôle se distribuerait lui-même, sans contrôle extérieur ; - un compte DSI créé par le Service Enfance lui ouvrirait le **journal d’activité**, c’est-à-dire la trace de ses propres actions. Le **second facteur** de la V7.12 reste hors de portée : ces deux rôles n’ont pas de mot de passe, et le Service Enfance n’a aucun moyen d’en effacer un. La réinitialisation de mot de passe reste une action DSI, sur la page DSI. La borne est posée **des deux côtés** : | Où | Quoi | | --- | --- | | `AccessAccountController::ROLES_DELEGUES` | La liste blanche PHP, contrôlée avant toute écriture et à la relecture de tout compte désigné | | `CompteModel::lister(['roles' => …])` | La liste SQL est bornée ; le filtre d’interface ne peut que **restreindre** à l’intérieur de cette borne | | `migration_v7_9h_jetons_service_enfance.sql` | La permission `can_manage_field_access`, accordée au seul Service Enfance | Une frontière de privilège ne doit pas dépendre d’un seul des deux côtés : une ligne mal saisie en base ne doit pas pouvoir ouvrir la création de comptes privilégiés, et inversement. ## Traçabilité Toutes les actions sont journalisées en catégorie `ADMINISTRATION`, avec le rôle de l’acteur et un contexte `canal = DELEGATION_SERVICE_ENFANCE` qui les distingue des mêmes actions faites par la DSI. Les **refus** le sont aussi, en `ACCES_REFUSE` : `ROLE_HORS_PERIMETRE` (création tentée sur un rôle privilégié) et `COMPTE_HORS_PERIMETRE` (action tentée sur un compte privilégié). La DSI garde donc la lecture complète de ce que le Service Enfance fait des accès. ## Fichiers | Fichier | Rôle | | --- | --- | | `database/migration_v7_9h_jetons_service_enfance.sql` | Permission `can_manage_field_access`. Aucune page nouvelle : « Administration » est déjà ouverte au Service Enfance | | `app/Controllers/AccessAccountController.php` | Les cinq endpoints, la liste blanche et les refus | | `api/acces_comptes.php`, `acces_compte_creer.php`, `acces_compte_jeton.php`, `acces_compte_statut.php`, `acces_comptes_manquants.php` | Points d’entrée minces | | `app/Models/CompteModel.php` | Filtre `roles` sur `lister()` | | `app/Views/partials/administration.php` | La section et le panneau des jetons | | `assets/js/access-tokens.js` | Module `accessTokens` | ## V7.28 — Couper l’accès depuis la fiche des agents La page **Agents** gagne, à côté de « Modifier », un bouton qui **coupe ou rétablit l’accès** de l’agent : « Couper l’accès » / « Rétablir l’accès ». C’est le geste courant — un départ, un arrêt long, un retour — là où la section « Jetons d’accès » de l’onglet Administration porte la distribution des jetons elle-même. Ce bouton **ne touche pas au jeton** : désactiver puis réactiver rend le *même* jeton valable, et l’agent n’a rien à recevoir. Il ne touche pas non plus à la fiche de l’agent, qui reste planifiable — c’est l’accès à l’application qui est coupé, pas la personne au planning. Un agent qui n’a pas encore de compte affiche un bouton inerte « Sans compte d’accès », qui renvoie vers Administration : seul cet écran sait afficher un jeton fraîchement produit. Aucun droit nouveau : le module `agentAccess` réutilise `api/acces_comptes.php` et `api/acces_compte_statut.php`, donc les mêmes gardes, la même liste blanche de rôles et la même journalisation. L’état des comptes n’est **pas** rendu par PHP mais chargé par le module — le HTML servi ne porte aucune information sur les comptes, comme sur l’écran DSI. ## V7.29 — Un accès coupé retire l’agent des listes d’affectation Couper l’accès d’un agent le retire désormais des listes **où l’on affecte** : | Écran | Effet | | --- | --- | | Saisie (`api/agents.php` → `AgentModel::listForLocation()`) | L’agent n’est plus proposé dans le sélecteur | | Équipes (`PageController::teams()` → `allSelectable()`) | L’agent n’est plus proposé à la composition | Trois bornes, toutes portées par le même `NOT EXISTS` de `AgentModel::SANS_COMPTE_COUPE` : - **un agent SANS compte reste proposé.** Il n’a pas encore reçu son jeton, ce qui est l’état normal avant distribution : le filtrer viderait les listes d’une installation neuve ; - le filtre porte sur `compte_acces.actif`, **pas** sur `agent.actif` : fermer une fiche d’agent et couper un accès restent deux décisions distinctes ; - les écrans de **consultation** (planning par agent, vue annuelle, PTA détaillé) et le **référentiel Agents** continuent de montrer l’agent. Sans quoi on ne pourrait plus ni relire son année passée, ni lui rétablir son accès — le bouton vit précisément sur cette page. **Ce qui n’est pas défait :** les créneaux déjà saisis et les équipes déjà composées restent en place. Couper un accès n’efface rien du passé. En revanche, un lien profond qui désigne un agent coupé (file de validation, tableau de bord) ne peut plus le sélectionner : l’écran de saisie le dit alors explicitement, plutôt que de rester vide — rétablir l’accès rend la saisie de nouveau possible. ## Correctif embarqué `CompteModel::lister()` répétait le paramètre nommé `:q` quatre fois dans sa clause de recherche. PDO tourne ici **sans émulation de requêtes préparées** (`ATTR_EMULATE_PREPARES = 0`) : MariaDB refuse alors un même paramètre employé plusieurs fois. **Toute recherche par nom, matricule ou début de jeton échouait en 500 sur la page DSI**, depuis la V7.11. Les marqueurs sont désormais numérotés (`:q_0` … `:q_3`). Le même champ de recherche existant dans la nouvelle section, le défaut aurait été reconduit tel quel.