V7.10 — Tableau de bord, guide, semaine type et enveloppe de formation

Reprise dans le répertoire de production du travail mené dans le worktree
ux-service-enfance :

- tableau de bord Service Enfance (dashboard.php) et navigation regroupée ;
- guide d'utilisation (faq.php) accessible depuis toute la navigation ;
- éditeur de semaine type mis en avant sur l'écran de saisie ;
- droit à l'enveloppe de formation par agent, en remplacement de la case
  « formation déjà prévue dans la répartition annuelle » ;
- correctif de « [object Object] » sur les cartes de quota du PTA ;
- motif de travail par défaut à la saisie, heures travaillées complètes
  dans les PDF.

Le docker-compose.yml de production est conservé : celui du worktree décrit
une pile de test (conteneurs et volume suffixés « test », ports 9080/9081),
qui aurait fait redémarrer l'application sur un volume MariaDB vide.

Les migrations database/migration_v7_9*.sql sont obligatoires ; le service
db-migrate les applique au déploiement.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
PTA
2026-08-17 16:53:04 +02:00
parent 57bd8da5c8
commit 4201894e28
69 changed files with 5228 additions and 246 deletions

View File

@@ -0,0 +1,201 @@
-- ============================================================================
-- PTA V7.8 — Sécurisation : les règles d'accès descendent en base
-- ============================================================================
-- À exécuter UNE SEULE FOIS sur une base PTA V7.7 existante :
-- SOURCE database/migration_v7_8_securite_acces.sql;
--
-- Objectif : plus aucune règle de périmètre ni de droit n'est reconstruite en
-- PHP. Le contrôleur pose une question à la base, la base répond oui ou non.
-- Toute évolution des droits se fait désormais en SQL, sans redéploiement.
-- ============================================================================
-- ----------------------------------------------------------------------------
-- 1. MATRICE DES DROITS : rôle -> pages et rôle -> permissions
-- ----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS acces_role_page (
role ENUM('AGENT', 'RESPONSABLE_STRUCTURE', 'SERVICE_ENFANCE') NOT NULL,
page VARCHAR(50) NOT NULL,
ordre_affichage SMALLINT UNSIGNED NOT NULL DEFAULT 100,
PRIMARY KEY (role, page)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS acces_role_permission (
role ENUM('AGENT', 'RESPONSABLE_STRUCTURE', 'SERVICE_ENFANCE') NOT NULL,
permission VARCHAR(50) NOT NULL,
autorise BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (role, permission)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Pages autorisées — reprise à l'identique des droits V7.7.
INSERT INTO acces_role_page (role, page, ordre_affichage) VALUES
('AGENT', 'agents', 10),
('AGENT', 'structures', 20),
('RESPONSABLE_STRUCTURE', 'planning', 10),
('RESPONSABLE_STRUCTURE', 'structures', 20),
('RESPONSABLE_STRUCTURE', 'agents', 30),
('RESPONSABLE_STRUCTURE', 'coverage', 40),
('SERVICE_ENFANCE', 'planning', 10),
('SERVICE_ENFANCE', 'pending', 20),
('SERVICE_ENFANCE', 'coverage', 30),
('SERVICE_ENFANCE', 'structures', 40),
('SERVICE_ENFANCE', 'agents', 50),
('SERVICE_ENFANCE', 'annual', 60),
('SERVICE_ENFANCE', 'pta', 70),
('SERVICE_ENFANCE', 'teams', 80),
('SERVICE_ENFANCE', 'vacations', 90),
('SERVICE_ENFANCE', 'administration', 100)
ON DUPLICATE KEY UPDATE ordre_affichage = VALUES(ordre_affichage);
-- Permissions fonctionnelles — reprise à l'identique des droits V7.7.
INSERT INTO acces_role_permission (role, permission, autorise) VALUES
('AGENT', 'read_only', TRUE),
('AGENT', 'can_edit_draft', FALSE),
('AGENT', 'can_edit_cross_structure', FALSE),
('AGENT', 'can_validate', FALSE),
('AGENT', 'can_reopen', FALSE),
('AGENT', 'can_use_templates', FALSE),
('AGENT', 'can_copy_full_week', FALSE),
('AGENT', 'can_view_pending', FALSE),
('AGENT', 'can_manage_coverage', FALSE),
('AGENT', 'can_manage_agents', FALSE),
('AGENT', 'can_manage_structures', FALSE),
('AGENT', 'can_manage_vacations', FALSE),
('AGENT', 'can_manage_teams', FALSE),
('AGENT', 'can_manage_pta', FALSE),
('AGENT', 'can_forecast_next_year', FALSE),
('RESPONSABLE_STRUCTURE', 'read_only', FALSE),
('RESPONSABLE_STRUCTURE', 'can_edit_draft', TRUE),
('RESPONSABLE_STRUCTURE', 'can_edit_cross_structure', FALSE),
('RESPONSABLE_STRUCTURE', 'can_validate', FALSE),
('RESPONSABLE_STRUCTURE', 'can_reopen', TRUE),
('RESPONSABLE_STRUCTURE', 'can_use_templates', FALSE),
('RESPONSABLE_STRUCTURE', 'can_copy_full_week', FALSE),
('RESPONSABLE_STRUCTURE', 'can_view_pending', FALSE),
('RESPONSABLE_STRUCTURE', 'can_manage_coverage', FALSE),
('RESPONSABLE_STRUCTURE', 'can_manage_agents', FALSE),
('RESPONSABLE_STRUCTURE', 'can_manage_structures', FALSE),
('RESPONSABLE_STRUCTURE', 'can_manage_vacations', FALSE),
('RESPONSABLE_STRUCTURE', 'can_manage_teams', FALSE),
('RESPONSABLE_STRUCTURE', 'can_manage_pta', FALSE),
('RESPONSABLE_STRUCTURE', 'can_forecast_next_year', FALSE),
('SERVICE_ENFANCE', 'read_only', FALSE),
('SERVICE_ENFANCE', 'can_edit_draft', TRUE),
('SERVICE_ENFANCE', 'can_edit_cross_structure', TRUE),
('SERVICE_ENFANCE', 'can_validate', TRUE),
('SERVICE_ENFANCE', 'can_reopen', TRUE),
('SERVICE_ENFANCE', 'can_use_templates', TRUE),
('SERVICE_ENFANCE', 'can_copy_full_week', TRUE),
('SERVICE_ENFANCE', 'can_view_pending', TRUE),
('SERVICE_ENFANCE', 'can_manage_coverage', TRUE),
('SERVICE_ENFANCE', 'can_manage_agents', TRUE),
('SERVICE_ENFANCE', 'can_manage_structures', TRUE),
('SERVICE_ENFANCE', 'can_manage_vacations', TRUE),
('SERVICE_ENFANCE', 'can_manage_teams', TRUE),
('SERVICE_ENFANCE', 'can_manage_pta', TRUE),
('SERVICE_ENFANCE', 'can_forecast_next_year', TRUE)
ON DUPLICATE KEY UPDATE autorise = VALUES(autorise);
-- ----------------------------------------------------------------------------
-- 2. VUES DE DROITS
-- ----------------------------------------------------------------------------
CREATE OR REPLACE VIEW v_acces_role_page AS
SELECT role, page, ordre_affichage
FROM acces_role_page;
CREATE OR REPLACE VIEW v_acces_role_permission AS
SELECT role, permission, autorise
FROM acces_role_permission;
-- ----------------------------------------------------------------------------
-- 3. VUES D'IDENTITÉ
-- ----------------------------------------------------------------------------
-- Identité complète d'un agent actif : sert à valider la sélection de rôle et
-- à construire le bandeau de profil. Un agent inactif n'apparaît jamais ici,
-- il ne peut donc pas être choisi comme identité.
CREATE OR REPLACE VIEW v_acces_identite_agent AS
SELECT
a.id_agent,
a.matricule,
a.nom,
a.prenom,
a.id_structure,
s.nom AS structure_nom,
s.code AS structure_code
FROM agent a
LEFT JOIN structure s
ON s.id_structure = a.id_structure
AND s.actif = TRUE
WHERE a.actif = TRUE;
-- Lieux d'affectation sélectionnables par un responsable de structure.
CREATE OR REPLACE VIEW v_acces_identite_structure AS
SELECT
s.id_structure,
s.code,
s.nom,
s.type_affectation
FROM structure s
WHERE s.actif = TRUE;
-- ----------------------------------------------------------------------------
-- 4. VUES DE PÉRIMÈTRE
-- ----------------------------------------------------------------------------
-- Quels agents un lieu d'affectation a-t-il le droit de consulter ?
-- Deux origines : le rattachement principal, ou une planification effective
-- sur ce lieu. C'est la règle exacte que le PHP reconstruisait à la main.
CREATE OR REPLACE VIEW v_acces_agent_par_structure AS
SELECT DISTINCT
a.id_agent,
a.id_structure AS id_structure_autorisee,
'RATTACHEMENT' AS origine
FROM agent a
WHERE a.actif = TRUE
AND a.id_structure IS NOT NULL
UNION
SELECT DISTINCT
a.id_agent,
p.id_structure AS id_structure_autorisee,
'PLANIFICATION' AS origine
FROM agent a
INNER JOIN planning p ON p.id_agent = a.id_agent
WHERE a.actif = TRUE;
-- Périmètre d'un créneau : à quel lieu et à quel planning appartient-il ?
-- Utilisé avant toute modification ou suppression d'affectation.
CREATE OR REPLACE VIEW v_acces_perimetre_creneau AS
SELECT
c.id_creneau,
c.id_planning,
p.id_structure,
p.id_agent,
p.statut AS statut_planning,
s.nom AS structure_nom
FROM creneau_horaire c
INNER JOIN planning p ON p.id_planning = c.id_planning
INNER JOIN structure s ON s.id_structure = p.id_structure;
-- Périmètre d'un planning : utilisé avant validation ou réouverture.
CREATE OR REPLACE VIEW v_acces_perimetre_planning AS
SELECT
p.id_planning,
p.id_structure,
p.id_agent,
p.statut AS statut_planning,
s.nom AS structure_nom,
a.nom AS agent_nom,
a.prenom AS agent_prenom
FROM planning p
INNER JOIN structure s ON s.id_structure = p.id_structure
INNER JOIN agent a ON a.id_agent = p.id_agent;
-- ============================================================================
-- Vérification recommandée après import :
-- SELECT role, COUNT(*) FROM acces_role_page GROUP BY role;
-- SELECT * FROM v_acces_agent_par_structure LIMIT 10;
-- ============================================================================

View File

@@ -0,0 +1,26 @@
-- ============================================================================
-- PTA V7.9 — Tableau de bord du Service Enfance
-- ============================================================================
-- À exécuter UNE SEULE FOIS sur une base PTA V7.8 existante :
-- SOURCE database/migration_v7_9_tableau_de_bord.sql;
--
-- La page « dashboard » suit la règle posée en V7.8 : une page absente de
-- acces_role_page est refusée. Tant que cette migration n'est pas jouée,
-- l'onglet n'apparaît pas et le Service Enfance atterrit comme avant sur
-- l'écran de saisie — l'application reste fonctionnelle, sans tableau de bord.
--
-- Aucune table, aucune vue, aucune donnée métier n'est modifiée : le tableau de
-- bord ne fait que relire ce que les autres écrans calculent déjà.
-- ============================================================================
INSERT INTO acces_role_page (role, page, ordre_affichage) VALUES
('SERVICE_ENFANCE', 'dashboard', 5)
ON DUPLICATE KEY UPDATE ordre_affichage = VALUES(ordre_affichage);
-- ============================================================================
-- Vérification après import :
-- SELECT * FROM v_acces_role_page WHERE role = 'SERVICE_ENFANCE' ORDER BY ordre_affichage;
--
-- Retour arrière :
-- DELETE FROM acces_role_page WHERE role = 'SERVICE_ENFANCE' AND page = 'dashboard';
-- ============================================================================

View File

@@ -0,0 +1,23 @@
-- ============================================================================
-- PTA — Droit à l'enveloppe annuelle de formation (14 h)
-- ============================================================================
-- Tous les agents ne bénéficient pas de l'enveloppe complémentaire de 14 h de
-- formation. Cette colonne porte ce droit, coché par défaut à la création d'un
-- agent. Un agent sans ce droit ne peut plus se voir affecter le motif
-- « Formation » lors de la saisie des horaires, et son enveloppe de formation
-- au PTA annuel vaut 0.
--
-- La colonne ne porte volontairement pas de clause AFTER : `migration_v7_9c`
-- retire `formation_repartition_annuelle`, qui aurait servi de repère, et le
-- socle ne la crée plus du tout sur une base neuve.
--
-- Nommage : les migrations sont jouées dans l'ordre alphabétique. Le suffixe
-- « 9b » place ce fichier après migration_v7_9_tableau_de_bord.sql, ce qu'un
-- « v7_10 » ou « v7_11 » n'aurait pas fait ('1' trie avant '8' et '9').
--
-- Idempotente : rejouable sans effet de bord.
-- ============================================================================
ALTER TABLE agent
ADD COLUMN IF NOT EXISTS droit_enveloppe_formation BOOLEAN NOT NULL DEFAULT TRUE
COMMENT 'Droit à l''enveloppe annuelle de 14 h de formation';

View File

@@ -0,0 +1,17 @@
-- ============================================================================
-- PTA — Retrait de `agent.formation_repartition_annuelle`
-- ============================================================================
-- Cette colonne portait une seconde case à cocher sur la fiche agent : « une
-- formation est déjà prévue dans la répartition annuelle, ne pas ajouter
-- l'enveloppe complémentaire de 14 h ». Elle faisait doublon avec le droit à
-- l'enveloppe de formation (`droit_enveloppe_formation`, migration v7_9b), seul
-- conservé : l'enveloppe vaut 14 h si l'agent y a droit, 0 sinon.
--
-- Le calcul de `pta_annuel.enveloppe_formation_minutes` ne dépend donc plus que
-- de `droit_enveloppe_formation` (AgentModel::trainingEnvelopeMinutes()).
--
-- Idempotente : rejouable sans effet de bord.
-- ============================================================================
ALTER TABLE agent
DROP COLUMN IF EXISTS formation_repartition_annuelle;

View File

@@ -0,0 +1,30 @@
-- ============================================================================
-- PTA — Guide d'utilisation (page « faq »)
-- ============================================================================
-- Appliquée automatiquement au démarrage par le service db-migrate.
-- Pour un import manuel sur une base existante :
-- SOURCE database/migration_v7_9d_faq.sql;
--
-- Le guide suit la règle posée en V7.8 : une page absente de acces_role_page
-- est refusée. Il est ouvert aux trois rôles — c'est un secours, pas un droit
-- métier — et son bouton d'accès n'apparaît dans la barre de navigation que si
-- cette migration a été jouée.
--
-- La page est purement statique : aucune table, aucune vue, aucune donnée
-- métier n'est touchée. L'ordre d'affichage est indicatif, le bouton étant
-- ancré à droite de la barre plutôt que rangé dans un groupe d'onglets.
-- ============================================================================
INSERT INTO acces_role_page (role, page, ordre_affichage) VALUES
('AGENT', 'faq', 200),
('RESPONSABLE_STRUCTURE', 'faq', 200),
('SERVICE_ENFANCE', 'faq', 200)
ON DUPLICATE KEY UPDATE ordre_affichage = VALUES(ordre_affichage);
-- ============================================================================
-- Vérification après import :
-- SELECT role, page FROM v_acces_role_page WHERE page = 'faq';
--
-- Retour arrière :
-- DELETE FROM acces_role_page WHERE page = 'faq';
-- ============================================================================

View File

@@ -105,7 +105,6 @@ CREATE TABLE agent (
id_poste INT UNSIGNED NULL,
type_contrat ENUM('PERMANENT', 'TEMPORAIRE') NOT NULL DEFAULT 'PERMANENT',
date_fin_contrat DATE NULL,
formation_repartition_annuelle BOOLEAN NOT NULL DEFAULT FALSE,
commentaire_pta VARCHAR(1000) NULL,
id_structure INT UNSIGNED NULL COMMENT 'Lieu principal de rattachement',
actif BOOLEAN NOT NULL DEFAULT TRUE,
@@ -277,6 +276,9 @@ VALUES
('MERCREDI_PERISCOLAIRE', 'Mercredi périscolaire', TRUE, 40, TRUE),
('EXTRASCOLAIRE', 'Temps extrascolaire', TRUE, 50, TRUE),
('FORMATION', 'Formation', TRUE, 60, TRUE),
-- compte_dans_quota = FALSE : les 1 607 h sont un volume NET dont les 25
-- jours de congés payés ont déjà été retranchés. Les imputer au quota les
-- décompterait une seconde fois. Idem pour les jours fériés.
('CONGE', 'Congé annuel (non décompté)', FALSE, 70, TRUE),
('JOUR_FRACTIONNEMENT', 'Journée de fractionnement', TRUE, 80, TRUE),
('PREPARATION_PERISCOLAIRE', 'Préparation périscolaire', TRUE, 90, TRUE),