-- ============================================================================ -- PTA V7 - Socle métier annuel, postes, contraintes et équipes -- MySQL 8.0.16+ -- À exécuter après le schéma V5.20 / V6. -- ============================================================================ START TRANSACTION; -- -------------------------------------------------------------------------- -- 1. Postes éligibles au PTA -- -------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS poste_agent ( id_poste INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, code VARCHAR(50) NOT NULL, libelle VARCHAR(150) NOT NULL, famille ENUM('ANIMATION', 'RESTAURATION_ENTRETIEN') NOT NULL, heures_mercredi_minutes SMALLINT UNSIGNED NOT NULL, heures_extrascolaire_minutes SMALLINT UNSIGNED NOT NULL, autorise_preparation BOOLEAN NOT NULL DEFAULT TRUE, peut_assurer_animation BOOLEAN NOT NULL DEFAULT TRUE, preparation_lundi_si_100 BOOLEAN NOT NULL DEFAULT FALSE, actif BOOLEAN NOT NULL DEFAULT TRUE, ordre_affichage SMALLINT UNSIGNED NOT NULL DEFAULT 100, CONSTRAINT uk_poste_agent_code UNIQUE (code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; INSERT INTO poste_agent ( code, libelle, famille, heures_mercredi_minutes, heures_extrascolaire_minutes, autorise_preparation, peut_assurer_animation, preparation_lundi_si_100, ordre_affichage ) VALUES ('RESPONSABLE', 'Responsable d’accueil', 'ANIMATION', 570, 570, TRUE, TRUE, TRUE, 10), ('ANIMATION_RELAIS', 'Agent d’animation relais', 'ANIMATION', 570, 570, TRUE, TRUE, TRUE, 20), ('ANIMATION', 'Agent d’animation', 'ANIMATION', 570, 570, TRUE, TRUE, TRUE, 30), ('RESTAURATION_ENTRETIEN', 'Agent de restauration collective et d’entretien', 'RESTAURATION_ENTRETIEN', 420, 420, FALSE, FALSE, FALSE, 40) ON DUPLICATE KEY UPDATE libelle = VALUES(libelle), famille = VALUES(famille), heures_mercredi_minutes = VALUES(heures_mercredi_minutes), heures_extrascolaire_minutes = VALUES(heures_extrascolaire_minutes), autorise_preparation = VALUES(autorise_preparation), peut_assurer_animation = VALUES(peut_assurer_animation), preparation_lundi_si_100 = VALUES(preparation_lundi_si_100), ordre_affichage = VALUES(ordre_affichage), actif = TRUE; -- -------------------------------------------------------------------------- -- 2. Compléments métier de l’agent -- -------------------------------------------------------------------------- ALTER TABLE agent ADD COLUMN id_poste INT UNSIGNED NULL AFTER date_debut_contrat, ADD COLUMN type_contrat ENUM('PERMANENT', 'TEMPORAIRE') NOT NULL DEFAULT 'PERMANENT' AFTER id_poste, ADD COLUMN date_fin_contrat DATE NULL AFTER type_contrat, ADD COLUMN formation_repartition_annuelle BOOLEAN NOT NULL DEFAULT FALSE AFTER date_fin_contrat, ADD COLUMN commentaire_pta VARCHAR(1000) NULL AFTER formation_repartition_annuelle; ALTER TABLE agent ADD CONSTRAINT fk_agent_poste FOREIGN KEY (id_poste) REFERENCES poste_agent(id_poste) ON UPDATE CASCADE ON DELETE SET NULL, ADD CONSTRAINT chk_agent_dates_contrat_v7 CHECK (date_fin_contrat IS NULL OR date_debut_contrat IS NULL OR date_fin_contrat >= date_debut_contrat); CREATE INDEX idx_agent_poste ON agent(id_poste); CREATE INDEX idx_agent_type_contrat ON agent(type_contrat, actif); -- -------------------------------------------------------------------------- -- 3. PTA annuel de l’agent -- -------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS pta_annuel ( id_pta INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, id_agent INT UNSIGNED NOT NULL, date_debut DATE NOT NULL, date_fin DATE NOT NULL, quotite_travail DECIMAL(5,2) NOT NULL, quota_reference_minutes INT UNSIGNED NOT NULL DEFAULT 96420, quota_cible_minutes INT UNSIGNED NOT NULL, enveloppe_formation_minutes SMALLINT UNSIGNED NOT NULL DEFAULT 840, conges_annuels_jours_cible DECIMAL(5,2) NOT NULL DEFAULT 25.00, jours_fractionnement_cible TINYINT UNSIGNED NOT NULL DEFAULT 2, statut ENUM('BROUILLON', 'A_CONTROLER', 'VALIDE') NOT NULL DEFAULT 'BROUILLON', commentaire VARCHAR(1000) NULL, date_validation DATETIME NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_pta_annuel_agent FOREIGN KEY (id_agent) REFERENCES agent(id_agent) ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT chk_pta_annuel_dates CHECK (date_fin >= date_debut), CONSTRAINT chk_pta_annuel_quotite CHECK (quotite_travail > 0 AND quotite_travail <= 100), CONSTRAINT chk_pta_annuel_quota CHECK (quota_reference_minutes > 0 AND quota_cible_minutes > 0), CONSTRAINT uk_pta_annuel_agent_periode UNIQUE (id_agent, date_debut, date_fin), INDEX idx_pta_annuel_agent_statut (id_agent, statut), INDEX idx_pta_annuel_periode (date_debut, date_fin) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- -------------------------------------------------------------------------- -- 4. Préférences, interdictions et proximité des lieux -- -------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS agent_structure_souhait ( id_souhait INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, id_agent INT UNSIGNED NOT NULL, id_structure INT UNSIGNED NOT NULL, type_souhait ENUM('PREFERENCE', 'INTERDICTION') NOT NULL, priorite TINYINT UNSIGNED NOT NULL DEFAULT 1, distance_km DECIMAL(7,2) NULL, commentaire VARCHAR(500) NULL, actif BOOLEAN NOT NULL DEFAULT TRUE, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_souhait_agent FOREIGN KEY (id_agent) REFERENCES agent(id_agent) ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT fk_souhait_structure FOREIGN KEY (id_structure) REFERENCES structure(id_structure) ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT chk_souhait_priorite CHECK (priorite BETWEEN 1 AND 5), CONSTRAINT chk_souhait_distance CHECK (distance_km IS NULL OR distance_km >= 0), CONSTRAINT uk_souhait_agent_structure UNIQUE (id_agent, id_structure), INDEX idx_souhait_agent_type (id_agent, type_souhait, actif) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- -------------------------------------------------------------------------- -- 5. Contraintes médicales et temps partiels thérapeutiques -- -------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS agent_contrainte ( id_contrainte INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, id_agent INT UNSIGNED NOT NULL, type_contrainte ENUM('MEDICALE', 'TEMPS_PARTIEL_THERAPEUTIQUE') NOT NULL, date_debut DATE NOT NULL, date_fin DATE NULL, quotite_temporaire DECIMAL(5,2) NULL, maximum_minutes_jour SMALLINT UNSIGNED NULL, maximum_minutes_semaine SMALLINT UNSIGNED NULL, interdit_matin BOOLEAN NOT NULL DEFAULT FALSE, interdit_midi BOOLEAN NOT NULL DEFAULT FALSE, interdit_soir BOOLEAN NOT NULL DEFAULT FALSE, commentaire VARCHAR(1000) NOT NULL, actif BOOLEAN NOT NULL DEFAULT TRUE, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_contrainte_agent FOREIGN KEY (id_agent) REFERENCES agent(id_agent) ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT chk_contrainte_dates CHECK (date_fin IS NULL OR date_fin >= date_debut), CONSTRAINT chk_contrainte_quotite CHECK (quotite_temporaire IS NULL OR (quotite_temporaire > 0 AND quotite_temporaire <= 100)), CONSTRAINT chk_contrainte_max_jour CHECK (maximum_minutes_jour IS NULL OR maximum_minutes_jour > 0), CONSTRAINT chk_contrainte_max_semaine CHECK (maximum_minutes_semaine IS NULL OR maximum_minutes_semaine > 0), INDEX idx_contrainte_agent_dates (id_agent, actif, date_debut, date_fin) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- -------------------------------------------------------------------------- -- 6. Équipes périscolaires des mercredis et extrascolaires -- -------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS equipe_pta ( id_equipe INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, id_structure INT UNSIGNED NOT NULL, type_equipe ENUM('MERCREDI_PERISCOLAIRE', 'EXTRASCOLAIRE', 'PERISCOLAIRE_SEMAINE') NOT NULL, libelle VARCHAR(180) NOT NULL, date_debut DATE NOT NULL, date_fin DATE NOT NULL, nombre_enfants_moins_6 SMALLINT UNSIGNED NOT NULL DEFAULT 0, nombre_enfants_6_plus SMALLINT UNSIGNED NOT NULL DEFAULT 0, ratio_moins_6 TINYINT UNSIGNED NOT NULL DEFAULT 8, ratio_6_plus TINYINT UNSIGNED NOT NULL DEFAULT 12, minimum_agents TINYINT UNSIGNED NOT NULL DEFAULT 1, pourcentage_diplomes_min DECIMAL(5,2) NOT NULL DEFAULT 50.00, statut ENUM('BROUILLON', 'A_CONTROLER', 'VALIDEE') NOT NULL DEFAULT 'BROUILLON', commentaire VARCHAR(1000) NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_equipe_pta_structure FOREIGN KEY (id_structure) REFERENCES structure(id_structure) ON UPDATE CASCADE ON DELETE RESTRICT, CONSTRAINT chk_equipe_dates CHECK (date_fin >= date_debut), CONSTRAINT chk_equipe_ratios CHECK (ratio_moins_6 > 0 AND ratio_6_plus > 0), CONSTRAINT chk_equipe_minimum CHECK (minimum_agents > 0), CONSTRAINT chk_equipe_diplomes CHECK (pourcentage_diplomes_min BETWEEN 0 AND 100), INDEX idx_equipe_pta_type_dates (type_equipe, date_debut, date_fin), INDEX idx_equipe_pta_structure_dates (id_structure, date_debut, date_fin) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; CREATE TABLE IF NOT EXISTS equipe_pta_membre ( id_equipe_membre INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, id_equipe INT UNSIGNED NOT NULL, id_agent INT UNSIGNED NOT NULL, fonction_equipe ENUM('RESPONSABLE', 'ANIMATION', 'RESTAURATION_ENTRETIEN') NOT NULL DEFAULT 'ANIMATION', date_debut DATE NULL, date_fin DATE NULL, commentaire VARCHAR(500) NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_equipe_membre_equipe FOREIGN KEY (id_equipe) REFERENCES equipe_pta(id_equipe) ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT fk_equipe_membre_agent FOREIGN KEY (id_agent) REFERENCES agent(id_agent) ON UPDATE CASCADE ON DELETE RESTRICT, CONSTRAINT chk_equipe_membre_dates CHECK (date_fin IS NULL OR date_debut IS NULL OR date_fin >= date_debut), CONSTRAINT uk_equipe_membre UNIQUE (id_equipe, id_agent), INDEX idx_equipe_membre_agent (id_agent, id_equipe) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- -------------------------------------------------------------------------- -- 7. Références horaires des lieux pour les automatismes de préparation -- -------------------------------------------------------------------------- CREATE TABLE IF NOT EXISTS structure_creneau_reference ( id_reference INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, id_structure INT UNSIGNED NOT NULL, code_plage ENUM('ALP_MATIN', 'RESTAURATION', 'ALP_SOIR') NOT NULL, jour_semaine TINYINT UNSIGNED NOT NULL COMMENT '1=lundi ... 5=vendredi', heure_debut TIME NOT NULL, heure_fin TIME NOT NULL, actif BOOLEAN NOT NULL DEFAULT TRUE, CONSTRAINT fk_reference_structure FOREIGN KEY (id_structure) REFERENCES structure(id_structure) ON UPDATE CASCADE ON DELETE CASCADE, CONSTRAINT chk_reference_jour CHECK (jour_semaine BETWEEN 1 AND 5), CONSTRAINT chk_reference_ordre CHECK (heure_fin > heure_debut), CONSTRAINT uk_reference_structure_plage_jour UNIQUE (id_structure, code_plage, jour_semaine), INDEX idx_reference_structure_jour (id_structure, jour_semaine, actif) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; -- -------------------------------------------------------------------------- -- 8. Motifs détaillés du PTA -- -------------------------------------------------------------------------- INSERT INTO motif_planning (code, libelle, compte_dans_quota, ordre_affichage, actif) VALUES ('ALP_MATIN', 'ALP matin', TRUE, 10, TRUE), ('RESTAURATION', 'Restauration', TRUE, 20, TRUE), ('ALP_SOIR', 'ALP soir', TRUE, 30, TRUE), ('MERCREDI_PERISCOLAIRE', 'Mercredi périscolaire', TRUE, 40, TRUE), ('EXTRASCOLAIRE', 'Temps extrascolaire', TRUE, 50, TRUE), ('FORMATION', 'Formation', TRUE, 60, TRUE), ('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), ('PREPARATION_EXTRASCOLAIRE', 'Préparation extrascolaire', TRUE, 100, TRUE), ('PREPARATION_LUNDI', 'Préparation du lundi', TRUE, 110, TRUE), ('MENAGE_FOND', 'Ménage de fond', TRUE, 120, TRUE), ('JNT', 'Journée non travaillée', FALSE, 130, TRUE) ON DUPLICATE KEY UPDATE libelle = VALUES(libelle), compte_dans_quota = VALUES(compte_dans_quota), ordre_affichage = VALUES(ordre_affichage), actif = TRUE; -- La règle métier communiquée précise que les congés annuels ne sont pas -- redécomptés du quota puisqu'ils sont déjà intégrés dans le calcul initial. UPDATE motif_planning SET libelle = 'Congé annuel (non décompté)', compte_dans_quota = FALSE WHERE code = 'CONGE'; -- -------------------------------------------------------------------------- -- 9. Vues de synthèse -- -------------------------------------------------------------------------- CREATE OR REPLACE VIEW v_pta_annuel_synthese AS SELECT pa.id_pta, pa.id_agent, a.matricule, a.nom, a.prenom, po.code AS poste_code, po.libelle AS poste_libelle, a.type_contrat, pa.date_debut, pa.date_fin, pa.quotite_travail, pa.quota_cible_minutes, COALESCE(SUM(CASE WHEN mp.compte_dans_quota = TRUE THEN TIMESTAMPDIFF(MINUTE, CONCAT(ch.date_jour, ' ', ch.heure_debut), CONCAT(ch.date_jour, ' ', ch.heure_fin)) ELSE 0 END), 0) AS minutes_decomptees, pa.quota_cible_minutes - COALESCE(SUM(CASE WHEN mp.compte_dans_quota = TRUE THEN TIMESTAMPDIFF(MINUTE, CONCAT(ch.date_jour, ' ', ch.heure_debut), CONCAT(ch.date_jour, ' ', ch.heure_fin)) ELSE 0 END), 0) AS minutes_restantes, pa.statut FROM pta_annuel pa JOIN agent a ON a.id_agent = pa.id_agent LEFT JOIN poste_agent po ON po.id_poste = a.id_poste LEFT JOIN planning p ON p.id_agent = pa.id_agent LEFT JOIN creneau_horaire ch ON ch.id_planning = p.id_planning AND ch.date_jour BETWEEN pa.date_debut AND pa.date_fin LEFT JOIN motif_planning mp ON mp.id_motif = ch.id_motif GROUP BY pa.id_pta, pa.id_agent, a.matricule, a.nom, a.prenom, po.code, po.libelle, a.type_contrat, pa.date_debut, pa.date_fin, pa.quotite_travail, pa.quota_cible_minutes, pa.statut; CREATE OR REPLACE VIEW v_equipe_pta_controle AS SELECT e.id_equipe, e.libelle, e.type_equipe, e.id_structure, s.nom AS structure_nom, e.date_debut, e.date_fin, e.nombre_enfants_moins_6, e.nombre_enfants_6_plus, GREATEST( e.minimum_agents, CEIL(e.nombre_enfants_moins_6 / e.ratio_moins_6) + CEIL(e.nombre_enfants_6_plus / e.ratio_6_plus) ) AS agents_requis, COUNT(em.id_agent) AS agents_affectes, SUM(CASE WHEN a.est_diplome = TRUE THEN 1 ELSE 0 END) AS agents_diplomes, CASE WHEN COUNT(em.id_agent) = 0 THEN 0 ELSE ROUND(100 * SUM(CASE WHEN a.est_diplome = TRUE THEN 1 ELSE 0 END) / COUNT(em.id_agent), 2) END AS pourcentage_diplomes, e.pourcentage_diplomes_min, SUM(CASE WHEN em.fonction_equipe = 'RESPONSABLE' THEN 1 ELSE 0 END) AS responsables, e.statut FROM equipe_pta e JOIN structure s ON s.id_structure = e.id_structure LEFT JOIN equipe_pta_membre em ON em.id_equipe = e.id_equipe LEFT JOIN agent a ON a.id_agent = em.id_agent GROUP BY e.id_equipe, e.libelle, e.type_equipe, e.id_structure, s.nom, e.date_debut, e.date_fin, e.nombre_enfants_moins_6, e.nombre_enfants_6_plus, e.ratio_moins_6, e.ratio_6_plus, e.minimum_agents, e.pourcentage_diplomes_min, e.statut; COMMIT;