-- ============================================================
-- Base de donnees de test PTA
-- Compatible MySQL 8.0.16+
-- ============================================================

USE nfbhnajdb1;

-- ============================================================
-- 1. REFERENTIEL
-- ============================================================

CREATE TABLE agent (
    id_agent INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    matricule VARCHAR(50) NOT NULL UNIQUE,
    nom VARCHAR(100) NOT NULL,
    prenom VARCHAR(100) NOT NULL,
    email VARCHAR(255) NULL,
    telephone VARCHAR(30) NULL,
    adresse VARCHAR(255) NULL,
    est_diplome BOOLEAN NOT NULL DEFAULT FALSE,
    diplome_libelle VARCHAR(150) NULL,
    date_debut_contrat DATE NULL,
    id_structure INT UNSIGNED NULL,
    actif BOOLEAN NOT NULL DEFAULT TRUE,
    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE = InnoDB;

CREATE TABLE structure (
    id_structure INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    code VARCHAR(50) NOT NULL UNIQUE,
    nom VARCHAR(150) NOT NULL,
    adresse VARCHAR(255) NULL,
    type_affectation ENUM('PERISCOLAIRE', 'EXTRASCOLAIRE') NOT NULL DEFAULT 'PERISCOLAIRE',
    actif BOOLEAN NOT NULL DEFAULT TRUE
) ENGINE = InnoDB;

ALTER TABLE agent
    ADD CONSTRAINT fk_agent_structure_principale
    FOREIGN KEY (id_structure) REFERENCES structure(id_structure)
    ON UPDATE CASCADE
    ON DELETE SET NULL;

CREATE INDEX idx_agent_structure_principale ON agent(id_structure);

CREATE TABLE 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);

CREATE TABLE tranche_age (
    id_tranche_age INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    libelle VARCHAR(100) NOT NULL,
    age_min_mois INT UNSIGNED NULL,
    age_max_mois INT UNSIGNED NULL,
    CONSTRAINT chk_tranche_age
        CHECK (
            age_min_mois IS NULL
            OR age_max_mois IS NULL
            OR age_max_mois >= age_min_mois
        )
) ENGINE = InnoDB;

CREATE TABLE structure_capacite (
    id_structure INT UNSIGNED NOT NULL,
    id_tranche_age INT UNSIGNED NOT NULL,
    nombre_places INT UNSIGNED NOT NULL,

    PRIMARY KEY (id_structure, id_tranche_age),

    CONSTRAINT fk_capacite_structure
        FOREIGN KEY (id_structure)
        REFERENCES structure(id_structure),

    CONSTRAINT fk_capacite_tranche_age
        FOREIGN KEY (id_tranche_age)
        REFERENCES tranche_age(id_tranche_age),

    CONSTRAINT chk_nombre_places
        CHECK (nombre_places > 0)
) ENGINE = InnoDB;

-- ============================================================
-- 2. CONTRATS ET QUOTAS
-- ============================================================

CREATE TABLE contrat_agent (
    id_contrat INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    id_agent INT UNSIGNED NOT NULL,
    date_debut DATE NOT NULL,
    date_fin DATE NULL,

    -- Quota total sur la periode du contrat, en minutes.
    -- Le stockage en minutes evite les problemes d'arrondis.
    quota_minutes INT UNSIGNED NOT NULL,

    type_contrat VARCHAR(100) NULL,
    actif BOOLEAN NOT NULL DEFAULT TRUE,

    CONSTRAINT fk_contrat_agent
        FOREIGN KEY (id_agent)
        REFERENCES agent(id_agent),

    CONSTRAINT chk_dates_contrat
        CHECK (date_fin IS NULL OR date_fin >= date_debut),

    CONSTRAINT chk_quota_contrat
        CHECK (quota_minutes > 0)
) ENGINE = InnoDB;

CREATE INDEX idx_contrat_agent_dates
    ON contrat_agent(id_agent, date_debut, date_fin);

-- ============================================================
-- 3. SEMAINES
-- ============================================================

CREATE TABLE semaine (
    id_semaine INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    annee SMALLINT UNSIGNED NOT NULL,
    numero_semaine TINYINT UNSIGNED NOT NULL,
    date_debut DATE NOT NULL,
    date_fin DATE NOT NULL,
    statut ENUM('OUVERTE', 'VALIDEE', 'VERROUILLEE')
        NOT NULL DEFAULT 'OUVERTE',

    UNIQUE KEY uk_semaine_annee_numero (annee, numero_semaine),
    UNIQUE KEY uk_semaine_dates (date_debut, date_fin),

    CONSTRAINT chk_numero_semaine
        CHECK (numero_semaine BETWEEN 1 AND 53),

    CONSTRAINT chk_dates_semaine
        CHECK (date_fin >= date_debut)
) ENGINE = InnoDB;

