From 6a586110e7b9ab617a91759ecdffdf4f39d695ed Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Wed, 23 Sep 2026 22:56:04 +0000 Subject: [PATCH 1/3] =?UTF-8?q?docs:=20norme=20merge=20squash=20(feature?= =?UTF-8?q?=E2=86=92develop=E2=86=92master)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Décision §26 + briefing + API Gitea. Les merges --no-ff #167/#168 restent inchangés. Co-authored-by: Julien Martin --- docs/24_DECISIONS-PROJET.md | 33 ++++++++++++++++++++++++--------- docs/26_GITEA-API.md | 9 ++++++++- docs/27_BRIEFING-FRONTEND.md | 18 ++++++++++++++---- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/docs/24_DECISIONS-PROJET.md b/docs/24_DECISIONS-PROJET.md index 8525001..1aee1c6 100644 --- a/docs/24_DECISIONS-PROJET.md +++ b/docs/24_DECISIONS-PROJET.md @@ -1,7 +1,7 @@ # 📋 Décisions Projet - P'titsPas -**Version** : 1.2 -**Date** : 16 Juin 2026 +**Version** : 1.3 +**Date** : 24 Septembre 2026 **Auteur** : Équipe PtitsPas --- @@ -465,19 +465,34 @@ docs/ --- -### 26. Branches Git +### 26. Branches Git + merge squash (norme) -**Décision** : ✅ **Stratégie simple : master + feature branches** +**Décision** : ✅ **`develop` + `master` + `feature/*`, merges en squash** **Justification** : - Simplicité (petite équipe) -- Flexibilité +- Historique lisible sur `develop` / `master` (1 commit = 1 ticket) +- Déploiement prod uniquement depuis `master` **Branches** : -- `master` : Production -- `archive/*` : Archives (ex: maquette initiale) -- `migration/*` : Migrations (ex: intégration YNOV) -- `feature/*` : Nouvelles fonctionnalités +- `master` : Production (déploiement auto) +- `develop` : Intégration / recette +- `feature/*` : Ticket en cours (depuis `develop`) +- `archive/*`, `migration/*` : Archives / migrations ponctuelles + +**Flux (norme à partir de #169+)** : +1. Branche `feature/N-…` depuis `develop` +2. PR **feature → `develop`** : merge **squash** (`Do: squash` côté Gitea) +3. Quand un lot est prêt : PR **`develop` → `master`** : merge **squash** également +4. Message squash : `feat(#N): …` / `fix(#N): …` + `Closes #N` si applicable + +**Exception** : les merges `--no-ff` déjà poussés (ex. #167, #168) restent tels quels — pas de réécriture d’historique. + +**API Gitea** (rappel) : +```http +POST /repos/jmartin/petitspas/pulls/{index}/merge +{"Do":"squash","MergeTitleField":"feat(#N): …","MergeMessageField":"Closes #N"} +``` --- diff --git a/docs/26_GITEA-API.md b/docs/26_GITEA-API.md index 1cbea2e..04c7d13 100644 --- a/docs/26_GITEA-API.md +++ b/docs/26_GITEA-API.md @@ -116,10 +116,17 @@ curl -s -H "Authorization: token $GITEA_TOKEN" \ "https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/pulls?state=open" | jq . # Créer une PR (head = branche source, base = branche cible) +# Norme : feature/* → develop, puis develop → master curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ - -d '{"head":"develop","base":"master","title":"Titre de la PR"}' \ + -d '{"head":"feature/XX-nom","base":"develop","title":"feat(#XX): Titre","body":"Closes #XX"}' \ "https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/pulls" + +# Merger en squash (norme — voir 24_DECISIONS-PROJET.md §26) +curl -s -X POST -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"Do":"squash","MergeTitleField":"feat(#XX): Titre","MergeMessageField":"Closes #XX"}' \ + "https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/pulls/{index}/merge" ``` ### 3.4 Branches diff --git a/docs/27_BRIEFING-FRONTEND.md b/docs/27_BRIEFING-FRONTEND.md index 8c16c06..f74def4 100644 --- a/docs/27_BRIEFING-FRONTEND.md +++ b/docs/27_BRIEFING-FRONTEND.md @@ -194,6 +194,9 @@ frontend/lib/ ## Workflow Git +Norme projet : **squash merge** (voir [24_DECISIONS-PROJET.md](./24_DECISIONS-PROJET.md) §26). +Cible habituelle : `feature/*` → `develop` ; puis `develop` → `master` quand le lot est prêt. + ```bash # 1. Créer une branche feature git checkout develop @@ -204,15 +207,22 @@ git checkout -b feature/XX-nom-ticket git add . git commit -m "feat(#XX): Description courte" -# 3. Pousser et créer PR +# 3. Pousser et créer PR vers develop git push -u origin feature/XX-nom-ticket -# 4. Créer PR vers master via Gitea ou API +# 4. Créer PR (base = develop) curl -X POST \ - -H "Authorization: token giteabu_1796c6aace0e2ef7e4fdb49cdc3bc1bf8ee31fbc" \ + -H "Authorization: token $GITEA_TOKEN" \ -H "Content-Type: application/json" \ - -d '{"title":"feat(#XX): Titre","body":"Description\n\nCloses #XX","head":"feature/XX-nom-ticket","base":"master"}' \ + -d '{"title":"feat(#XX): Titre","body":"Description\n\nCloses #XX","head":"feature/XX-nom-ticket","base":"develop"}' \ "https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/pulls" + +# 5. Merger en squash (pas merge commit) +curl -X POST \ + -H "Authorization: token $GITEA_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"Do":"squash","MergeTitleField":"feat(#XX): Titre","MergeMessageField":"Closes #XX"}' \ + "https://git.ptits-pas.fr/api/v1/repos/jmartin/petitspas/pulls/{index}/merge" ``` --- From 8d8627cf3844ae3310591e06eaf45d8ba0394a00 Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Thu, 24 Sep 2026 09:09:57 +0000 Subject: [PATCH 2/3] feat(#193): table absences_garde + drop evenements Closes #193 Co-authored-by: Julien Martin --- backend/src/entities/absences_garde.entity.ts | 88 +++++++++++++++++++ backend/src/entities/evenements.entity.ts | 79 ----------------- database/BDD.sql | 51 ++++++----- database/docs/ENUMS.md | 20 ++--- database/docs/FK_POLICIES.md | 16 ++-- database/migrations/2026_absences_garde.sql | 85 ++++++++++++++++++ database/seed/02_seed.sql | 18 +--- database/tests/sql/verify.sql | 33 ++++--- 8 files changed, 238 insertions(+), 152 deletions(-) create mode 100644 backend/src/entities/absences_garde.entity.ts delete mode 100644 backend/src/entities/evenements.entity.ts create mode 100644 database/migrations/2026_absences_garde.sql diff --git a/backend/src/entities/absences_garde.entity.ts b/backend/src/entities/absences_garde.entity.ts new file mode 100644 index 0000000..4799a17 --- /dev/null +++ b/backend/src/entities/absences_garde.entity.ts @@ -0,0 +1,88 @@ +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { AmChildren } from './am_children.entity'; +import { Users } from './users.entity'; + +export enum TypeAbsenceGardeType { + ABSENCE_ENFANT = 'absence_enfant', + CONGE_AM = 'conge_am', + ARRET_MALADIE_AM = 'arret_maladie_am', +} + +export enum StatutAbsenceGardeType { + EN_ATTENTE = 'en_attente', + ACCEPTE = 'accepte', + REFUSE = 'refuse', +} + +/** + * Vérité métier des absences / congés / arrêts par couple AM↔enfant. + * Les cartes (module ultérieur) collectent ; cette table stocke. + * Ticket #193. + */ +@Entity('absences_garde', { schema: 'public' }) +export class AbsencesGarde { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ name: 'id_placement', type: 'uuid' }) + id_placement: string; + + @ManyToOne(() => AmChildren, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'id_placement', referencedColumnName: 'id' }) + placement: AmChildren; + + @Column({ + type: 'enum', + enum: TypeAbsenceGardeType, + enumName: 'type_absence_garde_type', + name: 'type', + }) + type: TypeAbsenceGardeType; + + @Column({ name: 'date_debut', type: 'date' }) + date_debut: string; + + @Column({ name: 'date_fin', type: 'date' }) + date_fin: string; + + @Column({ + type: 'enum', + enum: StatutAbsenceGardeType, + enumName: 'statut_absence_garde_type', + name: 'statut', + default: StatutAbsenceGardeType.EN_ATTENTE, + }) + statut: StatutAbsenceGardeType; + + /** Purge auto pour en_attente/refuse ; accepte → infinity. */ + @Column({ name: 'expire_at', type: 'timestamptz' }) + expire_at: Date; + + @Column({ name: 'cree_par', type: 'uuid', nullable: true }) + cree_par?: string; + + @ManyToOne(() => Users, { nullable: true, onDelete: 'SET NULL' }) + @JoinColumn({ name: 'cree_par', referencedColumnName: 'id' }) + createdBy?: Users; + + /** Réf. carte de collecte (FK quand module Cartes existera). */ + @Column({ name: 'id_card_instance', type: 'uuid', nullable: true }) + id_card_instance?: string; + + @Column({ name: 'motif', type: 'text', nullable: true }) + motif?: string; + + @CreateDateColumn({ name: 'cree_le', type: 'timestamptz' }) + cree_le: Date; + + @UpdateDateColumn({ name: 'modifie_le', type: 'timestamptz' }) + modifie_le: Date; +} diff --git a/backend/src/entities/evenements.entity.ts b/backend/src/entities/evenements.entity.ts deleted file mode 100644 index 9d7a73c..0000000 --- a/backend/src/entities/evenements.entity.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm"; -import { Children } from "./children.entity"; -import { Users } from "./users.entity"; -import { Parents } from "./parents.entity"; - -export enum TypeEvenementType { - ABSENCE_ENFANT = 'absence_enfant', - CONGE_AM = 'conge_am', - CONGE_PARENT = 'conge_parent', - ARRET_MALADIE_AM = 'arret_maladie_am', - EVENEMENT_RPE = 'evenement_rpe', -} - -export enum StatutEvenementType { - PROPOSE = 'propose', - VALIDE = 'valide', - REFUSE = 'refuse', -} - -@Entity('evenements') -export class Evenement { - // Define your columns and relationships here - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ - type: 'enum', - enum: TypeEvenementType, - enumName: 'type_evenement_type', - name: 'type' - }) - type: TypeEvenementType; - - @ManyToOne(() => Children, { onDelete: 'CASCADE', nullable: true }) - @JoinColumn({ name: 'id_enfant', referencedColumnName: 'id' }) - child?: Children; - - @ManyToOne(() => Users, { nullable: true }) - @JoinColumn({ name: 'id_am', referencedColumnName: 'id' }) - assistanteMaternelle?: Users; - - @ManyToOne(() => Parents, { nullable: true }) - @JoinColumn({ name: 'id_parent', referencedColumnName: 'user_id' }) - parent?: Parents; - - @ManyToOne(() => Users, { nullable: true }) - @JoinColumn({ name: 'cree_par', referencedColumnName: 'id' }) - created_by?: Users; - - @Column({ type: 'timestamptz', nullable: true, name: 'date_debut' }) - start_date?: Date; - - @Column({ type: 'timestamptz', nullable: true, name: 'date_fin' }) - end_date?: Date; - - @Column({ type: 'text', nullable: true, name: 'commentaires' }) - comments?: string; - - @Column({ - type: 'enum', - enum: StatutEvenementType, - enumName: 'statut_evenement_type', - name: 'statut', - default: StatutEvenementType.PROPOSE - }) - status: StatutEvenementType; - - @Column({type: 'timestamptz', nullable: true, name: 'delai_grace'}) - grace_deadline?: Date; - - @Column({type: 'boolean', default: false, name: 'urgent'}) - urgent: boolean; - - @CreateDateColumn({ name: 'cree_le', type: 'timestamptz' }) - created_at: Date; - - @UpdateDateColumn({ name: 'modifie_le', type: 'timestamptz' }) - updated_at: Date; -} diff --git a/database/BDD.sql b/database/BDD.sql index 11aff46..a23e4a2 100644 --- a/database/BDD.sql +++ b/database/BDD.sql @@ -35,13 +35,15 @@ DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_avenant_type') THEN CREATE TYPE statut_avenant_type AS ENUM ('propose', 'accepte', 'refuse'); END IF; - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'type_evenement_type') THEN - CREATE TYPE type_evenement_type AS ENUM ( - 'absence_enfant', 'conge_am', 'conge_parent', 'arret_maladie_am', 'evenement_rpe' + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'type_absence_garde_type') THEN + CREATE TYPE type_absence_garde_type AS ENUM ( + 'absence_enfant', 'conge_am', 'arret_maladie_am' ); END IF; - IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_evenement_type') THEN - CREATE TYPE statut_evenement_type AS ENUM ('propose', 'valide', 'refuse'); + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_absence_garde_type') THEN + CREATE TYPE statut_absence_garde_type AS ENUM ( + 'en_attente', 'accepte', 'refuse' + ); END IF; IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_validation_type') THEN CREATE TYPE statut_validation_type AS ENUM ('en_attente', 'valide', 'refuse'); @@ -307,25 +309,34 @@ CREATE TABLE avenants_contrats ( ); -- ========================================================== --- Table : evenements +-- Table : absences_garde (vérité métier absences/congés/arrêt) +-- 1 ligne = 1 période ; rattachement = placement AM↔enfant (#193) -- ========================================================== -CREATE TABLE evenements ( +CREATE TABLE absences_garde ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - type type_evenement_type, - id_enfant UUID REFERENCES enfants(id) ON DELETE CASCADE, - id_am UUID REFERENCES utilisateurs(id), - id_parent UUID REFERENCES parents(id_utilisateur), - cree_par UUID REFERENCES utilisateurs(id), - date_debut TIMESTAMPTZ, - date_fin TIMESTAMPTZ, - commentaires TEXT, - statut statut_evenement_type DEFAULT 'propose', - delai_grace TIMESTAMPTZ, - urgent BOOLEAN DEFAULT false, - cree_le TIMESTAMPTZ DEFAULT now(), - modifie_le TIMESTAMPTZ DEFAULT now() + id_placement UUID NOT NULL + REFERENCES enfants_assistantes_maternelles(id) ON DELETE CASCADE, + type type_absence_garde_type NOT NULL, + date_debut DATE NOT NULL, + date_fin DATE NOT NULL, + statut statut_absence_garde_type NOT NULL DEFAULT 'en_attente', + expire_at TIMESTAMPTZ NOT NULL, + cree_par UUID REFERENCES utilisateurs(id) ON DELETE SET NULL, + id_card_instance UUID, + motif TEXT, + cree_le TIMESTAMPTZ NOT NULL DEFAULT now(), + modifie_le TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT chk_absences_garde_dates CHECK (date_fin >= date_debut) ); +CREATE INDEX idx_absences_garde_placement_dates + ON absences_garde (id_placement, date_debut, date_fin); +CREATE INDEX idx_absences_garde_placement_type_statut + ON absences_garde (id_placement, type, statut); +CREATE INDEX idx_absences_garde_expire_at + ON absences_garde (expire_at) + WHERE statut IN ('en_attente', 'refuse'); + -- ========================================================== -- Table : signalements_bugs -- ========================================================== diff --git a/database/docs/ENUMS.md b/database/docs/ENUMS.md index bda4d68..71162cb 100644 --- a/database/docs/ENUMS.md +++ b/database/docs/ENUMS.md @@ -104,31 +104,31 @@ Ce document recense **toutes les valeurs énumérées** utilisées dans la base --- -## 7) Type d’événement — `type` +## 7) Type d’absence de garde — `type` -**Tables/colonnes** : `evenements.type` +**Tables/colonnes** : `absences_garde.type` **Valeurs autorisées** : | Valeur | Description | |---|---| -| `absence_enfant` | Enfant absent | +| `absence_enfant` | Absence déclarée par un parent | | `conge_am` | Congé de l’assistante maternelle | -| `conge_parent` | Congé du parent | | `arret_maladie_am` | Arrêt maladie AM | -| `evenement_rpe` | Événement RPE | + +> Remplace l’ancien enum `type_evenement_type` / table `evenements` (supprimés ticket #193). --- -## 8) Statut d’événement — `statut` +## 8) Statut d’absence de garde — `statut` -**Tables/colonnes** : `evenements.statut` +**Tables/colonnes** : `absences_garde.statut` **Valeurs autorisées** : | Valeur | Description | |---|---| -| `propose` | Événement proposé | -| `valide` | Événement validé | -| `rejete` | Événement refusé | +| `en_attente` | En attente de validation / ack | +| `accepte` | Accepté / acté (conservé en historique métier) | +| `refuse` | Refusé (temporaire ; purge TTL) | --- diff --git a/database/docs/FK_POLICIES.md b/database/docs/FK_POLICIES.md index 2d861fa..9714b35 100644 --- a/database/docs/FK_POLICIES.md +++ b/database/docs/FK_POLICIES.md @@ -42,10 +42,8 @@ Documenter, de façon unique et partagée, les règles de suppression/mise à jo | **contrats(id_dossier)** → `dossiers(id)` | **CASCADE** | 1:1, contrat détruit si dossier supprimé | | **avenants_contrats(id_contrat)** → `contrats(id)` | **CASCADE** | Avenants détruits avec le contrat | | **avenants_contrats(initie_par)** → `utilisateurs(id)` | **SET NULL** | Historiser l’avenant sans bloquer | -| **evenements(id_enfant)** → `enfants(id)` | **CASCADE** | Événements n’ont plus de sens | -| **evenements(id_am)** → `utilisateurs(id)` | **SET NULL** | Garder la trace même si AM supprimée | -| **evenements(id_parent)** → `parents(id_utilisateur)` | **SET NULL** | Garder la trace si parent supprimé | -| **evenements(cree_par)** → `utilisateurs(id)` | **SET NULL** | Conserver l’historique de création | +| **absences_garde(id_placement)** → `enfants_assistantes_maternelles(id)` | **CASCADE** | Plus de couple = plus d’absences | +| **absences_garde(cree_par)** → `utilisateurs(id)` | **SET NULL** | Conserver la période si user supprimé | | **signalements_bugs(id_utilisateur)** → `utilisateurs(id)` | **SET NULL** | Conserver le ticket même si compte supprimé | | **uploads(id_utilisateur)** → `utilisateurs(id)` | **SET NULL** | Fichier reste référencé sans l’auteur | | **notifications(id_utilisateur)** → `utilisateurs(id)` | **CASCADE** | Notifications propres à l’utilisateur | @@ -79,10 +77,8 @@ Documenter, de façon unique et partagée, les règles de suppression/mise à jo - `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** +- `absences_garde.id_placement` → **CASCADE** +- `absences_garde.cree_par` → **SET NULL** ### Divers - `signalements_bugs.id_utilisateur` → **SET NULL** @@ -109,11 +105,11 @@ Documenter, de façon unique et partagée, les règles de suppression/mise à jo 4. **Suppression d’un enfant** - Supprimer `enfants(id=childA)` - - Attendu : `enfants_parents` (CASCADE), `dossiers` du childA (CASCADE), `evenements` du childA (CASCADE). + - Attendu : `enfants_parents` (CASCADE), `dossiers` du childA (CASCADE), `absences_garde` des placements du childA (CASCADE via EAM). 5. **Suppression d’un utilisateur AM** - Supprimer `utilisateurs(id=amB)` - - Attendu : `evenements.id_am` devient **NULL** (historique conservé). + - Attendu : absences liées via placement AM (CASCADE si le lien EAM est retiré). --- diff --git a/database/migrations/2026_absences_garde.sql b/database/migrations/2026_absences_garde.sql new file mode 100644 index 0000000..1587b6a --- /dev/null +++ b/database/migrations/2026_absences_garde.sql @@ -0,0 +1,85 @@ +-- Ticket #193 — Back métier absences / congés / arrêt (périodes par placement) +-- Idempotent : safe à rejouer. +-- Remplace la table legacy `evenements` (mal conçue, non utilisée en API). + +-- --------------------------------------------------------------------------- +-- Enums +-- --------------------------------------------------------------------------- +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'type_absence_garde_type') THEN + CREATE TYPE type_absence_garde_type AS ENUM ( + 'absence_enfant', + 'conge_am', + 'arret_maladie_am' + ); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_absence_garde_type') THEN + CREATE TYPE statut_absence_garde_type AS ENUM ( + 'en_attente', + 'accepte', + 'refuse' + ); + END IF; +END $$; + +-- --------------------------------------------------------------------------- +-- Table absences_garde (1 ligne = 1 période) +-- --------------------------------------------------------------------------- +CREATE TABLE IF NOT EXISTS absences_garde ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + id_placement UUID NOT NULL + REFERENCES enfants_assistantes_maternelles(id) ON DELETE CASCADE, + type type_absence_garde_type NOT NULL, + date_debut DATE NOT NULL, + date_fin DATE NOT NULL, + statut statut_absence_garde_type NOT NULL DEFAULT 'en_attente', + -- TTL purge : obligatoire ; pour statut=accepte utiliser 'infinity' (pas de purge métier) + expire_at TIMESTAMPTZ NOT NULL, + cree_par UUID REFERENCES utilisateurs(id) ON DELETE SET NULL, + id_card_instance UUID NULL, + motif TEXT, + cree_le TIMESTAMPTZ NOT NULL DEFAULT now(), + modifie_le TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT chk_absences_garde_dates CHECK (date_fin >= date_debut) +); + +CREATE INDEX IF NOT EXISTS idx_absences_garde_placement_dates + ON absences_garde (id_placement, date_debut, date_fin); + +CREATE INDEX IF NOT EXISTS idx_absences_garde_placement_type_statut + ON absences_garde (id_placement, type, statut); + +CREATE INDEX IF NOT EXISTS idx_absences_garde_expire_at + ON absences_garde (expire_at) + WHERE statut IN ('en_attente', 'refuse'); + +COMMENT ON TABLE absences_garde IS + 'Vérité métier des absences/congés/arrêts par couple AM↔enfant (placement). Les cartes collectent ; cette table stocke.'; +COMMENT ON COLUMN absences_garde.expire_at IS + 'Purge auto (job ultérieur) pour en_attente/refuse. accepte → infinity.'; +COMMENT ON COLUMN absences_garde.id_card_instance IS + 'Réf. carte de collecte (FK cards quand le module Cartes existera).'; + +-- --------------------------------------------------------------------------- +-- Drop legacy evenements (+ enums si plus référencés) +-- --------------------------------------------------------------------------- +DROP TABLE IF EXISTS evenements CASCADE; + +DO $$ +BEGIN + IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'type_evenement_type') + AND NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE udt_name = 'type_evenement_type' + ) THEN + DROP TYPE type_evenement_type; + END IF; + IF EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_evenement_type') + AND NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE udt_name = 'statut_evenement_type' + ) THEN + DROP TYPE statut_evenement_type; + END IF; +END $$; diff --git a/database/seed/02_seed.sql b/database/seed/02_seed.sql index 7fd21c7..c166098 100644 --- a/database/seed/02_seed.sql +++ b/database/seed/02_seed.sql @@ -161,23 +161,9 @@ VALUES ( ON CONFLICT (id) DO NOTHING; -- ------------------------------------------------------------ --- Événement (absence enfant) +-- Absences_garde : seeds quand placements EAM présents (API / ticket peuplement) +-- Legacy evenements supprimée (#193). -- ------------------------------------------------------------ -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) diff --git a/database/tests/sql/verify.sql b/database/tests/sql/verify.sql index 05593dd..4104ab0 100644 --- a/database/tests/sql/verify.sql +++ b/database/tests/sql/verify.sql @@ -79,12 +79,11 @@ LEFT JOIN avenants_contrats a ON a.id_contrat = c.id GROUP BY c.id, c.id_dossier, c.statut ORDER BY c.cree_le DESC; -\echo '=== 9) Evénements par enfant (30 derniers jours) ==============' -SELECT ev.id, ev.type, ev.id_enfant, e.prenom AS enfant, ev.date_debut, ev.date_fin, ev.statut -FROM evenements ev -JOIN enfants e ON e.id = ev.id_enfant -WHERE ev.date_debut >= (NOW()::date - INTERVAL '30 days') -ORDER BY ev.date_debut DESC; +\echo '=== 9) Absences_garde (30 derniers jours) =====================' +SELECT ag.id, ag.type, ag.id_placement, ag.date_debut, ag.date_fin, ag.statut, ag.expire_at +FROM absences_garde ag +WHERE ag.date_debut >= (NOW()::date - INTERVAL '30 days') +ORDER BY ag.date_debut DESC; \echo '=== 10) Uploads & notifications récentes =======================' SELECT u.courriel, up.fichier_url, up.type_fichier, up.cree_le @@ -135,11 +134,11 @@ FROM avenants_contrats a LEFT JOIN contrats c ON c.id = a.id_contrat WHERE c.id IS NULL; --- Evénements sans enfant -SELECT ev.* -FROM evenements ev -LEFT JOIN enfants e ON e.id = ev.id_enfant -WHERE e.id IS NULL; +-- Absences sans placement +SELECT ag.* +FROM absences_garde ag +LEFT JOIN enfants_assistantes_maternelles eam ON eam.id = ag.id_placement +WHERE eam.id IS NULL; \echo '=== 13) Performance : EXPLAIN sur requêtes clés ===============' @@ -151,12 +150,12 @@ WHERE m.id_dossier = 'dddddddd-dddd-dddd-dddd-dddddddddddd' ORDER BY m.cree_le DESC LIMIT 20; --- Evénements par enfant et période (idx_evenements_id_enfant_date_debut) +-- Absences par placement et période (idx_absences_garde_placement_dates) EXPLAIN ANALYZE -SELECT ev.* -FROM evenements ev -WHERE ev.id_enfant = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' - AND ev.date_debut >= '2025-01-01'; +SELECT ag.* +FROM absences_garde ag +WHERE ag.id_placement IS NOT NULL + AND ag.date_debut >= '2025-01-01'; -- Notifications non lues (idx_notifications_user_lu_cree_le) EXPLAIN ANALYZE @@ -199,7 +198,7 @@ SELECT (SELECT COUNT(*) FROM messages) AS nb_messages, (SELECT COUNT(*) FROM contrats) AS nb_contrats, (SELECT COUNT(*) FROM avenants_contrats) AS nb_avenants, - (SELECT COUNT(*) FROM evenements) AS nb_evenements, + (SELECT COUNT(*) FROM absences_garde) AS nb_absences_garde, (SELECT COUNT(*) FROM uploads) AS nb_uploads, (SELECT COUNT(*) FROM notifications) AS nb_notifications, (SELECT COUNT(*) FROM validations) AS nb_validations; From 41f700607302aae1eb906a15e6a7eebc9842d9a0 Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Thu, 24 Sep 2026 09:13:19 +0000 Subject: [PATCH 3/3] =?UTF-8?q?docs:=20Epic=20C=20=E2=80=94=20absences=20b?= =?UTF-8?q?ack=20=E2=89=A0=20cartes=20(=C2=A732)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Découpage + mini-spec + décision projet. Co-authored-by: Julien Martin --- docs/24_DECISIONS-PROJET.md | 22 ++++++++-- docs/30_DECOUPAGE-TICKETS-QUOTIDIEN.md | 51 ++++++++++++++---------- docs/31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md | 20 ++++++---- 3 files changed, 62 insertions(+), 31 deletions(-) diff --git a/docs/24_DECISIONS-PROJET.md b/docs/24_DECISIONS-PROJET.md index 1aee1c6..426643c 100644 --- a/docs/24_DECISIONS-PROJET.md +++ b/docs/24_DECISIONS-PROJET.md @@ -1,6 +1,6 @@ # 📋 Décisions Projet - P'titsPas -**Version** : 1.3 +**Version** : 1.4 **Date** : 24 Septembre 2026 **Auteur** : Équipe PtitsPas @@ -570,6 +570,19 @@ POST /repos/jmartin/petitspas/pulls/{index}/merge --- +### 32. Module Absences + Cartes (séparation back / collecte) + +**Décision** : ✅ **Back `absences_garde` = vérité métier** ; **Cartes = file d’attention / collecte** (plugin isolé). Types SYSTEM (absence, congé, arrêt) indéboulonnables ; types OPTIONNELS (sondages…) plus tard. Annulation = DELETE. `expire_at` dès V1. + +**Justification** : +- Premier vrai module d’interaction multi-acteurs +- Évite de stocker l’historique congés dans des bulles éphémères +- Portabilité / généricité des cartes sans coupler le métier garde + +**Flux V1** : BDD absences → API liste/CRUD → module Cartes SYSTEM → front bulles (séparé). + +--- + ## 📋 Résumé des décisions critiques | # | Décision | Statut | Impact | @@ -590,6 +603,7 @@ POST /repos/jmartin/petitspas/pulls/{index}/merge | 14 | Migration données | ❌ Rejeté | N/A | | 15 | Doc utilisateur | ⏸️ Phase 2 | Formation | | 31 | Logs Winston | ✅ Phase 1 | Monitoring | +| 32 | Absences back + Cartes collecte | ✅ 0.2.0 | Métier / archi | | 5bis | Familles recomposées — 2ᵉ compte v1.0.0 | ✅ v1.0.0 | Métier / dossier | --- @@ -601,11 +615,11 @@ POST /repos/jmartin/petitspas/pulls/{index}/merge | 25/11/2025 | 1.0 | Création du document - Toutes les décisions initiales | | 09/02/2026 | 1.1 | Configuration initiale : un seul panneau Paramètres (3 sections) dans le dashboard, plus de Setup Wizard dédié ; navigation bloquée jusqu'à sauvegarde | | 16/06/2026 | 1.2 | Décision 5bis — familles recomposées, contournement v1.0.0 ; lien doc [28](./28_EVOLUTION-FAMILLE-ET-RESPONSABLES.md) | -| 16/06/2026 | 1.3 | Précision 5bis — cible post-1.0.0 : parcours gestionnaire § 7.5 (#139), visibilité par enfant | +| 24/09/2026 | 1.4 | Décision 32 — Absences (`absences_garde`) ≠ Cartes ; Epic C recentré ; drop `evenements` | --- -**Dernière mise à jour** : 16 Juin 2026 -**Version** : 1.2 +**Dernière mise à jour** : 24 Septembre 2026 +**Version** : 1.4 **Statut** : ✅ Document validé diff --git a/docs/30_DECOUPAGE-TICKETS-QUOTIDIEN.md b/docs/30_DECOUPAGE-TICKETS-QUOTIDIEN.md index 80644a3..d519a14 100644 --- a/docs/30_DECOUPAGE-TICKETS-QUOTIDIEN.md +++ b/docs/30_DECOUPAGE-TICKETS-QUOTIDIEN.md @@ -1,9 +1,9 @@ # Découpage tickets — Quotidien parent / AM -> **Statut :** backlog Gitea créé (**milestone `0.2.0`**, **#165–#189**) — *création anticipée avant validation PO* ; **descriptions enrichies** ensuite à partir de la mini-spec / découpage. +> **Statut :** backlog Gitea **0.2.0** / épic [#165](https://git.ptits-pas.fr/jmartin/petitspas/issues/165) — architecture absences + cartes recentrée (sept. 2026). > **Réf. visuelle :** [maquettes/courantes/maquette-dashboard-parent-quotidien-v4.png](./maquettes/courantes/maquette-dashboard-parent-quotidien-v4.png) > **Mini-spec :** [31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md](./31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md) -> **Règle :** tickets **front** / **back** séparés · widgets partagés parent↔AM · **pas d’implé code** tant que le PO n’a pas validé le backlog. +> **Décision :** [24_DECISIONS-PROJET.md](./24_DECISIONS-PROJET.md) §32 (Cartes ≠ back Absences). ## Intention produit (rappel) @@ -96,22 +96,31 @@ Liste des enfants / foyers pour l’AM + contexte courant (symétrique A3). --- -## Epic C — Cartes (file du quotidien) +## Epic C — Absences + Cartes (file d’attention) -### C1 — [Back] Modèle + API cartes / événements de garde -Types V1 : absence enfant, congé AM, maladie AM (« arrêt »), sortie à valider. CRUD / transitions de statut ; liaison couple / agenda (hook minimal). Règles : absence enfant sans veto AM ; congé AM accepter/refuser (**1 parent suffit**) ; maladie AM accusé parent ; sortie **1 parent suffit**. Aucun doc médical stocké. +**Séparation :** back **`absences_garde`** = vérité métier (périodes / placement) ; module **Cartes** = collecte / bulles / workflow. Annulation = DELETE. `expire_at` dès le modèle. -### C2 — [Front] Flux de cartes colonne gauche (parent) -Liste scroll pastel (palette `assets/cards/` — 7 couleurs) ; ouverture détail ; actions valider / refuser / accusé selon type. Bouton **Déclarer une absence** (+ autres actions TBD plus tard). +### Back (ordre) -### C3 — [Front] Flux de cartes colonne gauche (AM) -Miroir : création congé / maladie / sortie ; lecture absences enfants du jour / à venir. +| Étape | Contenu | +|-------|---------| +| BDD | Table `absences_garde` + drop `evenements` | +| API | CRUD + **GET liste** (`placementId` \| tous les placements du user) | +| Cartes SYSTEM | Module `cards/` types S1–S3 (sans sondages V1) | +| Realtime | WS/SSE bulles | +| Purge TTL | Job `expire_at` / `purge_at` | -### C4 — [Front] Formulaire déclarer une absence (parent) -Saisie période (+ motif léger si besoin) → crée une carte côté AM. +### Front (après API — hors chantier back immédiat) -### C5 — [Front] Formulaires AM congé / maladie / sortie -Création des cartes correspondantes + ciblage enfants si sortie. +| Ticket | Contenu | +|--------|---------| +| Feed parent / AM | File d’attention bulles (widget partagé) | +| Modale « Ajouter une bulle » | Générique + absence parent / congé·arrêt AM | +| Agenda | Consommer GET liste (lignes) | + +### Hors V1 cartes + +Sondages / catalogue admin OPTIONNEL / sortie riche / desktop 2 panneaux (évolution séparée). --- @@ -185,13 +194,15 @@ Scripts / seeds : foyer 2 parents, AM, enfant(s), quelques cartes, posts blog, f ## Ordre de réalisation suggéré -1. **A1 → A2 → A3** (coquille + contexte parent) -2. **C1 → C2 / C4** (premières cartes utiles) -3. **D1 → D2 / D3** (blog central) -4. **E1 → E2 → E4** (messagerie AM) -5. **B1 → B2 → B3** + miroirs C3/C5/D3/E5 (AM) -6. **E3 / E5 / E6 / D4** (RPE) -7. **F1 → F2** + **G1** (nav, mobile, seeds) +1. **A1 → A2 → A3** (coquille + contexte parent) — *fait* +2. **BDD absences → API absences** puis **Cartes SYSTEM** (congés/absences/arrêt) +3. **Feed / modale bulles** (front) + agenda lignes +4. **D1 → D2 / D3** (blog) +5. **E1 → E2 → E4** (messagerie AM) +6. **B1 → B2 → B3** + miroirs AM +7. Realtime / purge TTL / RPE / F/G + +Desktop 2 panneaux (cartes prioritaires sur blog) = **évolution hors 0.2.0**. --- diff --git a/docs/31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md b/docs/31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md index 6405345..05d0d8a 100644 --- a/docs/31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md +++ b/docs/31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md @@ -61,14 +61,20 @@ Bandeau : **TdB** · **Agenda** · **Contrat** · menu user (recherche AM, param | **Mess. AM** | Chat foyer ↔ AM (style WhatsApp : texte, emoji, images) | | **Mess. RPE** | Privée par défaut ; ajout de participants pour médiation / conflit | -## 5. Règles métier V1 +## 5. Règles métier V1 (absences / congés) -| Type | Qui initie | Validation | -|------|------------|------------| -| Absence enfant | Parent | Pas de veto AM | -| Congé AM | AM | **1 parent** accepte ou refuse | -| Maladie AM | AM | Parent accuse réception (pas de doc médical in-app) | -| Sortie | AM / RPE | **1 parent** suffit (présence / absence) ; explication aussi via blog | +| Type | Qui initie | Validation / effet | +|------|------------|-------------------| +| Absence enfant | Parent | Pas de veto AM ; période **acceptée** tout de suite ; bulle info AM | +| Congé AM | AM | **1 parent** accepte **ou** refuse (+ motivation) → bounce AM (modif/renvoi ou DELETE) | +| Modification congé AM déjà accepté | AM | Même `id` absence ; re-validation parents ; dates actives = anciennes tant qu’en attente | +| Modification absence enfant | Parent | Update immédiat + bulle **ack** AM (OK) | +| Maladie AM | AM | Parent **ack** (« bien reçu ») ; **aucun** doc médical | +| Sortie / sondage | — | **Plus tard** (types optionnels) | + +**Stockage :** table `absences_garde` (1 ligne = période, `id_placement`, `expire_at`). Les **cartes** collectent ; elles ne sont pas la source de vérité. Annulation = DELETE. + +**Péremption cartes :** mémoire courte (`retention_days` / `purge_at`) — pas un historique de vie (≠ messagerie). - Mess. AM : les **2 parents** voient le **même** fil — **pas** de masquage V1 - Blog auteurs V1 : **AM** + **gestionnaire (RPE)** — parent auteur = plus tard