ajout de script / modif d'enum et simplification du lancement en local

This commit is contained in:
951095
2025-09-19 10:22:34 +02:00
parent 4d402bb7cc
commit 80f24dce5a
25 changed files with 849 additions and 923 deletions
+48 -148
View File
@@ -1,157 +1,57 @@
-- =============================================
-- 02_indexes.sql : Index FK + colonnes critiques
-- =============================================
-- ============================================================
-- 02_indexes.sql Indexes pour améliorer les perfs
-- ============================================================
-- Recommandation : exécuter après 01_init.sql
-- ===========
-- UTILISATEURS
-- ===========
-- Recherche par email (insensibilité à la casse pour lookup)
CREATE INDEX IF NOT EXISTS idx_utilisateurs_lower_courriel
ON utilisateurs (LOWER(courriel));
-- ===========
-- ASSISTANTES_MATERNELLES
-- ===========
-- FK -> utilisateurs(id)
CREATE INDEX IF NOT EXISTS idx_am_id_utilisateur
ON assistantes_maternelles (id_utilisateur);
-- =======
-- PARENTS
-- =======
-- FK -> utilisateurs(id)
CREATE INDEX IF NOT EXISTS idx_parents_id_utilisateur
ON parents (id_utilisateur);
-- Co-parent (nullable)
CREATE INDEX IF NOT EXISTS idx_parents_id_co_parent
ON parents (id_co_parent);
-- =======
-- ENFANTS
-- =======
-- (souvent filtrés par statut / date_naissance ? à activer si besoin)
-- CREATE INDEX IF NOT EXISTS idx_enfants_statut ON enfants (statut);
-- CREATE INDEX IF NOT EXISTS idx_enfants_date_naissance ON enfants (date_naissance);
-- ================
-- ENFANTS_PARENTS (N:N)
-- ================
-- PK composite déjà en place (id_parent, id_enfant), ajouter index individuels si jointures unilatérales fréquentes
CREATE INDEX IF NOT EXISTS idx_enfants_parents_id_parent
ON enfants_parents (id_parent);
CREATE INDEX IF NOT EXISTS idx_enfants_parents_id_enfant
ON enfants_parents (id_enfant);
-- ========
-- DOSSIERS
-- ========
-- FK -> parent / enfant
CREATE INDEX IF NOT EXISTS idx_dossiers_id_parent
ON dossiers (id_parent);
CREATE INDEX IF NOT EXISTS idx_dossiers_id_enfant
ON dossiers (id_enfant);
-- Statut (filtrages "à traiter", "envoyé", etc.)
CREATE INDEX IF NOT EXISTS idx_dossiers_statut
ON dossiers (statut);
-- JSONB : si on fait des requêtes @> sur le planning souhaité
-- CREATE INDEX IF NOT EXISTS idx_dossiers_planning_souhaite_gin
-- ON dossiers USING GIN (planning_souhaite);
-- ========
-- MESSAGES
-- ========
-- Filtrage par dossier + tri chronologique
-- ===========================================
-- Messages (par dossier + tri par date)
-- Utilisé dans verify.sql (EXPLAIN + ORDER BY cree_le)
-- ===========================================
CREATE INDEX IF NOT EXISTS idx_messages_id_dossier_cree_le
ON messages (id_dossier, cree_le);
ON messages (id_dossier, cree_le DESC);
-- Recherche par expéditeur
CREATE INDEX IF NOT EXISTS idx_messages_id_expediteur_cree_le
ON messages (id_expediteur, cree_le);
-- =========
-- CONTRATS
-- =========
-- UNIQUE(id_dossier) existe déjà -> index implicite
-- Tri / filtres fréquents
CREATE INDEX IF NOT EXISTS idx_contrats_statut
ON contrats (statut);
-- JSONB planning : activer si on requête par clé
-- CREATE INDEX IF NOT EXISTS idx_contrats_planning_gin
-- ON contrats USING GIN (planning);
-- ==================
-- AVENANTS_CONTRATS
-- ==================
CREATE INDEX IF NOT EXISTS idx_avenants_contrats_id_contrat_cree_le
ON avenants_contrats (id_contrat, cree_le);
CREATE INDEX IF NOT EXISTS idx_avenants_contrats_initie_par
ON avenants_contrats (initie_par);
CREATE INDEX IF NOT EXISTS idx_avenants_contrats_statut
ON avenants_contrats (statut);
-- =========
-- EVENEMENTS
-- =========
-- Accès par enfant + période
-- ===========================================
-- Evenements (filtrés par enfant + date)
-- Utilisé dans verify.sql (WHERE id_enfant AND date_debut >= ...)
-- ===========================================
CREATE INDEX IF NOT EXISTS idx_evenements_id_enfant_date_debut
ON evenements (id_enfant, date_debut);
ON evenements (id_enfant, date_debut DESC);
-- Filtrage par auteur / AM / parent
CREATE INDEX IF NOT EXISTS idx_evenements_cree_par
ON evenements (cree_par);
CREATE INDEX IF NOT EXISTS idx_evenements_id_am
ON evenements (id_am);
CREATE INDEX IF NOT EXISTS idx_evenements_id_parent
ON evenements (id_parent);
CREATE INDEX IF NOT EXISTS idx_evenements_type
ON evenements (type);
CREATE INDEX IF NOT EXISTS idx_evenements_statut
ON evenements (statut);
-- =================
-- SIGNALEMENTS_BUGS
-- =================
CREATE INDEX IF NOT EXISTS idx_signalements_bugs_id_utilisateur_cree_le
ON signalements_bugs (id_utilisateur, cree_le);
-- =======
-- UPLOADS
-- =======
CREATE INDEX IF NOT EXISTS idx_uploads_id_utilisateur_cree_le
ON uploads (id_utilisateur, cree_le);
-- =============
-- NOTIFICATIONS
-- =============
-- Requêtes fréquentes : non lues + ordre chrono
-- ===========================================
-- Notifications (par utilisateur + lues/non lues + tri par date)
-- Utilisé dans verify.sql (WHERE id_utilisateur AND lu=false ORDER BY cree_le DESC)
-- ===========================================
CREATE INDEX IF NOT EXISTS idx_notifications_user_lu_cree_le
ON notifications (id_utilisateur, lu, cree_le);
ON notifications (id_utilisateur, lu, cree_le DESC);
-- Option : index partiel pour "non lues"
-- CREATE INDEX IF NOT EXISTS idx_notifications_non_lues
-- ON notifications (id_utilisateur, cree_le)
-- WHERE lu = false;
-- ===========================================
-- Dossiers (filtrés par parent + enfant + statut + tri par date)
-- Utilisé dans verify.sql
-- ===========================================
CREATE INDEX IF NOT EXISTS idx_dossiers_id_parent_enfant_statut_cree_le
ON dossiers (id_parent, id_enfant, statut, cree_le DESC);
-- ===========
-- VALIDATIONS
-- ===========
-- Requêtes par utilisateur validé, par statut et par date
CREATE INDEX IF NOT EXISTS idx_validations_id_utilisateur_cree_le
ON validations (id_utilisateur, cree_le);
-- ===========================================
-- Contrats (lookup par dossier unique)
-- ===========================================
CREATE UNIQUE INDEX IF NOT EXISTS idx_contrats_id_dossier
ON contrats (id_dossier);
CREATE INDEX IF NOT EXISTS idx_validations_statut
ON validations (statut);
-- ===========================================
-- JSONB indexes (si filtres fréquents sur planning/planning_souhaite)
-- Exemple dans verify.sql : dossiers.planning_souhaite @> ...
-- ===========================================
CREATE INDEX IF NOT EXISTS idx_dossiers_planning_souhaite_gin
ON dossiers USING gin (planning_souhaite jsonb_path_ops);
CREATE INDEX IF NOT EXISTS idx_contrats_planning_gin
ON contrats USING gin (planning jsonb_path_ops);
-- ===========================================
-- Sanity check : validation des emails uniques
-- Déjà couvert par UNIQUE dans la table utilisateurs
-- ===========================================
-- ===========================================
-- Bonus : accélérer les jointures fréquentes
-- (utilisateurs.id, enfants.id, parents.id_utilisateur sont PK → déjà indexées automatiquement)
-- ===========================================
+58 -124
View File
@@ -1,140 +1,74 @@
-- =============================================
-- 03_checks.sql : Contraintes CHECK & NOT NULL
-- A exécuter après 01_init.sql (et 02_indexes.sql)
-- =============================================
-- ============================================================
-- 03_checks.sql Contraintes d'intégrité métier
-- ============================================================
-- ==============
-- ===========================================
-- UTILISATEURS
-- ==============
-- (Regex email déjà présente dans 01_init.sql)
-- Optionnel : forcer prenom/nom non vides si fournis
-- ===========================================
-- Email déjà couvert par UNIQUE + regex dans le schéma
-- Vérifier que mobile et téléphone fixe ne sont pas identiques
ALTER TABLE utilisateurs
ADD CONSTRAINT chk_utilisateurs_prenom_non_vide
CHECK (prenom IS NULL OR btrim(prenom) <> ''),
ADD CONSTRAINT chk_utilisateurs_nom_non_vide
CHECK (nom IS NULL OR btrim(nom) <> '');
-- =========================
-- ASSISTANTES_MATERNELLES
-- =========================
-- NIR : aujourdhui en 15 chiffres (Sprint 2 : chiffrement)
ALTER TABLE assistantes_maternelles
ADD CONSTRAINT chk_am_nir_format
CHECK (nir_chiffre IS NULL OR nir_chiffre ~ '^[0-9]{15}$'),
ADD CONSTRAINT chk_am_nb_max_enfants
CHECK (nb_max_enfants IS NULL OR nb_max_enfants BETWEEN 0 AND 10),
ADD CONSTRAINT chk_am_ville_non_vide
CHECK (ville_residence IS NULL OR btrim(ville_residence) <> '');
-- =========
-- PARENTS
-- =========
-- Interdiction d’être co-parent de soi-même
ALTER TABLE parents
ADD CONSTRAINT chk_parents_co_parent_diff
CHECK (id_co_parent IS NULL OR id_co_parent <> id_utilisateur);
-- =========
-- ENFANTS
-- =========
-- Cohérence statut / dates de naissance
ALTER TABLE enfants
ADD CONSTRAINT chk_enfants_dates_exclusives
CHECK (NOT (date_naissance IS NOT NULL AND date_prevue_naissance IS NOT NULL)),
ADD CONSTRAINT chk_enfants_statut_dates
CHECK (
-- a_naitre => date_prevue_naissance requise
(statut = 'a_naitre' AND date_prevue_naissance IS NOT NULL)
OR
-- actif/scolarise => date_naissance requise
(statut IN ('actif','scolarise') AND date_naissance IS NOT NULL)
OR statut IS NULL -- si statut non encore fixé
),
ADD CONSTRAINT chk_enfants_consentement_coherent
CHECK (
(consentement_photo = true AND date_consentement_photo IS NOT NULL)
OR
(consentement_photo = false AND date_consentement_photo IS NULL)
ADD CONSTRAINT chk_telephone_diff CHECK (
mobile IS NULL OR telephone_fixe IS NULL OR mobile <> telephone_fixe
);
-- =================
-- ENFANTS_PARENTS
-- =================
-- (PK composite déjà en place, rien à ajouter ici)
-- ===========================================
-- ENFANTS
-- ===========================================
-- ========
-- Si lenfant est "a_naitre", la date_prevue_naissance doit être renseignée
ALTER TABLE enfants
ADD CONSTRAINT chk_enfant_date_prevue
CHECK (
(statut = 'a_naitre' AND date_prevue_naissance IS NOT NULL)
OR (statut <> 'a_naitre')
);
-- Si lenfant est déjà né, sa date_naissance doit être renseignée
ALTER TABLE enfants
ADD CONSTRAINT chk_enfant_date_naissance
CHECK (
(statut IN ('actif','scolarise') AND date_naissance IS NOT NULL)
OR (statut = 'a_naitre')
);
-- ===========================================
-- DOSSIERS
-- ========
-- ===========================================
-- Budget positif si renseigné
ALTER TABLE dossiers
ADD CONSTRAINT chk_dossiers_budget_nonneg
CHECK (budget IS NULL OR budget >= 0),
ADD CONSTRAINT chk_dossiers_type_contrat_non_vide
CHECK (type_contrat IS NULL OR btrim(type_contrat) <> ''),
ADD CONSTRAINT chk_dossiers_planning_json
CHECK (planning_souhaite IS NULL OR jsonb_typeof(planning_souhaite) = 'object');
ADD CONSTRAINT chk_dossier_budget_pos CHECK (budget IS NULL OR budget > 0);
-- ========
-- MESSAGES
-- ========
-- Contenu obligatoire, non vide
ALTER TABLE messages
ALTER COLUMN contenu SET NOT NULL;
ALTER TABLE messages
ADD CONSTRAINT chk_messages_contenu_non_vide
CHECK (btrim(contenu) <> '');
-- =========
-- ===========================================
-- CONTRATS
-- =========
-- ===========================================
-- Cohérence des dates : date_debut obligatoire pour un contrat signé
ALTER TABLE contrats
ADD CONSTRAINT chk_contrats_tarif_nonneg
CHECK (tarif_horaire IS NULL OR tarif_horaire >= 0),
ADD CONSTRAINT chk_contrats_indemnites_nonneg
CHECK (indemnites_repas IS NULL OR indemnites_repas >= 0);
ADD CONSTRAINT chk_contrat_date_debut
CHECK (
(statut = 'brouillon' OR date_debut IS NOT NULL)
);
-- ==================
-- AVENANTS_CONTRATS
-- ==================
-- Rien de spécifique (statut enum déjà en place)
-- =========
-- ===========================================
-- EVENEMENTS
-- =========
-- ===========================================
-- Cohérence des dates : début < fin
ALTER TABLE evenements
ADD CONSTRAINT chk_evenements_dates_coherentes
CHECK (date_fin IS NULL OR date_debut IS NULL OR date_fin >= date_debut);
ADD CONSTRAINT chk_evenement_dates
CHECK (date_fin IS NULL OR date_debut <= date_fin);
-- =================
-- SIGNALEMENTS_BUGS
-- =================
-- Description obligatoire, non vide
ALTER TABLE signalements_bugs
ALTER COLUMN description SET NOT NULL;
ALTER TABLE signalements_bugs
ADD CONSTRAINT chk_bugs_description_non_vide
CHECK (btrim(description) <> '');
-- =======
-- UPLOADS
-- =======
-- URL obligatoire + format basique (chemin absolu ou http(s))
ALTER TABLE uploads
ALTER COLUMN fichier_url SET NOT NULL;
ALTER TABLE uploads
ADD CONSTRAINT chk_uploads_url_format
CHECK (fichier_url ~ '^(https?://.+|/[^\\s]+)$');
-- =============
-- NOTIFICATIONS
-- =============
-- Contenu obligatoire, non vide
ALTER TABLE notifications
ALTER COLUMN contenu SET NOT NULL;
ALTER TABLE notifications
ADD CONSTRAINT chk_notifications_contenu_non_vide
CHECK (btrim(contenu) <> '');
-- ===========
-- ===========================================
-- VALIDATIONS
-- ===========
-- Rien de plus ici (Sprint 1 Ticket 8 enrichira la table)
-- ===========================================
-- Si statut = 'valide', alors modifie_le doit être >= cree_le
ALTER TABLE validations
ADD CONSTRAINT chk_validation_modif
CHECK (
(statut <> 'valide')
OR (modifie_le IS NULL OR modifie_le >= cree_le)
);
+85 -132
View File
@@ -1,150 +1,103 @@
-- =============================================
-- 05_triggers.sql : Timestamps automatiques
-- - Ajoute (si absent) cree_le DEFAULT now() et modifie_le DEFAULT now()
-- - Crée un trigger BEFORE UPDATE pour mettre à jour modifie_le
-- - Idempotent (DROP TRIGGER IF EXISTS / IF NOT EXISTS)
-- A exécuter après 01_init.sql, 02_indexes.sql, 03_checks.sql
-- =============================================
-- ============================================================
-- 05_triggers.sql Triggers métier et maintenance
-- ============================================================
-- 1) Fonction unique de mise à jour du timestamp
-- ===========================================
-- 1. Mettre à jour automatiquement modifie_le
-- ===========================================
CREATE OR REPLACE FUNCTION set_modifie_le()
RETURNS TRIGGER AS $$
BEGIN
NEW.modifie_le := NOW();
RETURN NEW;
NEW.modifie_le := now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Helper macro-like: pour chaque table, s'assurer des colonnes & trigger
-- (on ne peut pas faire de macro, donc on répète pour chaque table)
-- Liste des tables concernées :
-- utilisateurs, assistantes_maternelles, parents, enfants, enfants_parents,
-- dossiers, messages, contrats, avenants_contrats, evenements,
-- signalements_bugs, uploads, notifications, validations
-- ========== UTILISATEURS
ALTER TABLE utilisateurs
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_utilisateurs_set_modifie_le ON utilisateurs;
CREATE TRIGGER trg_utilisateurs_set_modifie_le
-- Appliquer sur les tables qui ont modifie_le
CREATE TRIGGER trg_update_modifie_le_utilisateurs
BEFORE UPDATE ON utilisateurs
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
FOR EACH ROW
EXECUTE FUNCTION set_modifie_le();
-- ========== ASSISTANTES_MATERNELLES
ALTER TABLE assistantes_maternelles
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_am_set_modifie_le ON assistantes_maternelles;
CREATE TRIGGER trg_am_set_modifie_le
BEFORE UPDATE ON assistantes_maternelles
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== PARENTS
ALTER TABLE parents
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_parents_set_modifie_le ON parents;
CREATE TRIGGER trg_parents_set_modifie_le
BEFORE UPDATE ON parents
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== ENFANTS
ALTER TABLE enfants
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_enfants_set_modifie_le ON enfants;
CREATE TRIGGER trg_enfants_set_modifie_le
BEFORE UPDATE ON enfants
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== ENFANTS_PARENTS (table de liaison)
ALTER TABLE enfants_parents
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_enfants_parents_set_modifie_le ON enfants_parents;
CREATE TRIGGER trg_enfants_parents_set_modifie_le
BEFORE UPDATE ON enfants_parents
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== DOSSIERS
ALTER TABLE dossiers
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_dossiers_set_modifie_le ON dossiers;
CREATE TRIGGER trg_dossiers_set_modifie_le
CREATE TRIGGER trg_update_modifie_le_dossiers
BEFORE UPDATE ON dossiers
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
FOR EACH ROW
EXECUTE FUNCTION set_modifie_le();
-- ========== MESSAGES
ALTER TABLE messages
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_messages_set_modifie_le ON messages;
CREATE TRIGGER trg_messages_set_modifie_le
BEFORE UPDATE ON messages
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== CONTRATS
ALTER TABLE contrats
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_contrats_set_modifie_le ON contrats;
CREATE TRIGGER trg_contrats_set_modifie_le
CREATE TRIGGER trg_update_modifie_le_contrats
BEFORE UPDATE ON contrats
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
FOR EACH ROW
EXECUTE FUNCTION set_modifie_le();
-- ========== AVENANTS_CONTRATS
ALTER TABLE avenants_contrats
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_avenants_contrats_set_modifie_le ON avenants_contrats;
CREATE TRIGGER trg_avenants_contrats_set_modifie_le
CREATE TRIGGER trg_update_modifie_le_avenants
BEFORE UPDATE ON avenants_contrats
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
FOR EACH ROW
EXECUTE FUNCTION set_modifie_le();
-- ========== EVENEMENTS
ALTER TABLE evenements
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_evenements_set_modifie_le ON evenements;
CREATE TRIGGER trg_evenements_set_modifie_le
CREATE TRIGGER trg_update_modifie_le_evenements
BEFORE UPDATE ON evenements
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
FOR EACH ROW
EXECUTE FUNCTION set_modifie_le();
-- ========== SIGNALEMENTS_BUGS
ALTER TABLE signalements_bugs
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_signalements_bugs_set_modifie_le ON signalements_bugs;
CREATE TRIGGER trg_signalements_bugs_set_modifie_le
BEFORE UPDATE ON signalements_bugs
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== UPLOADS
ALTER TABLE uploads
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_uploads_set_modifie_le ON uploads;
CREATE TRIGGER trg_uploads_set_modifie_le
BEFORE UPDATE ON uploads
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== NOTIFICATIONS
ALTER TABLE notifications
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_notifications_set_modifie_le ON notifications;
CREATE TRIGGER trg_notifications_set_modifie_le
BEFORE UPDATE ON notifications
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== VALIDATIONS
ALTER TABLE validations
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_validations_set_modifie_le ON validations;
CREATE TRIGGER trg_validations_set_modifie_le
CREATE TRIGGER trg_update_modifie_le_validations
BEFORE UPDATE ON validations
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
FOR EACH ROW
EXECUTE FUNCTION set_modifie_le();
-- ===========================================
-- 2. Empêcher plusieurs contrats actifs par dossier
-- ===========================================
CREATE OR REPLACE FUNCTION prevent_multiple_active_contrats()
RETURNS TRIGGER AS $$
DECLARE
nb_actifs INT;
BEGIN
IF NEW.statut IN ('en_attente_signature','valide') THEN
SELECT COUNT(*) INTO nb_actifs
FROM contrats
WHERE id_dossier = NEW.id_dossier
AND statut IN ('en_attente_signature','valide')
AND id <> COALESCE(NEW.id, gen_random_uuid());
IF nb_actifs > 0 THEN
RAISE EXCEPTION 'Un contrat actif existe déjà pour ce dossier (%).', NEW.id_dossier;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_one_active_contrat_per_dossier
BEFORE INSERT OR UPDATE ON contrats
FOR EACH ROW
EXECUTE FUNCTION prevent_multiple_active_contrats();
-- ===========================================
-- 3. Empêcher événements incohérents
-- - Pas de chevauchement de congés pour le même AM
-- ===========================================
CREATE OR REPLACE FUNCTION prevent_overlapping_events()
RETURNS TRIGGER AS $$
DECLARE
nb_overlap INT;
BEGIN
IF NEW.id_am IS NOT NULL AND NEW.date_debut IS NOT NULL AND NEW.date_fin IS NOT NULL THEN
SELECT COUNT(*) INTO nb_overlap
FROM evenements ev
WHERE ev.id_am = NEW.id_am
AND ev.id <> COALESCE(NEW.id, gen_random_uuid())
AND ev.date_fin >= NEW.date_debut
AND ev.date_debut <= NEW.date_fin;
IF nb_overlap > 0 THEN
RAISE EXCEPTION 'Conflit d''événements : chevauchement détecté pour AM %', NEW.id_am;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_no_overlapping_am_events
BEFORE INSERT OR UPDATE ON evenements
FOR EACH ROW
EXECUTE FUNCTION prevent_overlapping_events();