-- ============================================================
-- 4. PLANNINGS ET CRENEAUX
-- ============================================================

CREATE TABLE planning (
    id_planning INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    id_agent INT UNSIGNED NOT NULL,
    id_semaine INT UNSIGNED NOT NULL,
    id_structure INT UNSIGNED NOT NULL,

    statut ENUM('BROUILLON', 'VALIDE')
        NOT NULL DEFAULT 'BROUILLON',

    commentaire VARCHAR(500) NULL,
    id_planning_source INT UNSIGNED NULL,

    date_creation DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
    date_modification DATETIME NOT NULL
        DEFAULT CURRENT_TIMESTAMP
        ON UPDATE CURRENT_TIMESTAMP,
    date_validation DATETIME NULL,

    CONSTRAINT fk_planning_agent
        FOREIGN KEY (id_agent)
        REFERENCES agent(id_agent),

    CONSTRAINT fk_planning_semaine
        FOREIGN KEY (id_semaine)
        REFERENCES semaine(id_semaine),

    CONSTRAINT fk_planning_structure
        FOREIGN KEY (id_structure)
        REFERENCES structure(id_structure),

    CONSTRAINT fk_planning_source
        FOREIGN KEY (id_planning_source)
        REFERENCES planning(id_planning)
        ON DELETE SET NULL,

    UNIQUE KEY uk_planning_agent_semaine_structure
        (id_agent, id_semaine, id_structure)
) ENGINE = InnoDB;

CREATE INDEX idx_planning_agent_statut
    ON planning(id_agent, statut);

CREATE TABLE creneau_horaire (
    id_creneau INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    id_planning INT UNSIGNED NOT NULL,
    date_jour DATE NOT NULL,
    heure_debut TIME NOT NULL,
    heure_fin TIME NOT NULL,

    CONSTRAINT fk_creneau_planning
        FOREIGN KEY (id_planning)
        REFERENCES planning(id_planning)
        ON DELETE CASCADE,

    CONSTRAINT chk_creneau_ordre
        CHECK (heure_fin > heure_debut),

    -- Les heures doivent tomber sur un quart d'heure exact.
    CONSTRAINT chk_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
        )
) ENGINE = InnoDB;

CREATE INDEX idx_creneau_planning_date
    ON creneau_horaire(id_planning, date_jour, heure_debut, heure_fin);

-- ============================================================
-- 5. VUES DE SUIVI
-- ============================================================

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'
                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'
                    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'
                        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')
GROUP BY
    ca.id_contrat,
    ca.id_agent,
    a.matricule,
    a.prenom,
    a.nom,
    ca.date_debut,
    ca.date_fin,
    ca.quota_minutes;

CREATE OR REPLACE VIEW v_planning_agent_semaine AS
SELECT
    p.id_planning,
    p.id_agent,
    a.matricule,
    CONCAT(a.prenom, ' ', a.nom) AS agent,
    s.annee,
    s.numero_semaine,
    st.nom AS structure,
    p.statut,
    ROUND(
        COALESCE(
            SUM(TIME_TO_SEC(TIMEDIFF(c.heure_fin, c.heure_debut)) / 3600),
            0
        ),
        2
    ) AS heures_planifiees
FROM planning p
JOIN agent a
    ON a.id_agent = p.id_agent
JOIN semaine s
    ON s.id_semaine = p.id_semaine
JOIN structure st
    ON st.id_structure = p.id_structure
LEFT JOIN creneau_horaire c
    ON c.id_planning = p.id_planning
GROUP BY
    p.id_planning,
    p.id_agent,
    a.matricule,
    a.prenom,
    a.nom,
    s.annee,
    s.numero_semaine,
    st.nom,
    p.statut;

-- ============================================================
-- 6. PROCEDURES DE CONTROLE ET DE VALIDATION
-- ============================================================

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
                        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
                        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
              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
              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$$

DROP PROCEDURE IF EXISTS sp_controler_planning$$
CREATE PROCEDURE sp_controler_planning(
    IN p_id_planning INT UNSIGNED
)
BEGIN
    CALL sp_preparer_controles_planning(p_id_planning);

    SELECT
        niveau,
        code,
        message
    FROM tmp_controles_planning
    ORDER BY
        FIELD(niveau, 'ERREUR', 'ALERTE', 'INFO'),
        id;
