update
This commit is contained in:
275
database/migration_v7_9e_dsi_comptes_journal.sql
Normal file
275
database/migration_v7_9e_dsi_comptes_journal.sql
Normal file
@@ -0,0 +1,275 @@
|
||||
-- ============================================================================
|
||||
-- PTA V7.11 — Accès par jeton, comptes gérés par la DSI et journal d'activité
|
||||
-- ============================================================================
|
||||
-- Trois apports, indissociables :
|
||||
-- 1. un compte d'accès par utilisateur (compte_acces), identifié par un JETON
|
||||
-- unique dont la base ne conserve qu'une empreinte SHA-256 ;
|
||||
-- 2. un quatrième rôle DSI, seul habilité à créer, régénérer et désactiver
|
||||
-- ces comptes, et à lire le journal ;
|
||||
-- 3. un journal d'activité (journal_activite) alimenté par le code PHP.
|
||||
--
|
||||
-- POURQUOI « v7_9e » ALORS QUE C'EST LA V7.11 : les migrations sont jouées dans
|
||||
-- l'ORDRE ALPHABÉTIQUE (docker/db/apply-migrations.sh). Un fichier nommé
|
||||
-- « migration_v7_11_… » passerait AVANT « migration_v7_8_… », donc avant la
|
||||
-- création des tables de droits qu'il doit étendre. Le préfixe suit donc la
|
||||
-- dernière migration en date (v7_9d) plutôt que le numéro de version
|
||||
-- fonctionnelle. Voir CLAUDE.md, section « Commandes ».
|
||||
--
|
||||
-- Prérequis : migration_v7_8_securite_acces.sql (tables acces_role_*).
|
||||
-- ============================================================================
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 1. LE RÔLE DSI ENTRE DANS LA MATRICE DES DROITS
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Un MODIFY d'ENUM est idempotent : rejouer la migration ne change rien.
|
||||
ALTER TABLE acces_role_page
|
||||
MODIFY COLUMN role ENUM('AGENT', 'RESPONSABLE_STRUCTURE', 'SERVICE_ENFANCE', 'DSI') NOT NULL;
|
||||
|
||||
ALTER TABLE acces_role_permission
|
||||
MODIFY COLUMN role ENUM('AGENT', 'RESPONSABLE_STRUCTURE', 'SERVICE_ENFANCE', 'DSI') NOT NULL;
|
||||
|
||||
-- Les vues sont recréées après l'ALTER : leurs métadonnées de colonnes sont
|
||||
-- figées à la définition, un ENUM élargi ne s'y propage pas tout seul.
|
||||
CREATE OR REPLACE VIEW v_acces_role_page AS
|
||||
SELECT role, page, ordre_affichage
|
||||
FROM acces_role_page;
|
||||
|
||||
CREATE OR REPLACE VIEW v_acces_role_permission AS
|
||||
SELECT role, permission, autorise
|
||||
FROM acces_role_permission;
|
||||
|
||||
-- La DSI n'a qu'une page : la sienne. Elle n'accède NI à la saisie, NI aux
|
||||
-- référentiels métier — c'est une administration technique, pas fonctionnelle.
|
||||
INSERT INTO acces_role_page (role, page, ordre_affichage) VALUES
|
||||
('DSI', 'dsi', 10)
|
||||
ON DUPLICATE KEY UPDATE ordre_affichage = VALUES(ordre_affichage);
|
||||
|
||||
-- Deux permissions nouvelles, refusées explicitement aux trois rôles métier :
|
||||
-- une ligne à FALSE documente le refus, là où une ligne absente le laisse
|
||||
-- deviner (une permission inconnue vaut de toute façon « refusé »).
|
||||
INSERT INTO acces_role_permission (role, permission, autorise) VALUES
|
||||
('DSI', 'can_manage_access', TRUE),
|
||||
('DSI', 'can_read_journal', TRUE),
|
||||
('DSI', 'read_only', FALSE),
|
||||
('DSI', 'can_edit_draft', FALSE),
|
||||
('DSI', 'can_edit_cross_structure', FALSE),
|
||||
('DSI', 'can_validate', FALSE),
|
||||
('DSI', 'can_reopen', FALSE),
|
||||
('DSI', 'can_use_templates', FALSE),
|
||||
('DSI', 'can_copy_full_week', FALSE),
|
||||
('DSI', 'can_view_pending', FALSE),
|
||||
('DSI', 'can_manage_coverage', FALSE),
|
||||
('DSI', 'can_manage_agents', FALSE),
|
||||
('DSI', 'can_manage_structures', FALSE),
|
||||
('DSI', 'can_manage_vacations', FALSE),
|
||||
('DSI', 'can_manage_teams', FALSE),
|
||||
('DSI', 'can_manage_pta', FALSE),
|
||||
('DSI', 'can_forecast_next_year', FALSE),
|
||||
|
||||
('AGENT', 'can_manage_access', FALSE),
|
||||
('AGENT', 'can_read_journal', FALSE),
|
||||
('RESPONSABLE_STRUCTURE', 'can_manage_access', FALSE),
|
||||
('RESPONSABLE_STRUCTURE', 'can_read_journal', FALSE),
|
||||
('SERVICE_ENFANCE', 'can_manage_access', FALSE),
|
||||
('SERVICE_ENFANCE', 'can_read_journal', FALSE)
|
||||
ON DUPLICATE KEY UPDATE autorise = VALUES(autorise);
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 2. COMPTES D'ACCÈS — un jeton par utilisateur, jamais de mot de passe
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Le jeton complet n'est JAMAIS stocké : la base ne garde qu'une empreinte
|
||||
-- SHA-256 et les huit premiers caractères, qui servent uniquement à reconnaître
|
||||
-- une ligne dans la liste. Un jeton perdu ne se retrouve pas, il se régénère.
|
||||
-- Une fuite du dump SQL ne donne donc aucun accès.
|
||||
--
|
||||
-- L'empreinte n'est pas salée, et c'est délibéré : le jeton est tiré au sort sur
|
||||
-- 120 bits par le serveur, il n'est pas devinable par force brute comme le
|
||||
-- serait un mot de passe choisi par un humain. L'absence de sel est ce qui
|
||||
-- permet de retrouver le compte en une lecture d'index à la connexion.
|
||||
--
|
||||
-- id_agent / id_structure sont UNIQUE tout en étant NULLables : MariaDB tolère
|
||||
-- autant de NULL que voulu dans un index unique. Un agent ne peut donc avoir
|
||||
-- qu'un seul compte, un lieu qu'un seul compte responsable, tandis que les
|
||||
-- comptes SERVICE_ENFANCE et DSI (les deux colonnes à NULL) sont libres en
|
||||
-- nombre.
|
||||
CREATE TABLE IF NOT EXISTS compte_acces (
|
||||
id_compte INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
role ENUM('AGENT', 'RESPONSABLE_STRUCTURE', 'SERVICE_ENFANCE', 'DSI') NOT NULL,
|
||||
libelle VARCHAR(180) NOT NULL COMMENT 'Titulaire du jeton, tel qu''affiché dans la liste',
|
||||
id_agent INT UNSIGNED NULL COMMENT 'Obligatoire pour un compte AGENT',
|
||||
id_structure INT UNSIGNED NULL COMMENT 'Obligatoire pour un compte RESPONSABLE_STRUCTURE',
|
||||
jeton_empreinte CHAR(64) NOT NULL COMMENT 'SHA-256 hexadécimal du jeton',
|
||||
jeton_prefixe VARCHAR(12) NOT NULL COMMENT 'Début du jeton, pour le reconnaître sans le révéler',
|
||||
actif BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
commentaire VARCHAR(255) NULL,
|
||||
date_creation DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
date_rotation DATETIME NULL COMMENT 'Dernière régénération du jeton',
|
||||
date_derniere_connexion DATETIME NULL,
|
||||
nombre_connexions INT UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
CONSTRAINT uk_compte_empreinte UNIQUE (jeton_empreinte),
|
||||
CONSTRAINT uk_compte_agent UNIQUE (id_agent),
|
||||
CONSTRAINT uk_compte_structure UNIQUE (id_structure),
|
||||
INDEX idx_compte_role_actif (role, actif),
|
||||
CONSTRAINT fk_compte_agent FOREIGN KEY (id_agent)
|
||||
REFERENCES agent (id_agent) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_compte_structure FOREIGN KEY (id_structure)
|
||||
REFERENCES structure (id_structure) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 3. COMPTE DSI D'INSTALLATION
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Problème de l'œuf et de la poule : les jetons ne sont distribuables que par la
|
||||
-- DSI, qui a elle-même besoin d'un jeton pour entrer. Cette ligne est la porte
|
||||
-- d'entrée initiale.
|
||||
--
|
||||
-- JETON D'INSTALLATION : PTA-INSTALLATION-DSI
|
||||
--
|
||||
-- Il est public — il est écrit ici, dans un fichier versionné. La page DSI
|
||||
-- affiche un avertissement rouge tant que ce compte porte encore cette
|
||||
-- empreinte : LE RÉGÉNÉRER EST LA PREMIÈRE ACTION À FAIRE APRÈS L'INSTALLATION.
|
||||
--
|
||||
-- L'identifiant 1 est forcé pour que le rejeu de la migration ne recrée pas un
|
||||
-- second compte d'installation après régénération du jeton (l'empreinte, elle,
|
||||
-- aura changé). Supprimer la ligne 1 puis rejouer la migration est d'ailleurs la
|
||||
-- procédure de secours documentée en cas de perte du jeton DSI.
|
||||
INSERT INTO compte_acces
|
||||
(id_compte, role, libelle, jeton_empreinte, jeton_prefixe, commentaire)
|
||||
VALUES
|
||||
(1, 'DSI', 'Compte DSI d''installation',
|
||||
'7f61ffc8efed4ed46d581a98286115ad03bb0122ffc70db3398584348aca431d',
|
||||
'PTA-INSTALL',
|
||||
'Jeton d''installation public : à régénérer immédiatement.')
|
||||
ON DUPLICATE KEY UPDATE id_compte = id_compte;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 4. JOURNAL D'ACTIVITÉ
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Alimenté exclusivement par App\Core\Journal, jamais par des déclencheurs SQL :
|
||||
-- le journal doit nommer l'UTILISATEUR à l'origine de l'action, information que
|
||||
-- la base n'a pas (la connexion MariaDB est la même pour tout le monde).
|
||||
--
|
||||
-- Sont tracés : les connexions et déconnexions, toutes les écritures en base et
|
||||
-- tous les refus d'accès. Les lectures ne le sont pas — c'est un choix de
|
||||
-- volume : elles représenteraient l'essentiel des lignes pour une valeur
|
||||
-- d'audit très faible.
|
||||
--
|
||||
-- L'acteur est FIGÉ au moment de l'action (rôle, libellé) : renommer un agent ou
|
||||
-- supprimer son compte ne doit pas réécrire l'histoire. C'est aussi pourquoi la
|
||||
-- clé étrangère est en ON DELETE SET NULL et non en CASCADE.
|
||||
CREATE TABLE IF NOT EXISTS journal_activite (
|
||||
id_journal BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
date_evenement DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
|
||||
categorie ENUM('CONNEXION', 'ECRITURE', 'ACCES_REFUSE', 'ADMINISTRATION', 'SYSTEME') NOT NULL,
|
||||
action VARCHAR(60) NOT NULL COMMENT 'Code stable de l''action, ex. PLANNING_VALIDE',
|
||||
resultat ENUM('SUCCES', 'REFUS', 'ERREUR') NOT NULL DEFAULT 'SUCCES',
|
||||
id_compte INT UNSIGNED NULL,
|
||||
role VARCHAR(30) NULL COMMENT 'Rôle au moment de l''action, figé',
|
||||
acteur VARCHAR(180) NULL COMMENT 'Libellé de l''utilisateur au moment de l''action, figé',
|
||||
cible_table VARCHAR(64) NULL COMMENT 'Table principale touchée par l''écriture',
|
||||
cible_id INT UNSIGNED NULL,
|
||||
description VARCHAR(500) NULL,
|
||||
contexte JSON NULL COMMENT 'Détail structuré, ex. semaine et lieu concernés',
|
||||
adresse_ip VARCHAR(45) NULL,
|
||||
agent_utilisateur VARCHAR(255) NULL,
|
||||
|
||||
INDEX idx_journal_date (date_evenement),
|
||||
INDEX idx_journal_categorie (categorie, date_evenement),
|
||||
INDEX idx_journal_compte (id_compte, date_evenement),
|
||||
INDEX idx_journal_cible (cible_table, cible_id),
|
||||
INDEX idx_journal_ip (adresse_ip, date_evenement),
|
||||
CONSTRAINT fk_journal_compte FOREIGN KEY (id_compte)
|
||||
REFERENCES compte_acces (id_compte) ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 5. VUES
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Comme pour la V7.8, le PHP interroge des vues et non les tables : l'identité
|
||||
-- réelle derrière un compte (nom de l'agent, nom du lieu) et son activité sont
|
||||
-- résolues en SQL, une seule fois, au même endroit.
|
||||
CREATE OR REPLACE VIEW v_acces_compte AS
|
||||
SELECT
|
||||
c.id_compte,
|
||||
c.role,
|
||||
c.libelle,
|
||||
c.id_agent,
|
||||
c.id_structure,
|
||||
c.jeton_empreinte,
|
||||
c.jeton_prefixe,
|
||||
c.actif,
|
||||
c.commentaire,
|
||||
c.date_creation,
|
||||
c.date_rotation,
|
||||
c.date_derniere_connexion,
|
||||
c.nombre_connexions,
|
||||
a.matricule,
|
||||
a.nom AS agent_nom,
|
||||
a.prenom AS agent_prenom,
|
||||
s.code AS structure_code,
|
||||
s.nom AS structure_nom,
|
||||
-- Libellé affiché : l'identité VIVE pour un agent ou un lieu (un agent
|
||||
-- renommé doit apparaître sous son nouveau nom), le libellé saisi pour les
|
||||
-- comptes Service Enfance et DSI, qui ne pointent vers rien.
|
||||
CASE c.role
|
||||
WHEN 'AGENT' THEN CONCAT(a.prenom, ' ', a.nom)
|
||||
WHEN 'RESPONSABLE_STRUCTURE' THEN s.nom
|
||||
ELSE c.libelle
|
||||
END AS identite_libelle,
|
||||
-- Un compte reste utilisable tant que l'identité qu'il porte est active :
|
||||
-- désactiver un agent coupe son accès sans avoir à toucher à son compte.
|
||||
CASE c.role
|
||||
WHEN 'AGENT' THEN COALESCE(a.actif, 0)
|
||||
WHEN 'RESPONSABLE_STRUCTURE' THEN COALESCE(s.actif, 0)
|
||||
ELSE 1
|
||||
END AS identite_active
|
||||
FROM compte_acces c
|
||||
LEFT JOIN agent a ON a.id_agent = c.id_agent
|
||||
LEFT JOIN structure s ON s.id_structure = c.id_structure;
|
||||
|
||||
-- Identités actives encore dépourvues de compte : alimente le bouton
|
||||
-- « générer les jetons manquants » de la page DSI, qui permet d'équiper d'un
|
||||
-- coup tous les agents et tous les lieux lors de la mise en service.
|
||||
CREATE OR REPLACE VIEW v_acces_compte_manquant AS
|
||||
SELECT
|
||||
'AGENT' AS role,
|
||||
a.id_agent AS id_agent,
|
||||
CAST(NULL AS UNSIGNED) AS id_structure,
|
||||
CONCAT(a.prenom, ' ', a.nom) AS libelle
|
||||
FROM agent a
|
||||
LEFT JOIN compte_acces c ON c.id_agent = a.id_agent
|
||||
WHERE a.actif = 1 AND c.id_compte IS NULL
|
||||
UNION ALL
|
||||
SELECT
|
||||
'RESPONSABLE_STRUCTURE',
|
||||
CAST(NULL AS UNSIGNED),
|
||||
s.id_structure,
|
||||
s.nom
|
||||
FROM structure s
|
||||
LEFT JOIN compte_acces c ON c.id_structure = s.id_structure
|
||||
WHERE s.actif = 1 AND c.id_compte IS NULL;
|
||||
|
||||
-- Journal enrichi du compte encore existant, quand il existe. Le libellé figé
|
||||
-- (`acteur`) reste la source d'affichage : la jointure ne sert qu'à retrouver le
|
||||
-- préfixe du jeton et à filtrer par compte.
|
||||
CREATE OR REPLACE VIEW v_journal_activite AS
|
||||
SELECT
|
||||
j.id_journal,
|
||||
j.date_evenement,
|
||||
j.categorie,
|
||||
j.action,
|
||||
j.resultat,
|
||||
j.id_compte,
|
||||
j.role,
|
||||
j.acteur,
|
||||
j.cible_table,
|
||||
j.cible_id,
|
||||
j.description,
|
||||
j.contexte,
|
||||
j.adresse_ip,
|
||||
j.agent_utilisateur,
|
||||
c.jeton_prefixe,
|
||||
c.actif AS compte_actif
|
||||
FROM journal_activite j
|
||||
LEFT JOIN compte_acces c ON c.id_compte = j.id_compte;
|
||||
91
database/migration_v7_9f_mot_de_passe_admin.sql
Normal file
91
database/migration_v7_9f_mot_de_passe_admin.sql
Normal file
@@ -0,0 +1,91 @@
|
||||
-- ============================================================================
|
||||
-- PTA V7.12 — Mot de passe des comptes privilégiés (Service Enfance et DSI)
|
||||
-- ============================================================================
|
||||
-- Le jeton reste le seul facteur pour les agents et les responsables de lieu.
|
||||
-- Les comptes qui VALIDENT (Service Enfance) et ceux qui DISTRIBUENT LES ACCÈS
|
||||
-- (DSI) en réclament un second : un mot de passe, connu du seul titulaire.
|
||||
--
|
||||
-- Deux facteurs de nature différente : le jeton est remis par la DSI et peut
|
||||
-- circuler par courriel ; le mot de passe est choisi par le titulaire à sa
|
||||
-- première connexion et n'est jamais affiché nulle part. Un jeton intercepté ne
|
||||
-- suffit donc plus à valider un planning ni à créer un compte.
|
||||
--
|
||||
-- CONTRAIREMENT AU JETON, le mot de passe est haché avec password_hash()
|
||||
-- (bcrypt) et non SHA-256 : il est choisi par un humain, donc attaquable par
|
||||
-- dictionnaire, et c'est précisément ce que le hachage lent rend coûteux. La
|
||||
-- vérification se fait sur un compte déjà identifié par son jeton, l'absence
|
||||
-- d'index sur le mot de passe n'a donc aucune importance.
|
||||
--
|
||||
-- POURQUOI « v7_9f » : les migrations sont jouées dans l'ORDRE ALPHABÉTIQUE
|
||||
-- (docker/db/apply-migrations.sh). Le préfixe suit la dernière migration en date
|
||||
-- (v7_9e), pas le numéro de version fonctionnelle. Voir CLAUDE.md.
|
||||
--
|
||||
-- Prérequis : migration_v7_9e_dsi_comptes_journal.sql.
|
||||
-- ============================================================================
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 1. DEUX COLONNES SUR LE COMPTE
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- `IF NOT EXISTS` est une extension MariaDB : elle rend l'ALTER idempotent, ce
|
||||
-- qu'exige le rejeu des migrations.
|
||||
--
|
||||
-- Le hachage est NULLABLE, et cet état porte du sens : « mot de passe pas encore
|
||||
-- défini ». C'est l'état d'un compte fraîchement créé, et c'est aussi ce que
|
||||
-- produit une réinitialisation par la DSI. Un compte privilégié dans cet état ne
|
||||
-- peut pas ouvrir de session : la connexion impose d'abord d'en choisir un.
|
||||
ALTER TABLE compte_acces
|
||||
ADD COLUMN IF NOT EXISTS mot_de_passe_hash VARCHAR(255) NULL
|
||||
COMMENT 'password_hash() du mot de passe ; NULL = à définir à la prochaine connexion',
|
||||
ADD COLUMN IF NOT EXISTS date_mot_de_passe DATETIME NULL
|
||||
COMMENT 'Dernière définition ou modification du mot de passe par le titulaire';
|
||||
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- 2. LA VUE PORTE LA RÈGLE « QUI DOIT AVOIR UN MOT DE PASSE »
|
||||
-- ----------------------------------------------------------------------------
|
||||
-- Comme pour les droits depuis la V7.8, la règle descend en base : le PHP
|
||||
-- demande à la vue si ce compte réclame un second facteur, il ne rejoue pas la
|
||||
-- liste des rôles concernés de son côté.
|
||||
CREATE OR REPLACE VIEW v_acces_compte AS
|
||||
SELECT
|
||||
c.id_compte,
|
||||
c.role,
|
||||
c.libelle,
|
||||
c.id_agent,
|
||||
c.id_structure,
|
||||
c.jeton_empreinte,
|
||||
c.jeton_prefixe,
|
||||
c.actif,
|
||||
c.commentaire,
|
||||
c.date_creation,
|
||||
c.date_rotation,
|
||||
c.date_derniere_connexion,
|
||||
c.nombre_connexions,
|
||||
c.mot_de_passe_hash,
|
||||
c.date_mot_de_passe,
|
||||
-- Les deux rôles qui engagent l'application : celui qui valide les plannings
|
||||
-- de toute la collectivité, et celui qui distribue les accès.
|
||||
CASE WHEN c.role IN ('SERVICE_ENFANCE', 'DSI') THEN 1 ELSE 0 END AS mot_de_passe_requis,
|
||||
CASE WHEN c.mot_de_passe_hash IS NULL THEN 0 ELSE 1 END AS mot_de_passe_defini,
|
||||
a.matricule,
|
||||
a.nom AS agent_nom,
|
||||
a.prenom AS agent_prenom,
|
||||
s.code AS structure_code,
|
||||
s.nom AS structure_nom,
|
||||
-- Libellé affiché : l'identité VIVE pour un agent ou un lieu (un agent
|
||||
-- renommé doit apparaître sous son nouveau nom), le libellé saisi pour les
|
||||
-- comptes Service Enfance et DSI, qui ne pointent vers rien.
|
||||
CASE c.role
|
||||
WHEN 'AGENT' THEN CONCAT(a.prenom, ' ', a.nom)
|
||||
WHEN 'RESPONSABLE_STRUCTURE' THEN s.nom
|
||||
ELSE c.libelle
|
||||
END AS identite_libelle,
|
||||
-- Un compte reste utilisable tant que l'identité qu'il porte est active :
|
||||
-- désactiver un agent coupe son accès sans avoir à toucher à son compte.
|
||||
CASE c.role
|
||||
WHEN 'AGENT' THEN COALESCE(a.actif, 0)
|
||||
WHEN 'RESPONSABLE_STRUCTURE' THEN COALESCE(s.actif, 0)
|
||||
ELSE 1
|
||||
END AS identite_active
|
||||
FROM compte_acces c
|
||||
LEFT JOIN agent a ON a.id_agent = c.id_agent
|
||||
LEFT JOIN structure s ON s.id_structure = c.id_structure;
|
||||
38
database/migration_v7_9g_management.sql
Normal file
38
database/migration_v7_9g_management.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
-- ============================================================================
|
||||
-- PTA V7.14 — Groupe « Management » : onglets Agents et Lieux d'affectation
|
||||
-- ============================================================================
|
||||
-- L'écran « Administration » réunissait trois objets sans rapport : le
|
||||
-- référentiel des agents, celui des lieux, et les horaires de couverture
|
||||
-- attendus. Les deux premiers deviennent deux ONGLETS distincts, réunis dans un
|
||||
-- groupe « Management » de la barre de navigation, au même titre que
|
||||
-- « Piloter » ou « Planifier ». « Administration » ne conserve que les horaires
|
||||
-- de couverture.
|
||||
--
|
||||
-- Le GROUPE lui-même est un simple regroupement d'affichage
|
||||
-- (app/Views/layouts/header.php) : la base ne connaît que des pages. Ce sont
|
||||
-- donc deux lignes qu'il faut ici, une par onglet.
|
||||
--
|
||||
-- Comme toute page de PTA, celles-ci n'existent que si le droit est en base :
|
||||
-- sans ces lignes, les pages renvoient vers l'accueil du rôle et les onglets
|
||||
-- n'apparaissent pas (voir CLAUDE.md, « Rôles et périmètres »).
|
||||
--
|
||||
-- La ligne « management » d'une version intermédiaire de cette migration est
|
||||
-- supprimée : cette page unique n'existe plus, et une page inconnue laissée dans
|
||||
-- acces_role_page finirait dans le groupe de repêchage « Autres » de la barre de
|
||||
-- navigation.
|
||||
--
|
||||
-- POURQUOI « v7_9g » : les migrations sont jouées dans l'ORDRE ALPHABÉTIQUE
|
||||
-- (docker/db/apply-migrations.sh). Le préfixe suit la dernière migration en date
|
||||
-- (v7_9f), pas le numéro de version fonctionnelle.
|
||||
--
|
||||
-- Prérequis : migration_v7_8_securite_acces.sql.
|
||||
-- ============================================================================
|
||||
|
||||
DELETE FROM acces_role_page WHERE page = 'management';
|
||||
|
||||
-- 95 et 96 : les deux onglets se rangent juste avant « Administration » (100),
|
||||
-- dont ils reprennent l'essentiel du contenu.
|
||||
INSERT INTO acces_role_page (role, page, ordre_affichage) VALUES
|
||||
('SERVICE_ENFANCE', 'manage_agents', 95),
|
||||
('SERVICE_ENFANCE', 'manage_structures', 96)
|
||||
ON DUPLICATE KEY UPDATE ordre_affichage = VALUES(ordre_affichage);
|
||||
Reference in New Issue
Block a user