Merge branch 'master' of https://git.ptits-pas.fr/Ynov/ptitspas-ynov-bdd
This commit is contained in:
commit
ef2622f8ed
157
docs/ENUMS.md
Normal file
157
docs/ENUMS.md
Normal file
@ -0,0 +1,157 @@
|
||||
|
||||
# ENUMS.md — Référentiel des valeurs énumérées (Sprint 1)
|
||||
|
||||
Ce document recense **toutes les valeurs énumérées** utilisées dans la base P’titsPas, leur **sens fonctionnel**, les **colonnes concernées** et les **transitions** attendues côté métier / API.
|
||||
|
||||
> Objectif : garantir la **cohérence** entre la DB, le backend (NestJS) et le frontend (Flutter).
|
||||
> Toute nouvelle valeur ou renommage **doit** être ajouté ici **avant** migration DB.
|
||||
|
||||
---
|
||||
|
||||
## Conventions générales
|
||||
|
||||
- Les valeurs ENUM sont **en minuscules** et **sans espace** (snake_case si nécessaire).
|
||||
- Côté DB, elles sont implémentées via **types ENUM PostgreSQL** *ou* via `CHECK` (selon ce qui est en place dans `01_init.sql`).
|
||||
- Côté API, ces valeurs sont **renvoyées telles quelles** et **documentées** dans l’OpenAPI / DTO.
|
||||
|
||||
---
|
||||
|
||||
## 1) Rôle utilisateur — `role`
|
||||
|
||||
**Tables/colonnes** : `utilisateurs.role`
|
||||
**Valeurs autorisées** :
|
||||
|
||||
| Valeur | Description |
|
||||
|---|---|
|
||||
| `super_admin` | Compte technique initial / administration globale |
|
||||
| `gestionnaire` | Gestion / validation des comptes, supervision |
|
||||
| `parent` | Parent ou co-parent |
|
||||
| `am` | Assistante maternelle |
|
||||
|
||||
---
|
||||
|
||||
## 2) Statut utilisateur — `statut`
|
||||
|
||||
**Tables/colonnes** : `utilisateurs.statut`
|
||||
**Valeurs autorisées** :
|
||||
|
||||
| Valeur | Description |
|
||||
|---|---|
|
||||
| `en_attente` | Compte créé mais non validé |
|
||||
| `accepte` | Compte validé et actif |
|
||||
| `rejete` | Demande refusée (peut être recréée ultérieurement) |
|
||||
|
||||
---
|
||||
|
||||
## 3) Statut enfant — `statut`
|
||||
|
||||
**Tables/colonnes** : `enfants.statut`
|
||||
**Valeurs autorisées** :
|
||||
|
||||
| Valeur | Description |
|
||||
|---|---|
|
||||
| `a_naitre` | Enfant à naître (date prévue renseignée) |
|
||||
| `actif` | Enfant pris en charge / en cours de garde |
|
||||
| `scolarise` | Enfant scolarisé, garde potentiellement périscolaire |
|
||||
|
||||
**Contraintes associées** :
|
||||
- `a_naitre` → **`date_prevue_naissance` obligatoire**
|
||||
- `actif`/`scolarise` → **`date_naissance` obligatoire**
|
||||
|
||||
---
|
||||
|
||||
## 4) Statut dossier — `statut`
|
||||
|
||||
**Tables/colonnes** : `dossiers.statut`
|
||||
**Valeurs autorisées (MVP)** :
|
||||
|
||||
| Valeur | Description |
|
||||
|---|---|
|
||||
| `envoye` | Dossier soumis par le parent (état initial) |
|
||||
| `en_cours` | Échanges en cours entre parent et AM |
|
||||
| `clos` | Dossier clôturé (contrat généré ou abandon) |
|
||||
|
||||
---
|
||||
|
||||
## 5) Statut contrat — `statut`
|
||||
|
||||
**Tables/colonnes** : `contrats.statut`
|
||||
**Valeurs autorisées** :
|
||||
|
||||
| Valeur | Description |
|
||||
|---|---|
|
||||
| `brouillon` | Contrat en préparation |
|
||||
| `valide` | Contrat finalisé (signatures complètes) |
|
||||
| `archive` | Contrat obsolète / terminé |
|
||||
|
||||
---
|
||||
|
||||
## 6) Statut avenant — `statut`
|
||||
|
||||
**Tables/colonnes** : `avenants_contrats.statut`
|
||||
**Valeurs autorisées** :
|
||||
|
||||
| Valeur | Description |
|
||||
|---|---|
|
||||
| `propose` | Avenant proposé (en attente d’accord) |
|
||||
| `valide` | Avenant accepté et appliqué |
|
||||
| `rejete` | Avenant refusé |
|
||||
|
||||
---
|
||||
|
||||
## 7) Type d’événement — `type`
|
||||
|
||||
**Tables/colonnes** : `evenements.type`
|
||||
**Valeurs autorisées** :
|
||||
|
||||
| Valeur | Description |
|
||||
|---|---|
|
||||
| `absence_enfant` | Enfant absent |
|
||||
| `conge_am` | Congé de l’assistante maternelle |
|
||||
| `conge_parent` | Congé du parent |
|
||||
| `arret_maladie_am` | Arrêt maladie AM |
|
||||
| `evenement_rpe` | Événement RPE |
|
||||
|
||||
---
|
||||
|
||||
## 8) Statut d’événement — `statut`
|
||||
|
||||
**Tables/colonnes** : `evenements.statut`
|
||||
**Valeurs autorisées** :
|
||||
|
||||
| Valeur | Description |
|
||||
|---|---|
|
||||
| `propose` | Événement proposé |
|
||||
| `valide` | Événement validé |
|
||||
| `rejete` | Événement refusé |
|
||||
|
||||
---
|
||||
|
||||
## 9) Statut de validation compte — `statut`
|
||||
|
||||
**Tables/colonnes** : `validations.statut`
|
||||
**Valeurs autorisées** :
|
||||
|
||||
| Valeur | Description |
|
||||
|---|---|
|
||||
| `accepte` | Compte validé |
|
||||
| `rejete` | Compte refusé |
|
||||
|
||||
---
|
||||
|
||||
## 10) Type de notification — `type`
|
||||
|
||||
**Tables/colonnes** : `notifications.type`
|
||||
**Valeurs proposées** :
|
||||
|
||||
| Valeur | Description |
|
||||
|---|---|
|
||||
| `nouveau_message` | Nouveau message sur un dossier |
|
||||
| `validation_compte` | Résultat de la validation de compte |
|
||||
| `maj_contrat` | Contrat mis à jour (avenant / signature) |
|
||||
| `evenement_a_venir` | Rappel d’un événement proche |
|
||||
|
||||
---
|
||||
|
||||
**Mainteneur** : Équipe BDD
|
||||
**Dernière mise à jour** : Sprint 1 — Ticket 9 (ENUMS)
|
||||
142
docs/FK_POLICIES.md
Normal file
142
docs/FK_POLICIES.md
Normal file
@ -0,0 +1,142 @@
|
||||
|
||||
# FK_POLICIES.md
|
||||
**Politique des clés étrangères (ON DELETE / ON UPDATE)** – Sprint 1
|
||||
|
||||
## 🎯 Objectif
|
||||
Documenter, de façon unique et partagée, les règles de suppression/mise à jour appliquées aux **clés étrangères** de la base P’titsPas pour :
|
||||
- préserver l’**intégrité référentielle** ;
|
||||
- conserver l’**historique** utile (messages, événements…) ;
|
||||
- respecter les exigences **RGPD** (suppression en cascade lorsque pertinent).
|
||||
|
||||
> Par défaut, **ON UPDATE = NO ACTION** (UUID immuables).
|
||||
> Ce document couvre **ON DELETE** table par table.
|
||||
|
||||
---
|
||||
|
||||
## 🧭 Principes généraux
|
||||
|
||||
- **CASCADE** quand la donnée fille **n’a pas de sens sans le parent**
|
||||
(ex. `dossiers` d’un parent, `avenants` d’un contrat).
|
||||
- **SET NULL** quand on veut **préserver l’historique** mais que le référent peut disparaître
|
||||
(ex. auteur d’un message supprimé, créateur d’un événement).
|
||||
- **RESTRICT/NO ACTION** non utilisé ici pour éviter des blocages au nettoyage.
|
||||
|
||||
---
|
||||
|
||||
## 📚 Récapitulatif rapide (matrice)
|
||||
|
||||
| Table (colonne FK) → Référence | ON DELETE | Raison |
|
||||
|---|---:|---|
|
||||
| **assistantes_maternelles(id_utilisateur)** → `utilisateurs(id)` | **CASCADE** | Profil AM supprimé avec son compte |
|
||||
| **parents(id_utilisateur)** → `utilisateurs(id)` | **CASCADE** | Extension parent supprimée avec son compte |
|
||||
| **parents(id_co_parent)** → `utilisateurs(id)` | **SET NULL** | Conserver le parent principal si co-parent disparaît |
|
||||
| **enfants_parents(id_parent)** → `parents(id_utilisateur)` | **CASCADE** | Nettoyage liaisons N:N |
|
||||
| **enfants_parents(id_enfant)** → `enfants(id)` | **CASCADE** | Idem |
|
||||
| **dossiers(id_parent)** → `parents(id_utilisateur)` | **CASCADE** | Dossier n’a pas de sens sans parent |
|
||||
| **dossiers(id_enfant)** → `enfants(id)` | **CASCADE** | Dossier n’a pas de sens sans enfant |
|
||||
| **messages(id_dossier)** → `dossiers(id)` | **CASCADE** | Messages détruits avec le dossier |
|
||||
| **messages(id_expediteur)** → `utilisateurs(id)` | **SET NULL** | Garder l’historique des échanges |
|
||||
| **contrats(id_dossier)** → `dossiers(id)` | **CASCADE** | 1:1, contrat détruit si dossier supprimé |
|
||||
| **avenants_contrats(id_contrat)** → `contrats(id)` | **CASCADE** | Avenants détruits avec le contrat |
|
||||
| **avenants_contrats(initie_par)** → `utilisateurs(id)` | **SET NULL** | Historiser l’avenant sans bloquer |
|
||||
| **evenements(id_enfant)** → `enfants(id)` | **CASCADE** | Événements n’ont plus de sens |
|
||||
| **evenements(id_am)** → `utilisateurs(id)` | **SET NULL** | Garder la trace même si AM supprimée |
|
||||
| **evenements(id_parent)** → `parents(id_utilisateur)` | **SET NULL** | Garder la trace si parent supprimé |
|
||||
| **evenements(cree_par)** → `utilisateurs(id)` | **SET NULL** | Conserver l’historique de création |
|
||||
| **signalements_bugs(id_utilisateur)** → `utilisateurs(id)` | **SET NULL** | Conserver le ticket même si compte supprimé |
|
||||
| **uploads(id_utilisateur)** → `utilisateurs(id)` | **SET NULL** | Fichier reste référencé sans l’auteur |
|
||||
| **notifications(id_utilisateur)** → `utilisateurs(id)` | **CASCADE** | Notifications propres à l’utilisateur |
|
||||
| **validations(id_utilisateur)** → `utilisateurs(id)` | **SET NULL** | Garder l’historique de décision |
|
||||
|
||||
> **ON UPDATE** : **NO ACTION** partout (les UUID ne changent pas).
|
||||
|
||||
---
|
||||
|
||||
## 🔎 Détail par domaine
|
||||
|
||||
### Utilisateurs & extensions
|
||||
- `assistantes_maternelles.id_utilisateur` → **CASCADE**
|
||||
- `parents.id_utilisateur` → **CASCADE**
|
||||
- `parents.id_co_parent` → **SET NULL** (interdit d’être co-parent de soi-même via CHECK déjà posé)
|
||||
|
||||
### Enfants & liaisons
|
||||
- `enfants_parents.id_parent` → **CASCADE**
|
||||
- `enfants_parents.id_enfant` → **CASCADE**
|
||||
|
||||
### Dossiers & échanges
|
||||
- `dossiers.id_parent` → **CASCADE**
|
||||
- `dossiers.id_enfant` → **CASCADE**
|
||||
- `messages.id_dossier` → **CASCADE**
|
||||
- `messages.id_expediteur` → **SET NULL**
|
||||
|
||||
### Contrats & avenants
|
||||
- `contrats.id_dossier` → **CASCADE** (unique 1:1)
|
||||
- `avenants_contrats.id_contrat` → **CASCADE**
|
||||
- `avenants_contrats.initie_par` → **SET NULL**
|
||||
|
||||
### Événements
|
||||
- `evenements.id_enfant` → **CASCADE**
|
||||
- `evenements.id_am` → **SET NULL**
|
||||
- `evenements.id_parent` → **SET NULL**
|
||||
- `evenements.cree_par` → **SET NULL**
|
||||
|
||||
### Divers
|
||||
- `signalements_bugs.id_utilisateur` → **SET NULL**
|
||||
- `uploads.id_utilisateur` → **SET NULL**
|
||||
- `notifications.id_utilisateur` → **CASCADE**
|
||||
- `validations.id_utilisateur` → **SET NULL**
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Scénarios de test (exemples)
|
||||
|
||||
1. **Suppression d’un parent**
|
||||
- Supprimer `utilisateurs(id=parentX)`
|
||||
- Attendu : `parents` (CASCADE), ses `dossiers` (CASCADE), `messages` liés aux `dossiers` (CASCADE) sont supprimés.
|
||||
|
||||
2. **Suppression d’un co-parent**
|
||||
- Supprimer `utilisateurs(id=coParentY)`
|
||||
- Attendu : `parents.id_co_parent` passe à **NULL**, aucun dossier supprimé.
|
||||
|
||||
3. **Suppression d’un utilisateur auteur de messages**
|
||||
- Supprimer `utilisateurs(id=uZ)`
|
||||
- Attendu : les lignes `messages` **restent**, `id_expediteur` devient **NULL**.
|
||||
|
||||
4. **Suppression d’un enfant**
|
||||
- Supprimer `enfants(id=childA)`
|
||||
- Attendu : `enfants_parents` (CASCADE), `dossiers` du childA (CASCADE), `evenements` du childA (CASCADE).
|
||||
|
||||
5. **Suppression d’un utilisateur AM**
|
||||
- Supprimer `utilisateurs(id=amB)`
|
||||
- Attendu : `evenements.id_am` devient **NULL** (historique conservé).
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Migrations associées
|
||||
|
||||
Les ajustements sont implémentés dans :
|
||||
- **`/bdd/migrations/04_fk_policies.sql`**
|
||||
– redéfinition des contraintes FK avec les bonnes politiques (**DROP puis ADD CONSTRAINT**), de façon idempotente.
|
||||
|
||||
---
|
||||
|
||||
## 📄 Notes & futures évolutions
|
||||
|
||||
- **RGPD (Sprint 2)** : si vous activez le **soft delete** (`deleted_at`) côté tables métier, ces politiques restent valides (les suppressions logiques se gèrent au niveau applicatif).
|
||||
- **Audit** : si vous voulez tracer les suppressions, ajoutez des triggers d’audit (voir ticket Sprint 2 – Audit log).
|
||||
- **Performance** : chaque FK doit être **indexée côté enfant** (cf. `02_indexes.sql`).
|
||||
|
||||
---
|
||||
|
||||
## ✅ Checklist de conformité
|
||||
|
||||
- [ ] Toutes les FK listées existent dans la base
|
||||
- [ ] Politique **ON DELETE** conforme au tableau ci-dessus
|
||||
- [ ] **ON UPDATE = NO ACTION** partout
|
||||
- [ ] Tests de suppression réalisés sur une base seedée
|
||||
- [ ] `04_fk_policies.sql` appliqué sans erreur
|
||||
|
||||
---
|
||||
|
||||
**Mainteneur** : Équipe BDD
|
||||
**Dernière mise à jour** : Sprint 1 – Politique FK consolidée
|
||||
157
migrations/02_indexes.sql
Normal file
157
migrations/02_indexes.sql
Normal file
@ -0,0 +1,157 @@
|
||||
-- =============================================
|
||||
-- 02_indexes.sql : Index FK + colonnes critiques
|
||||
-- =============================================
|
||||
|
||||
-- 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
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_id_dossier_cree_le
|
||||
ON messages (id_dossier, cree_le);
|
||||
|
||||
-- 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
|
||||
CREATE INDEX IF NOT EXISTS idx_evenements_id_enfant_date_debut
|
||||
ON evenements (id_enfant, date_debut);
|
||||
|
||||
-- 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
|
||||
CREATE INDEX IF NOT EXISTS idx_notifications_user_lu_cree_le
|
||||
ON notifications (id_utilisateur, lu, cree_le);
|
||||
|
||||
-- Option : index partiel pour "non lues"
|
||||
-- CREATE INDEX IF NOT EXISTS idx_notifications_non_lues
|
||||
-- ON notifications (id_utilisateur, cree_le)
|
||||
-- WHERE lu = false;
|
||||
|
||||
-- ===========
|
||||
-- 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);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_validations_statut
|
||||
ON validations (statut);
|
||||
140
migrations/03_checks.sql
Normal file
140
migrations/03_checks.sql
Normal file
@ -0,0 +1,140 @@
|
||||
-- =============================================
|
||||
-- 03_checks.sql : Contraintes CHECK & NOT NULL
|
||||
-- A exécuter après 01_init.sql (et 02_indexes.sql)
|
||||
-- =============================================
|
||||
|
||||
-- ==============
|
||||
-- UTILISATEURS
|
||||
-- ==============
|
||||
-- (Regex email déjà présente dans 01_init.sql)
|
||||
-- Optionnel : forcer prenom/nom non vides si fournis
|
||||
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 : aujourd’hui 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)
|
||||
);
|
||||
|
||||
-- =================
|
||||
-- ENFANTS_PARENTS
|
||||
-- =================
|
||||
-- (PK composite déjà en place, rien à ajouter ici)
|
||||
|
||||
-- ========
|
||||
-- DOSSIERS
|
||||
-- ========
|
||||
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');
|
||||
|
||||
-- ========
|
||||
-- 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
|
||||
-- =========
|
||||
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);
|
||||
|
||||
-- ==================
|
||||
-- AVENANTS_CONTRATS
|
||||
-- ==================
|
||||
-- Rien de spécifique (statut enum déjà en place)
|
||||
|
||||
-- =========
|
||||
-- EVENEMENTS
|
||||
-- =========
|
||||
ALTER TABLE evenements
|
||||
ADD CONSTRAINT chk_evenements_dates_coherentes
|
||||
CHECK (date_fin IS NULL OR date_debut IS NULL OR date_fin >= date_debut);
|
||||
|
||||
-- =================
|
||||
-- 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)
|
||||
190
migrations/04_fk_policies.sql
Normal file
190
migrations/04_fk_policies.sql
Normal file
@ -0,0 +1,190 @@
|
||||
-- ==========================================================
|
||||
-- 04_fk_policies.sql : normalisation des politiques ON DELETE
|
||||
-- A exécuter après 01_init.sql et 03_checks.sql
|
||||
-- ==========================================================
|
||||
|
||||
-- Helper: Drop FK d'une table/colonne si elle existe (par son/leurs noms de colonne)
|
||||
-- puis recrée la contrainte avec la clause fournie
|
||||
-- Utilise information_schema pour retrouver le nom de contrainte auto-généré
|
||||
-- NB: schema = public
|
||||
|
||||
-- ========== messages.id_expediteur -> utilisateurs.id : SET NULL (au lieu de CASCADE)
|
||||
DO $$
|
||||
DECLARE
|
||||
conname text;
|
||||
BEGIN
|
||||
SELECT tc.constraint_name INTO conname
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.table_schema='public'
|
||||
AND tc.table_name='messages'
|
||||
AND tc.constraint_type='FOREIGN KEY'
|
||||
AND kcu.column_name='id_expediteur';
|
||||
IF conname IS NOT NULL THEN
|
||||
EXECUTE format('ALTER TABLE public.messages DROP CONSTRAINT %I', conname);
|
||||
END IF;
|
||||
EXECUTE $sql$
|
||||
ALTER TABLE public.messages
|
||||
ADD CONSTRAINT fk_messages_id_expediteur
|
||||
FOREIGN KEY (id_expediteur) REFERENCES public.utilisateurs(id) ON DELETE SET NULL
|
||||
$sql$;
|
||||
END $$;
|
||||
|
||||
-- ========== parents.id_co_parent -> utilisateurs.id : SET NULL
|
||||
DO $$
|
||||
DECLARE conname text;
|
||||
BEGIN
|
||||
SELECT tc.constraint_name INTO conname
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.table_schema='public'
|
||||
AND tc.table_name='parents'
|
||||
AND tc.constraint_type='FOREIGN KEY'
|
||||
AND kcu.column_name='id_co_parent';
|
||||
IF conname IS NOT NULL THEN
|
||||
EXECUTE format('ALTER TABLE public.parents DROP CONSTRAINT %I', conname);
|
||||
END IF;
|
||||
EXECUTE $sql$
|
||||
ALTER TABLE public.parents
|
||||
ADD CONSTRAINT fk_parents_id_co_parent
|
||||
FOREIGN KEY (id_co_parent) REFERENCES public.utilisateurs(id) ON DELETE SET NULL
|
||||
$sql$;
|
||||
END $$;
|
||||
|
||||
-- ========== avenants_contrats.initie_par -> utilisateurs.id : SET NULL
|
||||
DO $$
|
||||
DECLARE conname text;
|
||||
BEGIN
|
||||
SELECT tc.constraint_name INTO conname
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.table_schema='public'
|
||||
AND tc.table_name='avenants_contrats'
|
||||
AND tc.constraint_type='FOREIGN KEY'
|
||||
AND kcu.column_name='initie_par';
|
||||
IF conname IS NOT NULL THEN
|
||||
EXECUTE format('ALTER TABLE public.avenants_contrats DROP CONSTRAINT %I', conname);
|
||||
END IF;
|
||||
EXECUTE $sql$
|
||||
ALTER TABLE public.avenants_contrats
|
||||
ADD CONSTRAINT fk_avenants_contrats_initie_par
|
||||
FOREIGN KEY (initie_par) REFERENCES public.utilisateurs(id) ON DELETE SET NULL
|
||||
$sql$;
|
||||
END $$;
|
||||
|
||||
-- ========== evenements.id_am -> utilisateurs.id : SET NULL
|
||||
DO $$
|
||||
DECLARE conname text;
|
||||
BEGIN
|
||||
SELECT tc.constraint_name INTO conname
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.table_schema='public'
|
||||
AND tc.table_name='evenements'
|
||||
AND tc.constraint_type='FOREIGN KEY'
|
||||
AND kcu.column_name='id_am';
|
||||
IF conname IS NOT NULL THEN
|
||||
EXECUTE format('ALTER TABLE public.evenements DROP CONSTRAINT %I', conname);
|
||||
END IF;
|
||||
EXECUTE $sql$
|
||||
ALTER TABLE public.evenements
|
||||
ADD CONSTRAINT fk_evenements_id_am
|
||||
FOREIGN KEY (id_am) REFERENCES public.utilisateurs(id) ON DELETE SET NULL
|
||||
$sql$;
|
||||
END $$;
|
||||
|
||||
-- ========== evenements.id_parent -> parents.id_utilisateur : SET NULL
|
||||
DO $$
|
||||
DECLARE conname text;
|
||||
BEGIN
|
||||
SELECT tc.constraint_name INTO conname
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.table_schema='public'
|
||||
AND tc.table_name='evenements'
|
||||
AND tc.constraint_type='FOREIGN KEY'
|
||||
AND kcu.column_name='id_parent';
|
||||
IF conname IS NOT NULL THEN
|
||||
EXECUTE format('ALTER TABLE public.evenements DROP CONSTRAINT %I', conname);
|
||||
END IF;
|
||||
EXECUTE $sql$
|
||||
ALTER TABLE public.evenements
|
||||
ADD CONSTRAINT fk_evenements_id_parent
|
||||
FOREIGN KEY (id_parent) REFERENCES public.parents(id_utilisateur) ON DELETE SET NULL
|
||||
$sql$;
|
||||
END $$;
|
||||
|
||||
-- ========== evenements.cree_par -> utilisateurs.id : SET NULL
|
||||
DO $$
|
||||
DECLARE conname text;
|
||||
BEGIN
|
||||
SELECT tc.constraint_name INTO conname
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.table_schema='public'
|
||||
AND tc.table_name='evenements'
|
||||
AND tc.constraint_type='FOREIGN KEY'
|
||||
AND kcu.column_name='cree_par';
|
||||
IF conname IS NOT NULL THEN
|
||||
EXECUTE format('ALTER TABLE public.evenements DROP CONSTRAINT %I', conname);
|
||||
END IF;
|
||||
EXECUTE $sql$
|
||||
ALTER TABLE public.evenements
|
||||
ADD CONSTRAINT fk_evenements_cree_par
|
||||
FOREIGN KEY (cree_par) REFERENCES public.utilisateurs(id) ON DELETE SET NULL
|
||||
$sql$;
|
||||
END $$;
|
||||
|
||||
-- ========== signalements_bugs.id_utilisateur -> utilisateurs.id : SET NULL
|
||||
DO $$
|
||||
DECLARE conname text;
|
||||
BEGIN
|
||||
SELECT tc.constraint_name INTO conname
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.table_schema='public'
|
||||
AND tc.table_name='signalements_bugs'
|
||||
AND tc.constraint_type='FOREIGN KEY'
|
||||
AND kcu.column_name='id_utilisateur';
|
||||
IF conname IS NOT NULL THEN
|
||||
EXECUTE format('ALTER TABLE public.signalements_bugs DROP CONSTRAINT %I', conname);
|
||||
END IF;
|
||||
EXECUTE $sql$
|
||||
ALTER TABLE public.signalements_bugs
|
||||
ADD CONSTRAINT fk_signalements_bugs_id_utilisateur
|
||||
FOREIGN KEY (id_utilisateur) REFERENCES public.utilisateurs(id) ON DELETE SET NULL
|
||||
$sql$;
|
||||
END $$;
|
||||
|
||||
-- ========== validations.id_utilisateur -> utilisateurs.id : SET NULL
|
||||
DO $$
|
||||
DECLARE conname text;
|
||||
BEGIN
|
||||
SELECT tc.constraint_name INTO conname
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.table_schema='public'
|
||||
AND tc.table_name='validations'
|
||||
AND tc.constraint_type='FOREIGN KEY'
|
||||
AND kcu.column_name='id_utilisateur';
|
||||
IF conname IS NOT NULL THEN
|
||||
EXECUTE format('ALTER TABLE public.validations DROP CONSTRAINT %I', conname);
|
||||
END IF;
|
||||
EXECUTE $sql$
|
||||
ALTER TABLE public.validations
|
||||
ADD CONSTRAINT fk_validations_id_utilisateur
|
||||
FOREIGN KEY (id_utilisateur) REFERENCES public.utilisateurs(id) ON DELETE SET NULL
|
||||
$sql$;
|
||||
END $$;
|
||||
|
||||
-- NB:
|
||||
-- D'autres FK déjà correctes : CASCADE (assistantes_maternelles, parents, enfants_parents, dossiers, messages.id_dossier, contrats, avenants_contrats.id_contrat, evenements.id_enfant), SET NULL (uploads).
|
||||
-- On laisse ON UPDATE par défaut (NO ACTION), car les UUID ne changent pas.
|
||||
150
migrations/05_triggers.sql
Normal file
150
migrations/05_triggers.sql
Normal file
@ -0,0 +1,150 @@
|
||||
-- =============================================
|
||||
-- 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
|
||||
-- =============================================
|
||||
|
||||
-- 1) Fonction unique de mise à jour du timestamp
|
||||
CREATE OR REPLACE FUNCTION set_modifie_le()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
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
|
||||
BEFORE UPDATE ON utilisateurs
|
||||
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
|
||||
BEFORE UPDATE ON dossiers
|
||||
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
|
||||
BEFORE UPDATE ON contrats
|
||||
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
|
||||
BEFORE UPDATE ON avenants_contrats
|
||||
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
|
||||
BEFORE UPDATE ON evenements
|
||||
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
|
||||
BEFORE UPDATE ON validations
|
||||
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
|
||||
53
migrations/06_validations_enrich.sql
Normal file
53
migrations/06_validations_enrich.sql
Normal file
@ -0,0 +1,53 @@
|
||||
-- ==========================================================
|
||||
-- 06_validations_enrich.sql : Traçabilité complète des validations
|
||||
-- - Ajoute la colonne 'valide_par' (FK -> utilisateurs.id)
|
||||
-- - ON DELETE SET NULL pour conserver l'historique
|
||||
-- - Ajoute index utiles pour les requêtes (valideur, statut, date)
|
||||
-- A exécuter après : 01_init.sql, 02_indexes.sql, 03_checks.sql, 04_fk_policies.sql, 05_triggers.sql
|
||||
-- ==========================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1) Colonne 'valide_par' si absente
|
||||
ALTER TABLE validations
|
||||
ADD COLUMN IF NOT EXISTS valide_par UUID NULL;
|
||||
|
||||
-- 2) FK vers utilisateurs(id), ON DELETE SET NULL
|
||||
DO $$
|
||||
DECLARE conname text;
|
||||
BEGIN
|
||||
SELECT tc.constraint_name INTO conname
|
||||
FROM information_schema.table_constraints tc
|
||||
JOIN information_schema.key_column_usage kcu
|
||||
ON tc.constraint_name = kcu.constraint_name
|
||||
AND tc.table_schema = kcu.table_schema
|
||||
WHERE tc.table_schema = 'public'
|
||||
AND tc.table_name = 'validations'
|
||||
AND tc.constraint_type= 'FOREIGN KEY'
|
||||
AND kcu.column_name = 'valide_par';
|
||||
|
||||
IF conname IS NOT NULL THEN
|
||||
EXECUTE format('ALTER TABLE public.validations DROP CONSTRAINT %I', conname);
|
||||
END IF;
|
||||
|
||||
EXECUTE $sql$
|
||||
ALTER TABLE public.validations
|
||||
ADD CONSTRAINT fk_validations_valide_par
|
||||
FOREIGN KEY (valide_par) REFERENCES public.utilisateurs(id) ON DELETE SET NULL
|
||||
$sql$;
|
||||
END $$;
|
||||
|
||||
-- 3) Index pour accélérer les recherches
|
||||
-- - qui a validé quoi récemment ?
|
||||
-- - toutes les validations par statut / par date
|
||||
CREATE INDEX IF NOT EXISTS idx_validations_valide_par_cree_le
|
||||
ON validations (valide_par, cree_le);
|
||||
|
||||
-- Certains existent peut-être déjà : on sécurise
|
||||
CREATE INDEX IF NOT EXISTS idx_validations_id_utilisateur_cree_le
|
||||
ON validations (id_utilisateur, cree_le);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_validations_statut
|
||||
ON validations (statut);
|
||||
|
||||
COMMIT;
|
||||
221
seed/02_seed.sql
Normal file
221
seed/02_seed.sql
Normal file
@ -0,0 +1,221 @@
|
||||
-- ============================================================
|
||||
-- 02_seed.sql : Données de test réalistes (Sprint 1)
|
||||
-- A exécuter après :
|
||||
-- 01_init.sql (création des tables)
|
||||
-- 02_indexes.sql
|
||||
-- 03_checks.sql
|
||||
-- 04_fk_policies.sql
|
||||
-- 05_triggers.sql
|
||||
-- ============================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Utilisateurs (super_admin, gestionnaire, 2 parents + co-parent, 1 AM)
|
||||
-- ------------------------------------------------------------
|
||||
|
||||
-- UUIDs fixes pour faciliter les tests / jointures
|
||||
-- super_admin
|
||||
INSERT INTO utilisateurs (id, courriel, mot_de_passe_hash, prenom, nom, role, statut)
|
||||
VALUES ('11111111-1111-1111-1111-111111111111', 'admin@ptits-pas.fr', '$2y$10$hashAdminIci', 'Super', 'Admin', 'super_admin', 'accepte')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- gestionnaire
|
||||
INSERT INTO utilisateurs (id, courriel, mot_de_passe_hash, prenom, nom, role, statut)
|
||||
VALUES ('22222222-2222-2222-2222-222222222222', 'gestion@ptits-pas.fr', '$2y$10$hashGestionIci', 'Gina', 'Gestion', 'gestionnaire', 'accepte')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- parent #1
|
||||
INSERT INTO utilisateurs (id, courriel, mot_de_passe_hash, prenom, nom, role, statut)
|
||||
VALUES ('33333333-3333-3333-3333-333333333333', 'parent1@example.com', '$2y$10$hashParent1', 'Paul', 'Parent', 'parent', 'accepte')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- co-parent du parent #1
|
||||
INSERT INTO utilisateurs (id, courriel, mot_de_passe_hash, prenom, nom, role, statut)
|
||||
VALUES ('44444444-4444-4444-4444-444444444444', 'coparent1@example.com', '$2y$10$hashCoParent1', 'Clara', 'CoParent', 'parent', 'accepte')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- parent #2
|
||||
INSERT INTO utilisateurs (id, courriel, mot_de_passe_hash, prenom, nom, role, statut)
|
||||
VALUES ('55555555-5555-5555-5555-555555555555', 'parent2@example.com', '$2y$10$hashParent2', 'Nora', 'Parent', 'parent', 'accepte')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- assistante maternelle #1
|
||||
INSERT INTO utilisateurs (id, courriel, mot_de_passe_hash, prenom, nom, role, statut)
|
||||
VALUES ('66666666-6666-6666-6666-666666666666', 'am1@example.com', '$2y$10$hashAM1', 'Alice', 'AM', 'am', 'accepte')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Extensions de rôles (parents / AM)
|
||||
-- ------------------------------------------------------------
|
||||
|
||||
-- parents (id_co_parent nullable)
|
||||
INSERT INTO parents (id_utilisateur, id_co_parent)
|
||||
VALUES ('33333333-3333-3333-3333-333333333333', '44444444-4444-4444-4444-444444444444') -- parent1 avec co-parent
|
||||
ON CONFLICT (id_utilisateur) DO NOTHING;
|
||||
|
||||
INSERT INTO parents (id_utilisateur, id_co_parent)
|
||||
VALUES ('55555555-5555-5555-5555-555555555555', NULL) -- parent2 sans co-parent
|
||||
ON CONFLICT (id_utilisateur) DO NOTHING;
|
||||
|
||||
-- assistantes_maternelles
|
||||
INSERT INTO assistantes_maternelles (id_utilisateur, numero_agrement, nb_max_enfants, disponible, ville_residence)
|
||||
VALUES ('66666666-6666-6666-6666-666666666666', 'AGR-2025-0001', 3, true, 'Lille')
|
||||
ON CONFLICT (id_utilisateur) DO NOTHING;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Enfants
|
||||
-- - child A : déjà né (statut = 'actif' et date_naissance requise)
|
||||
-- - child B : à naître (statut = 'a_naitre' et date_prevue_naissance requise)
|
||||
-- ------------------------------------------------------------
|
||||
|
||||
INSERT INTO enfants (id, prenom, nom, statut, date_naissance, jumeau_multiple)
|
||||
VALUES ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'Léo', 'Parent', 'actif', '2022-04-12', false)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
INSERT INTO enfants (id, prenom, nom, statut, date_prevue_naissance, jumeau_multiple)
|
||||
VALUES ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'Mila', 'Parent', 'a_naitre', '2026-02-15', false)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Liaison N:N parents_enfants
|
||||
-- - parent1 + co-parent ↔ enfant A & B
|
||||
-- - parent2 ↔ enfant B
|
||||
-- ------------------------------------------------------------
|
||||
|
||||
-- parent1 ↔ enfant A
|
||||
INSERT INTO enfants_parents (id_parent, id_enfant)
|
||||
VALUES ('33333333-3333-3333-3333-333333333333', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- co-parent1 ↔ enfant A
|
||||
INSERT INTO enfants_parents (id_parent, id_enfant)
|
||||
VALUES ('44444444-4444-4444-4444-444444444444', 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- parent1 ↔ enfant B
|
||||
INSERT INTO enfants_parents (id_parent, id_enfant)
|
||||
VALUES ('33333333-3333-3333-3333-333333333333', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- parent2 ↔ enfant B
|
||||
INSERT INTO enfants_parents (id_parent, id_enfant)
|
||||
VALUES ('55555555-5555-5555-5555-555555555555', 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Dossier (parent1 ↔ enfant A)
|
||||
-- ------------------------------------------------------------
|
||||
INSERT INTO dossiers (id, id_parent, id_enfant, presentation, type_contrat, repas, budget, planning_souhaite)
|
||||
VALUES (
|
||||
'dddddddd-dddd-dddd-dddd-dddddddddddd',
|
||||
'33333333-3333-3333-3333-333333333333',
|
||||
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'Besoin garde périscolaire lundi/mardi/jeudi/vendredi.',
|
||||
'mensuel',
|
||||
true,
|
||||
600.00,
|
||||
'{"lun_ven":{"matin":false,"midi":true,"soir":true}}'::jsonb
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Messages (sur le dossier)
|
||||
-- ------------------------------------------------------------
|
||||
INSERT INTO messages (id, id_dossier, id_expediteur, contenu)
|
||||
VALUES ('m0000000-0000-0000-0000-000000000001', 'dddddddd-dddd-dddd-dddd-dddddddddddd', '33333333-3333-3333-3333-333333333333', 'Bonjour, nous cherchons une garde périscolaire.')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
INSERT INTO messages (id, id_dossier, id_expediteur, contenu)
|
||||
VALUES ('m0000000-0000-0000-0000-000000000002', 'dddddddd-dddd-dddd-dddd-dddddddddddd', '66666666-6666-6666-6666-666666666666', 'Bonjour, je suis disponible les soirs. Discutons du contrat.')
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Contrat (1:1 avec le dossier)
|
||||
-- ------------------------------------------------------------
|
||||
INSERT INTO contrats (id, id_dossier, planning, tarif_horaire, indemnites_repas, date_debut, statut, signe_parent, signe_am)
|
||||
VALUES (
|
||||
'cccccccc-cccc-cccc-cccc-cccccccccccc',
|
||||
'dddddddd-dddd-dddd-dddd-dddddddddddd',
|
||||
'{"lun_ven":{"17h-19h":true}}'::jsonb,
|
||||
12.50,
|
||||
3.50,
|
||||
'2025-09-01',
|
||||
'brouillon',
|
||||
false,
|
||||
false
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Avenant de contrat
|
||||
-- ------------------------------------------------------------
|
||||
INSERT INTO avenants_contrats (id, id_contrat, modifications, initie_par, statut)
|
||||
VALUES (
|
||||
'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee',
|
||||
'cccccccc-cccc-cccc-cccc-cccccccccccc',
|
||||
'{"changement_horaire":{"vendredi":{"17h-20h":true}}}'::jsonb,
|
||||
'33333333-3333-3333-3333-333333333333',
|
||||
'propose'
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Événement (absence enfant)
|
||||
-- ------------------------------------------------------------
|
||||
INSERT INTO evenements (id, type, id_enfant, id_am, id_parent, cree_par, date_debut, date_fin, commentaires, statut, urgence)
|
||||
VALUES (
|
||||
'e0000000-0000-0000-0000-000000000001',
|
||||
'absence_enfant',
|
||||
'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa',
|
||||
'66666666-6666-6666-6666-666666666666',
|
||||
'33333333-3333-3333-3333-333333333333',
|
||||
'33333333-3333-3333-3333-333333333333',
|
||||
'2025-09-12',
|
||||
'2025-09-12',
|
||||
'Enfant malade (rhume).',
|
||||
'propose',
|
||||
false
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Upload (justificatif lié au dossier)
|
||||
-- ------------------------------------------------------------
|
||||
INSERT INTO uploads (id, id_utilisateur, id_dossier_lie, fichier_url, type_fichier)
|
||||
VALUES (
|
||||
'u0000000-0000-0000-0000-000000000001',
|
||||
'33333333-3333-3333-3333-333333333333',
|
||||
'dddddddd-dddd-dddd-dddd-dddddddddddd',
|
||||
'/uploads/justificatifs/dossier_dddddddd_attestation.pdf',
|
||||
'application/pdf'
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Notification (pour le parent1)
|
||||
-- ------------------------------------------------------------
|
||||
INSERT INTO notifications (id, id_utilisateur, type, contenu, lu)
|
||||
VALUES (
|
||||
'n0000000-0000-0000-0000-000000000001',
|
||||
'33333333-3333-3333-3333-333333333333',
|
||||
'nouveau_message',
|
||||
'Vous avez un nouveau message sur le dossier #dddd…',
|
||||
false
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Validation (compte de l’AM validé par le gestionnaire)
|
||||
-- ------------------------------------------------------------
|
||||
INSERT INTO validations (id, id_utilisateur, statut, commentaire, cree_le)
|
||||
VALUES (
|
||||
'v0000000-0000-0000-0000-000000000001',
|
||||
'66666666-6666-6666-6666-666666666666',
|
||||
'accepte',
|
||||
'Dossier AM vérifié par gestionnaire.',
|
||||
NOW()
|
||||
)
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
COMMIT;
|
||||
205
tests/sql/verify.sql
Normal file
205
tests/sql/verify.sql
Normal file
@ -0,0 +1,205 @@
|
||||
|
||||
-- ============================================================
|
||||
-- verify.sql — Jeux de requêtes de vérification (Sprint 1)
|
||||
-- Objectifs :
|
||||
-- 1) Vérifier l'intégrité fonctionnelle (joins, données seedées)
|
||||
-- 2) Détecter rapidement des problèmes d'index/perf (EXPLAIN)
|
||||
-- 3) Servir de référence pour le back/front (requêtes typiques)
|
||||
--
|
||||
-- Usage (Docker) :
|
||||
-- docker compose exec -T postgres \
|
||||
-- psql -U ${POSTGRES_USER} -d ${POSTGRES_DB} \
|
||||
-- -f /docker-entrypoint-initdb.d/tests/verify.sql
|
||||
--
|
||||
-- Pré-requis :
|
||||
-- - 01_init.sql
|
||||
-- - 02_indexes.sql
|
||||
-- - 03_checks.sql
|
||||
-- - 04_fk_policies.sql
|
||||
-- - 05_triggers.sql
|
||||
-- - 02_seed.sql (pour résultats non vides)
|
||||
-- ============================================================
|
||||
|
||||
\echo '=== 0) Version & horodatage ===================================='
|
||||
SELECT version();
|
||||
SELECT NOW() AS executed_at;
|
||||
|
||||
\echo '=== 1) Comptes & répartition par rôle =========================='
|
||||
SELECT role, COUNT(*) AS nb
|
||||
FROM utilisateurs
|
||||
GROUP BY role
|
||||
ORDER BY nb DESC;
|
||||
|
||||
\echo '=== 2) Utilisateurs en attente / acceptés / rejetés ============'
|
||||
SELECT statut, COUNT(*) AS nb
|
||||
FROM utilisateurs
|
||||
GROUP BY statut
|
||||
ORDER BY nb DESC;
|
||||
|
||||
\echo '=== 3) Parents avec co-parents (NULL si pas de co-parent) ======'
|
||||
SELECT p.id_utilisateur AS parent_id,
|
||||
u.courriel AS parent_email,
|
||||
p.id_co_parent,
|
||||
uc.courriel AS co_parent_email
|
||||
FROM parents p
|
||||
JOIN utilisateurs u ON u.id = p.id_utilisateur
|
||||
LEFT JOIN utilisateurs uc ON uc.id = p.id_co_parent
|
||||
ORDER BY parent_email;
|
||||
|
||||
\echo '=== 4) Enfants (statut, dates cohérentes) ======================'
|
||||
SELECT id, prenom, nom, statut, date_naissance, date_prevue_naissance
|
||||
FROM enfants
|
||||
ORDER BY nom, prenom;
|
||||
|
||||
\echo '=== 5) Liaison N:N parents_enfants ============================='
|
||||
SELECT ep.id_parent, up.courriel AS parent_email, ep.id_enfant, e.prenom AS enfant
|
||||
FROM enfants_parents ep
|
||||
JOIN utilisateurs up ON up.id = ep.id_parent
|
||||
JOIN enfants e ON e.id = ep.id_enfant
|
||||
ORDER BY parent_email, enfant;
|
||||
|
||||
\echo '=== 6) Dossiers (parent, enfant, statut) ======================='
|
||||
SELECT d.id, up.courriel AS parent_email, e.prenom AS enfant, d.statut, d.budget
|
||||
FROM dossiers d
|
||||
JOIN utilisateurs up ON up.id = d.id_parent
|
||||
JOIN enfants e ON e.id = d.id_enfant
|
||||
ORDER BY d.cree_le DESC;
|
||||
|
||||
\echo '=== 7) Messages par dossier (ordre chronologique) =============='
|
||||
SELECT m.id, m.id_dossier, m.id_expediteur, ue.courriel AS expediteur_email, m.contenu, m.cree_le
|
||||
FROM messages m
|
||||
LEFT JOIN utilisateurs ue ON ue.id = m.id_expediteur
|
||||
ORDER BY m.id_dossier, m.cree_le;
|
||||
|
||||
\echo '=== 8) Contrats 1:1 avec dossier + avenants ===================='
|
||||
SELECT c.id AS contrat_id, c.id_dossier, c.statut,
|
||||
COUNT(a.id) AS nb_avenants
|
||||
FROM contrats c
|
||||
LEFT JOIN avenants_contrats a ON a.id_contrat = c.id
|
||||
GROUP BY c.id, c.id_dossier, c.statut
|
||||
ORDER BY c.cree_le DESC;
|
||||
|
||||
\echo '=== 9) Evénements par enfant (30 derniers jours) =============='
|
||||
SELECT ev.id, ev.type, ev.id_enfant, e.prenom AS enfant, ev.date_debut, ev.date_fin, ev.statut
|
||||
FROM evenements ev
|
||||
JOIN enfants e ON e.id = ev.id_enfant
|
||||
WHERE ev.date_debut >= (NOW()::date - INTERVAL '30 days')
|
||||
ORDER BY ev.date_debut DESC;
|
||||
|
||||
\echo '=== 10) Uploads & notifications récentes ======================='
|
||||
SELECT u.courriel, up.fichier_url, up.type_fichier, up.cree_le
|
||||
FROM uploads up
|
||||
LEFT JOIN utilisateurs u ON u.id = up.id_utilisateur
|
||||
ORDER BY up.cree_le DESC;
|
||||
|
||||
SELECT u.courriel, n.type, n.contenu, n.lu, n.cree_le
|
||||
FROM notifications n
|
||||
LEFT JOIN utilisateurs u ON u.id = n.id_utilisateur
|
||||
ORDER BY n.cree_le DESC;
|
||||
|
||||
\echo '=== 11) Validations (qui a validé quoi) ========================'
|
||||
SELECT v.id, uu.courriel AS utilisateur_valide,
|
||||
uv.courriel AS valide_par, v.statut, v.commentaire, v.cree_le
|
||||
FROM validations v
|
||||
LEFT JOIN utilisateurs uu ON uu.id = v.id_utilisateur
|
||||
LEFT JOIN utilisateurs uv ON uv.id = v.valide_par
|
||||
ORDER BY v.cree_le DESC;
|
||||
|
||||
-- ============================================================
|
||||
-- Vérifications d'intégrité (requêtes de contrôle)
|
||||
-- ============================================================
|
||||
\echo '=== 12) Orphelins potentiels (doivent renvoyer 0 ligne) ======='
|
||||
|
||||
-- Messages orphelins (dossier manquant)
|
||||
SELECT m.*
|
||||
FROM messages m
|
||||
LEFT JOIN dossiers d ON d.id = m.id_dossier
|
||||
WHERE d.id IS NULL;
|
||||
|
||||
-- Liaisons enfants_parents orphelines
|
||||
SELECT ep.*
|
||||
FROM enfants_parents ep
|
||||
LEFT JOIN parents p ON p.id_utilisateur = ep.id_parent
|
||||
LEFT JOIN enfants e ON e.id = ep.id_enfant
|
||||
WHERE p.id_utilisateur IS NULL OR e.id IS NULL;
|
||||
|
||||
-- Contrats sans dossier
|
||||
SELECT c.*
|
||||
FROM contrats c
|
||||
LEFT JOIN dossiers d ON d.id = c.id_dossier
|
||||
WHERE d.id IS NULL;
|
||||
|
||||
-- Avenants sans contrat
|
||||
SELECT a.*
|
||||
FROM avenants_contrats a
|
||||
LEFT JOIN contrats c ON c.id = a.id_contrat
|
||||
WHERE c.id IS NULL;
|
||||
|
||||
-- Evénements sans enfant
|
||||
SELECT ev.*
|
||||
FROM evenements ev
|
||||
LEFT JOIN enfants e ON e.id = ev.id_enfant
|
||||
WHERE e.id IS NULL;
|
||||
|
||||
\echo '=== 13) Performance : EXPLAIN sur requêtes clés ==============='
|
||||
|
||||
-- Messages par dossier (doit utiliser idx_messages_id_dossier_cree_le)
|
||||
EXPLAIN ANALYZE
|
||||
SELECT m.*
|
||||
FROM messages m
|
||||
WHERE m.id_dossier = 'dddddddd-dddd-dddd-dddd-dddddddddddd'
|
||||
ORDER BY m.cree_le DESC
|
||||
LIMIT 20;
|
||||
|
||||
-- Evénements par enfant et période (idx_evenements_id_enfant_date_debut)
|
||||
EXPLAIN ANALYZE
|
||||
SELECT ev.*
|
||||
FROM evenements ev
|
||||
WHERE ev.id_enfant = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||
AND ev.date_debut >= '2025-01-01';
|
||||
|
||||
-- Notifications non lues (idx_notifications_user_lu_cree_le)
|
||||
EXPLAIN ANALYZE
|
||||
SELECT n.*
|
||||
FROM notifications n
|
||||
WHERE n.id_utilisateur = '33333333-3333-3333-3333-333333333333'
|
||||
AND n.lu = false
|
||||
ORDER BY n.cree_le DESC
|
||||
LIMIT 20;
|
||||
|
||||
-- Dossiers par parent/enfant (idx_dossiers_id_parent/id_enfant/statut)
|
||||
EXPLAIN ANALYZE
|
||||
SELECT d.*
|
||||
FROM dossiers d
|
||||
WHERE d.id_parent = '33333333-3333-3333-3333-333333333333'
|
||||
AND d.id_enfant = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||
ORDER BY d.cree_le DESC;
|
||||
|
||||
\echo '=== 14) JSONB : exemples de filtrage ==========================='
|
||||
-- Recherche de dossiers où planning_souhaite contient midi=true un jour ouvré
|
||||
-- (Index GIN recommandé si usage intensif : cf. 02_indexes.sql)
|
||||
SELECT d.id, d.planning_souhaite
|
||||
FROM dossiers d
|
||||
WHERE d.planning_souhaite @> '{"lun_ven":{"midi":true}}';
|
||||
|
||||
-- Contrats : présence d’un créneau donné
|
||||
SELECT c.id, c.planning
|
||||
FROM contrats c
|
||||
WHERE c.planning @> '{"lun_ven":{"17h-19h":true}}';
|
||||
|
||||
\echo '=== 15) Sanity check final ===================================='
|
||||
-- Quelques totaux utiles
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM utilisateurs) AS nb_utilisateurs,
|
||||
(SELECT COUNT(*) FROM parents) AS nb_parents,
|
||||
(SELECT COUNT(*) FROM assistantes_maternelles) AS nb_am,
|
||||
(SELECT COUNT(*) FROM enfants) AS nb_enfants,
|
||||
(SELECT COUNT(*) FROM enfants_parents) AS nb_liens_parent_enfant,
|
||||
(SELECT COUNT(*) FROM dossiers) AS nb_dossiers,
|
||||
(SELECT COUNT(*) FROM messages) AS nb_messages,
|
||||
(SELECT COUNT(*) FROM contrats) AS nb_contrats,
|
||||
(SELECT COUNT(*) FROM avenants_contrats) AS nb_avenants,
|
||||
(SELECT COUNT(*) FROM evenements) AS nb_evenements,
|
||||
(SELECT COUNT(*) FROM uploads) AS nb_uploads,
|
||||
(SELECT COUNT(*) FROM notifications) AS nb_notifications,
|
||||
(SELECT COUNT(*) FROM validations) AS nb_validations;
|
||||
Loading…
x
Reference in New Issue
Block a user