END$$

DROP PROCEDURE IF EXISTS sp_valider_planning$$
CREATE PROCEDURE sp_valider_planning(
    IN p_id_planning INT UNSIGNED,
    IN p_forcer_depassement_quota BOOLEAN
)
BEGIN
    DECLARE v_nb_erreurs INT DEFAULT 0;
    DECLARE v_nb_alertes_quota INT DEFAULT 0;
    DECLARE v_message VARCHAR(1000);

    CALL sp_preparer_controles_planning(p_id_planning);

    SELECT COUNT(*)
    INTO v_nb_erreurs
    FROM tmp_controles_planning
    WHERE niveau = 'ERREUR';

    IF v_nb_erreurs > 0 THEN
        SELECT message
        INTO v_message
        FROM tmp_controles_planning
        WHERE niveau = 'ERREUR'
        ORDER BY id
        LIMIT 1;

        SET v_message = CONCAT(
            'Validation impossible. ',
            v_nb_erreurs,
            ' erreur(s) detectee(s). Premiere erreur : ',
            v_message
        );

        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = v_message;
    END IF;

    SELECT COUNT(*)
    INTO v_nb_alertes_quota
    FROM tmp_controles_planning
    WHERE code = 'QUOTA_DEPASSE';

    IF v_nb_alertes_quota > 0
       AND COALESCE(p_forcer_depassement_quota, FALSE) = FALSE THEN

        SET v_message = CONCAT(
            'Validation suspendue : le quota contractuel serait depasse. ',
            'Relancer avec p_forcer_depassement_quota = TRUE pour forcer la validation.'
        );

        SIGNAL SQLSTATE '45000'
            SET MESSAGE_TEXT = v_message;
    END IF;

    UPDATE planning
    SET
        statut = 'VALIDE',
        date_validation = CURRENT_TIMESTAMP
    WHERE id_planning = p_id_planning;

    SELECT
        p.id_planning,
        p.statut,
        p.date_validation,
        'Planning valide avec succes.' AS message
    FROM planning p
    WHERE p.id_planning = p_id_planning;
END$$

DELIMITER ;

-- ============================================================
-- 7. JEU D'ESSAI
-- ============================================================

INSERT INTO agent (
    id_agent,
    matricule,
    nom,
    prenom,
    email,
    telephone,
    adresse,
    est_diplome,
    diplome_libelle,
    date_debut_contrat
)
VALUES
    (1, 'AG001', 'MARTIN', 'Alice', 'alice.martin@example.test', '06 10 20 30 40', '12 rue des Lilas, Orléans', TRUE, 'BAFA', '2026-07-01'),
    (2, 'AG002', 'DURAND', 'Bruno', 'bruno.durand@example.test', '06 11 21 31 41', '8 avenue de Paris, Orléans', FALSE, NULL, '2026-07-01'),
    (3, 'AG003', 'PETIT', 'Chloe', 'chloe.petit@example.test', '06 12 22 32 42', '5 rue Royale, Orléans', TRUE, 'CAP AEPE', '2026-07-22');

INSERT INTO structure (
    id_structure,
    code,
    nom,
    adresse,
    type_affectation
)
VALUES
    (1, 'STR001', 'Creche des Lilas', '10 rue des Lilas, Orleans', 'PERISCOLAIRE'),
    (2, 'STR002', 'Accueil Madeleine', '36 rue du Faubourg Madeleine, Orleans', 'EXTRASCOLAIRE');

-- Plages de couverture de test. Elles sont configurables dans l'administration.
INSERT INTO structure_couverture_horaire (id_structure, jour_semaine, heure_debut, heure_fin, minimum_agents)
VALUES
    (1, 1, '07:30:00', '09:00:00', 1),
    (1, 1, '16:30:00', '18:30:00', 1),
    (1, 2, '07:30:00', '09:00:00', 1),
    (1, 2, '16:30:00', '18:30:00', 1),
    (1, 3, '07:30:00', '09:00:00', 1),
    (1, 3, '16:30:00', '18:30:00', 1),
    (1, 4, '07:30:00', '09:00:00', 1),
    (1, 4, '16:30:00', '18:30:00', 1),
    (1, 5, '07:30:00', '09:00:00', 1),
    (1, 5, '16:30:00', '18:30:00', 1),
    (2, 1, '08:00:00', '18:00:00', 1),
    (2, 2, '08:00:00', '18:00:00', 1),
    (2, 3, '08:00:00', '18:00:00', 1),
    (2, 4, '08:00:00', '18:00:00', 1),
    (2, 5, '08:00:00', '18:00:00', 1);

