prod ready ?
This commit is contained in:
15
.env.example
Normal file
15
.env.example
Normal file
@@ -0,0 +1,15 @@
|
||||
# Application
|
||||
APP_BIND=0.0.0.0
|
||||
APP_PORT=8080
|
||||
TZ=Europe/Paris
|
||||
|
||||
# MariaDB
|
||||
DB_NAME=pta
|
||||
DB_USER=pta
|
||||
DB_PASSWORD=pta
|
||||
DB_ROOT_PASSWORD=pta
|
||||
|
||||
# phpMyAdmin
|
||||
# 127.0.0.1 = uniquement accessible depuis le serveur lui-même.
|
||||
PMA_BIND=127.0.0.1
|
||||
PMA_PORT=8081
|
||||
46
Dockerfile
Normal file
46
Dockerfile
Normal file
@@ -0,0 +1,46 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# ==========================================================
|
||||
# PHP-FPM
|
||||
# ==========================================================
|
||||
FROM php:8.3-fpm-bookworm AS php
|
||||
|
||||
ENV TZ=Europe/Paris
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
libcurl4-openssl-dev \
|
||||
libonig-dev \
|
||||
&& docker-php-ext-install -j"$(nproc)" \
|
||||
pdo_mysql \
|
||||
mysqli \
|
||||
mbstring \
|
||||
curl \
|
||||
opcache \
|
||||
&& apt-get purge -y --auto-remove libcurl4-openssl-dev libonig-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
COPY docker/php/php.ini /usr/local/etc/php/conf.d/99-pta.ini
|
||||
COPY . /var/www/html
|
||||
|
||||
RUN chown -R www-data:www-data /var/www/html
|
||||
|
||||
USER www-data
|
||||
|
||||
EXPOSE 9000
|
||||
|
||||
CMD ["php-fpm"]
|
||||
|
||||
# ==========================================================
|
||||
# NGINX
|
||||
# ==========================================================
|
||||
FROM nginx:1.27-alpine AS nginx
|
||||
|
||||
COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=php /var/www/html /var/www/html
|
||||
|
||||
EXPOSE 80
|
||||
96
README_DOCKER.md
Normal file
96
README_DOCKER.md
Normal file
@@ -0,0 +1,96 @@
|
||||
# PTA - Docker / Nginx / PHP / MariaDB / phpMyAdmin
|
||||
|
||||
## Installation
|
||||
|
||||
Copier les fichiers de ce kit à la racine du projet PTA :
|
||||
|
||||
- `Dockerfile`
|
||||
- `docker-compose.yml`
|
||||
- `.env.example`
|
||||
- `docker/nginx/default.conf`
|
||||
- `docker/php/php.ini`
|
||||
- `database/pta_prod_schema_mariadb.sql`
|
||||
|
||||
Puis :
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
Changer impérativement `DB_PASSWORD` et `DB_ROOT_PASSWORD`.
|
||||
|
||||
Démarrer :
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Application :
|
||||
|
||||
```text
|
||||
http://IP_DU_SERVEUR:8080
|
||||
```
|
||||
|
||||
phpMyAdmin est volontairement publié sur `127.0.0.1:8081` par défaut.
|
||||
Sur le serveur lui-même :
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8081
|
||||
```
|
||||
|
||||
Pour y accéder depuis un poste distant, le plus sûr est un tunnel SSH :
|
||||
|
||||
```bash
|
||||
ssh -L 8081:127.0.0.1:8081 utilisateur@serveur
|
||||
```
|
||||
|
||||
Puis ouvrir localement :
|
||||
|
||||
```text
|
||||
http://127.0.0.1:8081
|
||||
```
|
||||
|
||||
## Base de données
|
||||
|
||||
Le schéma `database/pta_prod_schema_mariadb.sql` est exécuté automatiquement uniquement
|
||||
lorsque le volume MariaDB est créé pour la première fois.
|
||||
|
||||
Il ne sera PAS rejoué à chaque démarrage du conteneur.
|
||||
|
||||
Si la base existe déjà, ne supprimez pas le volume pour appliquer une migration. Exécutez
|
||||
les migrations explicitement sur la base existante.
|
||||
|
||||
## Commandes utiles
|
||||
|
||||
```bash
|
||||
# Etat
|
||||
docker compose ps
|
||||
|
||||
# Logs
|
||||
docker compose logs -f nginx php db
|
||||
|
||||
# Rebuild après modification du PHP / JS / CSS
|
||||
docker compose up -d --build php nginx
|
||||
|
||||
# Arrêt
|
||||
docker compose down
|
||||
|
||||
# Arrêt SANS supprimer les données
|
||||
docker compose down
|
||||
|
||||
# ATTENTION : supprime la base MariaDB persistée
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
## Connexion de l'application
|
||||
|
||||
Le `config.php` PTA utilise déjà les variables :
|
||||
|
||||
- `PTA_DB_HOST`
|
||||
- `PTA_DB_PORT`
|
||||
- `PTA_DB_NAME`
|
||||
- `PTA_DB_USER`
|
||||
- `PTA_DB_PASS`
|
||||
|
||||
Le compose les fournit automatiquement au conteneur PHP.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,70 +0,0 @@
|
||||
-- ============================================================
|
||||
-- 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.
|
||||
@@ -1,449 +0,0 @@
|
||||
-- ============================================================
|
||||
-- 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 ;
|
||||
@@ -1,145 +0,0 @@
|
||||
-- ============================================================
|
||||
-- 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;
|
||||
@@ -1,10 +0,0 @@
|
||||
-- PTA V4.9 - Ajout du type d’affectation 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.
|
||||
@@ -1,39 +0,0 @@
|
||||
-- ============================================================
|
||||
-- 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);
|
||||
@@ -1,15 +0,0 @@
|
||||
-- ============================================================
|
||||
-- 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;
|
||||
@@ -1,36 +0,0 @@
|
||||
-- ============================================================
|
||||
-- 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;
|
||||
@@ -1,27 +0,0 @@
|
||||
-- ============================================================
|
||||
-- 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;
|
||||
@@ -1,14 +0,0 @@
|
||||
-- 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';
|
||||
@@ -1,33 +0,0 @@
|
||||
-- ============================================================
|
||||
-- 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;
|
||||
@@ -1,60 +0,0 @@
|
||||
-- ============================================================
|
||||
-- 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;
|
||||
@@ -1,95 +0,0 @@
|
||||
-- ============================================================================
|
||||
-- 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;
|
||||
@@ -1,313 +0,0 @@
|
||||
-- ============================================================================
|
||||
-- PTA V7 - Socle métier annuel, postes, contraintes et équipes
|
||||
-- MySQL 8.0.16+
|
||||
-- À exécuter après le schéma V5.20 / V6.
|
||||
-- ============================================================================
|
||||
|
||||
START TRANSACTION;
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- 1. Postes éligibles au PTA
|
||||
-- --------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS poste_agent (
|
||||
id_poste INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
code VARCHAR(50) NOT NULL,
|
||||
libelle VARCHAR(150) NOT NULL,
|
||||
famille ENUM('ANIMATION', 'RESTAURATION_ENTRETIEN') NOT NULL,
|
||||
heures_mercredi_minutes SMALLINT UNSIGNED NOT NULL,
|
||||
heures_extrascolaire_minutes SMALLINT UNSIGNED NOT NULL,
|
||||
autorise_preparation BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
peut_assurer_animation BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
preparation_lundi_si_100 BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
actif BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
ordre_affichage SMALLINT UNSIGNED NOT NULL DEFAULT 100,
|
||||
CONSTRAINT uk_poste_agent_code UNIQUE (code)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
INSERT INTO poste_agent (
|
||||
code, libelle, famille, heures_mercredi_minutes, heures_extrascolaire_minutes,
|
||||
autorise_preparation, peut_assurer_animation, preparation_lundi_si_100, ordre_affichage
|
||||
) VALUES
|
||||
('RESPONSABLE', 'Responsable d’accueil', 'ANIMATION', 570, 570, TRUE, TRUE, TRUE, 10),
|
||||
('ANIMATION_RELAIS', 'Agent d’animation relais', 'ANIMATION', 570, 570, TRUE, TRUE, TRUE, 20),
|
||||
('ANIMATION', 'Agent d’animation', 'ANIMATION', 570, 570, TRUE, TRUE, TRUE, 30),
|
||||
('RESTAURATION_ENTRETIEN', 'Agent de restauration collective et d’entretien', 'RESTAURATION_ENTRETIEN', 420, 420, FALSE, FALSE, FALSE, 40)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
libelle = VALUES(libelle),
|
||||
famille = VALUES(famille),
|
||||
heures_mercredi_minutes = VALUES(heures_mercredi_minutes),
|
||||
heures_extrascolaire_minutes = VALUES(heures_extrascolaire_minutes),
|
||||
autorise_preparation = VALUES(autorise_preparation),
|
||||
peut_assurer_animation = VALUES(peut_assurer_animation),
|
||||
preparation_lundi_si_100 = VALUES(preparation_lundi_si_100),
|
||||
ordre_affichage = VALUES(ordre_affichage),
|
||||
actif = TRUE;
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- 2. Compléments métier de l’agent
|
||||
-- --------------------------------------------------------------------------
|
||||
ALTER TABLE agent
|
||||
ADD COLUMN id_poste INT UNSIGNED NULL AFTER date_debut_contrat,
|
||||
ADD COLUMN type_contrat ENUM('PERMANENT', 'TEMPORAIRE') NOT NULL DEFAULT 'PERMANENT' AFTER id_poste,
|
||||
ADD COLUMN date_fin_contrat DATE NULL AFTER type_contrat,
|
||||
ADD COLUMN formation_repartition_annuelle BOOLEAN NOT NULL DEFAULT FALSE AFTER date_fin_contrat,
|
||||
ADD COLUMN commentaire_pta VARCHAR(1000) NULL AFTER formation_repartition_annuelle;
|
||||
|
||||
ALTER TABLE agent
|
||||
ADD CONSTRAINT fk_agent_poste
|
||||
FOREIGN KEY (id_poste) REFERENCES poste_agent(id_poste)
|
||||
ON UPDATE CASCADE ON DELETE SET NULL,
|
||||
ADD CONSTRAINT chk_agent_dates_contrat_v7
|
||||
CHECK (date_fin_contrat IS NULL OR date_debut_contrat IS NULL OR date_fin_contrat >= date_debut_contrat);
|
||||
|
||||
CREATE INDEX idx_agent_poste ON agent(id_poste);
|
||||
CREATE INDEX idx_agent_type_contrat ON agent(type_contrat, actif);
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- 3. PTA annuel de l’agent
|
||||
-- --------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS pta_annuel (
|
||||
id_pta INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
id_agent INT UNSIGNED NOT NULL,
|
||||
date_debut DATE NOT NULL,
|
||||
date_fin DATE NOT NULL,
|
||||
quotite_travail DECIMAL(5,2) NOT NULL,
|
||||
quota_reference_minutes INT UNSIGNED NOT NULL DEFAULT 96420,
|
||||
quota_cible_minutes INT UNSIGNED NOT NULL,
|
||||
enveloppe_formation_minutes SMALLINT UNSIGNED NOT NULL DEFAULT 840,
|
||||
conges_annuels_jours_cible DECIMAL(5,2) NOT NULL DEFAULT 25.00,
|
||||
jours_fractionnement_cible TINYINT UNSIGNED NOT NULL DEFAULT 2,
|
||||
statut ENUM('BROUILLON', 'A_CONTROLER', 'VALIDE') NOT NULL DEFAULT 'BROUILLON',
|
||||
commentaire VARCHAR(1000) NULL,
|
||||
date_validation DATETIME NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT fk_pta_annuel_agent
|
||||
FOREIGN KEY (id_agent) REFERENCES agent(id_agent)
|
||||
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT chk_pta_annuel_dates CHECK (date_fin >= date_debut),
|
||||
CONSTRAINT chk_pta_annuel_quotite CHECK (quotite_travail > 0 AND quotite_travail <= 100),
|
||||
CONSTRAINT chk_pta_annuel_quota CHECK (quota_reference_minutes > 0 AND quota_cible_minutes > 0),
|
||||
CONSTRAINT uk_pta_annuel_agent_periode UNIQUE (id_agent, date_debut, date_fin),
|
||||
INDEX idx_pta_annuel_agent_statut (id_agent, statut),
|
||||
INDEX idx_pta_annuel_periode (date_debut, date_fin)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- 4. Préférences, interdictions et proximité des lieux
|
||||
-- --------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS agent_structure_souhait (
|
||||
id_souhait INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
id_agent INT UNSIGNED NOT NULL,
|
||||
id_structure INT UNSIGNED NOT NULL,
|
||||
type_souhait ENUM('PREFERENCE', 'INTERDICTION') NOT NULL,
|
||||
priorite TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
distance_km DECIMAL(7,2) NULL,
|
||||
commentaire VARCHAR(500) NULL,
|
||||
actif BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT fk_souhait_agent FOREIGN KEY (id_agent) REFERENCES agent(id_agent)
|
||||
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT fk_souhait_structure FOREIGN KEY (id_structure) REFERENCES structure(id_structure)
|
||||
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT chk_souhait_priorite CHECK (priorite BETWEEN 1 AND 5),
|
||||
CONSTRAINT chk_souhait_distance CHECK (distance_km IS NULL OR distance_km >= 0),
|
||||
CONSTRAINT uk_souhait_agent_structure UNIQUE (id_agent, id_structure),
|
||||
INDEX idx_souhait_agent_type (id_agent, type_souhait, actif)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- 5. Contraintes médicales et temps partiels thérapeutiques
|
||||
-- --------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS agent_contrainte (
|
||||
id_contrainte INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
id_agent INT UNSIGNED NOT NULL,
|
||||
type_contrainte ENUM('MEDICALE', 'TEMPS_PARTIEL_THERAPEUTIQUE') NOT NULL,
|
||||
date_debut DATE NOT NULL,
|
||||
date_fin DATE NULL,
|
||||
quotite_temporaire DECIMAL(5,2) NULL,
|
||||
maximum_minutes_jour SMALLINT UNSIGNED NULL,
|
||||
maximum_minutes_semaine SMALLINT UNSIGNED NULL,
|
||||
interdit_matin BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
interdit_midi BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
interdit_soir BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
commentaire VARCHAR(1000) NOT NULL,
|
||||
actif BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT fk_contrainte_agent FOREIGN KEY (id_agent) REFERENCES agent(id_agent)
|
||||
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT chk_contrainte_dates CHECK (date_fin IS NULL OR date_fin >= date_debut),
|
||||
CONSTRAINT chk_contrainte_quotite CHECK (quotite_temporaire IS NULL OR (quotite_temporaire > 0 AND quotite_temporaire <= 100)),
|
||||
CONSTRAINT chk_contrainte_max_jour CHECK (maximum_minutes_jour IS NULL OR maximum_minutes_jour > 0),
|
||||
CONSTRAINT chk_contrainte_max_semaine CHECK (maximum_minutes_semaine IS NULL OR maximum_minutes_semaine > 0),
|
||||
INDEX idx_contrainte_agent_dates (id_agent, actif, date_debut, date_fin)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- 6. Équipes périscolaires des mercredis et extrascolaires
|
||||
-- --------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS equipe_pta (
|
||||
id_equipe INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
id_structure INT UNSIGNED NOT NULL,
|
||||
type_equipe ENUM('MERCREDI_PERISCOLAIRE', 'EXTRASCOLAIRE', 'PERISCOLAIRE_SEMAINE') NOT NULL,
|
||||
libelle VARCHAR(180) NOT NULL,
|
||||
date_debut DATE NOT NULL,
|
||||
date_fin DATE NOT NULL,
|
||||
nombre_enfants_moins_6 SMALLINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
nombre_enfants_6_plus SMALLINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
ratio_moins_6 TINYINT UNSIGNED NOT NULL DEFAULT 8,
|
||||
ratio_6_plus TINYINT UNSIGNED NOT NULL DEFAULT 12,
|
||||
minimum_agents TINYINT UNSIGNED NOT NULL DEFAULT 1,
|
||||
pourcentage_diplomes_min DECIMAL(5,2) NOT NULL DEFAULT 50.00,
|
||||
statut ENUM('BROUILLON', 'A_CONTROLER', 'VALIDEE') NOT NULL DEFAULT 'BROUILLON',
|
||||
commentaire VARCHAR(1000) NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT fk_equipe_pta_structure FOREIGN KEY (id_structure) REFERENCES structure(id_structure)
|
||||
ON UPDATE CASCADE ON DELETE RESTRICT,
|
||||
CONSTRAINT chk_equipe_dates CHECK (date_fin >= date_debut),
|
||||
CONSTRAINT chk_equipe_ratios CHECK (ratio_moins_6 > 0 AND ratio_6_plus > 0),
|
||||
CONSTRAINT chk_equipe_minimum CHECK (minimum_agents > 0),
|
||||
CONSTRAINT chk_equipe_diplomes CHECK (pourcentage_diplomes_min BETWEEN 0 AND 100),
|
||||
INDEX idx_equipe_pta_type_dates (type_equipe, date_debut, date_fin),
|
||||
INDEX idx_equipe_pta_structure_dates (id_structure, date_debut, date_fin)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS equipe_pta_membre (
|
||||
id_equipe_membre INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
id_equipe INT UNSIGNED NOT NULL,
|
||||
id_agent INT UNSIGNED NOT NULL,
|
||||
fonction_equipe ENUM('RESPONSABLE', 'ANIMATION', 'RESTAURATION_ENTRETIEN') NOT NULL DEFAULT 'ANIMATION',
|
||||
date_debut DATE NULL,
|
||||
date_fin DATE NULL,
|
||||
commentaire VARCHAR(500) NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT fk_equipe_membre_equipe FOREIGN KEY (id_equipe) REFERENCES equipe_pta(id_equipe)
|
||||
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT fk_equipe_membre_agent FOREIGN KEY (id_agent) REFERENCES agent(id_agent)
|
||||
ON UPDATE CASCADE ON DELETE RESTRICT,
|
||||
CONSTRAINT chk_equipe_membre_dates CHECK (date_fin IS NULL OR date_debut IS NULL OR date_fin >= date_debut),
|
||||
CONSTRAINT uk_equipe_membre UNIQUE (id_equipe, id_agent),
|
||||
INDEX idx_equipe_membre_agent (id_agent, id_equipe)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- 7. Références horaires des lieux pour les automatismes de préparation
|
||||
-- --------------------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS structure_creneau_reference (
|
||||
id_reference INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
id_structure INT UNSIGNED NOT NULL,
|
||||
code_plage ENUM('ALP_MATIN', 'RESTAURATION', 'ALP_SOIR') NOT NULL,
|
||||
jour_semaine TINYINT UNSIGNED NOT NULL COMMENT '1=lundi ... 5=vendredi',
|
||||
heure_debut TIME NOT NULL,
|
||||
heure_fin TIME NOT NULL,
|
||||
actif BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
|
||||
CONSTRAINT fk_reference_structure FOREIGN KEY (id_structure) REFERENCES structure(id_structure)
|
||||
ON UPDATE CASCADE ON DELETE CASCADE,
|
||||
CONSTRAINT chk_reference_jour CHECK (jour_semaine BETWEEN 1 AND 5),
|
||||
CONSTRAINT chk_reference_ordre CHECK (heure_fin > heure_debut),
|
||||
CONSTRAINT uk_reference_structure_plage_jour UNIQUE (id_structure, code_plage, jour_semaine),
|
||||
INDEX idx_reference_structure_jour (id_structure, jour_semaine, actif)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- 8. Motifs détaillés du PTA
|
||||
-- --------------------------------------------------------------------------
|
||||
INSERT INTO motif_planning (code, libelle, compte_dans_quota, ordre_affichage, actif)
|
||||
VALUES
|
||||
('ALP_MATIN', 'ALP matin', TRUE, 10, TRUE),
|
||||
('RESTAURATION', 'Restauration', TRUE, 20, TRUE),
|
||||
('ALP_SOIR', 'ALP soir', TRUE, 30, TRUE),
|
||||
('MERCREDI_PERISCOLAIRE', 'Mercredi périscolaire', TRUE, 40, TRUE),
|
||||
('EXTRASCOLAIRE', 'Temps extrascolaire', TRUE, 50, TRUE),
|
||||
('FORMATION', 'Formation', TRUE, 60, TRUE),
|
||||
('CONGE', 'Congé annuel (non décompté)', FALSE, 70, TRUE),
|
||||
('JOUR_FRACTIONNEMENT', 'Journée de fractionnement', TRUE, 80, TRUE),
|
||||
('PREPARATION_PERISCOLAIRE', 'Préparation périscolaire', TRUE, 90, TRUE),
|
||||
('PREPARATION_EXTRASCOLAIRE', 'Préparation extrascolaire', TRUE, 100, TRUE),
|
||||
('PREPARATION_LUNDI', 'Préparation du lundi', TRUE, 110, TRUE),
|
||||
('MENAGE_FOND', 'Ménage de fond', TRUE, 120, TRUE),
|
||||
('JNT', 'Journée non travaillée', FALSE, 130, TRUE)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
libelle = VALUES(libelle),
|
||||
compte_dans_quota = VALUES(compte_dans_quota),
|
||||
ordre_affichage = VALUES(ordre_affichage),
|
||||
actif = TRUE;
|
||||
|
||||
-- La règle métier communiquée précise que les congés annuels ne sont pas
|
||||
-- redécomptés du quota puisqu'ils sont déjà intégrés dans le calcul initial.
|
||||
UPDATE motif_planning
|
||||
SET libelle = 'Congé annuel (non décompté)', compte_dans_quota = FALSE
|
||||
WHERE code = 'CONGE';
|
||||
|
||||
-- --------------------------------------------------------------------------
|
||||
-- 9. Vues de synthèse
|
||||
-- --------------------------------------------------------------------------
|
||||
CREATE OR REPLACE VIEW v_pta_annuel_synthese AS
|
||||
SELECT
|
||||
pa.id_pta,
|
||||
pa.id_agent,
|
||||
a.matricule,
|
||||
a.nom,
|
||||
a.prenom,
|
||||
po.code AS poste_code,
|
||||
po.libelle AS poste_libelle,
|
||||
a.type_contrat,
|
||||
pa.date_debut,
|
||||
pa.date_fin,
|
||||
pa.quotite_travail,
|
||||
pa.quota_cible_minutes,
|
||||
COALESCE(SUM(CASE WHEN mp.compte_dans_quota = TRUE THEN TIMESTAMPDIFF(MINUTE, CONCAT(ch.date_jour, ' ', ch.heure_debut), CONCAT(ch.date_jour, ' ', ch.heure_fin)) ELSE 0 END), 0) AS minutes_decomptees,
|
||||
pa.quota_cible_minutes - COALESCE(SUM(CASE WHEN mp.compte_dans_quota = TRUE THEN TIMESTAMPDIFF(MINUTE, CONCAT(ch.date_jour, ' ', ch.heure_debut), CONCAT(ch.date_jour, ' ', ch.heure_fin)) ELSE 0 END), 0) AS minutes_restantes,
|
||||
pa.statut
|
||||
FROM pta_annuel pa
|
||||
JOIN agent a ON a.id_agent = pa.id_agent
|
||||
LEFT JOIN poste_agent po ON po.id_poste = a.id_poste
|
||||
LEFT JOIN planning p ON p.id_agent = pa.id_agent
|
||||
LEFT JOIN creneau_horaire ch ON ch.id_planning = p.id_planning AND ch.date_jour BETWEEN pa.date_debut AND pa.date_fin
|
||||
LEFT JOIN motif_planning mp ON mp.id_motif = ch.id_motif
|
||||
GROUP BY pa.id_pta, pa.id_agent, a.matricule, a.nom, a.prenom, po.code, po.libelle,
|
||||
a.type_contrat, pa.date_debut, pa.date_fin, pa.quotite_travail,
|
||||
pa.quota_cible_minutes, pa.statut;
|
||||
|
||||
CREATE OR REPLACE VIEW v_equipe_pta_controle AS
|
||||
SELECT
|
||||
e.id_equipe,
|
||||
e.libelle,
|
||||
e.type_equipe,
|
||||
e.id_structure,
|
||||
s.nom AS structure_nom,
|
||||
e.date_debut,
|
||||
e.date_fin,
|
||||
e.nombre_enfants_moins_6,
|
||||
e.nombre_enfants_6_plus,
|
||||
GREATEST(
|
||||
e.minimum_agents,
|
||||
CEIL(e.nombre_enfants_moins_6 / e.ratio_moins_6) + CEIL(e.nombre_enfants_6_plus / e.ratio_6_plus)
|
||||
) AS agents_requis,
|
||||
COUNT(em.id_agent) AS agents_affectes,
|
||||
SUM(CASE WHEN a.est_diplome = TRUE THEN 1 ELSE 0 END) AS agents_diplomes,
|
||||
CASE WHEN COUNT(em.id_agent) = 0 THEN 0
|
||||
ELSE ROUND(100 * SUM(CASE WHEN a.est_diplome = TRUE THEN 1 ELSE 0 END) / COUNT(em.id_agent), 2)
|
||||
END AS pourcentage_diplomes,
|
||||
e.pourcentage_diplomes_min,
|
||||
SUM(CASE WHEN em.fonction_equipe = 'RESPONSABLE' THEN 1 ELSE 0 END) AS responsables,
|
||||
e.statut
|
||||
FROM equipe_pta e
|
||||
JOIN structure s ON s.id_structure = e.id_structure
|
||||
LEFT JOIN equipe_pta_membre em ON em.id_equipe = e.id_equipe
|
||||
LEFT JOIN agent a ON a.id_agent = em.id_agent
|
||||
GROUP BY e.id_equipe, e.libelle, e.type_equipe, e.id_structure, s.nom,
|
||||
e.date_debut, e.date_fin, e.nombre_enfants_moins_6, e.nombre_enfants_6_plus,
|
||||
e.ratio_moins_6, e.ratio_6_plus, e.minimum_agents,
|
||||
e.pourcentage_diplomes_min, e.statut;
|
||||
|
||||
COMMIT;
|
||||
File diff suppressed because it is too large
Load Diff
1180
database/pta_prod_schema_mariadb.sql
Normal file
1180
database/pta_prod_schema_mariadb.sql
Normal file
File diff suppressed because it is too large
Load Diff
87
docker-compose.yml
Normal file
87
docker-compose.yml
Normal file
@@ -0,0 +1,87 @@
|
||||
services:
|
||||
db:
|
||||
image: mariadb:11.4
|
||||
container_name: pta-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MARIADB_DATABASE: ${DB_NAME:-pta}
|
||||
MARIADB_USER: ${DB_USER:-pta}
|
||||
MARIADB_PASSWORD: ${DB_PASSWORD:-pta}
|
||||
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-pta}
|
||||
TZ: ${TZ:-Europe/Paris}
|
||||
command:
|
||||
- --character-set-server=utf8mb4
|
||||
- --collation-server=utf8mb4_unicode_ci
|
||||
volumes:
|
||||
- mariadb_data:/var/lib/mysql
|
||||
# Ce fichier n'est exécuté QUE lors de l'initialisation d'un volume vide.
|
||||
- ./database/pta_prod_schema_mariadb.sql:/docker-entrypoint-initdb.d/01-pta-schema.sql:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "mariadb-admin ping -h 127.0.0.1 -uroot -p\"$${MARIADB_ROOT_PASSWORD}\" --silent"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 20s
|
||||
networks:
|
||||
- pta
|
||||
|
||||
php:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: php
|
||||
container_name: pta-php
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PTA_DB_HOST: db
|
||||
PTA_DB_PORT: 3306
|
||||
PTA_DB_NAME: ${DB_NAME:-pta}
|
||||
PTA_DB_USER: ${DB_USER:-pta}
|
||||
PTA_DB_PASS: ${DB_PASSWORD:-change_me}
|
||||
TZ: ${TZ:-Europe/Paris}
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- pta
|
||||
|
||||
nginx:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
target: nginx
|
||||
container_name: pta-nginx
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${APP_BIND:-0.0.0.0}:${APP_PORT:-8080}:80"
|
||||
depends_on:
|
||||
- php
|
||||
networks:
|
||||
- pta
|
||||
|
||||
phpmyadmin:
|
||||
image: phpmyadmin:5.2.2
|
||||
container_name: pta-phpmyadmin
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PMA_HOST: db
|
||||
PMA_PORT: 3306
|
||||
PMA_USER: ${DB_USER:-pta}
|
||||
PMA_PASSWORD: ${DB_PASSWORD:-change_me}
|
||||
UPLOAD_LIMIT: 64M
|
||||
TZ: ${TZ:-Europe/Paris}
|
||||
ports:
|
||||
# Par défaut phpMyAdmin n'est accessible que depuis la machine hôte.
|
||||
- "${PMA_BIND:-127.0.0.1}:${PMA_PORT:-8081}:80"
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- pta
|
||||
|
||||
networks:
|
||||
pta:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
mariadb_data:
|
||||
55
docker/nginx/default.conf
Normal file
55
docker/nginx/default.conf
Normal file
@@ -0,0 +1,55 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
root /var/www/html;
|
||||
index index.php;
|
||||
|
||||
charset utf-8;
|
||||
client_max_body_size 64M;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php?$query_string;
|
||||
}
|
||||
|
||||
location ~ \.php$ {
|
||||
try_files $uri =404;
|
||||
|
||||
include fastcgi_params;
|
||||
fastcgi_pass php:9000;
|
||||
fastcgi_index index.php;
|
||||
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_param DOCUMENT_ROOT $document_root;
|
||||
fastcgi_param HTTP_PROXY "";
|
||||
|
||||
fastcgi_read_timeout 120s;
|
||||
}
|
||||
|
||||
# Les classes, modèles et fichiers SQL ne doivent jamais être servis directement.
|
||||
location ^~ /app/ {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
|
||||
location ^~ /database/ {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
|
||||
location ~* ^/(config\.php|db\.php|page_bootstrap\.php|README[^/]*)$ {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
|
||||
location ~ /\. {
|
||||
deny all;
|
||||
return 404;
|
||||
}
|
||||
|
||||
location ~* \.(?:css|js|png|jpg|jpeg|gif|svg|ico|webp)$ {
|
||||
expires 7d;
|
||||
add_header Cache-Control "public, max-age=604800";
|
||||
try_files $uri =404;
|
||||
}
|
||||
}
|
||||
24
docker/php/php.ini
Normal file
24
docker/php/php.ini
Normal file
@@ -0,0 +1,24 @@
|
||||
date.timezone = Europe/Paris
|
||||
memory_limit = 256M
|
||||
max_execution_time = 120
|
||||
max_input_time = 120
|
||||
post_max_size = 64M
|
||||
upload_max_filesize = 64M
|
||||
|
||||
session.cookie_httponly = 1
|
||||
session.cookie_samesite = Lax
|
||||
session.use_strict_mode = 1
|
||||
|
||||
expose_php = Off
|
||||
|
||||
display_errors = Off
|
||||
display_startup_errors = Off
|
||||
log_errors = On
|
||||
error_reporting = E_ALL
|
||||
|
||||
opcache.enable = 1
|
||||
opcache.enable_cli = 0
|
||||
opcache.memory_consumption = 128
|
||||
opcache.interned_strings_buffer = 16
|
||||
opcache.max_accelerated_files = 10000
|
||||
opcache.validate_timestamps = 0
|
||||
Reference in New Issue
Block a user