Compare commits

...
14 Commits
37 changed files with 1413 additions and 944 deletions
Vendored
BIN
View File
Binary file not shown.
-10
View File
@@ -1,10 +0,0 @@
# Configuration de la base de données en développement local
# Configuration PostgreSQL
POSTGRES_USER=admin
POSTGRES_PASSWORD=admin123
POSTGRES_DB=ptitpas_db
# Configuration PgAdmin (accessible sur http://localhost:8080)
PGADMIN_DEFAULT_EMAIL=admin@localhost
PGADMIN_DEFAULT_PASSWORD=admin123
+1
View File
@@ -1 +1,2 @@
.env
PtitsPas_CDC.pdf
+23 -14
View File
@@ -5,10 +5,10 @@ CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ==========================================================
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');
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', 'Autre');
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');
@@ -17,9 +17,9 @@ DO $$ BEGIN
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');
CREATE TYPE statut_dossier_type AS ENUM ('envoye','accepte','refuse', 'cloture');
END IF;
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_contrat_type') THEN
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
@@ -34,6 +34,9 @@ DO $$ BEGIN
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 $$;
-- ==========================================================
@@ -41,22 +44,28 @@ END $$;
-- ==========================================================
CREATE TABLE utilisateurs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
courriel VARCHAR(255) NOT NULL UNIQUE,
CHECK (courriel ~* '^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$'),
mot_de_passe_hash TEXT NOT NULL,
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),
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()
modifie_le TIMESTAMPTZ DEFAULT now(),
ville VARCHAR(150),
code_postal VARCHAR(10),
profession VARCHAR(150),
situation_familiale situation_familiale_type
);
-- ==========================================================
@@ -65,14 +74,14 @@ CREATE TABLE utilisateurs (
CREATE TABLE assistantes_maternelles (
id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE,
numero_agrement VARCHAR(50),
date_naissance DATE,
ville_naissance VARCHAR(100),
pays_naissance CHAR(2),
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,
ville_residence VARCHAR(100)
disponible BOOLEAN DEFAULT true
);
-- ==========================================================
+84 -29
View File
@@ -1,62 +1,117 @@
# PtitsPas Ynov - Base de Données
Ce projet contient uniquement la **base de données** pour l'application PtitsPas.
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 installé et à jour
- Docker Compose installé
- Docker Desktop (https://www.docker.com/products/docker-desktop/)
- Docker Compose
---
## Lancer la base de données
## Structure du projet
Dans le terminal, depuis le dossier du projet, exécute :
- `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 up -d
````
make demo
```
Et pour arreter le container
Pour arrêter et supprimer les volumes:
```bash
docker-compose down
````
make stop
```
---
## 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
Ouvre ton navigateur et va sur :
### Via Docker (local)
Ouvre ton navigateur sur:
```
http://localhost:8080
http://localhost:8081
```
### Connexion à pgAdmin4
Par défaut un administrateur est créé par la migration d'initialisation (`migrations/01_init.sql`).
Pour des raisons de sécurité, le mot de passe n'est pas gardé en clair dans le README.
* **Email** : `admin@bdd.com`
* **Mot de passe** : `admin123`
Si tu veux un mot de passe connu en dev, génère le hash bcrypt puis modifie la migration
ou crée un seed SQL :
```sql
-- Exemple pour définir le mot de passe en dev :
UPDATE utilisateurs
SET password = crypt('admin123', gen_salt('bf'))
WHERE email = 'admin@ptits-pas.fr';
```
Exécute la commande suivante pour appliquer ce seed sur ta base dev :
```bash
docker exec -i ptitspas-postgres-standalone psql -U admin -d ptitpas_db -c "UPDATE utilisateurs SET password = crypt('admin123', gen_salt('bf')) WHERE email = 'admin@ptits-pas.fr';"
```
## 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/`
### Synchroniser les types ENUM avec les CSV d'import
Un utilitaire est fourni pour générer une migration sûre qui ajoute les valeurs
manquantes aux types ENUM en base en se basant sur les CSV présents dans
`bdd/data_test/`.
Générer la migration :
```bash
make sync-enums
```
Le fichier généré est `migrations/00_sync_enums.sql`. Relis ce fichier avant de
l'appliquer (il contient des blocs `DO $$ ... ALTER TYPE ... ADD VALUE` qui
ne suppriment rien mais modifient le type ENUM). Pour appliquer la migration
sur ta base locale :
```bash
# Après avoir démarré la base (make reset ou make demo)
docker exec -i ptitspas-postgres-standalone psql -U admin -d ptitpas_db -f migrations/00_sync_enums.sql
```
Attention : ne pas appliquer directement en production sans vérification.
---
### Ajouter le serveur PostgreSQL
## Contact
1. Clique sur **Add New Server** (ou **Register Server**)
2. Onglet **General** :
* **Name** : `Postgres Docker`
3. Onglet **Connection** :
* **Host name/address** : `postgres_bdd`
* **Port** : `5432` (ou `5433` si mappé dans Docker Compose)
* **Username** : `admin`
* **Password** : `admin123`
4. Clique sur **Save**
---
Pour toute question ou contribution, consulte la documentation ou contacte l'équipe PtitsPas.
BIN
View File
Binary file not shown.
@@ -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
1 id_utilisateur numero_agrement nir_chiffre nb_max_enfants biographie disponible ville_residence date_agrement annee_experience specialite place_disponible
2 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
3 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
View 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
1 id id_dossier planning tarif_horaire indemnites_repas date_debut statut signe_parent signe_am finalise_le cree_le modifie_le
2 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
View 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
1 id id_parent id_enfant presentation type_contrat repas budget planning_souhaite statut cree_le modifie_le
2 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
View 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
1 id statut prenom nom genre date_naissance date_prevue_naissance photo_url consentement_photo date_consentement_photo est_multiple
2 5e8574b7-63e6-4d48-9af3-8d3bf7a6a6cf actif Emma Dupont F 2020-06-01 False False
3 a5c3268e-07eb-41a4-9f6c-2f9f16f37c3d actif 2020-01-01 2025-01-01 False False
4 e1a2b3c4-d5e6-4f7a-8b9c-1d2e3f4a5b6c actif Emma Martin 2023-02-15 False False
5 e2b3c4d5-e6f7-4a8b-9c1d-2e3f4a5b6c7d actif Noah Martin 2023-02-15 False False
6 e3c4d5e6-f7a8-4b9c-1d2e-3f4a5b6c7d8e actif Léa Martin 2023-02-15 False False
7 e4d5e6f7-a8b9-4c1d-2e3f-4a5b6c7d8e9f actif Chloé Rousseau 2022-04-20 False False
8 e5e6f7a8-b9c1-4d2e-3f4a-5b6c7d8e9f1a actif Hugo Rousseau 2024-03-10 False False
9 e6f7a8b9-c1d2-4e3f-5a6b-7c8d9e0f1a2b actif Maxime Lecomte 2023-04-15 False False
10 edd19cd1-bb67-4f14-8a37-c66b75c94537 scolarise Lucas Durand H 2018-09-15 False False
+9
View 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
1 id_parent id_enfant
2 b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e e4d5e6f7-a8b9-4c1d-2e3f-4a5b6c7d8e9f
3 b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e e5e6f7a8-b9c1-4d2e-3f4a-5b6c7d8e9f1a
4 c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f e1a2b3c4-d5e6-4f7a-8b9c-1d2e3f4a5b6c
5 c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f e2b3c4d5-e6f7-4a8b-9c1d-2e3f4a5b6c7d
6 c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f e3c4d5e6-f7a8-4b9c-1d2e-3f4a5b6c7d8e
7 d3e5f7a9-1c2b-4d6e-8f3a-2b4c6d8e9f1a e6f7a8b9-c1d2-4e3f-5a6b-7c8d9e0f1a2b
8 f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b e4d5e6f7-a8b9-4c1d-2e3f-4a5b6c7d8e9f
9 f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b e5e6f7a8-b9c1-4d2e-3f4a-5b6c7d8e9f1a
+2
View 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
1 id type id_enfant id_am id_parent cree_par date_debut date_fin commentaires statut delai_grace urgent cree_le modifie_le
2 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
View 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
1 id id_utilisateur contenu lu cree_le
2 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
View 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
1 id_utilisateur id_co_parent
2 b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b
3 c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f
4 d3e5f7a9-1c2b-4d6e-8f3a-2b4c6d8e9f1a
5 f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e
+2
View 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
1 id id_utilisateur fichier_url type cree_le
2 db1eb36d-5f30-4027-b529-1d972b79180a bbcae75c-0e60-4b84-b281-079dba23b44e https://placeholder.local/file image 2025-09-02 12:57:35.140078+00
+12
View 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
1 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
2 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
3 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
4 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
5 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
6 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
7 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
8 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
9 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
10 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
11 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
12 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
View 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
1 id id_utilisateur type statut cree_le modifie_le valide_par commentaire
2 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
3 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
4 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
Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

+22 -6
View File
@@ -9,30 +9,46 @@ services:
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-admin}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-admin123}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB:-ptitpas_db}
ports:
- "5432:5432"
- "5433:5432"
volumes:
# Scripts de migration (init first). NOTE: `00_sync_enums.sql` is intentionally
# NOT mounted here because it must be executed after `01_init.sql` (it alters
# existing enum types). The local pipeline applies it explicitly after init.
- ./migrations/01_init.sql:/docker-entrypoint-initdb.d/01_init.sql
- ./migrations/02_indexes.sql:/docker-entrypoint-initdb.d/02_indexes.sql
- ./migrations/03_checks.sql:/docker-entrypoint-initdb.d/03_checks.sql
- ./migrations/05_triggers.sql:/docker-entrypoint-initdb.d/05_triggers.sql
- ./migrations/07_import.sql:/docker-entrypoint-initdb.d/07_import.sql
# Dossier de données de test (CSV, etc.)
- ./bdd/data_test:/bdd/data_test
# Scripts de vérification
- ./tests/sql:/tests/sql
# Données persistées Postgres
- postgres_standalone_data:/var/lib/postgresql/data
networks:
- ptitspas_dev
# Interface d'administration DB
pgadmin:
image: dpage/pgadmin4
image: dpage/pgadmin4:9.8
container_name: ptitspas-pgadmin-standalone
restart: unless-stopped
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-admin@localhost}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin123}
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-admin@ptits-pas.fr}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
ports:
- "8080:80"
- "8081:80"
depends_on:
- postgres
networks:
- ptitspas_dev
volumes:
- ./pgadmin/servers.json:/pgadmin4/servers.json
volumes:
postgres_standalone_data:
+5 -5
View File
@@ -4,9 +4,9 @@ services:
container_name: ynov-postgres
restart: unless-stopped
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: admin123
POSTGRES_DB: ptitpas_db
POSTGRES_USER: ${POSTGRES_USER:-admin}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB:-ptitpas_db}
volumes:
- ./migrations/01_init.sql:/docker-entrypoint-initdb.d/01_init.sql
- postgres_data:/var/lib/postgresql/data
@@ -18,8 +18,8 @@ services:
container_name: ynov-pgadmin
restart: unless-stopped
environment:
PGADMIN_DEFAULT_EMAIL: admin@ynov.local
PGADMIN_DEFAULT_PASSWORD: admin123
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-admin@ptits-pas.fr}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD}
depends_on:
- db
labels:
+89 -100
View File
@@ -1,157 +1,146 @@
# ENUMS.md — Référentiel des valeurs énumérées
# 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 **PtitsPas**, leur **sens fonctionnel**, et les **tables/colonnes concernées**.
Ce document recense **toutes les valeurs énumérées** utilisées dans la base PtitsPas, 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.
> Objectif : garantir la cohérence entre la **DB**, le **backend (NestJS)** et le **frontend (Flutter)**.
> Toute évolution doit être documentée 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 lOpenAPI / DTO.
* Les valeurs ENUM sont **en minuscules** (snake\_case si nécessaire).
* Implémentées via `CREATE TYPE … AS ENUM` dans PostgreSQL.
* Les valeurs sont **renvoyées telles quelles** côté API.
---
## 1) Rôle utilisateur — `role`
## 1) Rôle utilisateur — `role_type`
**Tables/colonnes** : `utilisateurs.role`
**Valeurs autorisées** :
**Tables/colonnes** : `utilisateurs.role`
| Valeur | Description |
|---|---|
| `super_admin` | Compte technique initial / administration globale |
| `gestionnaire` | Gestion / validation des comptes, supervision |
| `parent` | Parent ou co-parent |
| `am` | Assistante maternelle |
| Valeur | Description |
| ----------------------- | ------------------------------------------------- |
| `parent` | Parent ou co-parent |
| `gestionnaire` | Gestion/validation des comptes, supervision |
| `super_admin` | Compte technique initial / administration globale |
| `assistante_maternelle` | Profil professionnel dassistante maternelle |
| `administrateur` | Administration locale / restreinte |
---
## 2) Statut utilisateur — `statut`
## 2) Genre utilisateur — `genre_type`
**Tables/colonnes** : `utilisateurs.statut`
**Valeurs autorisées** :
**Tables/colonnes** : `utilisateurs.genre`, `enfants.genre`
| Valeur | Description |
|---|---|
| `en_attente` | Compte créé mais non validé |
| `accepte` | Compte validé et actif |
| `rejete` | Demande refusée (peut être recréée ultérieurement) |
| Valeur | Description |
| ------- | ------------------- |
| `H` | Homme |
| `F` | Femme |
| `Autre` | Autre / non précisé |
---
## 3) Statut enfant — `statut`
## 3) Statut utilisateur — `statut_utilisateur_type`
**Tables/colonnes** : `enfants.statut`
**Valeurs autorisées** :
**Tables/colonnes** : `utilisateurs.statut`
| 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**
| Valeur | Description |
| ------------ | ------------------------------- |
| `en_attente` | Compte créé mais non validé |
| `actif` | Compte validé et actif |
| `suspendu` | Compte temporairement désactivé |
---
## 4) Statut dossier — `statut`
## 4) Statut enfant — `statut_enfant_type`
**Tables/colonnes** : `dossiers.statut`
**Valeurs autorisées (MVP)** :
**Tables/colonnes** : `enfants.statut`
| 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) |
| Valeur | Description |
| ----------- | ---------------------------------------------- |
| `a_naitre` | Enfant à naître (date prévue) |
| `actif` | Enfant pris en charge |
| `scolarise` | Enfant scolarisé (garde périscolaire possible) |
---
## 5) Statut contrat — `statut`
## 5) Statut dossier — `statut_dossier_type`
**Tables/colonnes** : `contrats.statut`
**Valeurs autorisées** :
**Tables/colonnes** : `dossiers.statut`
| Valeur | Description |
|---|---|
| `brouillon` | Contrat en préparation |
| `valide` | Contrat finalisé (signatures complètes) |
| `archive` | Contrat obsolète / terminé |
| Valeur | Description |
| --------- | ---------------------------- |
| `envoye` | Dossier soumis par le parent |
| `accepte` | Dossier validé par lAM |
| `refuse` | Dossier rejeté |
---
## 6) Statut avenant — `statut`
## 6) Statut contrat — `statut_contrat_type`
**Tables/colonnes** : `avenants_contrats.statut`
**Valeurs autorisées** :
**Tables/colonnes** : `contrats.statut`
| Valeur | Description |
|---|---|
| `propose` | Avenant proposé (en attente daccord) |
| `valide` | Avenant accepté et appliqué |
| `rejete` | Avenant refusé |
| Valeur | Description |
| ---------------------- | ----------------------------------------- |
| `brouillon` | Contrat en préparation |
| `en_attente_signature` | Contrat généré, en attente des signatures |
| `valide` | Contrat signé et actif |
| `resilie` | Contrat résilié |
---
## 7) Type d’événement — `type`
## 7) Statut avenant — `statut_avenant_type`
**Tables/colonnes** : `evenements.type`
**Valeurs autorisées** :
**Tables/colonnes** : `avenants_contrats.statut`
| Valeur | Description |
|---|---|
| `absence_enfant` | Enfant absent |
| `conge_am` | Congé de lassistante maternelle |
| `conge_parent` | Congé du parent |
| `arret_maladie_am` | Arrêt maladie AM |
| `evenement_rpe` | Événement RPE |
| Valeur | Description |
| --------- | --------------------------- |
| `propose` | Avenant proposé |
| `accepte` | Avenant accepté et appliqué |
| `refuse` | Avenant rejeté |
---
## 8) Statut d’événement — `statut`
## 8) Type d’événement — `type_evenement_type`
**Tables/colonnes** : `evenements.statut`
**Valeurs autorisées** :
**Tables/colonnes** : `evenements.type`
| Valeur | Description |
|---|---|
| Valeur | Description |
| ------------------ | -------------------------------- |
| `absence_enfant` | Absence de lenfant |
| `conge_am` | Congé de lassistante maternelle |
| `conge_parent` | Congé du parent |
| `arret_maladie_am` | Arrêt maladie de lAM |
| `evenement_rpe` | Événement organisé par le RPE |
---
## 9) Statut d’événement — `statut_evenement_type`
**Tables/colonnes** : `evenements.statut`
| Valeur | Description |
| --------- | ----------------- |
| `propose` | Événement proposé |
| `valide` | Événement validé |
| `rejete` | Événement refusé |
| `valide` | Événement validé |
| `refuse` | Événement rejeté |
---
## 9) Statut de validation compte — `statut`
## 10) Statut validation — `statut_validation_type`
**Tables/colonnes** : `validations.statut`
**Valeurs autorisées** :
**Tables/colonnes** : `validations.statut`
| Valeur | Description |
|---|---|
| `accepte` | Compte validé |
| `rejete` | Compte refusé |
| Valeur | Description |
| ------------ | ------------------------ |
| `en_attente` | En attente de validation |
| `valide` | Validation acceptée |
| `refuse` | Validation refusée |
---
## 10) Type de notification — `type`
📌 **Mainteneur** : Équipe BDD
📌 **Dernière mise à jour** : alignée sur `init.sql` (septembre 2025)
**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 dun événement proche |
---
**Mainteneur** : Équipe BDD
**Dernière mise à jour** : Sprint 1 — Ticket 9 (ENUMS)
---
+107 -105
View File
@@ -1,142 +1,144 @@
# FK\_POLICIES.md — Politiques de clés étrangères
# FK_POLICIES.md
**Politique des clés étrangères (ON DELETE / ON UPDATE)** Sprint 1
Ce document recense lensemble des **relations entre tables** dans la base **PtitsPas**, avec leurs **règles de suppression** et les implications fonctionnelles.
## 🎯 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 PtitsPas 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.
> Objectif : assurer la cohérence entre la **DB**, le **backend** et le **fonctionnel métier**.
> Toute nouvelle relation ou modification doit être ajoutée ici avant migration DB.
---
## 🧭 Principes généraux
## Conventions générales
- **CASCADE** quand la donnée fille **na pas de sens sans le parent**
(ex. `dossiers` dun parent, `avenants` dun contrat).
- **SET NULL** quand on veut **préserver lhistorique** mais que le référent peut disparaître
(ex. auteur dun message supprimé, créateur dun événement).
- **RESTRICT/NO ACTION** non utilisé ici pour éviter des blocages au nettoyage.
* Les FK sont définies en `REFERENCES ...`.
* Sauf précision, `ON UPDATE` est implicite = `NO ACTION`.
* Les comportements documentés sont **côté PostgreSQL**.
---
## 📚 Récapitulatif rapide (matrice)
## 1) Table `assistantes_maternelles`
| 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 na pas de sens sans parent |
| **dossiers(id_enfant)**`enfants(id)` | **CASCADE** | Dossier na 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 lhistorique 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 lavenant sans bloquer |
| **evenements(id_enfant)**`enfants(id)` | **CASCADE** | Événements nont 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 lhistorique 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 lauteur |
| **notifications(id_utilisateur)**`utilisateurs(id)` | **CASCADE** | Notifications propres à lutilisateur |
| **validations(id_utilisateur)**`utilisateurs(id)` | **SET NULL** | Garder lhistorique de décision |
> **ON UPDATE** : **NO ACTION** partout (les UUID ne changent pas).
* **FK** : `id_utilisateur → utilisateurs.id`
* **ON DELETE CASCADE**
* 🔎 Si un utilisateur AM est supprimé, son profil AM est supprimé automatiquement.
---
## 🔎 Détail par domaine
## 2) Table `parents`
### 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é)
* **FK** : `id_utilisateur → utilisateurs.id`
### Enfants & liaisons
- `enfants_parents.id_parent`**CASCADE**
- `enfants_parents.id_enfant`**CASCADE**
* **ON DELETE CASCADE** → suppression dun parent entraîne suppression automatique du parent.
* **FK** : `id_co_parent → utilisateurs.id`
### 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**
* **ON DELETE NO ACTION** (par défaut) → suppression du co-parent interdite si référence active.
---
## 🧪 Scénarios de test (exemples)
## 3) Table `enfants_parents`
1. **Suppression dun parent**
- Supprimer `utilisateurs(id=parentX)`
- Attendu : `parents` (CASCADE), ses `dossiers` (CASCADE), `messages` liés aux `dossiers` (CASCADE) sont supprimés.
* **FK** : `id_parent → parents.id_utilisateur`
2. **Suppression dun co-parent**
- Supprimer `utilisateurs(id=coParentY)`
- Attendu : `parents.id_co_parent` passe à **NULL**, aucun dossier supprimé.
* **ON DELETE CASCADE** → si le parent est supprimé, le lien avec lenfant disparaît.
* **FK** : `id_enfant → enfants.id`
3. **Suppression dun utilisateur auteur de messages**
- Supprimer `utilisateurs(id=uZ)`
- Attendu : les lignes `messages` **restent**, `id_expediteur` devient **NULL**.
4. **Suppression dun enfant**
- Supprimer `enfants(id=childA)`
- Attendu : `enfants_parents` (CASCADE), `dossiers` du childA (CASCADE), `evenements` du childA (CASCADE).
5. **Suppression dun utilisateur AM**
- Supprimer `utilisateurs(id=amB)`
- Attendu : `evenements.id_am` devient **NULL** (historique conservé).
* **ON DELETE CASCADE** → si lenfant est supprimé, toutes ses associations avec des parents disparaissent.
---
## 🛠 Migrations associées
## 4) Table `dossiers`
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.
* **FK** : `id_parent → parents.id_utilisateur`
* **ON DELETE CASCADE** → si le parent est supprimé, ses dossiers disparaissent.
* **FK** : `id_enfant → enfants.id`
* **ON DELETE CASCADE** → si lenfant est supprimé, les dossiers liés disparaissent.
---
## 📄 Notes & futures évolutions
## 5) Table `messages`
- **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 daudit (voir ticket Sprint 2 Audit log).
- **Performance** : chaque FK doit être **indexée côté enfant** (cf. `02_indexes.sql`).
* **FK** : `id_dossier → dossiers.id`
* **ON DELETE CASCADE** → si un dossier est supprimé, les messages disparaissent.
* **FK** : `id_expediteur → utilisateurs.id`
* **ON DELETE CASCADE** → si un utilisateur est supprimé, ses messages disparaissent.
---
## ✅ Checklist de conformité
## 6) Table `contrats`
- [ ] 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
* **FK** : `id_dossier → dossiers.id`
* **UNIQUE** + **ON DELETE CASCADE** → si un dossier est supprimé, le contrat disparaît.
---
**Mainteneur** : Équipe BDD
**Dernière mise à jour** : Sprint 1 Politique FK consolidée
## 7) Table `avenants_contrats`
* **FK** : `id_contrat → contrats.id`
* **ON DELETE CASCADE** → si un contrat est supprimé, ses avenants disparaissent.
* **FK** : `initie_par → utilisateurs.id`
* **ON DELETE NO ACTION** → suppression interdite tant que des avenants existent.
---
## 8) Table `evenements`
* **FK** : `id_enfant → enfants.id`
* **ON DELETE CASCADE** → si un enfant est supprimé, ses événements disparaissent.
* **FK** : `id_am → utilisateurs.id`
* **ON DELETE NO ACTION** → suppression interdite si des événements liés.
* **FK** : `id_parent → parents.id_utilisateur`
* **ON DELETE NO ACTION** → suppression interdite si des événements liés.
* **FK** : `cree_par → utilisateurs.id`
* **ON DELETE NO ACTION** → suppression interdite si référence encore utilisée.
---
## 9) Table `signalements_bugs`
* **FK** : `id_utilisateur → utilisateurs.id`
* **ON DELETE NO ACTION** → utilisateur non supprimable si bug référencé.
---
## 10) Table `uploads`
* **FK** : `id_utilisateur → utilisateurs.id`
* **ON DELETE SET NULL** → si un utilisateur est supprimé, ses fichiers restent, mais sans lien utilisateur.
---
## 11) Table `notifications`
* **FK** : `id_utilisateur → utilisateurs.id`
* **ON DELETE CASCADE** → si un utilisateur est supprimé, ses notifications disparaissent.
---
## 12) Table `validations`
* **FK** : `id_utilisateur → utilisateurs.id`
* **ON DELETE NO ACTION** → suppression interdite si validations liées.
* **FK** : `valide_par → utilisateurs.id`
* **ON DELETE NO ACTION** → suppression interdite si validations effectuées par lutilisateur.
---
📌 **Mainteneur** : Équipe BDD
📌 **Dernière mise à jour** : alignée sur `init.sql` (septembre 2025)
---
+85
View File
@@ -0,0 +1,85 @@
# ============================================================
# Makefile - Gestion de la base Ptits Pas (Postgres + pgAdmin)
# Compatible macOS, Linux, Windows (Git Bash)
# ============================================================
# Détection de l'OS
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin) # macOS
OPEN_CMD = open
else ifeq ($(UNAME_S),Linux) # Linux
OPEN_CMD = xdg-open
else # Windows (Git Bash / Cygwin)
OPEN_CMD = start
endif
# Paramètres
PG_CONTAINER = ptitspas-postgres-standalone
PG_USER = admin
PG_DB = ptitpas_db
VERIFY_SQL = /tests/sql/verify.sql
IMPORT_SQL = /docker-entrypoint-initdb.d/07_import.sql
# ============================================================
# Cibles principales
# ============================================================
reset:
@echo "🗑️ Réinitialisation de la base..."
docker compose -f docker-compose.dev.yml down -v
docker compose -f docker-compose.dev.yml up -d
@echo "✅ Base de données réinitialisée."
import:
@echo "⏳ Attente que Postgres démarre..."
@until docker exec -i $(PG_CONTAINER) pg_isready -U $(PG_USER) -d $(PG_DB); do \
sleep 2; \
done
@echo "🧹 Nettoyage des CSV avant import (local -> in-place)..."
@python3 scripts/clean_csv.py
@if [ $$? -ne 0 ]; then echo "❌ Erreurs lors du nettoyage des CSV - corrige les fichiers avant import"; exit 1; fi
@echo "📥 Import des données de démo..."
@docker exec -i $(PG_CONTAINER) psql -U $(PG_USER) -d $(PG_DB) -f $(IMPORT_SQL)
@echo "✅ Données importées."
set-dev-admin-pw:
@echo "🔐 Définir mot de passe dev pour admin@ptits-pas.fr (admin123)"
@docker exec -i $(PG_CONTAINER) psql -U $(PG_USER) -d $(PG_DB) -c "UPDATE utilisateurs SET password = crypt('admin123', gen_salt('bf')) WHERE email = 'admin@ptits-pas.fr';"
@echo "✅ Mot de passe admin dev mis à jour."
verify:
@echo "⏳ Attente que Postgres démarre pour vérifier..."
@until docker exec -i $(PG_CONTAINER) pg_isready -U $(PG_USER) -d $(PG_DB); do \
sleep 2; \
done
@echo "🔎 Vérification des données..."
-@docker exec -i $(PG_CONTAINER) psql -U $(PG_USER) -d $(PG_DB) -f $(VERIFY_SQL) | tee verify.log
@if grep -q "ERROR" verify.log; then \
echo "❌ Des erreurs détectées (voir verify.log)"; \
else \
echo "✅ Vérification OK : pas d'erreurs détectées."; \
fi
demo: reset import verify
@echo "🌐 Ouverture de pgAdmin sur http://localhost:8081 ..."
@$(OPEN_CMD) http://localhost:8081
# ============================================================
# Utilitaires
# ============================================================
logs:
docker compose -f docker-compose.dev.yml logs -f
psql:
docker exec -it $(PG_CONTAINER) psql -U $(PG_USER) -d $(PG_DB)
stop:
docker compose -f docker-compose.dev.yml down
sync-enums:
@echo "🔁 Génération de migrations/00_sync_enums.sql depuis les CSV"
@python3 scripts/sync_enums.py > migrations/00_sync_enums.sql
@echo "✅ migrations/00_sync_enums.sql générée. Relis le fichier avant de l'appliquer."
+183
View File
@@ -0,0 +1,183 @@
-- Generated by scripts/sync_enums.py — review before applying
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_contrat_type'
AND e.enumlabel = 'brouillon'
) THEN
ALTER TYPE statut_contrat_type ADD VALUE 'brouillon';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_dossier_type'
AND e.enumlabel = 'envoye'
) THEN
ALTER TYPE statut_dossier_type ADD VALUE 'envoye';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_enfant_type'
AND e.enumlabel = 'actif'
) THEN
ALTER TYPE statut_enfant_type ADD VALUE 'actif';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_enfant_type'
AND e.enumlabel = 'scolarise'
) THEN
ALTER TYPE statut_enfant_type ADD VALUE 'scolarise';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'genre_type'
AND e.enumlabel = 'F'
) THEN
ALTER TYPE genre_type ADD VALUE 'F';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'genre_type'
AND e.enumlabel = 'H'
) THEN
ALTER TYPE genre_type ADD VALUE 'H';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'type_evenement_type'
AND e.enumlabel = 'absence_enfant'
) THEN
ALTER TYPE type_evenement_type ADD VALUE 'absence_enfant';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_evenement_type'
AND e.enumlabel = 'propose'
) THEN
ALTER TYPE statut_evenement_type ADD VALUE 'propose';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'role_type'
AND e.enumlabel = 'administrateur'
) THEN
ALTER TYPE role_type ADD VALUE 'administrateur';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'role_type'
AND e.enumlabel = 'assistante_maternelle'
) THEN
ALTER TYPE role_type ADD VALUE 'assistante_maternelle';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'role_type'
AND e.enumlabel = 'gestionnaire'
) THEN
ALTER TYPE role_type ADD VALUE 'gestionnaire';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'role_type'
AND e.enumlabel = 'parent'
) THEN
ALTER TYPE role_type ADD VALUE 'parent';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_utilisateur_type'
AND e.enumlabel = 'actif'
) THEN
ALTER TYPE statut_utilisateur_type ADD VALUE 'actif';
END IF;
END$$;
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = 'statut_validation_type'
AND e.enumlabel = 'valide'
) THEN
ALTER TYPE statut_validation_type ADD VALUE 'valide';
END IF;
END$$;
+30 -17
View File
@@ -5,7 +5,7 @@ CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ==========================================================
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');
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');
@@ -17,7 +17,7 @@ DO $$ BEGIN
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');
CREATE TYPE statut_dossier_type AS ENUM ('envoye','accepte','refuse', 'cloture');
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');
@@ -41,9 +41,9 @@ END $$;
-- ==========================================================
CREATE TABLE utilisateurs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
courriel VARCHAR(255) NOT NULL UNIQUE,
CHECK (courriel ~* '^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$'),
mot_de_passe_hash TEXT NOT NULL,
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,
@@ -56,7 +56,14 @@ CREATE TABLE utilisateurs (
date_consentement_photo TIMESTAMPTZ,
changement_mdp_obligatoire BOOLEAN DEFAULT false,
cree_le TIMESTAMPTZ DEFAULT now(),
modifie_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
);
-- ==========================================================
@@ -65,18 +72,19 @@ CREATE TABLE utilisateurs (
CREATE TABLE assistantes_maternelles (
id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE,
numero_agrement VARCHAR(50),
date_naissance DATE,
ville_naissance VARCHAR(100),
pays_naissance CHAR(2),
nir_chiffre CHAR(15),
nb_max_enfants INT,
biographie TEXT,
disponible BOOLEAN DEFAULT true,
ville_residence VARCHAR(100)
ville_residence VARCHAR(100),
date_agrement DATE,
annee_experience SMALLINT,
specialite VARCHAR(100),
place_disponible INT
);
-- ==========================================================
-- Table : parents
-- Table : parentschange les donnée de init
-- ==========================================================
CREATE TABLE parents (
id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE,
@@ -230,7 +238,9 @@ CREATE TABLE validations (
type VARCHAR(50),
statut statut_validation_type DEFAULT 'en_attente',
cree_le TIMESTAMPTZ DEFAULT now(),
modifie_le TIMESTAMPTZ DEFAULT now()
modifie_le TIMESTAMPTZ DEFAULT now(),
valide_par UUID REFERENCES utilisateurs(id),
commentaire TEXT
);
@@ -240,8 +250,8 @@ CREATE TABLE validations (
INSERT INTO utilisateurs (
id,
courriel,
mot_de_passe_hash,
email,
password,
prenom,
nom,
role,
@@ -251,8 +261,11 @@ INSERT INTO utilisateurs (
)
VALUES (
gen_random_uuid(),
'admin@ptitspas.com',
'motdepasse_hashé', -- ⚠️ à remplacer par le hash réel du mot de passe
'admin@ptits-pas.fr',
-- Use a bcrypt hash via pgcrypto for the default admin password in dev.
-- In dev you can keep a known password by using: crypt('admin123', gen_salt('bf'))
-- For security, avoid committing plaintext passwords in migrations for production.
crypt('admin123', gen_salt('bf')),
'Admin',
'PtitsPas',
'super_admin',
@@ -260,4 +273,4 @@ VALUES (
now(),
now()
)
ON CONFLICT (courriel) DO NOTHING;
ON CONFLICT (email) DO NOTHING;
+48 -148
View File
@@ -1,157 +1,57 @@
-- =============================================
-- 02_indexes.sql : Index FK + colonnes critiques
-- =============================================
-- ============================================================
-- 02_indexes.sql Indexes pour améliorer les perfs
-- ============================================================
-- Recommandation : exécuter après 01_init.sql
-- ===========
-- UTILISATEURS
-- ===========
-- Recherche par email (insensibilité à la casse pour lookup)
CREATE INDEX IF NOT EXISTS idx_utilisateurs_lower_courriel
ON utilisateurs (LOWER(courriel));
-- ===========
-- ASSISTANTES_MATERNELLES
-- ===========
-- FK -> utilisateurs(id)
CREATE INDEX IF NOT EXISTS idx_am_id_utilisateur
ON assistantes_maternelles (id_utilisateur);
-- =======
-- PARENTS
-- =======
-- FK -> utilisateurs(id)
CREATE INDEX IF NOT EXISTS idx_parents_id_utilisateur
ON parents (id_utilisateur);
-- Co-parent (nullable)
CREATE INDEX IF NOT EXISTS idx_parents_id_co_parent
ON parents (id_co_parent);
-- =======
-- ENFANTS
-- =======
-- (souvent filtrés par statut / date_naissance ? à activer si besoin)
-- CREATE INDEX IF NOT EXISTS idx_enfants_statut ON enfants (statut);
-- CREATE INDEX IF NOT EXISTS idx_enfants_date_naissance ON enfants (date_naissance);
-- ================
-- ENFANTS_PARENTS (N:N)
-- ================
-- PK composite déjà en place (id_parent, id_enfant), ajouter index individuels si jointures unilatérales fréquentes
CREATE INDEX IF NOT EXISTS idx_enfants_parents_id_parent
ON enfants_parents (id_parent);
CREATE INDEX IF NOT EXISTS idx_enfants_parents_id_enfant
ON enfants_parents (id_enfant);
-- ========
-- DOSSIERS
-- ========
-- FK -> parent / enfant
CREATE INDEX IF NOT EXISTS idx_dossiers_id_parent
ON dossiers (id_parent);
CREATE INDEX IF NOT EXISTS idx_dossiers_id_enfant
ON dossiers (id_enfant);
-- Statut (filtrages "à traiter", "envoyé", etc.)
CREATE INDEX IF NOT EXISTS idx_dossiers_statut
ON dossiers (statut);
-- JSONB : si on fait des requêtes @> sur le planning souhaité
-- CREATE INDEX IF NOT EXISTS idx_dossiers_planning_souhaite_gin
-- ON dossiers USING GIN (planning_souhaite);
-- ========
-- MESSAGES
-- ========
-- Filtrage par dossier + tri chronologique
-- ===========================================
-- Messages (par dossier + tri par date)
-- Utilisé dans verify.sql (EXPLAIN + ORDER BY cree_le)
-- ===========================================
CREATE INDEX IF NOT EXISTS idx_messages_id_dossier_cree_le
ON messages (id_dossier, cree_le);
ON messages (id_dossier, cree_le DESC);
-- Recherche par expéditeur
CREATE INDEX IF NOT EXISTS idx_messages_id_expediteur_cree_le
ON messages (id_expediteur, cree_le);
-- =========
-- CONTRATS
-- =========
-- UNIQUE(id_dossier) existe déjà -> index implicite
-- Tri / filtres fréquents
CREATE INDEX IF NOT EXISTS idx_contrats_statut
ON contrats (statut);
-- JSONB planning : activer si on requête par clé
-- CREATE INDEX IF NOT EXISTS idx_contrats_planning_gin
-- ON contrats USING GIN (planning);
-- ==================
-- AVENANTS_CONTRATS
-- ==================
CREATE INDEX IF NOT EXISTS idx_avenants_contrats_id_contrat_cree_le
ON avenants_contrats (id_contrat, cree_le);
CREATE INDEX IF NOT EXISTS idx_avenants_contrats_initie_par
ON avenants_contrats (initie_par);
CREATE INDEX IF NOT EXISTS idx_avenants_contrats_statut
ON avenants_contrats (statut);
-- =========
-- EVENEMENTS
-- =========
-- Accès par enfant + période
-- ===========================================
-- Evenements (filtrés par enfant + date)
-- Utilisé dans verify.sql (WHERE id_enfant AND date_debut >= ...)
-- ===========================================
CREATE INDEX IF NOT EXISTS idx_evenements_id_enfant_date_debut
ON evenements (id_enfant, date_debut);
ON evenements (id_enfant, date_debut DESC);
-- Filtrage par auteur / AM / parent
CREATE INDEX IF NOT EXISTS idx_evenements_cree_par
ON evenements (cree_par);
CREATE INDEX IF NOT EXISTS idx_evenements_id_am
ON evenements (id_am);
CREATE INDEX IF NOT EXISTS idx_evenements_id_parent
ON evenements (id_parent);
CREATE INDEX IF NOT EXISTS idx_evenements_type
ON evenements (type);
CREATE INDEX IF NOT EXISTS idx_evenements_statut
ON evenements (statut);
-- =================
-- SIGNALEMENTS_BUGS
-- =================
CREATE INDEX IF NOT EXISTS idx_signalements_bugs_id_utilisateur_cree_le
ON signalements_bugs (id_utilisateur, cree_le);
-- =======
-- UPLOADS
-- =======
CREATE INDEX IF NOT EXISTS idx_uploads_id_utilisateur_cree_le
ON uploads (id_utilisateur, cree_le);
-- =============
-- NOTIFICATIONS
-- =============
-- Requêtes fréquentes : non lues + ordre chrono
-- ===========================================
-- Notifications (par utilisateur + lues/non lues + tri par date)
-- Utilisé dans verify.sql (WHERE id_utilisateur AND lu=false ORDER BY cree_le DESC)
-- ===========================================
CREATE INDEX IF NOT EXISTS idx_notifications_user_lu_cree_le
ON notifications (id_utilisateur, lu, cree_le);
ON notifications (id_utilisateur, lu, cree_le DESC);
-- Option : index partiel pour "non lues"
-- CREATE INDEX IF NOT EXISTS idx_notifications_non_lues
-- ON notifications (id_utilisateur, cree_le)
-- WHERE lu = false;
-- ===========================================
-- Dossiers (filtrés par parent + enfant + statut + tri par date)
-- Utilisé dans verify.sql
-- ===========================================
CREATE INDEX IF NOT EXISTS idx_dossiers_id_parent_enfant_statut_cree_le
ON dossiers (id_parent, id_enfant, statut, cree_le DESC);
-- ===========
-- VALIDATIONS
-- ===========
-- Requêtes par utilisateur validé, par statut et par date
CREATE INDEX IF NOT EXISTS idx_validations_id_utilisateur_cree_le
ON validations (id_utilisateur, cree_le);
-- ===========================================
-- Contrats (lookup par dossier unique)
-- ===========================================
CREATE UNIQUE INDEX IF NOT EXISTS idx_contrats_id_dossier
ON contrats (id_dossier);
CREATE INDEX IF NOT EXISTS idx_validations_statut
ON validations (statut);
-- ===========================================
-- JSONB indexes (si filtres fréquents sur planning/planning_souhaite)
-- Exemple dans verify.sql : dossiers.planning_souhaite @> ...
-- ===========================================
CREATE INDEX IF NOT EXISTS idx_dossiers_planning_souhaite_gin
ON dossiers USING gin (planning_souhaite jsonb_path_ops);
CREATE INDEX IF NOT EXISTS idx_contrats_planning_gin
ON contrats USING gin (planning jsonb_path_ops);
-- ===========================================
-- Sanity check : validation des emails uniques
-- Déjà couvert par UNIQUE dans la table utilisateurs
-- ===========================================
-- ===========================================
-- Bonus : accélérer les jointures fréquentes
-- (utilisateurs.id, enfants.id, parents.id_utilisateur sont PK → déjà indexées automatiquement)
-- ===========================================
+58 -124
View File
@@ -1,140 +1,74 @@
-- =============================================
-- 03_checks.sql : Contraintes CHECK & NOT NULL
-- A exécuter après 01_init.sql (et 02_indexes.sql)
-- =============================================
-- ============================================================
-- 03_checks.sql Contraintes d'intégrité métier
-- ============================================================
-- ==============
-- ===========================================
-- UTILISATEURS
-- ==============
-- (Regex email déjà présente dans 01_init.sql)
-- Optionnel : forcer prenom/nom non vides si fournis
-- ===========================================
-- Email déjà couvert par UNIQUE + regex dans le schéma
-- Vérifier que mobile et téléphone fixe ne sont pas identiques
ALTER TABLE utilisateurs
ADD CONSTRAINT chk_utilisateurs_prenom_non_vide
CHECK (prenom IS NULL OR btrim(prenom) <> ''),
ADD CONSTRAINT chk_utilisateurs_nom_non_vide
CHECK (nom IS NULL OR btrim(nom) <> '');
-- =========================
-- ASSISTANTES_MATERNELLES
-- =========================
-- NIR : aujourdhui en 15 chiffres (Sprint 2 : chiffrement)
ALTER TABLE assistantes_maternelles
ADD CONSTRAINT chk_am_nir_format
CHECK (nir_chiffre IS NULL OR nir_chiffre ~ '^[0-9]{15}$'),
ADD CONSTRAINT chk_am_nb_max_enfants
CHECK (nb_max_enfants IS NULL OR nb_max_enfants BETWEEN 0 AND 10),
ADD CONSTRAINT chk_am_ville_non_vide
CHECK (ville_residence IS NULL OR btrim(ville_residence) <> '');
-- =========
-- PARENTS
-- =========
-- Interdiction d’être co-parent de soi-même
ALTER TABLE parents
ADD CONSTRAINT chk_parents_co_parent_diff
CHECK (id_co_parent IS NULL OR id_co_parent <> id_utilisateur);
-- =========
-- ENFANTS
-- =========
-- Cohérence statut / dates de naissance
ALTER TABLE enfants
ADD CONSTRAINT chk_enfants_dates_exclusives
CHECK (NOT (date_naissance IS NOT NULL AND date_prevue_naissance IS NOT NULL)),
ADD CONSTRAINT chk_enfants_statut_dates
CHECK (
-- a_naitre => date_prevue_naissance requise
(statut = 'a_naitre' AND date_prevue_naissance IS NOT NULL)
OR
-- actif/scolarise => date_naissance requise
(statut IN ('actif','scolarise') AND date_naissance IS NOT NULL)
OR statut IS NULL -- si statut non encore fixé
),
ADD CONSTRAINT chk_enfants_consentement_coherent
CHECK (
(consentement_photo = true AND date_consentement_photo IS NOT NULL)
OR
(consentement_photo = false AND date_consentement_photo IS NULL)
ADD CONSTRAINT chk_telephone_diff CHECK (
mobile IS NULL OR telephone_fixe IS NULL OR mobile <> telephone_fixe
);
-- =================
-- ENFANTS_PARENTS
-- =================
-- (PK composite déjà en place, rien à ajouter ici)
-- ===========================================
-- ENFANTS
-- ===========================================
-- ========
-- Si lenfant est "a_naitre", la date_prevue_naissance doit être renseignée
ALTER TABLE enfants
ADD CONSTRAINT chk_enfant_date_prevue
CHECK (
(statut = 'a_naitre' AND date_prevue_naissance IS NOT NULL)
OR (statut <> 'a_naitre')
);
-- Si lenfant est déjà né, sa date_naissance doit être renseignée
ALTER TABLE enfants
ADD CONSTRAINT chk_enfant_date_naissance
CHECK (
(statut IN ('actif','scolarise') AND date_naissance IS NOT NULL)
OR (statut = 'a_naitre')
);
-- ===========================================
-- DOSSIERS
-- ========
-- ===========================================
-- Budget positif si renseigné
ALTER TABLE dossiers
ADD CONSTRAINT chk_dossiers_budget_nonneg
CHECK (budget IS NULL OR budget >= 0),
ADD CONSTRAINT chk_dossiers_type_contrat_non_vide
CHECK (type_contrat IS NULL OR btrim(type_contrat) <> ''),
ADD CONSTRAINT chk_dossiers_planning_json
CHECK (planning_souhaite IS NULL OR jsonb_typeof(planning_souhaite) = 'object');
ADD CONSTRAINT chk_dossier_budget_pos CHECK (budget IS NULL OR budget > 0);
-- ========
-- MESSAGES
-- ========
-- Contenu obligatoire, non vide
ALTER TABLE messages
ALTER COLUMN contenu SET NOT NULL;
ALTER TABLE messages
ADD CONSTRAINT chk_messages_contenu_non_vide
CHECK (btrim(contenu) <> '');
-- =========
-- ===========================================
-- CONTRATS
-- =========
-- ===========================================
-- Cohérence des dates : date_debut obligatoire pour un contrat signé
ALTER TABLE contrats
ADD CONSTRAINT chk_contrats_tarif_nonneg
CHECK (tarif_horaire IS NULL OR tarif_horaire >= 0),
ADD CONSTRAINT chk_contrats_indemnites_nonneg
CHECK (indemnites_repas IS NULL OR indemnites_repas >= 0);
ADD CONSTRAINT chk_contrat_date_debut
CHECK (
(statut = 'brouillon' OR date_debut IS NOT NULL)
);
-- ==================
-- AVENANTS_CONTRATS
-- ==================
-- Rien de spécifique (statut enum déjà en place)
-- =========
-- ===========================================
-- EVENEMENTS
-- =========
-- ===========================================
-- Cohérence des dates : début < fin
ALTER TABLE evenements
ADD CONSTRAINT chk_evenements_dates_coherentes
CHECK (date_fin IS NULL OR date_debut IS NULL OR date_fin >= date_debut);
ADD CONSTRAINT chk_evenement_dates
CHECK (date_fin IS NULL OR date_debut <= date_fin);
-- =================
-- SIGNALEMENTS_BUGS
-- =================
-- Description obligatoire, non vide
ALTER TABLE signalements_bugs
ALTER COLUMN description SET NOT NULL;
ALTER TABLE signalements_bugs
ADD CONSTRAINT chk_bugs_description_non_vide
CHECK (btrim(description) <> '');
-- =======
-- UPLOADS
-- =======
-- URL obligatoire + format basique (chemin absolu ou http(s))
ALTER TABLE uploads
ALTER COLUMN fichier_url SET NOT NULL;
ALTER TABLE uploads
ADD CONSTRAINT chk_uploads_url_format
CHECK (fichier_url ~ '^(https?://.+|/[^\\s]+)$');
-- =============
-- NOTIFICATIONS
-- =============
-- Contenu obligatoire, non vide
ALTER TABLE notifications
ALTER COLUMN contenu SET NOT NULL;
ALTER TABLE notifications
ADD CONSTRAINT chk_notifications_contenu_non_vide
CHECK (btrim(contenu) <> '');
-- ===========
-- ===========================================
-- VALIDATIONS
-- ===========
-- Rien de plus ici (Sprint 1 Ticket 8 enrichira la table)
-- ===========================================
-- Si statut = 'valide', alors modifie_le doit être >= cree_le
ALTER TABLE validations
ADD CONSTRAINT chk_validation_modif
CHECK (
(statut <> 'valide')
OR (modifie_le IS NULL OR modifie_le >= cree_le)
);
+85 -132
View File
@@ -1,150 +1,103 @@
-- =============================================
-- 05_triggers.sql : Timestamps automatiques
-- - Ajoute (si absent) cree_le DEFAULT now() et modifie_le DEFAULT now()
-- - Crée un trigger BEFORE UPDATE pour mettre à jour modifie_le
-- - Idempotent (DROP TRIGGER IF EXISTS / IF NOT EXISTS)
-- A exécuter après 01_init.sql, 02_indexes.sql, 03_checks.sql
-- =============================================
-- ============================================================
-- 05_triggers.sql Triggers métier et maintenance
-- ============================================================
-- 1) Fonction unique de mise à jour du timestamp
-- ===========================================
-- 1. Mettre à jour automatiquement modifie_le
-- ===========================================
CREATE OR REPLACE FUNCTION set_modifie_le()
RETURNS TRIGGER AS $$
BEGIN
NEW.modifie_le := NOW();
RETURN NEW;
NEW.modifie_le := now();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- Helper macro-like: pour chaque table, s'assurer des colonnes & trigger
-- (on ne peut pas faire de macro, donc on répète pour chaque table)
-- Liste des tables concernées :
-- utilisateurs, assistantes_maternelles, parents, enfants, enfants_parents,
-- dossiers, messages, contrats, avenants_contrats, evenements,
-- signalements_bugs, uploads, notifications, validations
-- ========== UTILISATEURS
ALTER TABLE utilisateurs
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_utilisateurs_set_modifie_le ON utilisateurs;
CREATE TRIGGER trg_utilisateurs_set_modifie_le
-- Appliquer sur les tables qui ont modifie_le
CREATE TRIGGER trg_update_modifie_le_utilisateurs
BEFORE UPDATE ON utilisateurs
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
FOR EACH ROW
EXECUTE FUNCTION set_modifie_le();
-- ========== ASSISTANTES_MATERNELLES
ALTER TABLE assistantes_maternelles
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_am_set_modifie_le ON assistantes_maternelles;
CREATE TRIGGER trg_am_set_modifie_le
BEFORE UPDATE ON assistantes_maternelles
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== PARENTS
ALTER TABLE parents
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_parents_set_modifie_le ON parents;
CREATE TRIGGER trg_parents_set_modifie_le
BEFORE UPDATE ON parents
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== ENFANTS
ALTER TABLE enfants
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_enfants_set_modifie_le ON enfants;
CREATE TRIGGER trg_enfants_set_modifie_le
BEFORE UPDATE ON enfants
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== ENFANTS_PARENTS (table de liaison)
ALTER TABLE enfants_parents
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_enfants_parents_set_modifie_le ON enfants_parents;
CREATE TRIGGER trg_enfants_parents_set_modifie_le
BEFORE UPDATE ON enfants_parents
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== DOSSIERS
ALTER TABLE dossiers
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_dossiers_set_modifie_le ON dossiers;
CREATE TRIGGER trg_dossiers_set_modifie_le
CREATE TRIGGER trg_update_modifie_le_dossiers
BEFORE UPDATE ON dossiers
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
FOR EACH ROW
EXECUTE FUNCTION set_modifie_le();
-- ========== MESSAGES
ALTER TABLE messages
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_messages_set_modifie_le ON messages;
CREATE TRIGGER trg_messages_set_modifie_le
BEFORE UPDATE ON messages
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== CONTRATS
ALTER TABLE contrats
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_contrats_set_modifie_le ON contrats;
CREATE TRIGGER trg_contrats_set_modifie_le
CREATE TRIGGER trg_update_modifie_le_contrats
BEFORE UPDATE ON contrats
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
FOR EACH ROW
EXECUTE FUNCTION set_modifie_le();
-- ========== AVENANTS_CONTRATS
ALTER TABLE avenants_contrats
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_avenants_contrats_set_modifie_le ON avenants_contrats;
CREATE TRIGGER trg_avenants_contrats_set_modifie_le
CREATE TRIGGER trg_update_modifie_le_avenants
BEFORE UPDATE ON avenants_contrats
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
FOR EACH ROW
EXECUTE FUNCTION set_modifie_le();
-- ========== EVENEMENTS
ALTER TABLE evenements
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_evenements_set_modifie_le ON evenements;
CREATE TRIGGER trg_evenements_set_modifie_le
CREATE TRIGGER trg_update_modifie_le_evenements
BEFORE UPDATE ON evenements
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
FOR EACH ROW
EXECUTE FUNCTION set_modifie_le();
-- ========== SIGNALEMENTS_BUGS
ALTER TABLE signalements_bugs
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_signalements_bugs_set_modifie_le ON signalements_bugs;
CREATE TRIGGER trg_signalements_bugs_set_modifie_le
BEFORE UPDATE ON signalements_bugs
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== UPLOADS
ALTER TABLE uploads
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_uploads_set_modifie_le ON uploads;
CREATE TRIGGER trg_uploads_set_modifie_le
BEFORE UPDATE ON uploads
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== NOTIFICATIONS
ALTER TABLE notifications
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_notifications_set_modifie_le ON notifications;
CREATE TRIGGER trg_notifications_set_modifie_le
BEFORE UPDATE ON notifications
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
-- ========== VALIDATIONS
ALTER TABLE validations
ADD COLUMN IF NOT EXISTS cree_le TIMESTAMP DEFAULT NOW(),
ADD COLUMN IF NOT EXISTS modifie_le TIMESTAMP DEFAULT NOW();
DROP TRIGGER IF EXISTS trg_validations_set_modifie_le ON validations;
CREATE TRIGGER trg_validations_set_modifie_le
CREATE TRIGGER trg_update_modifie_le_validations
BEFORE UPDATE ON validations
FOR EACH ROW EXECUTE FUNCTION set_modifie_le();
FOR EACH ROW
EXECUTE FUNCTION set_modifie_le();
-- ===========================================
-- 2. Empêcher plusieurs contrats actifs par dossier
-- ===========================================
CREATE OR REPLACE FUNCTION prevent_multiple_active_contrats()
RETURNS TRIGGER AS $$
DECLARE
nb_actifs INT;
BEGIN
IF NEW.statut IN ('en_attente_signature','valide') THEN
SELECT COUNT(*) INTO nb_actifs
FROM contrats
WHERE id_dossier = NEW.id_dossier
AND statut IN ('en_attente_signature','valide')
AND id <> COALESCE(NEW.id, gen_random_uuid());
IF nb_actifs > 0 THEN
RAISE EXCEPTION 'Un contrat actif existe déjà pour ce dossier (%).', NEW.id_dossier;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_one_active_contrat_per_dossier
BEFORE INSERT OR UPDATE ON contrats
FOR EACH ROW
EXECUTE FUNCTION prevent_multiple_active_contrats();
-- ===========================================
-- 3. Empêcher événements incohérents
-- - Pas de chevauchement de congés pour le même AM
-- ===========================================
CREATE OR REPLACE FUNCTION prevent_overlapping_events()
RETURNS TRIGGER AS $$
DECLARE
nb_overlap INT;
BEGIN
IF NEW.id_am IS NOT NULL AND NEW.date_debut IS NOT NULL AND NEW.date_fin IS NOT NULL THEN
SELECT COUNT(*) INTO nb_overlap
FROM evenements ev
WHERE ev.id_am = NEW.id_am
AND ev.id <> COALESCE(NEW.id, gen_random_uuid())
AND ev.date_fin >= NEW.date_debut
AND ev.date_debut <= NEW.date_fin;
IF nb_overlap > 0 THEN
RAISE EXCEPTION 'Conflit d''événements : chevauchement détecté pour AM %', NEW.id_am;
END IF;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_no_overlapping_am_events
BEFORE INSERT OR UPDATE ON evenements
FOR EACH ROW
EXECUTE FUNCTION prevent_overlapping_events();
+42
View 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
View File
@@ -0,0 +1,13 @@
{
"Servers": {
"1": {
"Name": "Postgres Dev",
"Group": "Local",
"Host": "postgres",
"Port": 5432,
"Username": "admin",
"SSLMode": "prefer",
"MaintenanceDB": "ptitpas_db"
}
}
}
+124
View File
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""scripts/clean_csv.py
Usage:
python3 scripts/clean_csv.py [input_dir] [--out-dir OUT] [--dry-run]
Ce script nettoie les fichiers CSV (trim, suppression de 'NULL', correction du
nombre de colonnes) et réalise des validations simples (colonnes ressemblant
à des UUID, colonnes date).
"""
import csv
import sys
from pathlib import Path
from datetime import datetime
import argparse
import re
UUID_RE = re.compile(r'^[0-9a-fA-F-]{36}$')
def is_uuid(s: str) -> bool:
return bool(UUID_RE.match(s))
def looks_like_date(s: str) -> bool:
if not s:
return False
# try ISO-like detection
try:
datetime.fromisoformat(s)
return True
except Exception:
return False
def clean_csv_file(file_path: Path, out_path: Path = None, dry_run: bool = False):
cleaned_rows = []
errors = []
with file_path.open("r", encoding="utf-8-sig", newline="") as infile:
reader = csv.reader(infile)
try:
header = next(reader)
except StopIteration:
return {'file': str(file_path), 'error': 'empty', 'errors': []}
nb_cols = len(header)
cleaned_rows.append([h.strip() for h in header])
# Heuristiques pour les validations
uuid_cols = [i for i, h in enumerate(header) if h.lower() == 'id' or h.lower().endswith('_id')]
date_cols = [i for i, h in enumerate(header) if any(k in h.lower() for k in ('date', '_le', '_at'))]
for i, row in enumerate(reader, start=2):
if len(row) < nb_cols:
row.extend([""] * (nb_cols - len(row)))
elif len(row) > nb_cols:
row = row[:nb_cols]
# Nettoyage cellule par cellule
row = [cell.strip().replace('NULL', '') for cell in row]
# Validations simples
for ci in uuid_cols:
if ci < len(row) and row[ci]:
if not is_uuid(row[ci]):
errors.append(f"Ligne {i} : colonne {header[ci]} ne ressemble pas à un UUID : {row[ci]!r}")
for ci in date_cols:
if ci < len(row) and row[ci]:
if not looks_like_date(row[ci]):
errors.append(f"Ligne {i} : colonne {header[ci]} n'est pas une date ISO-like : {row[ci]!r}")
cleaned_rows.append(row)
# Écrire la sortie si non --dry-run
if not dry_run:
target = out_path if out_path else file_path
with target.open('w', encoding='utf-8', newline='') as outfile:
writer = csv.writer(outfile)
writer.writerows(cleaned_rows)
return {'file': str(file_path), 'errors': errors}
def main():
parser = argparse.ArgumentParser()
parser.add_argument('input_dir', nargs='?', default='bdd/data_test')
parser.add_argument('--out-dir', '-o', help='Optional output directory for cleaned files')
parser.add_argument('--dry-run', action='store_true', help='Do not write files, just report')
args = parser.parse_args()
base_dir = Path(args.input_dir)
out_dir = Path(args.out_dir) if args.out_dir else None
if out_dir and not out_dir.exists():
out_dir.mkdir(parents=True)
results = []
for file_path in sorted(base_dir.glob('*.csv')):
out_path = out_dir / file_path.name if out_dir else None
res = clean_csv_file(file_path, out_path, args.dry_run)
results.append(res)
# Rapport
any_errors = False
for r in results:
if 'error' in r:
print(f"[AVERT] {r['file']}: {r['error']}")
if r.get('errors'):
any_errors = True
print(f"[ERR] {r['file']} ->")
for e in r['errors'][:20]:
print(' -', e)
else:
print(f"[OK] {r['file']}")
if any_errors:
print('\nCertains fichiers présentent des problèmes de validation. Corrigez-les ou lancez avec --dry-run pour inspecter.')
sys.exit(2)
else:
print('\nTous les fichiers ont été nettoyés avec succès.')
if __name__ == '__main__':
main()
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""
scripts/sync_enums.py
Génère une migration SQL non destructive pour ajouter les valeurs d'ENUM
manquantes en se basant sur les CSV de `bdd/data_test`.
Usage:
python3 scripts/sync_enums.py > migrations/00_sync_enums.sql
Relire le SQL généré avant application en production.
"""
from pathlib import Path
import csv
from collections import defaultdict
import sys
# Mapping colonne CSV -> nom du type enum en base
# NOTE: mapping is now based on filename (more precise)
CSV_DIR = Path('bdd/data_test')
# Define per-file mappings: filename stem -> {column_name: enum_type}
FILE_COLUMN_ENUM_MAP = {
'utilisateurs': {
'role': 'role_type',
'genre': 'genre_type',
'statut': 'statut_utilisateur_type',
},
'enfants': {
'statut': 'statut_enfant_type',
'genre': 'genre_type',
},
'contrats': {
'statut': 'statut_contrat_type',
},
'dossiers': {
'statut': 'statut_dossier_type',
},
'evenements': {
'type': 'type_evenement_type',
'statut': 'statut_evenement_type',
},
'validations': {
'statut': 'statut_validation_type',
}
}
def quote_sql_literal(s: str) -> str:
return "'" + s.replace("'", "''") + "'"
def discover_values():
# heuristique : map common column names to enums
enum_values = defaultdict(set)
for p in sorted(CSV_DIR.glob('*.csv')):
try:
with p.open(newline='', encoding='utf-8') as fh:
reader = csv.DictReader(fh)
fname = p.stem
per_file_map = FILE_COLUMN_ENUM_MAP.get(fname, {})
for row in reader:
for col, enum in per_file_map.items():
if col in row and row[col] is not None:
v = row[col].strip()
if v != '':
enum_values[enum].add(v)
except Exception as e:
print(f"-- Error reading {p}: {e}", file=sys.stderr)
return enum_values
def emit_sql(enum_values):
lines = []
lines.append('-- Generated by scripts/sync_enums.py — review before applying')
for enum_type, vals in enum_values.items():
if not vals:
continue
for v in sorted(vals):
lit = quote_sql_literal(v)
block = f"""
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_enum e
JOIN pg_type t ON e.enumtypid = t.oid
WHERE t.typname = {quote_sql_literal(enum_type)}
AND e.enumlabel = {lit}
) THEN
ALTER TYPE {enum_type} ADD VALUE {lit};
END IF;
END$$;
"""
lines.append(block)
return '\n'.join(lines)
def main():
enum_values = discover_values()
sql = emit_sql(enum_values)
print(sql)
if __name__ == '__main__':
main()
+64
View File
@@ -0,0 +1,64 @@
-- ==============================================
-- Script de vérification d'intégrité des données
-- ==============================================
-- 1) Vérifier unicité des emails
SELECT email, COUNT(*) AS doublons
FROM utilisateurs
GROUP BY email
HAVING COUNT(*) > 1;
-- 2) Vérifier utilisateurs sans rôle
SELECT id, email
FROM utilisateurs
WHERE role IS NULL OR role = '';
-- 3) Vérifier cohérence enfants → parents
SELECT e.id AS enfant_id, e.nom, e.prenom
FROM enfants e
LEFT JOIN enfants_parents ep ON e.id = ep.enfant_id
WHERE ep.parent_id IS NULL;
-- 4) Vérifier cohérence enfants_parents → parents existants
SELECT ep.enfant_id, ep.parent_id
FROM enfants_parents ep
LEFT JOIN parents p ON ep.parent_id = p.id
WHERE p.id IS NULL;
-- 5) Vérifier cohérence dossiers → utilisateurs
SELECT d.id, d.parent_id
FROM dossiers d
LEFT JOIN parents p ON d.parent_id = p.id
WHERE p.id IS NULL;
-- 6) Vérifier cohérence contrats → utilisateurs et assistantes
SELECT c.id, c.parent_id, c.assistante_id
FROM contrats c
LEFT JOIN parents p ON c.parent_id = p.id
LEFT JOIN assistantes_maternelles am ON c.assistante_id = am.id
WHERE p.id IS NULL OR am.id IS NULL;
-- 7) Vérifier cohérence validations → utilisateurs
SELECT v.id, v.valide_par
FROM validations v
LEFT JOIN utilisateurs u ON v.valide_par = u.id
WHERE u.id IS NULL;
-- 8) Vérifier cohérence dates enfants
SELECT id, nom, prenom, date_naissance
FROM enfants
WHERE date_naissance > CURRENT_DATE;
-- 9) Vérifier cohérence date agrément assistantes
SELECT id, nom, prenom, date_agrement
FROM assistantes_maternelles
WHERE date_agrement > CURRENT_DATE;
-- 10) Vérifier NULL obligatoires (ex: NIR des assistantes)
SELECT id, nom, prenom
FROM assistantes_maternelles
WHERE nir IS NULL OR nir = '';
-- ==============================================
-- Fin du script
-- ==============================================
-221
View File
@@ -1,221 +0,0 @@
-- ============================================================
-- 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 lAM 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;
+9 -33
View File
@@ -1,23 +1,5 @@
-- ============================================================
-- 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 ===================================='
@@ -38,9 +20,9 @@ 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,
u.email AS parent_email,
p.id_co_parent,
uc.courriel AS co_parent_email
uc.email 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
@@ -52,21 +34,21 @@ 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
SELECT ep.id_parent, up.email 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
SELECT d.id, up.email 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
SELECT m.id, m.id_dossier, m.id_expediteur, ue.email 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;
@@ -87,22 +69,20 @@ 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
SELECT u.email, up.fichier_url, up.type AS fichier_type, 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
SELECT u.email, 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
SELECT v.id, u.email AS utilisateur_email, v.type, v.statut, 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
LEFT JOIN utilisateurs u ON u.id = v.id_utilisateur
ORDER BY v.cree_le DESC;
-- ============================================================
@@ -176,19 +156,15 @@ WHERE d.id_parent = '33333333-3333-3333-3333-333333333333'
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 dun 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,
+188
View File
@@ -0,0 +1,188 @@
=== 0) Version & horodatage ====================================
version
--------------------------------------------------------------------------------------------------------------------
PostgreSQL 17.6 (Debian 17.6-1.pgdg13+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
(1 row)
executed_at
-------------------------------
2025-09-22 08:30:37.786643+00
(1 row)
=== 1) Comptes & répartition par rôle ==========================
role | nb
-----------------------+----
parent | 6
assistante_maternelle | 3
administrateur | 1
gestionnaire | 1
super_admin | 1
(5 rows)
=== 2) Utilisateurs en attente / acceptés / rejetés ============
statut | nb
--------+----
actif | 12
(1 row)
=== 3) Parents avec co-parents (NULL si pas de co-parent) ======
parent_id | parent_email | id_co_parent | co_parent_email
--------------------------------------+------------------------------+--------------------------------------+------------------------------
f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b | amelie.durand@ptits-pas.fr | b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e | julien.rousseau@ptits-pas.fr
c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f | claire.martin@ptits-pas.fr | |
d3e5f7a9-1c2b-4d6e-8f3a-2b4c6d8e9f1a | david.lecomte@ptits-pas.fr | |
b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e | julien.rousseau@ptits-pas.fr | f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b | amelie.durand@ptits-pas.fr
(4 rows)
=== 4) Enfants (statut, dates cohérentes) ======================
id | prenom | nom | statut | date_naissance | date_prevue_naissance
--------------------------------------+--------+----------+-----------+----------------+-----------------------
5e8574b7-63e6-4d48-9af3-8d3bf7a6a6cf | Emma | Dupont | actif | 2020-06-01 |
edd19cd1-bb67-4f14-8a37-c66b75c94537 | Lucas | Durand | scolarise | 2018-09-15 |
e6f7a8b9-c1d2-4e3f-5a6b-7c8d9e0f1a2b | Maxime | Lecomte | actif | 2023-04-15 |
e1a2b3c4-d5e6-4f7a-8b9c-1d2e3f4a5b6c | Emma | Martin | actif | 2023-02-15 |
e3c4d5e6-f7a8-4b9c-1d2e-3f4a5b6c7d8e | Léa | Martin | actif | 2023-02-15 |
e2b3c4d5-e6f7-4a8b-9c1d-2e3f4a5b6c7d | Noah | Martin | actif | 2023-02-15 |
e4d5e6f7-a8b9-4c1d-2e3f-4a5b6c7d8e9f | Chloé | Rousseau | actif | 2022-04-20 |
e5e6f7a8-b9c1-4d2e-3f4a-5b6c7d8e9f1a | Hugo | Rousseau | actif | 2024-03-10 |
a5c3268e-07eb-41a4-9f6c-2f9f16f37c3d | | | actif | 2020-01-01 | 2025-01-01
(9 rows)
=== 5) Liaison N:N parents_enfants =============================
id_parent | parent_email | id_enfant | enfant
--------------------------------------+------------------------------+--------------------------------------+--------
f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b | amelie.durand@ptits-pas.fr | e4d5e6f7-a8b9-4c1d-2e3f-4a5b6c7d8e9f | Chloé
f1d3c5b7-8a9e-4f2d-9c1b-3e7a5d8c2f1b | amelie.durand@ptits-pas.fr | e5e6f7a8-b9c1-4d2e-3f4a-5b6c7d8e9f1a | Hugo
c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f | claire.martin@ptits-pas.fr | e1a2b3c4-d5e6-4f7a-8b9c-1d2e3f4a5b6c | Emma
c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f | claire.martin@ptits-pas.fr | e3c4d5e6-f7a8-4b9c-1d2e-3f4a5b6c7d8e | Léa
c4e2d1f5-6b7a-4c3d-8f2a-1e9c3b5a7d6f | claire.martin@ptits-pas.fr | e2b3c4d5-e6f7-4a8b-9c1d-2e3f4a5b6c7d | Noah
d3e5f7a9-1c2b-4d6e-8f3a-2b4c6d8e9f1a | david.lecomte@ptits-pas.fr | e6f7a8b9-c1d2-4e3f-5a6b-7c8d9e0f1a2b | Maxime
b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e | julien.rousseau@ptits-pas.fr | e4d5e6f7-a8b9-4c1d-2e3f-4a5b6c7d8e9f | Chloé
b6c4d2e3-5f7a-4b8c-9d1e-2a3c5f7b8d9e | julien.rousseau@ptits-pas.fr | e5e6f7a8-b9c1-4d2e-3f4a-5b6c7d8e9f1a | Hugo
(8 rows)
=== 6) Dossiers (parent, enfant, statut) =======================
id | parent_email | enfant | statut | budget
--------------------------------------+----------------------------+--------+--------+---------
bb9c30a0-60b4-4832-9947-8a7d2366673d | amelie.durand@ptits-pas.fr | Emma | envoye | 1200.00
(1 row)
=== 7) Messages par dossier (ordre chronologique) ==============
id | id_dossier | id_expediteur | expediteur_email | contenu | cree_le
----+------------+---------------+------------------+---------+---------
(0 rows)
=== 8) Contrats 1:1 avec dossier + avenants ====================
contrat_id | id_dossier | statut | nb_avenants
--------------------------------------+--------------------------------------+-----------+-------------
f09c6ffa-4627-4aa8-b20b-829c2c828f0d | bb9c30a0-60b4-4832-9947-8a7d2366673d | brouillon | 0
(1 row)
=== 9) Evénements par enfant (30 derniers jours) ==============
id | type | id_enfant | enfant | date_debut | date_fin | statut
----+------+-----------+--------+------------+----------+--------
(0 rows)
=== 10) Uploads & notifications récentes =======================
email | fichier_url | fichier_type | cree_le
---------------------+--------------------------------+--------------+-------------------------------
parent1@example.com | https://placeholder.local/file | image | 2025-09-02 12:57:35.140078+00
(1 row)
email | contenu | lu | cree_le
---------------------+-----------------------------+----+-------------------------------
parent1@example.com | Votre dossier a été accepté | f | 2025-09-02 12:57:42.845264+00
(1 row)
=== 11) Validations (qui a validé quoi) ========================
id | utilisateur_email | type | statut | cree_le
--------------------------------------+---------------------------+----------+--------+-------------------------------
fcc45701-5708-4368-b467-b95ddb7e1580 | marie.dubois@ptits-pas.fr | | valide | 2025-09-09 14:52:47.339858+00
be1c4779-341b-436d-b17e-8bc486d22ef8 | am1@example.com | | valide | 2025-09-09 14:47:01.963573+00
8ec99565-e8c2-469f-9641-01b99b8281eb | am1@example.com | identité | valide | 2025-09-02 12:57:49.846424+00
(3 rows)
=== 12) Orphelins potentiels (doivent renvoyer 0 ligne) =======
id | id_dossier | id_expediteur | contenu | re_redige_par_ia | cree_le
----+------------+---------------+---------+------------------+---------
(0 rows)
id_parent | id_enfant
-----------+-----------
(0 rows)
id | id_dossier | planning | tarif_horaire | indemnites_repas | date_debut | statut | signe_parent | signe_am | finalise_le | cree_le | modifie_le
----+------------+----------+---------------+------------------+------------+--------+--------------+----------+-------------+---------+------------
(0 rows)
id | id_contrat | modifications | initie_par | statut | cree_le | modifie_le
----+------------+---------------+------------+--------+---------+------------
(0 rows)
id | type | id_enfant | id_am | id_parent | cree_par | date_debut | date_fin | commentaires | statut | delai_grace | urgent | cree_le | modifie_le
----+------+-----------+-------+-----------+----------+------------+----------+--------------+--------+-------------+--------+---------+------------
(0 rows)
=== 13) Performance : EXPLAIN sur requêtes clés ===============
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=11.31..11.31 rows=3 width=89) (actual time=0.020..0.021 rows=0 loops=1)
-> Sort (cost=11.31..11.31 rows=3 width=89) (actual time=0.019..0.019 rows=0 loops=1)
Sort Key: cree_le DESC
Sort Method: quicksort Memory: 25kB
-> Bitmap Heap Scan on messages m (cost=4.17..11.28 rows=3 width=89) (actual time=0.013..0.014 rows=0 loops=1)
Recheck Cond: (id_dossier = 'dddddddd-dddd-dddd-dddd-dddddddddddd'::uuid)
-> Bitmap Index Scan on idx_messages_id_dossier_cree_le (cost=0.00..4.17 rows=3 width=0) (actual time=0.006..0.007 rows=0 loops=1)
Index Cond: (id_dossier = 'dddddddd-dddd-dddd-dddd-dddddddddddd'::uuid)
Planning Time: 0.106 ms
Execution Time: 0.105 ms
(10 rows)
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------
Index Scan using idx_evenements_id_enfant_date_debut on evenements ev (cost=0.15..8.17 rows=1 width=161) (actual time=0.021..0.021 rows=0 loops=1)
Index Cond: ((id_enfant = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid) AND (date_debut >= '2025-01-01 00:00:00+00'::timestamp with time zone))
Planning Time: 0.064 ms
Execution Time: 0.032 ms
(4 rows)
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=9.52..9.53 rows=2 width=73) (actual time=0.018..0.019 rows=0 loops=1)
-> Sort (cost=9.52..9.53 rows=2 width=73) (actual time=0.017..0.018 rows=0 loops=1)
Sort Key: cree_le DESC
Sort Method: quicksort Memory: 25kB
-> Bitmap Heap Scan on notifications n (cost=4.17..9.51 rows=2 width=73) (actual time=0.014..0.014 rows=0 loops=1)
Recheck Cond: ((id_utilisateur = '33333333-3333-3333-3333-333333333333'::uuid) AND (NOT lu))
-> Bitmap Index Scan on idx_notifications_user_lu_cree_le (cost=0.00..4.17 rows=2 width=0) (actual time=0.012..0.012 rows=0 loops=1)
Index Cond: ((id_utilisateur = '33333333-3333-3333-3333-333333333333'::uuid) AND (lu = false))
Planning Time: 0.059 ms
Execution Time: 0.141 ms
(10 rows)
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
Sort (cost=8.18..8.18 rows=1 width=267) (actual time=0.043..0.043 rows=0 loops=1)
Sort Key: cree_le DESC
Sort Method: quicksort Memory: 25kB
-> Index Scan using idx_dossiers_id_parent_enfant_statut_cree_le on dossiers d (cost=0.15..8.17 rows=1 width=267) (actual time=0.038..0.038 rows=0 loops=1)
Index Cond: ((id_parent = '33333333-3333-3333-3333-333333333333'::uuid) AND (id_enfant = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'::uuid))
Planning Time: 0.103 ms
Execution Time: 0.067 ms
(7 rows)
=== 14) JSONB : exemples de filtrage ===========================
id | planning_souhaite
----+-------------------
(0 rows)
id | planning
----+----------
(0 rows)
=== 15) Sanity check final ====================================
nb_utilisateurs | nb_parents | nb_am | nb_enfants | nb_liens_parent_enfant | nb_dossiers | nb_messages | nb_contrats | nb_avenants | nb_evenements | nb_uploads | nb_notifications | nb_validations
-----------------+------------+-------+------------+------------------------+-------------+-------------+-------------+-------------+---------------+------------+------------------+----------------
12 | 4 | 2 | 9 | 8 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 3
(1 row)