-- Une structure principale par agent.
UPDATE agent SET id_structure = 1 WHERE id_agent IN (1, 2);
UPDATE agent SET id_structure = 2 WHERE id_agent = 3;

INSERT INTO tranche_age (
    id_tranche_age,
    libelle,
    age_min_mois,
    age_max_mois
)
VALUES
    (1, '0 - 3 ans', 0, 35),
    (2, '3 - 6 ans', 36, 71),
    (3, '6 - 11 ans', 72, 131);

INSERT INTO structure_capacite (
    id_structure,
    id_tranche_age,
    nombre_places
)
VALUES
    (1, 1, 20),
    (1, 2, 12),
    (2, 2, 18),
    (2, 3, 30);

-- Alice : 100 h sur juillet/aout pour permettre plusieurs plannings valides.
-- Bruno : 35 h au total, afin de provoquer un depassement de quota.
-- Chloe : contrat qui commence le 22/07, afin de tester un planning hors contrat.
INSERT INTO contrat_agent (
    id_contrat,
    id_agent,
    date_debut,
    date_fin,
    quota_minutes,
    type_contrat
)
VALUES
    (1, 1, '2026-07-01', '2026-08-31', 6000, 'PTA ete - 100 h'),
    (2, 2, '2026-07-01', '2026-07-31', 2100, 'PTA juillet - 35 h'),
    (3, 3, '2026-07-22', '2026-08-31', 2100, 'PTA ete - 35 h');

INSERT INTO semaine (
    id_semaine,
    annee,
    numero_semaine,
    date_debut,
    date_fin
)
VALUES
    (1, 2026, 29, '2026-07-13', '2026-07-19'),
    (2, 2026, 30, '2026-07-20', '2026-07-26'),
    (3, 2026, 31, '2026-07-27', '2026-08-02');

-- ------------------------------------------------------------
-- Planning #1 : Alice, semaine 29, 32 h, deja valide
-- ------------------------------------------------------------
INSERT INTO planning (
    id_planning,
    id_agent,
    id_semaine,
    id_structure,
    statut,
    commentaire,
    date_validation
)
VALUES
    (1, 1, 1, 1, 'VALIDE', 'Planning valide de reference - 32 h', CURRENT_TIMESTAMP);

INSERT INTO creneau_horaire (id_planning, date_jour, heure_debut, heure_fin)
VALUES
    (1, '2026-07-13', '08:00:00', '12:00:00'),
    (1, '2026-07-13', '13:00:00', '17:00:00'),
    (1, '2026-07-14', '08:00:00', '12:00:00'),
    (1, '2026-07-14', '13:00:00', '17:00:00'),
    (1, '2026-07-15', '08:00:00', '12:00:00'),
    (1, '2026-07-15', '13:00:00', '17:00:00'),
    (1, '2026-07-16', '08:00:00', '12:00:00'),
    (1, '2026-07-16', '13:00:00', '17:00:00');

-- ------------------------------------------------------------
-- Planning #2 : Alice, semaine 30, 32 h, deja valide
-- Sert de planning de reference pour tester les chevauchements.
-- ------------------------------------------------------------
INSERT INTO planning (
    id_planning,
    id_agent,
    id_semaine,
    id_structure,
    statut,
    commentaire,
    date_validation
)
VALUES
    (2, 1, 2, 1, 'VALIDE', 'Deuxieme planning valide de reference - 32 h', CURRENT_TIMESTAMP);

INSERT INTO creneau_horaire (id_planning, date_jour, heure_debut, heure_fin)
VALUES
    (2, '2026-07-20', '08:00:00', '12:00:00'),
    (2, '2026-07-20', '13:00:00', '17:00:00'),
    (2, '2026-07-21', '08:00:00', '12:00:00'),
    (2, '2026-07-21', '13:00:00', '17:00:00'),
    (2, '2026-07-22', '08:00:00', '12:00:00'),
    (2, '2026-07-22', '13:00:00', '17:00:00'),
    (2, '2026-07-23', '08:00:00', '12:00:00'),
    (2, '2026-07-23', '13:00:00', '17:00:00');

