450 lines
15 KiB
SQL
450 lines
15 KiB
SQL
-- ============================================================
|
|
-- 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 ;
|