pour prod

This commit is contained in:
Loic Masi
2026-08-07 16:13:43 +02:00
commit 5fbf76868f
157 changed files with 24085 additions and 0 deletions

1735
database/install_complet.sql Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,70 @@
-- ============================================================
-- PTA V4.5 - Une structure principale par agent
-- MySQL 8+
-- ============================================================
SET @col_exists = (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'agent'
AND COLUMN_NAME = 'id_structure'
);
SET @sql = IF(
@col_exists = 0,
'ALTER TABLE agent ADD COLUMN id_structure INT UNSIGNED NULL AFTER email',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @fk_exists = (
SELECT COUNT(*)
FROM information_schema.REFERENTIAL_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE()
AND TABLE_NAME = 'agent'
AND CONSTRAINT_NAME = 'fk_agent_structure_principale'
);
SET @sql = IF(
@fk_exists = 0,
'ALTER TABLE agent ADD CONSTRAINT fk_agent_structure_principale FOREIGN KEY (id_structure) REFERENCES structure(id_structure) ON UPDATE CASCADE ON DELETE SET NULL',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET @idx_exists = (
SELECT COUNT(*)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'agent'
AND INDEX_NAME = 'idx_agent_structure_principale'
);
SET @sql = IF(
@idx_exists = 0,
'CREATE INDEX idx_agent_structure_principale ON agent(id_structure)',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- Reprise automatique uniquement lorsqu'un agent n'a qu'un seul rattachement actif.
UPDATE agent a
JOIN (
SELECT id_agent, MIN(id_structure) AS id_structure
FROM agent_structure
WHERE actif = TRUE
AND (date_fin IS NULL OR date_fin >= CURRENT_DATE)
GROUP BY id_agent
HAVING COUNT(DISTINCT id_structure) = 1
) x ON x.id_agent = a.id_agent
SET a.id_structure = x.id_structure
WHERE a.id_structure IS NULL;
-- Les agents encore sans structure doivent être affectés depuis l'onglet
-- "Gestion des agents" de l'application.

View File

@@ -0,0 +1,449 @@
-- ============================================================
-- Extension de la base PTA pour l'interface de gestion planning
-- À exécuter APRES pta_mysql_test.sql
-- Compatible MySQL 8+
-- ============================================================
USE pta_test;
-- 1. Motifs d'affectation
CREATE TABLE IF NOT EXISTS motif_planning (
id_motif INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
code VARCHAR(50) NOT NULL UNIQUE,
libelle VARCHAR(120) NOT NULL,
compte_dans_quota BOOLEAN NOT NULL DEFAULT FALSE,
ordre_affichage SMALLINT UNSIGNED NOT NULL DEFAULT 100,
actif BOOLEAN NOT NULL DEFAULT TRUE
) ENGINE = InnoDB;
INSERT INTO motif_planning (code, libelle, compte_dans_quota, ordre_affichage)
VALUES
('TRAVAIL', 'Heure de travail', TRUE, 10),
('CONGE', 'Congé', FALSE, 20),
('MALADIE', 'Absence', TRUE, 30),
('FORMATION', 'Formation', TRUE, 40),
('AUTRE', 'Autre absence (non décomptée)', FALSE, 100)
ON DUPLICATE KEY UPDATE
libelle = VALUES(libelle),
compte_dans_quota = VALUES(compte_dans_quota),
ordre_affichage = VALUES(ordre_affichage),
actif = TRUE;
-- 2. Rattachement des agents aux structures
CREATE TABLE IF NOT EXISTS agent_structure (
id_agent_structure INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
id_agent INT UNSIGNED NOT NULL,
id_structure INT UNSIGNED NOT NULL,
date_debut DATE NULL,
date_fin DATE NULL,
actif BOOLEAN NOT NULL DEFAULT TRUE,
CONSTRAINT fk_agent_structure_agent
FOREIGN KEY (id_agent) REFERENCES agent(id_agent),
CONSTRAINT fk_agent_structure_structure
FOREIGN KEY (id_structure) REFERENCES structure(id_structure),
CONSTRAINT chk_agent_structure_dates
CHECK (date_fin IS NULL OR date_debut IS NULL OR date_fin >= date_debut),
UNIQUE KEY uk_agent_structure_periode (id_agent, id_structure, date_debut)
) ENGINE = InnoDB;
-- Jeu d'essai : un seul rattachement principal par agent.
INSERT INTO agent_structure (id_agent, id_structure, date_debut, date_fin, actif)
VALUES
(1, 1, '2026-01-01', NULL, TRUE),
(2, 1, '2026-01-01', NULL, TRUE),
(3, 2, '2026-01-01', NULL, TRUE)
ON DUPLICATE KEY UPDATE
date_fin = VALUES(date_fin),
actif = VALUES(actif);
-- 3. Ajout du motif à chaque créneau existant
SET @travail_id = (
SELECT id_motif FROM motif_planning WHERE code = 'TRAVAIL' LIMIT 1
);
SET @col_exists = (
SELECT COUNT(*)
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'creneau_horaire'
AND COLUMN_NAME = 'id_motif'
);
SET @sql = IF(
@col_exists = 0,
'ALTER TABLE creneau_horaire ADD COLUMN id_motif INT UNSIGNED NULL AFTER id_planning',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
UPDATE creneau_horaire
SET id_motif = @travail_id
WHERE id_motif IS NULL;
SET @fk_exists = (
SELECT COUNT(*)
FROM information_schema.TABLE_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = DATABASE()
AND TABLE_NAME = 'creneau_horaire'
AND CONSTRAINT_NAME = 'fk_creneau_motif'
AND CONSTRAINT_TYPE = 'FOREIGN KEY'
);
SET @sql = IF(
@fk_exists = 0,
'ALTER TABLE creneau_horaire ADD CONSTRAINT fk_creneau_motif FOREIGN KEY (id_motif) REFERENCES motif_planning(id_motif)',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
ALTER TABLE creneau_horaire
MODIFY id_motif INT UNSIGNED NOT NULL;
SET @idx_exists = (
SELECT COUNT(*)
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'creneau_horaire'
AND INDEX_NAME = 'idx_creneau_motif'
);
SET @sql = IF(
@idx_exists = 0,
'CREATE INDEX idx_creneau_motif ON creneau_horaire(id_motif)',
'SELECT 1'
);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 4. Vue de synthèse par agent en tenant compte du motif
CREATE OR REPLACE VIEW v_suivi_quota_contrat AS
SELECT
ca.id_contrat,
ca.id_agent,
a.matricule,
CONCAT(a.prenom, ' ', a.nom) AS agent,
ca.date_debut,
ca.date_fin,
ca.quota_minutes,
ROUND(ca.quota_minutes / 60, 2) AS quota_heures,
COALESCE(
SUM(
CASE
WHEN p.statut = 'VALIDE' AND m.compte_dans_quota = TRUE
THEN TIME_TO_SEC(TIMEDIFF(c.heure_fin, c.heure_debut)) / 60
ELSE 0
END
),
0
) AS minutes_planifiees_validees,
ROUND(
COALESCE(
SUM(
CASE
WHEN p.statut = 'VALIDE' AND m.compte_dans_quota = TRUE
THEN TIME_TO_SEC(TIMEDIFF(c.heure_fin, c.heure_debut)) / 60
ELSE 0
END
),
0
) / 60,
2
) AS heures_planifiees_validees,
ROUND(
(
ca.quota_minutes
- COALESCE(
SUM(
CASE
WHEN p.statut = 'VALIDE' AND m.compte_dans_quota = TRUE
THEN TIME_TO_SEC(TIMEDIFF(c.heure_fin, c.heure_debut)) / 60
ELSE 0
END
),
0
)
) / 60,
2
) AS heures_restantes
FROM contrat_agent ca
JOIN agent a ON a.id_agent = ca.id_agent
LEFT JOIN planning p ON p.id_agent = ca.id_agent
LEFT JOIN creneau_horaire c
ON c.id_planning = p.id_planning
AND c.date_jour BETWEEN ca.date_debut AND COALESCE(ca.date_fin, '9999-12-31')
LEFT JOIN motif_planning m ON m.id_motif = c.id_motif
GROUP BY
ca.id_contrat,
ca.id_agent,
a.matricule,
a.prenom,
a.nom,
ca.date_debut,
ca.date_fin,
ca.quota_minutes;
-- 5. Mise à jour du contrôle de quota : seuls les motifs marqués
-- compte_dans_quota = TRUE sont additionnés.
DELIMITER $$
DROP PROCEDURE IF EXISTS sp_preparer_controles_planning$$
CREATE PROCEDURE sp_preparer_controles_planning(
IN p_id_planning INT UNSIGNED
)
controle: BEGIN
DECLARE v_planning_existe INT DEFAULT 0;
DROP TEMPORARY TABLE IF EXISTS tmp_controles_planning;
CREATE TEMPORARY TABLE tmp_controles_planning (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
niveau ENUM('ERREUR', 'ALERTE', 'INFO') NOT NULL,
code VARCHAR(100) NOT NULL,
message VARCHAR(1000) NOT NULL
);
SELECT COUNT(*)
INTO v_planning_existe
FROM planning
WHERE id_planning = p_id_planning;
IF v_planning_existe = 0 THEN
INSERT INTO tmp_controles_planning(niveau, code, message)
VALUES (
'ERREUR',
'PLANNING_INTROUVABLE',
CONCAT('Le planning #', p_id_planning, ' n''existe pas.')
);
LEAVE controle;
END IF;
-- --------------------------------------------------------
-- Controle 1 : le planning doit contenir au moins un creneau
-- --------------------------------------------------------
IF NOT EXISTS (
SELECT 1
FROM creneau_horaire
WHERE id_planning = p_id_planning
) THEN
INSERT INTO tmp_controles_planning(niveau, code, message)
VALUES (
'ERREUR',
'AUCUN_CRENEAU',
'Le planning ne contient aucun creneau horaire.'
);
END IF;
-- --------------------------------------------------------
-- Controle 2 : chaque creneau doit etre dans la semaine
-- --------------------------------------------------------
INSERT INTO tmp_controles_planning(niveau, code, message)
SELECT
'ERREUR',
'CRENEAU_HORS_SEMAINE',
CONCAT(
'Le creneau #', c.id_creneau,
' du ', DATE_FORMAT(c.date_jour, '%d/%m/%Y'),
' est en dehors de la semaine ', s.numero_semaine,
' (', DATE_FORMAT(s.date_debut, '%d/%m/%Y'),
' au ', DATE_FORMAT(s.date_fin, '%d/%m/%Y'), ').'
)
FROM planning p
JOIN semaine s
ON s.id_semaine = p.id_semaine
JOIN creneau_horaire c
ON c.id_planning = p.id_planning
WHERE p.id_planning = p_id_planning
AND c.date_jour NOT BETWEEN s.date_debut AND s.date_fin;
-- --------------------------------------------------------
-- Controle 3 : chaque creneau doit etre couvert par un contrat
-- --------------------------------------------------------
INSERT INTO tmp_controles_planning(niveau, code, message)
SELECT
'ERREUR',
'HORS_CONTRAT',
CONCAT(
'Le creneau #', c.id_creneau,
' du ', DATE_FORMAT(c.date_jour, '%d/%m/%Y'),
' n''est couvert par aucun contrat actif pour cet agent.'
)
FROM planning p
JOIN creneau_horaire c
ON c.id_planning = p.id_planning
WHERE p.id_planning = p_id_planning
AND NOT EXISTS (
SELECT 1
FROM contrat_agent ca
WHERE ca.id_agent = p.id_agent
AND ca.actif = TRUE
AND c.date_jour BETWEEN ca.date_debut AND COALESCE(ca.date_fin, '9999-12-31')
);
-- --------------------------------------------------------
-- Controle 4 : chevauchements dans le meme planning
-- --------------------------------------------------------
INSERT INTO tmp_controles_planning(niveau, code, message)
SELECT
'ERREUR',
'CHEVAUCHEMENT_INTERNE',
CONCAT(
'Les creneaux #', c1.id_creneau,
' et #', c2.id_creneau,
' se chevauchent le ', DATE_FORMAT(c1.date_jour, '%d/%m/%Y'),
' (', TIME_FORMAT(c1.heure_debut, '%H:%i'), '-', TIME_FORMAT(c1.heure_fin, '%H:%i'),
' et ', TIME_FORMAT(c2.heure_debut, '%H:%i'), '-', TIME_FORMAT(c2.heure_fin, '%H:%i'), ').'
)
FROM creneau_horaire c1
JOIN creneau_horaire c2
ON c2.id_planning = c1.id_planning
AND c2.date_jour = c1.date_jour
AND c2.id_creneau > c1.id_creneau
AND c1.heure_debut < c2.heure_fin
AND c1.heure_fin > c2.heure_debut
WHERE c1.id_planning = p_id_planning;
-- --------------------------------------------------------
-- Controle 5 : chevauchement avec un autre planning valide
-- pour le meme agent, meme si la structure est differente.
-- --------------------------------------------------------
INSERT INTO tmp_controles_planning(niveau, code, message)
SELECT
'ERREUR',
'CHEVAUCHEMENT_AUTRE_PLANNING',
CONCAT(
'Le creneau #', c1.id_creneau,
' chevauche le creneau #', c2.id_creneau,
' du planning valide #', p2.id_planning,
' le ', DATE_FORMAT(c1.date_jour, '%d/%m/%Y'), '.'
)
FROM planning p1
JOIN creneau_horaire c1
ON c1.id_planning = p1.id_planning
JOIN planning p2
ON p2.id_agent = p1.id_agent
AND p2.id_planning <> p1.id_planning
AND p2.statut = 'VALIDE'
JOIN creneau_horaire c2
ON c2.id_planning = p2.id_planning
AND c2.date_jour = c1.date_jour
AND c1.heure_debut < c2.heure_fin
AND c1.heure_fin > c2.heure_debut
WHERE p1.id_planning = p_id_planning;
-- --------------------------------------------------------
-- Controle 6 : depassement du quota du contrat
-- Le quota est ici un quota total sur la periode du contrat.
-- Il s'agit d'une ALERTE et non d'une erreur bloquante.
-- --------------------------------------------------------
INSERT INTO tmp_controles_planning(niveau, code, message)
SELECT
'ALERTE',
'QUOTA_DEPASSE',
CONCAT(
'Le contrat #', ca.id_contrat,
' prevoit ', ROUND(ca.quota_minutes / 60, 2), ' h. ',
'Apres validation, ',
ROUND(
(
-- Minutes deja validees, hors planning courant
COALESCE((
SELECT SUM(
TIME_TO_SEC(TIMEDIFF(cv.heure_fin, cv.heure_debut)) / 60
)
FROM planning pv
JOIN creneau_horaire cv
ON cv.id_planning = pv.id_planning
JOIN motif_planning mv
ON mv.id_motif = cv.id_motif
AND mv.compte_dans_quota = TRUE
WHERE pv.id_agent = ca.id_agent
AND pv.statut = 'VALIDE'
AND pv.id_planning <> p_id_planning
AND cv.date_jour BETWEEN ca.date_debut AND COALESCE(ca.date_fin, '9999-12-31')
), 0)
+
-- Minutes du planning courant appartenant a ce contrat
COALESCE((
SELECT SUM(
TIME_TO_SEC(TIMEDIFF(cc.heure_fin, cc.heure_debut)) / 60
)
FROM creneau_horaire cc
JOIN motif_planning mc
ON mc.id_motif = cc.id_motif
AND mc.compte_dans_quota = TRUE
WHERE cc.id_planning = p_id_planning
AND cc.date_jour BETWEEN ca.date_debut AND COALESCE(ca.date_fin, '9999-12-31')
), 0)
) / 60,
2
),
' h seraient planifiees.'
)
FROM planning p
JOIN contrat_agent ca
ON ca.id_agent = p.id_agent
AND ca.actif = TRUE
WHERE p.id_planning = p_id_planning
AND EXISTS (
SELECT 1
FROM creneau_horaire cc
WHERE cc.id_planning = p_id_planning
AND cc.date_jour BETWEEN ca.date_debut AND COALESCE(ca.date_fin, '9999-12-31')
)
AND (
COALESCE((
SELECT SUM(
TIME_TO_SEC(TIMEDIFF(cv.heure_fin, cv.heure_debut)) / 60
)
FROM planning pv
JOIN creneau_horaire cv
ON cv.id_planning = pv.id_planning
JOIN motif_planning mv
ON mv.id_motif = cv.id_motif
AND mv.compte_dans_quota = TRUE
WHERE pv.id_agent = ca.id_agent
AND pv.statut = 'VALIDE'
AND pv.id_planning <> p_id_planning
AND cv.date_jour BETWEEN ca.date_debut AND COALESCE(ca.date_fin, '9999-12-31')
), 0)
+
COALESCE((
SELECT SUM(
TIME_TO_SEC(TIMEDIFF(cc.heure_fin, cc.heure_debut)) / 60
)
FROM creneau_horaire cc
JOIN motif_planning mc
ON mc.id_motif = cc.id_motif
AND mc.compte_dans_quota = TRUE
WHERE cc.id_planning = p_id_planning
AND cc.date_jour BETWEEN ca.date_debut AND COALESCE(ca.date_fin, '9999-12-31')
), 0)
) > ca.quota_minutes;
-- Si aucun probleme n'a ete detecte, on retourne explicitement OK.
IF NOT EXISTS (
SELECT 1
FROM tmp_controles_planning
) THEN
INSERT INTO tmp_controles_planning(niveau, code, message)
VALUES (
'INFO',
'OK',
'Aucune anomalie detectee. Le planning peut etre valide.'
);
END IF;
END$$
DELIMITER ;

View File

@@ -0,0 +1,145 @@
-- ============================================================
-- PTA - Quotas annuels et vues de pilotage
-- À exécuter APRÈS :
-- 1. pta_mysql_test.sql
-- 2. migration_planning_motifs.sql
-- Compatible MySQL 8+
-- ============================================================
USE pta_test;
-- ------------------------------------------------------------
-- 1. Quota annuel d'un agent
-- 1607 h = 96 420 minutes à 100 %.
-- La quotité permet de proratiser automatiquement le quota cible.
-- ------------------------------------------------------------
CREATE TABLE IF NOT EXISTS quota_agent_annuel (
id_quota INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
id_agent INT UNSIGNED NOT NULL,
annee SMALLINT UNSIGNED NOT NULL,
quotite_travail DECIMAL(5,2) NOT NULL DEFAULT 100.00,
quota_reference_minutes INT UNSIGNED NOT NULL DEFAULT 96420,
quota_cible_minutes INT UNSIGNED NOT NULL,
commentaire VARCHAR(255) NULL,
CONSTRAINT fk_quota_annuel_agent
FOREIGN KEY (id_agent) REFERENCES agent(id_agent),
CONSTRAINT chk_quotite_travail
CHECK (quotite_travail > 0 AND quotite_travail <= 100),
CONSTRAINT chk_quota_annuel_positif
CHECK (quota_cible_minutes > 0),
UNIQUE KEY uk_quota_agent_annee (id_agent, annee)
) ENGINE = InnoDB;
-- Jeu d'essai 2026 : tous les agents existants à 100 %, soit 1607 h.
INSERT INTO quota_agent_annuel (
id_agent,
annee,
quotite_travail,
quota_reference_minutes,
quota_cible_minutes,
commentaire
)
SELECT
a.id_agent,
2026,
100.00,
96420,
96420,
'Quota annuel de référence à 100 %'
FROM agent a
WHERE NOT EXISTS (
SELECT 1
FROM quota_agent_annuel q
WHERE q.id_agent = a.id_agent
AND q.annee = 2026
);
-- Exemple de modification pour un agent à 80 % :
-- UPDATE quota_agent_annuel
-- SET quotite_travail = 80.00,
-- quota_cible_minutes = ROUND(96420 * 0.80)
-- WHERE id_agent = 2 AND annee = 2026;
-- ------------------------------------------------------------
-- 2. Règle de décompte des motifs
-- L'application reste paramétrable : le booléen compte_dans_quota
-- indique si le motif consomme le quota annuel.
-- Selon le besoin exprimé, les motifs principaux ci-dessous sont
-- comptés par défaut. Il est possible de changer cette règle ensuite.
-- ------------------------------------------------------------
UPDATE motif_planning
SET compte_dans_quota = TRUE
WHERE code IN ('TRAVAIL', 'FORMATION', 'CONGE', 'MALADIE');
-- ------------------------------------------------------------
-- 3. Vue annuelle de suivi des quotas
-- Les brouillons et les plannings validés sont distingués.
-- ------------------------------------------------------------
CREATE OR REPLACE VIEW v_suivi_quota_annuel AS
SELECT
q.id_agent,
a.matricule,
a.nom,
a.prenom,
q.annee,
q.quotite_travail,
q.quota_reference_minutes,
q.quota_cible_minutes,
ROUND(q.quota_cible_minutes / 60, 2) AS quota_cible_heures,
COALESCE(SUM(
CASE
WHEN p.statut = 'VALIDE' AND m.compte_dans_quota = TRUE
THEN TIMESTAMPDIFF(
MINUTE,
CONCAT(c.date_jour, ' ', c.heure_debut),
CONCAT(c.date_jour, ' ', c.heure_fin)
)
ELSE 0
END
), 0) AS minutes_valides,
COALESCE(SUM(
CASE
WHEN p.statut = 'BROUILLON' AND m.compte_dans_quota = TRUE
THEN TIMESTAMPDIFF(
MINUTE,
CONCAT(c.date_jour, ' ', c.heure_debut),
CONCAT(c.date_jour, ' ', c.heure_fin)
)
ELSE 0
END
), 0) AS minutes_brouillon,
q.quota_cible_minutes - COALESCE(SUM(
CASE
WHEN m.compte_dans_quota = TRUE
THEN TIMESTAMPDIFF(
MINUTE,
CONCAT(c.date_jour, ' ', c.heure_debut),
CONCAT(c.date_jour, ' ', c.heure_fin)
)
ELSE 0
END
), 0) AS minutes_restantes
FROM quota_agent_annuel q
INNER JOIN agent a ON a.id_agent = q.id_agent
LEFT JOIN planning p ON p.id_agent = q.id_agent
LEFT JOIN creneau_horaire c
ON c.id_planning = p.id_planning
AND YEAR(c.date_jour) = q.annee
LEFT JOIN motif_planning m ON m.id_motif = c.id_motif
GROUP BY
q.id_agent,
a.matricule,
a.nom,
a.prenom,
q.annee,
q.quotite_travail,
q.quota_reference_minutes,
q.quota_cible_minutes;

View File

@@ -0,0 +1,10 @@
-- PTA V4.9 - Ajout du type daffectation des agents
-- À exécuter une seule fois sur une base existante V4.8.
ALTER TABLE agent
ADD COLUMN type_affectation ENUM('PERISCOLAIRE', 'EXTRASCOLAIRE')
NOT NULL DEFAULT 'PERISCOLAIRE'
AFTER id_structure;
-- Valeur par défaut : les agents existants sont considérés périscolaires.
-- Modifiez ensuite individuellement les agents extrascolaires depuis lécran Administration.

View File

@@ -0,0 +1,39 @@
-- ============================================================
-- PTA V5.13 - Contrôle de couverture des lieux d'affectation
-- MySQL 8+
-- ============================================================
CREATE TABLE IF NOT EXISTS structure_couverture_horaire (
id_couverture INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
id_structure INT UNSIGNED NOT NULL,
jour_semaine TINYINT UNSIGNED NOT NULL COMMENT '1=lundi, 5=vendredi',
heure_debut TIME NOT NULL,
heure_fin TIME NOT NULL,
minimum_agents TINYINT UNSIGNED NOT NULL DEFAULT 1,
CONSTRAINT fk_couverture_structure
FOREIGN KEY (id_structure)
REFERENCES structure(id_structure)
ON UPDATE CASCADE
ON DELETE CASCADE,
CONSTRAINT chk_couverture_jour
CHECK (jour_semaine BETWEEN 1 AND 5),
CONSTRAINT chk_couverture_ordre
CHECK (heure_fin > heure_debut),
CONSTRAINT chk_couverture_minimum
CHECK (minimum_agents BETWEEN 1 AND 50),
CONSTRAINT chk_couverture_quart_heure
CHECK (
MOD(MINUTE(heure_debut), 15) = 0
AND MOD(MINUTE(heure_fin), 15) = 0
AND SECOND(heure_debut) = 0
AND SECOND(heure_fin) = 0
)
) ENGINE = InnoDB;
CREATE INDEX idx_couverture_structure_jour
ON structure_couverture_horaire(id_structure, jour_semaine, heure_debut, heure_fin);

View File

@@ -0,0 +1,15 @@
-- ============================================================
-- PTA V5.16 - Diplôme et coordonnées des agents
-- MySQL 8+
-- ============================================================
-- À exécuter une seule fois sur une base V5.15.
ALTER TABLE agent
ADD COLUMN telephone VARCHAR(30) NULL AFTER email,
ADD COLUMN adresse VARCHAR(255) NULL AFTER telephone,
ADD COLUMN est_diplome BOOLEAN NOT NULL DEFAULT FALSE AFTER adresse,
ADD COLUMN diplome_libelle VARCHAR(150) NULL AFTER est_diplome;
-- Exemples de mise à jour :
-- UPDATE agent SET est_diplome = TRUE, diplome_libelle = 'BAFA' WHERE id_agent = 1;
-- UPDATE agent SET telephone = '06 00 00 00 00', adresse = '1 rue Exemple, Orléans' WHERE id_agent = 1;

View File

@@ -0,0 +1,36 @@
-- ============================================================
-- PTA V5.17 - Périodes de vacances scolaires
-- ============================================================
-- Les dates sont validées manuellement par l'utilisateur avant
-- d'être enregistrées, même lorsqu'elles proviennent de l'API
-- officielle du calendrier scolaire.
--
-- date_fin est stockée de façon INCLUSIVE dans PTA.
-- Pour une proposition issue de l'API, la date de reprise fournie
-- par l'API est convertie en dernier jour de vacances (veille).
CREATE TABLE IF NOT EXISTS periode_vacance (
id_periode INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
libelle VARCHAR(150) NOT NULL,
date_debut DATE NOT NULL,
date_fin DATE NOT NULL,
annee_scolaire VARCHAR(9) NOT NULL,
academie VARCHAR(100) NOT NULL DEFAULT '',
zone VARCHAR(50) NOT NULL DEFAULT '',
source ENUM('MANUEL', 'DATA_GOUV') NOT NULL DEFAULT 'MANUEL',
actif BOOLEAN NOT NULL DEFAULT TRUE,
date_creation DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT chk_periode_vacance_dates
CHECK (date_fin >= date_debut),
UNIQUE KEY uk_periode_vacance (
date_debut,
date_fin,
annee_scolaire,
academie
),
KEY idx_periode_vacance_dates (date_debut, date_fin),
KEY idx_periode_vacance_annee (annee_scolaire)
) ENGINE = InnoDB;

View File

@@ -0,0 +1,27 @@
-- ============================================================
-- PTA V5.20 - Année contractuelle pour le calcul des quotas
-- MySQL 8+
-- ============================================================
-- À exécuter une seule fois sur une base V5.19.
--
-- Le quota annuel (1607 h à 100 %) n'est plus consommé sur la
-- période 01/01 -> 31/12. La période de référence commence à la
-- date anniversaire du contrat de chaque agent.
--
-- Exemple : date_debut_contrat = 2026-09-01
-- période 1 : 2026-09-01 -> 2027-08-31
-- période 2 : 2027-09-01 -> 2028-08-31
--
-- La migration ne devine volontairement PAS la date de contrat
-- des agents existants. Il faut la renseigner depuis Administration
-- > Modifier l'agent. Tant qu'elle est NULL, l'application conserve
-- temporairement le calcul par année civile et signale le fallback.
ALTER TABLE agent
ADD COLUMN date_debut_contrat DATE NULL AFTER diplome_libelle;
CREATE INDEX idx_agent_date_debut_contrat ON agent(date_debut_contrat);
-- Exemples de mise à jour manuelle :
-- UPDATE agent SET date_debut_contrat = '2024-09-01' WHERE id_agent = 1;
-- UPDATE agent SET date_debut_contrat = '2026-01-15' WHERE id_agent = 2;

View File

@@ -0,0 +1,14 @@
-- PTA V5.5 - Mise à jour des motifs d'affectation
-- Compatible avec une base existante.
UPDATE motif_planning
SET libelle = 'Absence',
compte_dans_quota = TRUE,
actif = TRUE
WHERE code = 'MALADIE';
UPDATE motif_planning
SET libelle = 'Autre absence (non décomptée)',
compte_dans_quota = FALSE,
actif = TRUE
WHERE code = 'AUTRE';

View File

@@ -0,0 +1,33 @@
-- ============================================================
-- PTA V5.7 - Le type d'affectation appartient au lieu
-- ============================================================
-- A exécuter une seule fois sur une base V5.6.
-- La migration reprend, quand c'est possible, le type majoritaire
-- des agents actuellement rattachés au lieu puis supprime la colonne
-- type_affectation de la table agent.
ALTER TABLE structure
ADD COLUMN type_affectation ENUM('PERISCOLAIRE', 'EXTRASCOLAIRE')
NOT NULL DEFAULT 'PERISCOLAIRE'
AFTER adresse;
UPDATE structure s
LEFT JOIN (
SELECT
id_structure,
CASE
WHEN SUM(type_affectation = 'EXTRASCOLAIRE') > SUM(type_affectation = 'PERISCOLAIRE')
THEN 'EXTRASCOLAIRE'
ELSE 'PERISCOLAIRE'
END AS type_affectation_migre
FROM agent
WHERE id_structure IS NOT NULL
GROUP BY id_structure
) source ON source.id_structure = s.id_structure
SET s.type_affectation = COALESCE(source.type_affectation_migre, 'PERISCOLAIRE');
ALTER TABLE agent
DROP COLUMN type_affectation;
-- Vérification conseillée après migration :
-- SELECT id_structure, code, nom, type_affectation FROM structure ORDER BY nom;

View File

@@ -0,0 +1,60 @@
-- ============================================================
-- PTA V5.9 - Semaines types par agent
-- ============================================================
-- Un modèle mémorise les affectations d'une semaine de référence :
-- jour ouvré, lieu, motif et plage horaire.
CREATE TABLE IF NOT EXISTS modele_semaine_agent (
id_modele INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
id_agent INT UNSIGNED NOT NULL,
nom VARCHAR(120) NOT NULL,
date_creation DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
CONSTRAINT fk_modele_semaine_agent
FOREIGN KEY (id_agent)
REFERENCES agent(id_agent)
ON DELETE CASCADE,
UNIQUE KEY uk_modele_semaine_agent_nom (id_agent, nom),
KEY idx_modele_semaine_agent (id_agent, date_modification)
) ENGINE = InnoDB;
CREATE TABLE IF NOT EXISTS modele_semaine_creneau (
id_modele_creneau INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
id_modele INT UNSIGNED NOT NULL,
jour_semaine TINYINT UNSIGNED NOT NULL COMMENT '1=lundi ... 5=vendredi',
id_structure INT UNSIGNED NOT NULL,
id_motif INT UNSIGNED NOT NULL,
heure_debut TIME NOT NULL,
heure_fin TIME NOT NULL,
CONSTRAINT fk_modele_creneau_modele
FOREIGN KEY (id_modele)
REFERENCES modele_semaine_agent(id_modele)
ON DELETE CASCADE,
CONSTRAINT fk_modele_creneau_structure
FOREIGN KEY (id_structure)
REFERENCES structure(id_structure),
CONSTRAINT fk_modele_creneau_motif
FOREIGN KEY (id_motif)
REFERENCES motif_planning(id_motif),
CONSTRAINT chk_modele_creneau_jour
CHECK (jour_semaine BETWEEN 1 AND 5),
CONSTRAINT chk_modele_creneau_ordre
CHECK (heure_fin > heure_debut),
CONSTRAINT chk_modele_creneau_quart_heure
CHECK (
MOD(MINUTE(heure_debut), 15) = 0
AND MOD(MINUTE(heure_fin), 15) = 0
AND SECOND(heure_debut) = 0
AND SECOND(heure_fin) = 0
),
KEY idx_modele_creneau_modele_jour (id_modele, jour_semaine, heure_debut)
) ENGINE = InnoDB;

View File

@@ -0,0 +1,95 @@
-- ============================================================================
-- PTA V7.5 - Quota PTA sur l'année civile
-- MySQL 8+
-- À exécuter après la V7 / V7.4.
-- ============================================================================
-- Règle métier :
-- * 1607 h à 100 % sont planifiées sur l'année civile : 01/01 -> 31/12.
-- * la date de début du contrat reste une information RH et ne décale plus
-- la période annuelle du PTA ;
-- * un agent peut participer à des équipes périscolaires ET extrascolaires.
-- Aucun type périscolaire/extrascolaire n'est porté par l'agent.
--
-- Cette migration ne supprime pas les anciens PTA construits sur une période
-- contractuelle. Elle crée, si besoin, une ligne civile équivalente afin de
-- conserver l'historique et d'éviter une migration destructive.
-- ============================================================================
START TRANSACTION;
UPDATE quota_agent_annuel
SET commentaire = 'Quota PTA de référence - année civile'
WHERE commentaire IS NULL
OR commentaire LIKE '%contrat%'
OR commentaire LIKE '%période%';
-- Recrée une ligne PTA civile à partir des anciennes lignes non civiles.
-- INSERT IGNORE évite tout conflit si la ligne annuelle existe déjà.
INSERT IGNORE INTO pta_annuel (
id_agent,
date_debut,
date_fin,
quotite_travail,
quota_reference_minutes,
quota_cible_minutes,
enveloppe_formation_minutes,
conges_annuels_jours_cible,
jours_fractionnement_cible,
statut,
commentaire,
date_validation
)
SELECT
pa.id_agent,
STR_TO_DATE(CONCAT(YEAR(pa.date_debut), '-01-01'), '%Y-%m-%d') AS date_debut,
STR_TO_DATE(CONCAT(YEAR(pa.date_debut), '-12-31'), '%Y-%m-%d') AS date_fin,
pa.quotite_travail,
pa.quota_reference_minutes,
pa.quota_cible_minutes,
pa.enveloppe_formation_minutes,
pa.conges_annuels_jours_cible,
pa.jours_fractionnement_cible,
pa.statut,
CONCAT(
COALESCE(NULLIF(pa.commentaire, ''), ''),
CASE WHEN COALESCE(NULLIF(pa.commentaire, ''), '') = '' THEN '' ELSE ' - ' END,
'Période convertie en année civile par migration V7.5'
),
pa.date_validation
FROM pta_annuel pa
WHERE pa.date_debut <> STR_TO_DATE(CONCAT(YEAR(pa.date_debut), '-01-01'), '%Y-%m-%d')
OR pa.date_fin <> STR_TO_DATE(CONCAT(YEAR(pa.date_debut), '-12-31'), '%Y-%m-%d');
-- La vue de synthèse ne présente désormais que les PTA civils.
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
WHERE pa.date_debut = STR_TO_DATE(CONCAT(YEAR(pa.date_debut), '-01-01'), '%Y-%m-%d')
AND pa.date_fin = STR_TO_DATE(CONCAT(YEAR(pa.date_debut), '-12-31'), '%Y-%m-%d')
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;
COMMIT;

View File

@@ -0,0 +1,313 @@
-- ============================================================================
-- 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 daccueil', 'ANIMATION', 570, 570, TRUE, TRUE, TRUE, 10),
('ANIMATION_RELAIS', 'Agent danimation relais', 'ANIMATION', 570, 570, TRUE, TRUE, TRUE, 20),
('ANIMATION', 'Agent danimation', 'ANIMATION', 570, 570, TRUE, TRUE, TRUE, 30),
('RESTAURATION_ENTRETIEN', 'Agent de restauration collective et dentretien', '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 lagent
-- --------------------------------------------------------------------------
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 lagent
-- --------------------------------------------------------------------------
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;

1739
database/pta_mysql_test.sql Normal file

File diff suppressed because it is too large Load Diff