-- ------------------------------------------------------------
-- Planning #3 : Alice, semaine 30, autre structure
-- ERREUR ATTENDUE : chevauchement avec le planning #2.
-- ------------------------------------------------------------
INSERT INTO planning (
    id_planning,
    id_agent,
    id_semaine,
    id_structure,
    statut,
    commentaire
)
VALUES
    (3, 1, 2, 2, 'BROUILLON', 'Test chevauchement entre deux structures');

INSERT INTO creneau_horaire (id_planning, date_jour, heure_debut, heure_fin)
VALUES
    (3, '2026-07-20', '10:00:00', '12:00:00');

-- ------------------------------------------------------------
-- Planning #4 : Bruno, semaine 29, 30 h, deja valide
-- ------------------------------------------------------------
INSERT INTO planning (
    id_planning,
    id_agent,
    id_semaine,
    id_structure,
    statut,
    commentaire,
    date_validation
)
VALUES
    (4, 2, 1, 1, 'VALIDE', 'Planning valide Bruno - 30 h sur quota de 35 h', CURRENT_TIMESTAMP);

INSERT INTO creneau_horaire (id_planning, date_jour, heure_debut, heure_fin)
VALUES
    (4, '2026-07-13', '08:00:00', '12:00:00'),
    (4, '2026-07-13', '13:00:00', '16:00:00'),
    (4, '2026-07-14', '08:00:00', '12:00:00'),
    (4, '2026-07-14', '13:00:00', '16:00:00'),
    (4, '2026-07-15', '08:00:00', '12:00:00'),
    (4, '2026-07-15', '13:00:00', '16:00:00'),
    (4, '2026-07-16', '08:00:00', '12:00:00'),
    (4, '2026-07-16', '13:00:00', '16:00:00'),
    (4, '2026-07-17', '08:00:00', '10:00:00');

-- ------------------------------------------------------------
-- Planning #5 : Bruno, semaine 30, 10 h
-- ALERTE ATTENDUE : quota depasse (30 h deja validees + 10 h = 40 h > 35 h).
-- ------------------------------------------------------------
INSERT INTO planning (
    id_planning,
    id_agent,
    id_semaine,
    id_structure,
    statut,
    commentaire
)
VALUES
    (5, 2, 2, 1, 'BROUILLON', 'Test depassement de quota');

INSERT INTO creneau_horaire (id_planning, date_jour, heure_debut, heure_fin)
VALUES
    (5, '2026-07-20', '08:00:00', '12:00:00'),
    (5, '2026-07-21', '08:00:00', '12:00:00'),
    (5, '2026-07-22', '08:00:00', '10:00:00');

-- ------------------------------------------------------------
-- Planning #6 : Chloe, semaine 29
-- ERREUR ATTENDUE : hors contrat, son contrat commence le 22/07.
-- ------------------------------------------------------------
INSERT INTO planning (
    id_planning,
    id_agent,
    id_semaine,
    id_structure,
    statut,
    commentaire
)
VALUES
    (6, 3, 1, 1, 'BROUILLON', 'Test creneau avant le debut du contrat');

INSERT INTO creneau_horaire (id_planning, date_jour, heure_debut, heure_fin)
VALUES
    (6, '2026-07-15', '08:00:00', '12:00:00');

-- ------------------------------------------------------------
-- Planning #7 : Bruno, semaine 30, deuxieme structure
-- ERREUR ATTENDUE : deux creneaux se chevauchent dans le meme planning.
-- ------------------------------------------------------------
INSERT INTO planning (
    id_planning,
    id_agent,
    id_semaine,
    id_structure,
    statut,
    commentaire
)
VALUES
    (7, 2, 2, 2, 'BROUILLON', 'Test chevauchement interne');

INSERT INTO creneau_horaire (id_planning, date_jour, heure_debut, heure_fin)
VALUES
    (7, '2026-07-23', '08:00:00', '12:00:00'),
    (7, '2026-07-23', '11:00:00', '14:00:00');

-- ------------------------------------------------------------
-- Planning #8 : Alice, semaine 31, 24 h
-- CAS VALIDE ATTENDU : 64 h deja validees + 24 h = 88 h sur quota de 100 h.
-- ------------------------------------------------------------
INSERT INTO planning (
    id_planning,
    id_agent,
    id_semaine,
    id_structure,
    statut,
    commentaire,
    id_planning_source
)
VALUES
    (8, 1, 3, 1, 'BROUILLON', 'Cas nominal pouvant etre valide', 2);

