feat: Intégration de la base de données PostgreSQL depuis YNOV
- Structure complète: utilisateurs, parents, assmat, enfants, contrats - Migrations SQL avec enums et contraintes - Seed: 1 super_admin (admin@ptits-pas.fr) - Mot de passe: 4dm1n1strateur (hash bcrypt)
This commit is contained in:
parent
9cb4162165
commit
bbf73458cb
1
database/.gitignore
vendored
Normal file
1
database/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
.env
|
||||
243
database/BDD.sql
Normal file
243
database/BDD.sql
Normal file
@ -0,0 +1,243 @@
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
-- ==========================================================
|
||||
-- ENUMS
|
||||
-- ==========================================================
|
||||
DO $$ BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'role_type') THEN
|
||||
CREATE TYPE role_type AS ENUM ('parent', 'gestionnaire', 'super_admin', 'administrateur', 'assistante_maternelle');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'genre_type') THEN
|
||||
CREATE TYPE genre_type AS ENUM ('H', 'F');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_utilisateur_type') THEN
|
||||
CREATE TYPE statut_utilisateur_type AS ENUM ('en_attente','actif','suspendu');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_enfant_type') THEN
|
||||
CREATE TYPE statut_enfant_type AS ENUM ('a_naitre','actif','scolarise');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_dossier_type') THEN
|
||||
CREATE TYPE statut_dossier_type AS ENUM ('envoye','accepte','refuse');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_contrat_type') THEN
|
||||
CREATE TYPE statut_contrat_type AS ENUM ('brouillon','en_attente_signature','valide','resilie');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_avenant_type') THEN
|
||||
CREATE TYPE statut_avenant_type AS ENUM ('propose','accepte','refuse');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'type_evenement_type') THEN
|
||||
CREATE TYPE type_evenement_type AS ENUM ('absence_enfant','conge_am','conge_parent','arret_maladie_am','evenement_rpe');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_evenement_type') THEN
|
||||
CREATE TYPE statut_evenement_type AS ENUM ('propose','valide','refuse');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_validation_type') THEN
|
||||
CREATE TYPE statut_validation_type AS ENUM ('en_attente','valide','refuse');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'situation_familiale_type') THEN
|
||||
CREATE TYPE situation_familiale_type AS ENUM ('celibataire','marie','concubinage','pacse','separe','divorce','veuf','parent_isole');
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : utilisateurs
|
||||
-- ==========================================================
|
||||
CREATE TABLE utilisateurs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'),
|
||||
password TEXT NOT NULL,
|
||||
prenom VARCHAR(100),
|
||||
nom VARCHAR(100),
|
||||
genre genre_type,
|
||||
role role_type NOT NULL,
|
||||
statut statut_utilisateur_type DEFAULT 'en_attente',
|
||||
mobile VARCHAR(20),
|
||||
telephone_fixe VARCHAR(20),
|
||||
adresse TEXT,
|
||||
date_naissance DATE,
|
||||
photo_url TEXT,
|
||||
consentement_photo BOOLEAN DEFAULT false,
|
||||
date_consentement_photo TIMESTAMPTZ,
|
||||
changement_mdp_obligatoire BOOLEAN DEFAULT false,
|
||||
cree_le TIMESTAMPTZ DEFAULT now(),
|
||||
modifie_le TIMESTAMPTZ DEFAULT now(),
|
||||
ville VARCHAR(150),
|
||||
code_postal VARCHAR(10),
|
||||
profession VARCHAR(150),
|
||||
situation_familiale situation_familiale_type
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : assistantes_maternelles
|
||||
-- ==========================================================
|
||||
CREATE TABLE assistantes_maternelles (
|
||||
id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE,
|
||||
numero_agrement VARCHAR(50),
|
||||
date_agrement DATE,
|
||||
nir_chiffre CHAR(15),
|
||||
annee_experience SMALLINT,
|
||||
specialite VARCHAR (100),
|
||||
nb_max_enfants INT,
|
||||
place_disponible INT,
|
||||
biographie TEXT,
|
||||
disponible BOOLEAN DEFAULT true
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : parents
|
||||
-- ==========================================================
|
||||
CREATE TABLE parents (
|
||||
id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE,
|
||||
id_co_parent UUID REFERENCES utilisateurs(id)
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : enfants
|
||||
-- ==========================================================
|
||||
CREATE TABLE enfants (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
statut statut_enfant_type,
|
||||
prenom VARCHAR(100),
|
||||
nom VARCHAR(100),
|
||||
genre genre_type,
|
||||
date_naissance DATE,
|
||||
date_prevue_naissance DATE,
|
||||
photo_url TEXT,
|
||||
consentement_photo BOOLEAN DEFAULT false,
|
||||
date_consentement_photo TIMESTAMPTZ,
|
||||
est_multiple BOOLEAN DEFAULT false
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : enfants_parents
|
||||
-- ==========================================================
|
||||
CREATE TABLE enfants_parents (
|
||||
id_parent UUID REFERENCES parents(id_utilisateur) ON DELETE CASCADE,
|
||||
id_enfant UUID REFERENCES enfants(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (id_parent, id_enfant)
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : dossiers
|
||||
-- ==========================================================
|
||||
CREATE TABLE dossiers (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_parent UUID REFERENCES parents(id_utilisateur) ON DELETE CASCADE,
|
||||
id_enfant UUID REFERENCES enfants(id) ON DELETE CASCADE,
|
||||
presentation TEXT,
|
||||
type_contrat VARCHAR(50),
|
||||
repas BOOLEAN DEFAULT false,
|
||||
budget NUMERIC(10,2),
|
||||
planning_souhaite JSONB,
|
||||
statut statut_dossier_type DEFAULT 'envoye',
|
||||
cree_le TIMESTAMPTZ DEFAULT now(),
|
||||
modifie_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : messages
|
||||
-- ==========================================================
|
||||
CREATE TABLE messages (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_dossier UUID REFERENCES dossiers(id) ON DELETE CASCADE,
|
||||
id_expediteur UUID REFERENCES utilisateurs(id) ON DELETE CASCADE,
|
||||
contenu TEXT,
|
||||
re_redige_par_ia BOOLEAN DEFAULT false,
|
||||
cree_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : contrats
|
||||
-- ==========================================================
|
||||
CREATE TABLE contrats (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_dossier UUID UNIQUE REFERENCES dossiers(id) ON DELETE CASCADE,
|
||||
planning JSONB,
|
||||
tarif_horaire NUMERIC(6,2),
|
||||
indemnites_repas NUMERIC(6,2),
|
||||
date_debut DATE,
|
||||
statut statut_contrat_type DEFAULT 'brouillon',
|
||||
signe_parent BOOLEAN DEFAULT false,
|
||||
signe_am BOOLEAN DEFAULT false,
|
||||
finalise_le TIMESTAMPTZ,
|
||||
cree_le TIMESTAMPTZ DEFAULT now(),
|
||||
modifie_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : avenants_contrats
|
||||
-- ==========================================================
|
||||
CREATE TABLE avenants_contrats (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_contrat UUID REFERENCES contrats(id) ON DELETE CASCADE,
|
||||
modifications JSONB,
|
||||
initie_par UUID REFERENCES utilisateurs(id),
|
||||
statut statut_avenant_type DEFAULT 'propose',
|
||||
cree_le TIMESTAMPTZ DEFAULT now(),
|
||||
modifie_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : evenements
|
||||
-- ==========================================================
|
||||
CREATE TABLE evenements (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
type type_evenement_type,
|
||||
id_enfant UUID REFERENCES enfants(id) ON DELETE CASCADE,
|
||||
id_am UUID REFERENCES utilisateurs(id),
|
||||
id_parent UUID REFERENCES parents(id_utilisateur),
|
||||
cree_par UUID REFERENCES utilisateurs(id),
|
||||
date_debut TIMESTAMPTZ,
|
||||
date_fin TIMESTAMPTZ,
|
||||
commentaires TEXT,
|
||||
statut statut_evenement_type DEFAULT 'propose',
|
||||
delai_grace TIMESTAMPTZ,
|
||||
urgent BOOLEAN DEFAULT false,
|
||||
cree_le TIMESTAMPTZ DEFAULT now(),
|
||||
modifie_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : signalements_bugs
|
||||
-- ==========================================================
|
||||
CREATE TABLE signalements_bugs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_utilisateur UUID REFERENCES utilisateurs(id),
|
||||
description TEXT,
|
||||
cree_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : uploads
|
||||
-- ==========================================================
|
||||
CREATE TABLE uploads (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_utilisateur UUID REFERENCES utilisateurs(id) ON DELETE SET NULL,
|
||||
fichier_url TEXT NOT NULL,
|
||||
type VARCHAR(50),
|
||||
cree_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : notifications
|
||||
-- ==========================================================
|
||||
CREATE TABLE notifications (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_utilisateur UUID REFERENCES utilisateurs(id) ON DELETE CASCADE,
|
||||
contenu TEXT,
|
||||
lu BOOLEAN DEFAULT false,
|
||||
cree_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : validations
|
||||
-- ==========================================================
|
||||
CREATE TABLE validations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_utilisateur UUID REFERENCES utilisateurs(id),
|
||||
type VARCHAR(50),
|
||||
statut statut_validation_type DEFAULT 'en_attente',
|
||||
cree_le TIMESTAMPTZ DEFAULT now(),
|
||||
modifie_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
80
database/README.md
Normal file
80
database/README.md
Normal file
@ -0,0 +1,80 @@
|
||||
|
||||
|
||||
# PtitsPas Ynov - Base de Données
|
||||
|
||||
Ce projet contient la **base de données** pour l'application PtitsPas, avec scripts de migration, import de données, documentation et configuration Docker.
|
||||
|
||||
---
|
||||
|
||||
## Prérequis
|
||||
|
||||
- Docker Desktop (https://www.docker.com/products/docker-desktop/)
|
||||
- Docker Compose
|
||||
|
||||
---
|
||||
|
||||
## Structure du projet
|
||||
|
||||
- `migrations/` : scripts SQL pour la création et l'import de la base
|
||||
- `bdd/data_test/` : fichiers CSV pour l'import de données de test
|
||||
- `docs/` : documentation métier et technique
|
||||
- `seed/` : scripts de seed
|
||||
- `tests/` : tests SQL
|
||||
- `docker-compose.dev.yml` : configuration Docker pour le développement
|
||||
|
||||
---
|
||||
|
||||
## Lancer la base de données en local
|
||||
|
||||
Dans le terminal, depuis le dossier du projet :
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.dev.yml up -d
|
||||
```
|
||||
|
||||
Pour arrêter et supprimer les volumes :
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.dev.yml down -v
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Importation automatique des données de test
|
||||
|
||||
Les données de test (CSV) sont automatiquement importées dans la base au démarrage du conteneur Docker grâce aux scripts présents dans le dossier `migrations/`.
|
||||
|
||||
Il n'est pas nécessaire de lancer manuellement le script d'import.
|
||||
|
||||
---
|
||||
|
||||
## Accéder à pgAdmin4
|
||||
|
||||
### Via Docker (local)
|
||||
|
||||
Ouvre ton navigateur sur :
|
||||
|
||||
```
|
||||
http://localhost:8081
|
||||
```
|
||||
|
||||
**Email** : `admin@ptits-pas.fr`
|
||||
**Mot de passe** : `admin123`
|
||||
|
||||
**Mot de passse pour se connecter au server local** : `admin123`
|
||||
|
||||
|
||||
## Conseils et bonnes pratiques
|
||||
|
||||
- Vérifie la cohérence des identifiants dans les CSV avant import
|
||||
- Pour modifier la structure, utilise les scripts de migration dans `migrations/`
|
||||
- Pour ajouter des scripts d'automatisation, crée un dossier `scripts/`
|
||||
- Documente les étapes spécifiques dans le README ou dans `docs/`
|
||||
|
||||
---
|
||||
|
||||
## Contact
|
||||
|
||||
Pour toute question ou contribution, consulte la documentation ou contacte l'équipe PtitsPas.
|
||||
|
||||
3
database/bdd/data_test/assistantes_maternelles.csv
Normal file
3
database/bdd/data_test/assistantes_maternelles.csv
Normal file
@ -0,0 +1,3 @@
|
||||
"id_utilisateur","numero_agrement","nir_chiffre","nb_max_enfants","biographie","disponible","ville_residence","date_agrement","annee_experience","specialite","place_disponible"
|
||||
"a1f3d5c7-8b9a-4e2f-9c1d-3b2a4f6e7d8c","AGR5678",,3,"Agrément 3 enfants - Spécialité 1-3 ans - 1 place disponible",True,,"2010-09-01",14,"1-3 ans",1
|
||||
"d9c2e3f4-5b6a-4c3d-9f1a-2e7b3c5d8a1f","AGR1234",,4,"Agrément 4 enfants - Spécialité bébés 0-18 mois - 2 places disponibles",True,,"2005-06-15",18,"Bébés 0-18 mois",2
|
||||
|
2
database/bdd/data_test/contrats.csv
Normal file
2
database/bdd/data_test/contrats.csv
Normal file
@ -0,0 +1,2 @@
|
||||
"id","id_dossier","planning","tarif_horaire","indemnites_repas","date_debut","statut","signe_parent","signe_am","finalise_le","cree_le","modifie_le"
|
||||
"f09c6ffa-4627-4aa8-b20b-829c2c828f0d","bb9c30a0-60b4-4832-9947-8a7d2366673d","{""jours"": [""Lundi"", ""Mardi"", ""Mercredi""]}","10.50","4.50","2024-09-01","brouillon",True,False,,"2025-09-09 11:05:47.933418+00","2025-09-09 11:05:47.933418+00"
|
||||
|
2
database/bdd/data_test/dossiers.csv
Normal file
2
database/bdd/data_test/dossiers.csv
Normal file
@ -0,0 +1,2 @@
|
||||
"id","id_parent","id_enfant","presentation","type_contrat","repas","budget","planning_souhaite","statut","cree_le","modifie_le"
|
||||
"bb9c30a0-60b4-4832-9947-8a7d2366673d","f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b","5e8574b7-63e6-4d48-9af3-8d3bf7a6a6cf","Contrat test pour garde à temps plein","temps_plein",False,"1200.00",,"envoye","2025-09-09 10:58:28.718654+00","2025-09-09 10:58:28.718654+00"
|
||||
|
10
database/bdd/data_test/enfants.csv
Normal file
10
database/bdd/data_test/enfants.csv
Normal file
@ -0,0 +1,10 @@
|
||||
"id","statut","prenom","nom","genre","date_naissance","date_prevue_naissance","photo_url","consentement_photo","date_consentement_photo","est_multiple"
|
||||
"5e8574b7-63e6-4d48-9af3-8d3bf7a6a6cf","actif","Emma","Dupont","F","2020-06-01",,,False,,False
|
||||
"a5c3268e-07eb-41a4-9f6c-2f9f16f37c3d","actif",,,,"2020-01-01","2025-01-01",,False,,False
|
||||
"e1a2b3c4-d5e6-4f7a-8b9c-1d2e3f4a5b6c","actif","Emma","Martin",,"2023-02-15",,,False,,False
|
||||
"e2b3c4d5-e6f7-4a8b-9c1d-2e3f4a5b6c7d","actif","Noah","Martin",,"2023-02-15",,,False,,False
|
||||
"e3c4d5e6-f7a8-4b9c-1d2e-3f4a5b6c7d8e","actif","Léa","Martin",,"2023-02-15",,,False,,False
|
||||
"e4d5e6f7-a8b9-4c1d-2e3f-4a5b6c7d8e9f","actif","Chloé","Rousseau",,"2022-04-20",,,False,,False
|
||||
"e5e6f7a8-b9c1-4d2e-3f4a-5b6c7d8e9f1a","actif","Hugo","Rousseau",,"2024-03-10",,,False,,False
|
||||
"e6f7a8b9-c1d2-4e3f-5a6b-7c8d9e0f1a2b","actif","Maxime","Lecomte",,"2023-04-15",,,False,,False
|
||||
"edd19cd1-bb67-4f14-8a37-c66b75c94537","scolarise","Lucas","Durand","H","2018-09-15",,,False,,False
|
||||
|
9
database/bdd/data_test/enfants_parents.csv
Normal file
9
database/bdd/data_test/enfants_parents.csv
Normal file
@ -0,0 +1,9 @@
|
||||
"id_parent","id_enfant"
|
||||
"b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e","e4d5e6f7-a8b9-4c1d-2e3f-4a5b6c7d8e9f"
|
||||
"b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e","e5e6f7a8-b9c1-4d2e-3f4a-5b6c7d8e9f1a"
|
||||
"c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f","e1a2b3c4-d5e6-4f7a-8b9c-1d2e3f4a5b6c"
|
||||
"c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f","e2b3c4d5-e6f7-4a8b-9c1d-2e3f4a5b6c7d"
|
||||
"c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f","e3c4d5e6-f7a8-4b9c-1d2e-3f4a5b6c7d8e"
|
||||
"d3e5f7a9-1c2b-4d6e-8f3a-2b4c6d8e9f1a","e6f7a8b9-c1d2-4e3f-5a6b-7c8d9e0f1a2b"
|
||||
"f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b","e4d5e6f7-a8b9-4c1d-2e3f-4a5b6c7d8e9f"
|
||||
"f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b","e5e6f7a8-b9c1-4d2e-3f4a-5b6c7d8e9f1a"
|
||||
|
2
database/bdd/data_test/evenements.csv
Normal file
2
database/bdd/data_test/evenements.csv
Normal file
@ -0,0 +1,2 @@
|
||||
"id","type","id_enfant","id_am","id_parent","cree_par","date_debut","date_fin","commentaires","statut","delai_grace","urgent","cree_le","modifie_le"
|
||||
"9f09425c-a374-4c9f-b3b1-be7258b60cd3","absence_enfant","5e8574b7-63e6-4d48-9af3-8d3bf7a6a6cf","62de8e71-8082-4383-a3a2-4277bdd07516",,"bbcae75c-0e60-4b84-b281-079dba23b44e","2024-11-10 00:00:00+00","2024-11-11 00:00:00+00","Enfant malade","propose",,True,"2025-09-02 12:55:43.781467+00","2025-09-05 14:39:38.390126+00"
|
||||
|
2
database/bdd/data_test/notifications.csv
Normal file
2
database/bdd/data_test/notifications.csv
Normal file
@ -0,0 +1,2 @@
|
||||
"id","id_utilisateur","contenu","lu","cree_le"
|
||||
"71e90c37-f2cb-4aff-ad34-1c728f620afb","bbcae75c-0e60-4b84-b281-079dba23b44e","Votre dossier a été accepté",False,"2025-09-02 12:57:42.845264+00"
|
||||
|
5
database/bdd/data_test/parents.csv
Normal file
5
database/bdd/data_test/parents.csv
Normal file
@ -0,0 +1,5 @@
|
||||
"id_utilisateur","id_co_parent"
|
||||
"b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e","f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b"
|
||||
"c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f",
|
||||
"d3e5f7a9-1c2b-4d6e-8f3a-2b4c6d8e9f1a",
|
||||
"f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b","b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e"
|
||||
|
2
database/bdd/data_test/uploads.csv
Normal file
2
database/bdd/data_test/uploads.csv
Normal file
@ -0,0 +1,2 @@
|
||||
"id","id_utilisateur","fichier_url","type","cree_le"
|
||||
"db1eb36d-5f30-4027-b529-1d972b79180a","bbcae75c-0e60-4b84-b281-079dba23b44e","https://placeholder.local/file","image","2025-09-02 12:57:35.140078+00"
|
||||
|
12
database/bdd/data_test/utilisateurs.csv
Normal file
12
database/bdd/data_test/utilisateurs.csv
Normal file
@ -0,0 +1,12 @@
|
||||
"id","email","password","prenom","nom","genre","role","statut","telephone","adresse","photo_url","consentement_photo","date_consentement_photo","changement_mdp_obligatoire","cree_le","modifie_le","ville","code_postal","mobile","telephone_fixe","profession","situation_familiale","date_naissance"
|
||||
"62de8e71-8082-4383-a3a2-4277bdd07516","am1@example.com","hash125","Claire","Martin","F","assistante_maternelle","actif","0609091011","5 place Bellecour",,False,,False,"2025-09-02 12:30:48.724463+00","2025-09-02 12:30:48.724463+00","Lyon","69002",,,,,
|
||||
"76c40571-5da6-4d27-8e07-303185875b36","gest1@example.com","hash126","Paul","Lemoine","H","gestionnaire","actif","0612131415","10 rue Victor Hugo",,False,,False,"2025-09-02 12:30:48.724463+00","2025-09-02 12:30:48.724463+00","Paris","75002",,,,,
|
||||
"9bc30373-91a4-45ed-9c05-ffe1651ad906","coparent@example.com","hash124","Marc","Durand","H","parent","actif","0605060708","45 avenue de Lyon",,False,,False,"2025-09-02 12:30:48.724463+00","2025-09-02 12:30:48.724463+00","Lyon","69000",,,,,
|
||||
"a1f3d5c7-8b9a-4e2f-9c1d-3b2a4f6e7d8c","fatima.elmansouri@ptits-pas.fr","password","Fatima","El Mansouri",,"assistante_maternelle","actif",,"17 Boulevard Aristide Briand",,False,,False,"2025-09-04 11:49:22.636003+00","2025-09-04 11:49:22.636003+00","Bezons","95870","06 75 45 67 89","01 39 98 78 90","Assistante maternelle","marie","1975-11-12"
|
||||
"b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e","julien.rousseau@ptits-pas.fr","password","Julien","Rousseau",,"parent","actif",,"14 Rue Pasteur",,False,,False,"2025-09-04 11:49:22.636003+00","2025-09-04 11:49:22.636003+00","Bezons","95870","06 56 67 78 89","01 39 98 01 23","Commercial","divorce","1985-08-29"
|
||||
"bbcae75c-0e60-4b84-b281-079dba23b44e","parent1@example.com","hash123","Alice","Dupont","F","parent","actif","0601020304","12 rue de Paris",,False,,False,"2025-09-02 12:30:48.724463+00","2025-09-02 12:30:48.724463+00","Paris","75001",,,,,
|
||||
"c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f","claire.martin@ptits-pas.fr","password","Claire","Martin",,"parent","actif",,"5 Avenue du Général de Gaulle",,False,,False,"2025-09-04 11:49:22.636003+00","2025-09-04 11:49:22.636003+00","Bezons","95870","06 89 56 78 90","01 39 98 89 01","Infirmière","marie","1990-04-03"
|
||||
"d3e5f7a9-1c2b-4d6e-8f3a-2b4c6d8e9f1a","david.lecomte@ptits-pas.fr","password","David","Lecomte",,"parent","actif",,"31 Rue Émile Zola",,False,,False,"2025-09-04 11:49:22.636003+00","2025-09-04 11:49:22.636003+00","Bezons","95870","06 45 56 67 78","01 39 98 12 34","Développeur web","celibataire","1992-10-07"
|
||||
"d9c2e3f4-5b6a-4c3d-9f1a-2e7b3c5d8a1f","marie.dubois@ptits-pas.fr","password","Marie","Dubois",,"assistante_maternelle","actif",,"25 Rue de la République",,False,,False,"2025-09-04 11:49:22.636003+00","2025-09-04 12:44:51.654512+00","Bezons","95870","06 96 34 56 78","01 39 98 67 89","Assistante maternelle","marie","1980-06-08"
|
||||
"f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b","amelie.durand@ptits-pas.fr","password","Amélie","Durand",,"parent","actif",,"23 Rue Victor Hugo",,False,,False,"2025-09-04 11:49:22.636003+00","2025-09-04 11:49:22.636003+00","Bezons","95870","06 67 78 89 90","01 39 98 90 12","Comptable","divorce","1987-12-14"
|
||||
"f3b1a2d4-1c7e-4c2f-8b1a-9d3a8e2f5b6c","sophie.bernard@ptits-pas.fr","password","Sophie","Bernard",,"administrateur","actif",,"12 Avenue Gabriel Péri",,False,,False,"2025-09-04 11:49:22.636003+00","2025-09-04 11:49:22.636003+00","Bezons","95870","06 78 12 34 56","01 39 98 45 67","Responsable administrative","marie","1978-03-15"
|
||||
|
4
database/bdd/data_test/validations.csv
Normal file
4
database/bdd/data_test/validations.csv
Normal file
@ -0,0 +1,4 @@
|
||||
"id","id_utilisateur","type","statut","cree_le","modifie_le","valide_par","commentaire"
|
||||
"8ec99565-e8c2-469f-9641-01b99b8281eb","62de8e71-8082-4383-a3a2-4277bdd07516","identité","valide","2025-09-02 12:57:49.846424+00","2025-09-02 12:57:49.846424+00",,
|
||||
"be1c4779-341b-436d-b17e-8bc486d22ef8","62de8e71-8082-4383-a3a2-4277bdd07516",,"valide","2025-09-09 14:47:01.963573+00","2025-09-09 14:47:01.963573+00",,"Contrôle OK"
|
||||
"fcc45701-5708-4368-b467-b95ddb7e1580","d9c2e3f4-5b6a-4c3d-9f1a-2e7b3c5d8a1f",,"valide","2025-09-09 14:52:47.339858+00","2025-09-09 14:52:47.339858+00","76c40571-5da6-4d27-8e07-303185875b36","Contrôle OK"
|
||||
|
BIN
database/bdd/schemas/bdd_simple.png
Normal file
BIN
database/bdd/schemas/bdd_simple.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 262 KiB |
BIN
database/bdd/schemas/bdd_type.png
Normal file
BIN
database/bdd/schemas/bdd_type.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 340 KiB |
46
database/docker-compose.dev.yml
Normal file
46
database/docker-compose.dev.yml
Normal file
@ -0,0 +1,46 @@
|
||||
# Docker Compose pour développement local de la base de données uniquement
|
||||
# Usage: docker compose -f docker-compose.dev.yml up -d
|
||||
|
||||
services:
|
||||
# Base de données PostgreSQL
|
||||
postgres:
|
||||
image: postgres:17
|
||||
container_name: ptitspas-postgres-standalone
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: ${POSTGRES_USER:-admin}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-admin123}
|
||||
POSTGRES_DB: ${POSTGRES_DB:-ptitpas_db}
|
||||
ports:
|
||||
- "5433:5432"
|
||||
volumes:
|
||||
- ./migrations/01_init.sql:/docker-entrypoint-initdb.d/01_init.sql
|
||||
- ./migrations/07_import.sql:/docker-entrypoint-initdb.d/07_import.sql
|
||||
- ./bdd/data_test:/bdd/data_test
|
||||
- postgres_standalone_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- ptitspas_dev
|
||||
|
||||
# Interface d'administration DB
|
||||
pgadmin:
|
||||
image: dpage/pgadmin4:9.8
|
||||
container_name: ptitspas-pgadmin-standalone
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-admin@ptits-pas.fr}
|
||||
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin123}
|
||||
ports:
|
||||
- "8081:80"
|
||||
depends_on:
|
||||
- postgres
|
||||
networks:
|
||||
- ptitspas_dev
|
||||
volumes:
|
||||
- ./pgadmin/servers.json:/pgadmin4/servers.json
|
||||
|
||||
volumes:
|
||||
postgres_standalone_data:
|
||||
|
||||
networks:
|
||||
ptitspas_dev:
|
||||
driver: bridge
|
||||
42
database/docker-compose.yml
Normal file
42
database/docker-compose.yml
Normal file
@ -0,0 +1,42 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:17
|
||||
container_name: ynov-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: admin
|
||||
POSTGRES_PASSWORD: admin123
|
||||
POSTGRES_DB: ptitpas_db
|
||||
volumes:
|
||||
- ./migrations/01_init.sql:/docker-entrypoint-initdb.d/01_init.sql
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- ynov_network
|
||||
|
||||
pgadmin:
|
||||
image: dpage/pgadmin4
|
||||
container_name: ynov-pgadmin
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
PGADMIN_DEFAULT_EMAIL: admin@ptits-pas.fr
|
||||
PGADMIN_DEFAULT_PASSWORD: admin123
|
||||
depends_on:
|
||||
- db
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.ynov-pgadmin.rule=Host(\"ynov-pgadmin.ptits-pas.fr\")"
|
||||
- "traefik.http.routers.ynov-pgadmin.entrypoints=websecure"
|
||||
- "traefik.http.routers.ynov-pgadmin.tls.certresolver=leresolver"
|
||||
- "traefik.http.services.ynov-pgadmin.loadbalancer.server.port=80"
|
||||
networks:
|
||||
- ynov_network
|
||||
- proxy_network
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
|
||||
networks:
|
||||
ynov_network:
|
||||
driver: bridge
|
||||
proxy_network:
|
||||
external: true
|
||||
157
database/docs/ENUMS.md
Normal file
157
database/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
database/docs/FK_POLICIES.md
Normal file
142
database/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
|
||||
277
database/migrations/01_init.sql
Normal file
277
database/migrations/01_init.sql
Normal file
@ -0,0 +1,277 @@
|
||||
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
||||
|
||||
-- ==========================================================
|
||||
-- ENUMS
|
||||
-- ==========================================================
|
||||
DO $$ BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'role_type') THEN
|
||||
CREATE TYPE role_type AS ENUM ('parent', 'gestionnaire', 'super_admin', 'assistante_maternelle','administrateur');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'genre_type') THEN
|
||||
CREATE TYPE genre_type AS ENUM ('H', 'F', 'Autre');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_utilisateur_type') THEN
|
||||
CREATE TYPE statut_utilisateur_type AS ENUM ('en_attente','actif','suspendu');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_enfant_type') THEN
|
||||
CREATE TYPE statut_enfant_type AS ENUM ('a_naitre','actif','scolarise');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_dossier_type') THEN
|
||||
CREATE TYPE statut_dossier_type AS ENUM ('envoye','accepte','refuse');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_contrat_type') THEN
|
||||
CREATE TYPE statut_contrat_type AS ENUM ('brouillon','en_attente_signature','valide','resilie');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_avenant_type') THEN
|
||||
CREATE TYPE statut_avenant_type AS ENUM ('propose','accepte','refuse');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'type_evenement_type') THEN
|
||||
CREATE TYPE type_evenement_type AS ENUM ('absence_enfant','conge_am','conge_parent','arret_maladie_am','evenement_rpe');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_evenement_type') THEN
|
||||
CREATE TYPE statut_evenement_type AS ENUM ('propose','valide','refuse');
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_validation_type') THEN
|
||||
CREATE TYPE statut_validation_type AS ENUM ('en_attente','valide','refuse');
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : utilisateurs
|
||||
-- ==========================================================
|
||||
CREATE TABLE utilisateurs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
CHECK (email ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'),
|
||||
password TEXT NOT NULL,
|
||||
prenom VARCHAR(100),
|
||||
nom VARCHAR(100),
|
||||
genre genre_type,
|
||||
role role_type NOT NULL,
|
||||
statut statut_utilisateur_type DEFAULT 'en_attente',
|
||||
telephone VARCHAR(20),
|
||||
adresse TEXT,
|
||||
photo_url TEXT,
|
||||
consentement_photo BOOLEAN DEFAULT false,
|
||||
date_consentement_photo TIMESTAMPTZ,
|
||||
changement_mdp_obligatoire BOOLEAN DEFAULT false,
|
||||
cree_le TIMESTAMPTZ DEFAULT now(),
|
||||
modifie_le TIMESTAMPTZ DEFAULT now(),
|
||||
ville VARCHAR(150),
|
||||
code_postal VARCHAR(10),
|
||||
mobile VARCHAR(20),
|
||||
telephone_fixe VARCHAR(20),
|
||||
profession VARCHAR(150),
|
||||
situation_familiale VARCHAR(50),
|
||||
date_naissance DATE
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : assistantes_maternelles
|
||||
-- ==========================================================
|
||||
CREATE TABLE assistantes_maternelles (
|
||||
id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE,
|
||||
numero_agrement VARCHAR(50),
|
||||
nir_chiffre CHAR(15),
|
||||
nb_max_enfants INT,
|
||||
biographie TEXT,
|
||||
disponible BOOLEAN DEFAULT true,
|
||||
ville_residence VARCHAR(100),
|
||||
date_agrement DATE,
|
||||
annee_experience SMALLINT,
|
||||
specialite VARCHAR(100),
|
||||
place_disponible INT
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : parentschange les donnée de init
|
||||
-- ==========================================================
|
||||
CREATE TABLE parents (
|
||||
id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE,
|
||||
id_co_parent UUID REFERENCES utilisateurs(id)
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : enfants
|
||||
-- ==========================================================
|
||||
CREATE TABLE enfants (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
statut statut_enfant_type,
|
||||
prenom VARCHAR(100),
|
||||
nom VARCHAR(100),
|
||||
genre genre_type,
|
||||
date_naissance DATE,
|
||||
date_prevue_naissance DATE,
|
||||
photo_url TEXT,
|
||||
consentement_photo BOOLEAN DEFAULT false,
|
||||
date_consentement_photo TIMESTAMPTZ,
|
||||
est_multiple BOOLEAN DEFAULT false
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : enfants_parents
|
||||
-- ==========================================================
|
||||
CREATE TABLE enfants_parents (
|
||||
id_parent UUID REFERENCES parents(id_utilisateur) ON DELETE CASCADE,
|
||||
id_enfant UUID REFERENCES enfants(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (id_parent, id_enfant)
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : dossiers
|
||||
-- ==========================================================
|
||||
CREATE TABLE dossiers (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_parent UUID REFERENCES parents(id_utilisateur) ON DELETE CASCADE,
|
||||
id_enfant UUID REFERENCES enfants(id) ON DELETE CASCADE,
|
||||
presentation TEXT,
|
||||
type_contrat VARCHAR(50),
|
||||
repas BOOLEAN DEFAULT false,
|
||||
budget NUMERIC(10,2),
|
||||
planning_souhaite JSONB,
|
||||
statut statut_dossier_type DEFAULT 'envoye',
|
||||
cree_le TIMESTAMPTZ DEFAULT now(),
|
||||
modifie_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : messages
|
||||
-- ==========================================================
|
||||
CREATE TABLE messages (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_dossier UUID REFERENCES dossiers(id) ON DELETE CASCADE,
|
||||
id_expediteur UUID REFERENCES utilisateurs(id) ON DELETE CASCADE,
|
||||
contenu TEXT,
|
||||
re_redige_par_ia BOOLEAN DEFAULT false,
|
||||
cree_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : contrats
|
||||
-- ==========================================================
|
||||
CREATE TABLE contrats (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_dossier UUID UNIQUE REFERENCES dossiers(id) ON DELETE CASCADE,
|
||||
planning JSONB,
|
||||
tarif_horaire NUMERIC(6,2),
|
||||
indemnites_repas NUMERIC(6,2),
|
||||
date_debut DATE,
|
||||
statut statut_contrat_type DEFAULT 'brouillon',
|
||||
signe_parent BOOLEAN DEFAULT false,
|
||||
signe_am BOOLEAN DEFAULT false,
|
||||
finalise_le TIMESTAMPTZ,
|
||||
cree_le TIMESTAMPTZ DEFAULT now(),
|
||||
modifie_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : avenants_contrats
|
||||
-- ==========================================================
|
||||
CREATE TABLE avenants_contrats (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_contrat UUID REFERENCES contrats(id) ON DELETE CASCADE,
|
||||
modifications JSONB,
|
||||
initie_par UUID REFERENCES utilisateurs(id),
|
||||
statut statut_avenant_type DEFAULT 'propose',
|
||||
cree_le TIMESTAMPTZ DEFAULT now(),
|
||||
modifie_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : evenements
|
||||
-- ==========================================================
|
||||
CREATE TABLE evenements (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
type type_evenement_type,
|
||||
id_enfant UUID REFERENCES enfants(id) ON DELETE CASCADE,
|
||||
id_am UUID REFERENCES utilisateurs(id),
|
||||
id_parent UUID REFERENCES parents(id_utilisateur),
|
||||
cree_par UUID REFERENCES utilisateurs(id),
|
||||
date_debut TIMESTAMPTZ,
|
||||
date_fin TIMESTAMPTZ,
|
||||
commentaires TEXT,
|
||||
statut statut_evenement_type DEFAULT 'propose',
|
||||
delai_grace TIMESTAMPTZ,
|
||||
urgent BOOLEAN DEFAULT false,
|
||||
cree_le TIMESTAMPTZ DEFAULT now(),
|
||||
modifie_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : signalements_bugs
|
||||
-- ==========================================================
|
||||
CREATE TABLE signalements_bugs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_utilisateur UUID REFERENCES utilisateurs(id),
|
||||
description TEXT,
|
||||
cree_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : uploads
|
||||
-- ==========================================================
|
||||
CREATE TABLE uploads (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_utilisateur UUID REFERENCES utilisateurs(id) ON DELETE SET NULL,
|
||||
fichier_url TEXT NOT NULL,
|
||||
type VARCHAR(50),
|
||||
cree_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : notifications
|
||||
-- ==========================================================
|
||||
CREATE TABLE notifications (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_utilisateur UUID REFERENCES utilisateurs(id) ON DELETE CASCADE,
|
||||
contenu TEXT,
|
||||
lu BOOLEAN DEFAULT false,
|
||||
cree_le TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- ==========================================================
|
||||
-- Table : validations
|
||||
-- ==========================================================
|
||||
CREATE TABLE validations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
id_utilisateur UUID REFERENCES utilisateurs(id),
|
||||
type VARCHAR(50),
|
||||
statut statut_validation_type DEFAULT 'en_attente',
|
||||
cree_le TIMESTAMPTZ DEFAULT now(),
|
||||
modifie_le TIMESTAMPTZ DEFAULT now(),
|
||||
valide_par UUID REFERENCES utilisateurs(id),
|
||||
commentaire TEXT
|
||||
);
|
||||
|
||||
|
||||
-- ==========================================================
|
||||
-- Initialisation d'un administrateur par défaut
|
||||
-- ==========================================================
|
||||
|
||||
-- ==========================================================
|
||||
-- SEED: Super Administrateur par défaut
|
||||
-- ==========================================================
|
||||
-- Mot de passe: 4dm1n1strateur
|
||||
INSERT INTO utilisateurs (
|
||||
id,
|
||||
email,
|
||||
password,
|
||||
prenom,
|
||||
nom,
|
||||
role,
|
||||
statut,
|
||||
cree_le,
|
||||
modifie_le
|
||||
)
|
||||
VALUES (
|
||||
gen_random_uuid(),
|
||||
'admin@ptits-pas.fr',
|
||||
'$2b$12$Fo5ly1YlTj3O6lXf.IUgoeUqEebBGpmoM5zLbzZx.CueorSE7z2E2',
|
||||
'Admin',
|
||||
'Système',
|
||||
'super_admin',
|
||||
'actif',
|
||||
now(),
|
||||
now()
|
||||
)
|
||||
ON CONFLICT (email) DO NOTHING;
|
||||
157
database/migrations/02_indexes.sql
Normal file
157
database/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
database/migrations/03_checks.sql
Normal file
140
database/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
database/migrations/04_fk_policies.sql
Normal file
190
database/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
database/migrations/05_triggers.sql
Normal file
150
database/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
database/migrations/06_validations_enrich.sql
Normal file
53
database/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;
|
||||
42
database/migrations/07_import.sql
Normal file
42
database/migrations/07_import.sql
Normal file
@ -0,0 +1,42 @@
|
||||
-- Script d'importation des données CSV dans la base Postgres du docker dev
|
||||
-- À exécuter dans le conteneur ou via psql connecté à la base
|
||||
-- psql -U admin -d ptitpas_db -f /docker-entrypoint-initdb.d/07_import.sql
|
||||
-- Exemple d'utilisation :
|
||||
|
||||
|
||||
-- Import utilisateurs
|
||||
\copy utilisateurs FROM 'bdd/data_test/utilisateurs.csv' DELIMITER ',' CSV HEADER;
|
||||
|
||||
-- Import assistantes_maternelles
|
||||
\copy assistantes_maternelles FROM 'bdd/data_test/assistantes_maternelles.csv' DELIMITER ',' CSV HEADER;
|
||||
|
||||
-- Import parents
|
||||
\copy parents FROM 'bdd/data_test/parents.csv' DELIMITER ',' CSV HEADER;
|
||||
|
||||
-- Import enfants
|
||||
\copy enfants FROM 'bdd/data_test/enfants.csv' DELIMITER ',' CSV HEADER;
|
||||
|
||||
-- Import enfants_parents
|
||||
\copy enfants_parents FROM 'bdd/data_test/enfants_parents.csv' DELIMITER ',' CSV HEADER;
|
||||
|
||||
-- Import dossiers
|
||||
\copy dossiers FROM 'bdd/data_test/dossiers.csv' DELIMITER ',' CSV HEADER;
|
||||
|
||||
-- Import contrats
|
||||
\copy contrats FROM 'bdd/data_test/contrats.csv' DELIMITER ',' CSV HEADER;
|
||||
|
||||
-- Import validations
|
||||
\copy validations FROM 'bdd/data_test/validations.csv' DELIMITER ',' CSV HEADER;
|
||||
|
||||
-- Import notifications
|
||||
\copy notifications FROM 'bdd/data_test/notifications.csv' DELIMITER ',' CSV HEADER;
|
||||
|
||||
-- Import uploads
|
||||
\copy uploads FROM 'bdd/data_test/uploads.csv' DELIMITER ',' CSV HEADER;
|
||||
|
||||
-- Import evenements
|
||||
\copy evenements FROM 'bdd/data_test/evenements.csv' DELIMITER ',' CSV HEADER;
|
||||
|
||||
-- Remarque :
|
||||
-- Les chemins doivent être accessibles depuis le conteneur Docker (monter le dossier si besoin)
|
||||
-- Adapter l'utilisateur, la base et le chemin si nécessaire
|
||||
13
database/pgadmin/servers.json
Normal file
13
database/pgadmin/servers.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"Servers": {
|
||||
"1": {
|
||||
"Name": "Postgres Dev",
|
||||
"Group": "Local",
|
||||
"Host": "postgres",
|
||||
"Port": 5432,
|
||||
"Username": "admin",
|
||||
"SSLMode": "prefer",
|
||||
"MaintenanceDB": "ptitpas_db"
|
||||
}
|
||||
}
|
||||
}
|
||||
221
database/seed/02_seed.sql
Normal file
221
database/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
database/tests/sql/verify.sql
Normal file
205
database/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