INSERT INTO creneau_horaire (id_planning, date_jour, heure_debut, heure_fin)
VALUES
    (8, '2026-07-27', '08:00:00', '12:00:00'),
    (8, '2026-07-27', '13:00:00', '17:00:00'),
    (8, '2026-07-28', '08:00:00', '12:00:00'),
    (8, '2026-07-28', '13:00:00', '17:00:00'),
    (8, '2026-07-29', '08:00:00', '12:00:00'),
    (8, '2026-07-29', '13:00:00', '17:00:00');

-- ------------------------------------------------------------
-- Planning #9 : Chloe, semaine 30
-- ERREUR ATTENDUE : le 27/07 appartient a la semaine 31, pas a la semaine 30.
-- Le creneau est volontairement couvert par le contrat pour isoler ce controle.
-- ------------------------------------------------------------
INSERT INTO planning (
    id_planning,
    id_agent,
    id_semaine,
    id_structure,
    statut,
    commentaire
)
VALUES
    (9, 3, 2, 1, 'BROUILLON', 'Test date hors de la semaine du planning');

INSERT INTO creneau_horaire (id_planning, date_jour, heure_debut, heure_fin)
VALUES
    (9, '2026-07-27', '08:00:00', '12:00:00');

-- ============================================================
-- 8. REQUETES DE TEST A EXECUTER MANUELLEMENT
-- ============================================================

-- CAS 1 : cas nominal, aucune anomalie attendue.
-- CALL sp_controler_planning(8);

-- CAS 2 : validation du cas nominal.
-- CALL sp_valider_planning(8, FALSE);

-- CAS 3 : chevauchement avec un autre planning valide.
-- CALL sp_controler_planning(3);
-- CALL sp_valider_planning(3, FALSE);

-- CAS 4 : depassement de quota.
-- CALL sp_controler_planning(5);
-- La validation ci-dessous doit etre refusee :
-- CALL sp_valider_planning(5, FALSE);
-- La validation peut etre forcee apres confirmation utilisateur :
-- CALL sp_valider_planning(5, TRUE);

-- CAS 5 : creneau hors contrat.
-- CALL sp_controler_planning(6);
-- CALL sp_valider_planning(6, FALSE);

-- CAS 6 : chevauchement interne.
-- CALL sp_controler_planning(7);
-- CALL sp_valider_planning(7, FALSE);

-- CAS 7 : date en dehors de la semaine selectionnee.
-- CALL sp_controler_planning(9);
-- CALL sp_valider_planning(9, FALSE);

-- CAS 8 : controle direct par contrainte SQL des quarts d'heure.
-- L'INSERT suivant doit echouer a cause du CHECK chk_creneau_quart_heure :
-- INSERT INTO creneau_horaire(id_planning, date_jour, heure_debut, heure_fin)
-- VALUES (8, '2026-07-30', '08:10:00', '10:00:00');

-- CAS 9 : consultation du suivi de quota par contrat.
-- SELECT * FROM v_suivi_quota_contrat ORDER BY id_contrat;

-- CAS 10 : synthese des heures par planning.
-- SELECT * FROM v_planning_agent_semaine ORDER BY id_planning;

-- ============================================================
-- 9. LIMITES FONCTIONNELLES VOLONTAIRES DU MVP
-- ============================================================
-- La capacite par tranche d'age est stockee dans structure_capacite,
-- mais elle ne peut pas encore etre controlee automatiquement avec le
-- modele actuel car aucun effectif d'enfants/usagers n'est associe a un
-- planning. Pour controler la capacite, il faudra ajouter une table
-- d'effectif ou d'affectation par structure, date et tranche d'age.
--
-- De meme, les heures ci-dessus sont des heures PLANIFIEES et non des
-- heures reellement EFFECTUEES. Pour distinguer les deux, il faudra
-- ajouter un module de pointage ou de realisation effective.
-- ============================================================
-- Extension de la base PTA pour l'interface de gestion planning
-- À exécuter APRES pta_mysql_test.sql
-- Compatible MySQL 8+
-- ============================================================

USE nfbhnajdb1;

-- 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;

-- Historique initial cohérent avec la structure principale de chaque 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 ;
-- ============================================================
-- 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 nfbhnajdb1;

-- ------------------------------------------------------------
-- 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;


-- ============================================================
-- 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;



-- ============================================================
-- 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;



-- ============================================================
-- Historique V5.20 - date de début de contrat
-- IMPORTANT : depuis la V7.5, le quota PTA est suivi sur l'année civile
-- (01/01 -> 31/12). La date de contrat n'est plus l'ancre du quota.
-- ============================================================
