Compare commits

..
Author SHA1 Message Date
jmartinandCursor 86701731e3 fix(#129): resserrer les champs enfant pour éviter le scroll.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 13:13:21 +02:00
jmartinandCursor b4abb7d6de fix(#129): peaufiner le wizard famille (co-parent, hauteurs, à naître).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 13:01:54 +02:00
jmartinandCursor cb5c1a5518 feat(#129): wizard admin création dossier famille (create/review).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 12:34:24 +02:00
jmartinandCursor 30ca99fb65 feat(#129): POST /parents/dossier — création dossier famille staff.
Factorise createParentDossier (actif + mail MDP) depuis l’inscription
publique qui reste en_attente + mail pending. Miroir #156 AM.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 16:59:09 +02:00
jmartinandCursor 8ee2ca8ea6 fix(#156): caler la modale AM sur les TF et unifier create/review.
ValidationFormMetrics (titres, TF, écarts) pilote la hauteur ; photo
étirée sur le corps ; mêmes widgets create et validation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 16:49:59 +02:00
jmartinandCursor e3552667bc fix(#156): valider téléphone au blur et NIR en live.
Formatage NIR progressif + contrôles au fil de la saisie ; téléphone
validé à la perte de focus comme e-mail / CP.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-23 16:28:35 +02:00
jmartinandCursor 4ae334b247 fix(#156): superviser nom, e-mail et CP comme à l’inscription.
IdentityBlock editable : capitalisation live, e-mail/CP normalisés
et validés à la perte de focus (aligné création de compte).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 19:27:20 +02:00
jmartinandCursor 471a62ddb7 feat(#156): wizard admin création dossier AM (create/review).
Généralise ValidationAmWizard en AmDossierWizard ; le + Asmat ouvre
le mode création vers POST /assistantes-maternelles/dossier.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 19:07:14 +02:00
jmartinandCursor 59afeb0a8d feat(#156): POST /assistantes-maternelles/dossier staff (actif + mail MDP).
Factorise createAmDossier depuis register/am ; staff crée un dossier
déjà actif et envoie l’e-mail de création de mot de passe.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 19:03:37 +02:00
jmartinandCursor d2172eafdb docs: snapshot empreinte code / disque (2026-07-22).
Mémoire après alerte disque plein — lignes de code et contexte VPS.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 18:27:49 +02:00
jmartinandCursor d247867fa0 feat(#158): front — attach/detach foyer (un seul appel API).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 19:53:31 +02:00
jmartinandCursor 93912d1374 feat(#158): attach/detach enfant au niveau foyer (pivot + co-parent).
POST/DELETE /parents/:id/enfants/:enfantId propagent les liens à tous
les responsables du foyer (co-parent bidirectionnel + même dossier).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 19:47:13 +02:00
jmartinandCursor 925b6d5cd4 fix(#157): texte détach + compteur enfants foyer (interim).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 19:42:36 +02:00
jmartinandCursor b0dddd6695 feat(#157): consommer le flag API sans_responsable.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 19:33:21 +02:00
jmartinandCursor ef7512dc1e feat(#157): autoriser détachement dernier parent + flag sans_responsable.
Supprime la garde totalLinks<=1 ; GET/findOne enfants exposent
sans_responsable pour les orphelins toujours listés.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 19:31:18 +02:00
jmartinandCursor 2ececa711b feat(#157): alerte enfants sans responsable + rattachement foyer.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 19:02:21 +02:00
jmartinandCursor 7a3d997ff9 fix(#132): UX création enfant — champs, genre et validation.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 18:51:00 +02:00
jmartinandCursor 6b89d7b405 fix(#132): envoyer birth_date à la création (enfant né).
Le if imbriqué n’incluait birth_date que pour le cas à naître.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 18:46:48 +02:00
jmartinandCursor f7ac628445 feat(#132): photo à la création enfant staff (multipart + bools).
Transform "true"/"false" pour class-validator ; multipart staff
avec FileInterceptor('photo') inchangé côté stockage.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 18:08:52 +02:00
jmartinandCursor 26e9738ec7 feat(#132): upload photo à la création d'enfant (admin).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 18:07:47 +02:00
jmartinandCursor 0ec3410457 feat(#132): POST /enfants staff — parent_user_id + liens foyer.
Autorise GESTIONNAIRE/ADMIN/SUPER_ADMIN à créer un enfant rattaché
à un foyer existant (JSON + parent_user_id). Parent multipart inchangé.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 18:03:41 +02:00
jmartinandCursor c3acf1970d feat(#132): création enfant admin — modale + sélection famille.
Onglet Enfants « Ajouter » ouvre la fiche en mode création ; bloc Famille/dossier ; client POST /enfants avec parent_user_id (contrat back à livrer).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 18:00:41 +02:00
jmartinandCursor 6fe2b89a61 fix(#151): autoriser GET /relais pour gestionnaire + robustesse combo
- Back: RoleType.GESTIONNAIRE sur GET list/get (CRUD write admin-only)
- Front: ne plus effacer la sélection si le chargement échoue
- Fallback affichage depuis relaisNom de l'utilisateur

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 16:52:46 +02:00
jmartin d6a9b3fd66 Merge branch 'feature/149-clic-case-libre-rattacher-enfant' into develop
Clic case libre → rattacher enfant (#149).
2026-07-17 16:19:14 +02:00
jmartinandCursor 134b9781c8 feat(#149): clic sur case libre pour rattacher un enfant (fiche AM).
Les emplacements vides de la grille ouvrent le même flux que le lien footer, avec hover et respect de la capacité max.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 16:19:12 +02:00
jmartin b936d27445 Merge branch 'feature/148-rattacher-enfant-capacite-max' into develop
Fix capacité max — lien rattacher enfant (#148).
2026-07-17 16:12:54 +02:00
jmartinandCursor 18c1d1eba7 fix(#148): désactiver « Rattacher un enfant » si capacité AM pleine.
Le lien du footer est grisé avec tooltip quand enfants ≥ capacité max ; il se réactive après détachement.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 16:12:53 +02:00
jmartin f74b14c203 Merge branch 'feature/147-modale-selection-am-enfant' into develop
Modale de sélection d'AM (#147) depuis la fiche enfant.
2026-07-17 16:12:23 +02:00
jmartinandCursor f194b5f9e8 feat(#147): modale de sélection d'AM depuis la fiche enfant.
Réutilise AdminSelectListModal avec filtre Libre, cartes saturées en rouge, avertissement + lien fiche AM, et recalcul des places à chaque rattachement.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 16:11:55 +02:00
jmartin 5ab8ae3423 Merge branch 'feature/146-modale-selection-enfant-am' into develop
Modale de sélection d'enfant (#146) pour rattachement AM/parent.
2026-07-17 15:43:10 +02:00
jmartinandCursor 3277f77846 fix(#146): densifier la liste et corriger l'affichage des responsables.
Les cartes compactes montrent plus d'enfants d'un coup ; le nom ne monopolise plus la largeur au détriment des infos.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 15:39:59 +02:00
jmartinandCursor 2d80ad0d7e feat(#146): modale de sélection d'enfant pour rattachement AM/parent.
Recherche dynamique, filtre Sans garde côté AM, confirmation de transfert et détachement préalable pour laisser le point de vigilance places sur l'AM d'origine.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 15:31:47 +02:00
jmartinandCursor 09386f8aa6 feat(#145): lien co-parent cliquable dans la fiche parent.
Même UX que les responsables de la fiche enfant : ouverture de
AdminParentEditModal au clic sur le nom du co-parent.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 14:56:32 +02:00
jmartinandCursor faa50f637c fix(#138): enrichir les noms des responsables sur GET enfant.
GET /enfants/:id ne joint pas parent.user ; on complète les noms via
GET parent pour l'affichage des liens (ex. ouverture depuis la fiche AM).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 13:19:09 +02:00
jmartinandCursor 267fe63aec fix(#138): différer le rattachement AM de la fiche enfant au Sauvegarder.
Attach/détach AM restent locaux comme sur la fiche AM ; la sync API
n'a lieu qu'à l'enregistrement, avec rechargement du statut au retour
de la fiche AM.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 13:12:46 +02:00
jmartinandCursor 90b185740c feat(#138): ouvrir la fiche parent depuis les responsables de la modale enfant.
Les noms du sous-titre deviennent des liens cliquables vers
AdminParentEditModal, comme l'ouverture de fiche AM.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 12:54:32 +02:00
jmartinandCursor b903dbf60b fix(#138): libellé garde accordé au genre (Gardé / Gardée).
Remplace « En garde » par Gardé/Gardée selon le genre, comme
Scolarisé/Scolarisée.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 12:51:07 +02:00
jmartinandCursor 291ea26b34 fix(#138): lire le consentement photo sans heuristique photo.
Admin et reprise utilisentent uniquement la valeur API ; plus de
pré-cochage implicite si une photo est présente.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 12:46:54 +02:00
jmartinandCursor 59919834b9 fix(#138): carte AM dédiée, titre placement et consent_photo au payload.
Améliore la zone AM/scolarisation (carte dédiée + titre), envoie
consent_photo à l'inscription parent, et parse le consentement enfant
de façon plus robuste (heuristique photo tant que le back ne persiste pas).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 12:37:45 +02:00
jmartinandCursor cf6acbae7c fix(#138): cadre vide en sans garde et passage auto en garde à l'attach AM.
En statut sans_garde, la zone placement affiche toujours le cadre vide ;
sélectionner une AM passe le statut à en garde, et passer à sans_garde
détache l'AM rattachée.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 12:37:45 +02:00
jmartinandCursor 7951c38d4d feat(#138): refonte modale enfant admin en format paysage.
Alignement sur les fiches AM/parent : photo identité, champs en grille,
zone AM rattachable (y compris à naître) ou carte scolarisation,
suppression réservée au super_admin et ouverture au clic sur la carte
depuis la fiche parent.

Refs: #138
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 12:37:45 +02:00
jmartinandCursor 77d952a6f7 fix(#144): persister le consentement photo des enfants
- DTO inscription enfant : champ consent_photo
- Inscription + reprise parent : sauvegarde bool + date
- Front payload : envoi consent_photo
- PATCH /enfants : horodatage du consentement

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 12:36:05 +02:00
jmartin b4b546044d merge fix(#143): masquer Supprimer gestionnaire sur sa propre fiche 2026-07-12 12:10:11 +02:00
jmartinandCursor 6788351070 fix(#143): masquer Supprimer quand un gestionnaire édite sa propre fiche
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 12:10:06 +02:00
jmartinandCursor d6dac181d2 feat(#142): ouvrir la modale au clic sur les cartes du dashboard admin.
Squash merge develop → master.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 23:20:56 +02:00
jmartinandCursor 7b69f27ca5 feat(#142): ouvrir la modale au clic sur les cartes du dashboard admin.
Unifie le comportement des listes Parents, AM, Enfants, Gestionnaires, Admins et À valider avec le bouton d'action au survol.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 23:18:40 +02:00
jmartinandCursor 003fe6b762 feat(#131): fiches parent/AM éditable, placement AM↔enfant, statuts garde/sans_garde
Squash merge develop → master.

- Fiche parent éditable (co-parent, PATCH fiche, GET /parents)
- Fiche AM 3 onglets (PATCH fiche, rattacher/détacher enfants)
- Table enfants_assistantes_maternelles + enum garde/sans_garde
- Migration SQL + BDD.sql canonique
- Correctifs recette : @Get() parents, DTO fiche AM, fix NIR

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-11 22:49:57 +02:00
jmartinandCursor 8ed68797aa fix(#131): PATCH fiche AM — NIR sans crash date ni effacement NOT NULL
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 23:41:45 +02:00
jmartinandCursor 9ad371a342 fix(#131): aligner PATCH fiche AM back sur le payload front
Étend UpdateAmFicheAdminDto (nir, date/lieu naissance, agreement_date)
et persiste ces champs dans updateFicheAdmin avec validation NIR.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 23:37:42 +02:00
jmartinandCursor 18718670e9 fix(#131): différer rattachement enfants AM jusqu'à Sauvegarder.
Rattachement et détachement restent locaux, recalculent places_available et sont persistés avec le PATCH fiche.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 23:36:06 +02:00
jmartin fbf22f2540 Revert "fix(#131): aligner PATCH fiche AM sur le DTO back actuel."
This reverts commit 43a2cd213b.
2026-07-07 23:13:58 +02:00
jmartinandCursor 43a2cd213b fix(#131): aligner PATCH fiche AM sur le DTO back actuel.
Retire nir, dates et lieux de naissance du body tant que UpdateAmFicheAdminDto ne les accepte pas.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 23:12:38 +02:00
jmartin c865d11dc3 Revert "fix(#131): activer Sauvegarder sur statut, enfants et champs AM."
This reverts commit d03a8e6c8b.
2026-07-07 23:12:09 +02:00
jmartinandCursor d03a8e6c8b fix(#131): activer Sauvegarder sur statut, enfants et champs AM.
Compare l'état de la fiche à un snapshot initial (liste enfants, statut, places, formulaire) et corrige la gélule statut pour les valeurs hors liste.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 23:11:24 +02:00
jmartinandCursor 66c7f22280 feat(#131): grille enfants AM, statuts garde et polish champs admin.
Aligne le front sur les statuts enfant back (garde/sans_garde), refond l’onglet Enfants accueillis en grille 2×2 avec correction des places, et unifie les champs validation éditables/lecture seule.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 23:01:41 +02:00
jmartinandCursor 53721ffbb3 fix(#131): ajouter @Get() manquant sur liste parents
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 22:07:24 +02:00
jmartinandCursor 52e40d0001 feat(#131): back placement AM↔enfant + BDD garde/sans_garde
- API PATCH …/fiche, POST/DELETE …/enfants/:enfantId, GET avec amChildren
- Table enfants_assistantes_maternelles (option D, 1 garde active/enfant)
- Statuts enfant: garde/sans_garde remplacent actif (inscription → sans_garde)
- BDD.sql canonique réécrit; migration pour BDD existantes
- Seed test: hash bcrypt corrigé pour mot de passe « password »

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 14:05:39 +02:00
jmartinandCursor 4985726bc6 fix(#131): polish fiche AM — champs pro, places, vigilance et photo.
Fiche pro entièrement éditable, places déclarées en lecture seule avec alerte rouge, icône vigilance sur la liste AM, et cadrage photo aligné sur le wizard validation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-06 17:37:21 +02:00
jmartinandCursor 479a32b4bf feat(#131): fiche AM éditable en 3 onglets et widgets admin partagés.
Modale admin AM (identité, dossier pro, enfants), API front avec repli, note back affiliation, et extraction gélule statut / liste enfants pour la fiche parent.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 17:33:41 +02:00
jmartinandCursor 2fd97ddecb fix(#131): aligner la gélule de statut sur l'en-tête parent.
Le titre, le sélecteur de statut et le bouton fermer partagent le même
padding : la gélule n'est plus collée au bord supérieur de la modale.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 16:34:12 +02:00
jmartinandCursor ce474797c4 fix(#131): co_parent en réponse parents + masquage secrets user
Contrat API fiche parent : co_parent peuplé (déjà chargé), sans password
ni tokens sur user/co_parent. Doc tmp front→back.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 23:39:30 +02:00
jmartinandCursor ebf794e1ac feat(#131): en-tête fiche parent avec nom et co-parent.
Affiche le prénom/nom du parent en titre et le co-parent en sous-titre ; note handoff back dans archive/temporaires.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 23:37:04 +02:00
jmartin d55f240f56 Merge branch 'feature/140-dashboard-admin-ch6-famille' into develop
Livraison epic #140 — dashboard admin ch.6 famille (doc 28 §6.1–§6.2).

Inclus :
- Backend : PATCH fiche parent, rattachement/détachement enfant (#115)
- Frontend : fiche parent éditable (#131), onglet Enfants (#137), fiche/liste enfant (#138), affiliation UI (#116), UserService (#130)
- IdentityBlock partagé, parsing parentChildren, avatars web /uploads

Reste ouvert sur tickets dédiés : fiche enfant UI (#138), modale rattacher (#116), fiche AM (#131), création enfant (#129).
2026-06-24 23:22:39 +02:00
jmartinandCursor 966f4a9c9c feat(#140): liste enfants unifiée — cartes, âge précis et cadre scrollable
Partage AdminEnfantUserCard entre fiche parent et onglet Enfants, enrichit ParentChildSummary (photo, dates) et compacte la modale parent.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 23:06:38 +02:00
jmartinandCursor 8494341b56 feat(#140): fiches admin famille — IdentityBlock, parsing enfants et avatars web
Extrait IdentityBlock réutilisable, aligne les modales parent/enfant sur le style validation, corrige le parsing parentChildren et les URLs /uploads en Flutter web.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-21 17:42:00 +02:00
jmartinandCursor 1dddc67933 feat(#140): dashboard admin ch.6 — fiche parent, enfants, affiliation
Back: PATCH /parents/:id/fiche, attach/detach enfant, GET /enfants enrichi.
Front: modale parent éditable, onglet Enfants, fiche enfant, UserService.

Couvre doc 28 §6.1–6.2 ; tickets liés #115 #116 #130 #131 #137 #138.
Hors scope: fiche AM (#131), création admin (#129).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-17 00:35:00 +02:00
jmartinandCursor b99745e0fe feat(#112): reprise après refus — dossier complet, email resoumission
- GET/PATCH reprise-dossier enrichis (parents, enfants, motivation, fiche AM)
- Front: lien mail, modale identify login, wizards préremplis, PATCH complet
- Email accusé resoumission aux parents avec n° de dossier
- Fixes préremplissage AM (dates, places, ValueKey étape 2)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 19:19:55 +02:00
jmartinandCursor df776d8200 fix(#112): dates et places AM en reprise (resetForReprise)
Corrige l’ombre des paramètres dateOfBirth, agreementDate et placesAvailable
dans resetForReprise ; renforce le parsing reprise-dossier et ajoute un test.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 19:12:14 +02:00
jmartinandCursor c26ed00374 fix(#112): préremplissage AM complet en reprise + lien login repositionné
Parse dates/places/consentement de façon robuste, rafraîchit l'étape 2 AM
et place « J'ai un numéro de dossier » sous « Créer un compte ».

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 17:29:31 +02:00
jmartinandCursor 9d54d9b19b feat(#112): email accusé resoumission aux parents
Après PATCH reprise-resoumettre (parent), envoi à chaque parent du
dossier : confirmation resoumission + rappel numero_dossier.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 17:26:23 +02:00
jmartinandCursor c438009286 feat(#112): lien login « J'ai un numéro de dossier » + reprise-identify
Modale numéro + e-mail, obtention du token puis redirection vers /reprise.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 17:01:18 +02:00
jmartinandCursor 9b7231f1da fix(#112): afficher photos enfants en reprise + consentement cohérent
Charge existingPhotoUrl dans les cartes enfant (étapes 3 et 5) et pré-coche
le consentement photo lorsqu'une photo est déjà en base.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 17:00:15 +02:00
jmartinandCursor f300505225 feat(#112): aligner reprise front sur dossier complet GET/PATCH
Préremplit parents, enfants, motivation et fiche AM depuis reprise-dossier
et envoie le body PATCH complet (co-parent, enfants par id, champs pro AM).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:46:51 +02:00
jmartinandCursor 25c10c885a docs(#112): note alignement front reprise dossier complet
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:42:35 +02:00
jmartinandCursor d70577b1c3 feat(#112): reprise après refus — dossier complet GET/PATCH
- GET reprise-dossier : parents[], enfants[], texte_motivation (famille) ou fiche AM (#119)
- PATCH reprise-resoumettre : co-parent, enfants (update par id), motivation, fiche AM
- Resoumission famille : tous les parents en en_attente + invalidation token groupée
- DTOs étendus + est_multiple sur enfants dossier famille
- Tests unitaires getRepriseDossier parent/AM

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:39:53 +02:00
jmartinandCursor 671da71752 feat(#112): reprise après refus via lien e-mail (/reprise?token=)
Branche le flux front : chargement du dossier, session reprise, wizards
parent/AM préremplis et resoumission via reprise-resoumettre.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:28:41 +02:00
jmartinandCursor c226c2fcdf fix(mail): cast SMTP pool options pour build TypeScript Docker
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-16 16:07:03 +02:00
jmartinandCursor e1684676a7 fix(mail): retry SMTP transient + erreur API si email refus échoue
- Transporteur SMTP réutilisé (pool) au lieu d'une auth par email
- 3 tentatives avec backoff sur erreurs 454/timeout/coupure Dovecot
- refuseUser renvoie 503 si un email de refus échoue après retry
- Tests unitaires isTransientSmtpError

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 22:50:53 +02:00
jmartinandCursor f71943fa79 fix(front): afficher le statut Refusé pour les parents (#110)
_displayStatus refuse → Refusé ; filtre dropdown Parents aligné.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-15 22:19:29 +02:00
jmartinandCursor 65f10e9ca6 fix(front): refus famille en un seul appel API (#110)
Le back propage le refus et les mails aux co-parents ; évite la 2e requête en erreur.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 22:36:38 +02:00
jmartinandCursor 9eb62ddd13 feat(#110): refus dossier propagé aux co-parents + mail aligné
- `refuseUser` charge tous les parents du même numéro de dossier, leur
  applique le même token_reprise (7 jours), trace une validation refus
  par compte et envoie un mail de refus à chacun. Le refus devient une
  action dossier : peu importe quel parent ouvre ensuite le lien.
- Échec d'envoi mail loggé sans bloquer les autres destinataires.
- Mail de refus aligné sur le look des autres templates (titre/bouton
  verts, bloc motif gestionnaire en encart, échappement HTML cohérent,
  appUrl normalisée).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 19:44:30 +02:00
jmartinandCursor b6e83bf9ef fix(front): SnackBar de confirmation après refus dossier (#110)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 18:56:31 +02:00
jmartinandCursor 919148fa75 fix(front): en mode debug sans API_BASE_URL, cibler le back prod (web inclus)
Évite Uri.base.origin sur flutter run -d chrome qui visait le serveur de dev.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 17:52:54 +02:00
jmartinandCursor 7ba8d51668 feat(front): câbler PATCH refus utilisateur depuis wizards validation (#110)
- UserService.refuseUser → /users/:id/refuser
- AM + famille : envoi motifs, onSuccess + refresh liste
- Famille : refus séquentiel pour chaque parent en_attente
- ValidationRefusForm : état isSubmitting (UX)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 17:38:29 +02:00
jmartin 43dabd1885 merge(master): doc milestone #127 2026-05-04 22:35:14 +02:00
jmartinandCursor a4eee819cd docs(#127): préciser milestone 0.1.0 vs label v0.1.0 (Gitea)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 22:35:12 +02:00
jmartin 3fd4812a07 merge(master): doc #127 v0.1.0 2026-05-04 22:33:46 +02:00
jmartinandCursor 6f39813bb4 docs(#127): statut fermé + label v0.1.0 (alignement Gitea)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-04 22:33:43 +02:00
jmartin 18f3a7f276 feat(#127): mot de passe oublié — flux complet (API + web + e-mail)
- API publique POST /auth/forgot-password et /auth/reset-password (jeton
  password_reset_*, anti-énumération, e-mail P'titsPas)
- BDD: colonnes password_reset_token / password_reset_expires + index
- Front: écrans /forgot-password, /reset-password, lien login, mutualisation
  formulaires par token
- Template e-mail + alignement couleur; tests unitaires (auth, mail)

Merge depuis develop: branche feature/127.

Made-with: Cursor
2026-04-24 12:53:42 +02:00
jmartin e6a087147a merge(develop): #127 mot de passe oublié — flux complet (API, front, e-mail) 2026-04-24 12:53:32 +02:00
jmartin ca88710e49 fix(mail): aligner la couleur du template reset-password
Made-with: Cursor
2026-04-24 12:05:06 +02:00
jmartin 644ba6c9c9 refactor(front): mutualiser les formulaires token password (#127)
Extrait un composant commun PasswordTokenFormScreen pour les pages create/reset password, conserve les comportements métier via wrappers (verify/create vs reset) et active la soumission sur Entrée dans mot de passe oublié.

Made-with: Cursor
2026-04-24 12:03:43 +02:00
jmartin 15123f691c feat(auth): #127 forgot-password + reset-password API (BDD + mail)
Made-with: Cursor
2026-04-23 18:17:39 +02:00
jmartin e51e625d93 feat(front): mot de passe oublié — écrans et API (#127)
- Routes /forgot-password et /reset-password (distinct de /create-password).
- AuthService : POST forgot-password (réponse neutre côté UX) et reset-password.
- Login : lien « Mot de passe oublié ? » vers la demande de réinitialisation.

Made-with: Cursor
2026-04-23 17:02:59 +02:00
jmartin 7765350d9a merge develop: docs #127 liste tickets 2026-04-23 16:48:22 +02:00
jmartin e2188fbc87 docs(#127): liste tickets + issue Gitea mot de passe oublié; chmod create-gitea-issue.sh
Made-with: Cursor
2026-04-23 16:48:13 +02:00
jmartin 46d0f82f54 feat(#118): création du mot de passe depuis le lien e-mail
Livre le parcours complet « lien e-mail → page web → mot de passe initial » :
- API : vérification du token (comportement neutre si invalide/expiré) et création du mot de passe ; tests associés.
- Front : route /create-password, appels verify-token et create-password, validation alignée sur le backend, retour login après succès.
- Web : stratégie d’URL en path pour que les deep links /create-password?token=… fonctionnent sans redirection vers #/login.

Refs: #118
Made-with: Cursor
2026-04-23 16:45:47 +02:00
jmartin e887562a1c fix(front): activer path URL strategy pour create-password (#118)
Active usePathUrlStrategy pour que les deep links web /create-password?token=... soient correctement résolus sans redirection implicite vers #login, et déclare flutter_web_plugins côté front.

Made-with: Cursor
2026-04-23 12:09:46 +02:00
jmartin 29623f33b9 feat(front): implémenter la page create-password du ticket #118
Ajoute le flux frontend de création initiale du mot de passe via token email (route /create-password, vérification de token et soumission /auth/create-password), avec redirection login en succès et gestion neutre des liens invalides/expirés.

Made-with: Cursor
2026-04-23 11:58:10 +02:00
jmartin 3716dfe611 test(auth): couvrir verify-token/create-password pour le ticket #118
Ajoute des tests unitaires sur le flux API de création de mot de passe: forwarding verify-token, rejet confirmation mismatch et validation du comportement lien invalide/expiré côté service.

Made-with: Cursor
2026-04-23 11:53:34 +02:00
jmartin 29cbbb1d08 feat(auth): align verify-token with #118 invalid-link behavior
Treat missing token on GET /auth/verify-token as the same neutral invalid/expired/used response (404), matching ticket #118 expectations for /create-password flows.

Made-with: Cursor
2026-04-23 11:49:31 +02:00
jmartin dff108c7d5 merge(develop): clôture ticket #28 et stabilisation build/prod
Intègre en un seul commit master les changements de develop : templates email de validation (#28), alignement schéma validations + patches SQL, montée Flutter 3.29 (CI/Docker), correctifs bootstrap web/nginx/API origin et gestion HTTP 201 sur validation dossier famille.

Made-with: Cursor
2026-04-23 11:44:09 +02:00
jmartin e434fe2fbe Merge branch 'feature/28-templates-email-validation' into develop 2026-04-23 11:43:44 +02:00
jmartin 12296c917e merge develop: Flutter 3.29 web, nginx, index bootstrap (build Docker) 2026-04-22 19:10:08 +02:00
jmartin a140db8e9d fix(front): validation dossier famille — accepter HTTP 201 (Nest POST)
NestJS renvoie 201 Created sur POST /parents/:id/valider-dossier.
Le client ne gérait que 200 : la validation et l’envoi d’e-mails étaient déjà effectués mais l’UI affichait une erreur après lecture du corps (liste JSON).

Made-with: Cursor
2026-04-22 19:05:24 +02:00
jmartin 2c746ceff3 fix(front,db,ci): déploiement web, API même origine, Flutter 3.29, schéma validations
Frontend (prod web):
- Image builder Flutter 3.29.3 (Dockerfile + workflow CI alignés)
- web/index.html: bootstrap Flutter 3.29 (flutter_bootstrap.js), scripts pdf.js conservés
- Dockerfile: rm html avant COPY pour éviter l’index « Welcome to nginx »
- nginx: server_name _ pour Traefik
- env: sur le web, API = Uri.base.origin si pas de API_BASE_URL (évite mixed content http/https)
- auth: message d’erreur réseau avec type/détail si non-Exception
- CGU/PDF: commentaire sans mention Flutter 3.19 obsolète
- pubspec.lock: résolution dépendances Flutter 3.29

Base de données & doc:
- BDD.sql: validations.commentaire, valide_par, FK ON DELETE SET NULL + commentaire
- patch 2026-04-17: valide_par avec ON DELETE SET NULL
- patch 2026-04-18: FK validations en SET NULL sur bases existantes
- seed: statut validation « valide » (enum)
- FK_POLICIES.md: valide_par
- doc workflow: Flutter 3.29.3

Made-with: Cursor
2026-04-22 18:48:17 +02:00
jmartin fc86181a73 fix(frontend): CGU/PDF compatible Flutter 3.19 + pdfx 2.6 (build web Docker)
- Color.withOpacity à la place de withValues
- PdfViewPinch sans API absente (documentProgress, minScale)

Made-with: Cursor
2026-04-17 18:57:34 +02:00
jmartin f2daab1325 fix(db): validations — colonnes commentaire et valide_par
- BDD.sql alignée sur l’entité TypeORM
- Patch SQL pour bases existantes + README patches

Made-with: Cursor
2026-04-17 18:49:44 +02:00
jmartin 7590c95f06 refactor(mail): #28 un seul template email parent (principal + co-parent)
Made-with: Cursor
2026-04-17 18:37:47 +02:00
jmartin 94d9428c43 feat(backend): #28 templates email validation compte (Handlebars)
- Templates .hbs parent principal, co-parent, AM + lien create-password
- app_name / app_url / expéditeur via ConfigService (sendEmail)
- validateUser: renouvelle token création MDP + envoi email si sans password
- Dist: assets *.hbs (nest-cli)
- Tests unitaires MailService (mock sendEmail)
- Doc liste tickets #28 terminé

Made-with: Cursor
2026-04-17 18:34:04 +02:00
jmartin 4723c78bbb merge(develop): Android embedding v2 / build web Docker
Made-with: Cursor
2026-04-17 17:50:34 +02:00
jmartin 68efa91c39 fix(frontend): projet Android complet (embedding v2) pour build web Docker
- Régénération android/ (MainActivity, Gradle, manifestes, icônes)
- gradle-wrapper.jar versionné (wrapper requis hors machine locale)
- Ignore local.properties à la racine du dépôt
- pubspec.lock / .metadata alignés Flutter 3.19 (résolution pub get CI)

Made-with: Cursor
2026-04-17 17:49:42 +02:00
jmartin 699d163c6a feat: documents légaux upload stable (#126)
Squash develop: correction FK televersePar, droits Docker / volume
/app/documents/legaux pour l’upload PDF admin.

Made-with: Cursor
2026-04-17 17:35:32 +02:00
jmartin 25b5e0dd93 merge: fix upload documents légaux (#126) 2026-04-17 17:35:16 +02:00
jmartin d91caef0c5 merge(master): intégration develop — ticket #50, documents légaux et validation CGU
Squash merge de la branche develop dans master : un seul commit sur master pour regrouper la livraison liée au ticket #50 (affichage dynamique des CGU et de la politique de confidentialité à l'inscription) ainsi que la documentation et les ajustements associés.

Frontend (P'titsPas) : modale de validation bloquante avec onglets CGU / confidentialité, chargement des PDF depuis l'API des documents légaux actifs, affichage via pdfx (PdfViewPinch et barre de progression latérale sur le web, repli PdfView sous Windows). Branchement dans le flux d'inscription (écran de présentation), enrichissement du UserService et correction des chemins média pour les URL absolues.

Documentation: jeux de fichiers juridiques (markdown et PDF de référence), réorganisation (dossier docs/juridique, archives, renommage du briefing), mises à jour index, liste de tickets et décisions projet. Scripts et métadonnées mineures (Gitea, pubspec, index web).
Made-with: Cursor
2026-04-17 17:33:47 +02:00
jmartin 7381ce356d feat(frontend): #50 — validation CGU et politique de confidentialité
- Modale de validation avec chargement des PDF distants, PdfViewPinch et barre de progression latérale (repli PdfView sous Windows).

- Service documents légaux actifs, correction des URLs média, intégration au formulaire de présentation.

- Documentation juridique et réorganisation (archive, index, tickets).

Made-with: Cursor
2026-04-17 17:28:07 +02:00
jmartin 9acfc31e72 fix(infra): droits écriture /app/documents/legaux pour upload PDF (#126)
Le conteneur tournait en nestjs sans permission sur /app : mkdir sur
/app/documents/legaux échouait → 500 sur POST documents-legaux.
Création + chown dans l’image Docker + volume nommé pour persistance.

Made-with: Cursor
2026-04-17 13:42:35 +02:00
jmartin d8a81a56ae fix(documents-legaux): éviter 500 upload sans utilisateur valide (#126)
Ne plus associer televersePar à un UUID fictif inexistant ; laisser null tant que l’auth n’est pas branchée sur l’endpoint d’upload.

Made-with: Cursor
2026-04-17 13:33:36 +02:00
jmartin 241ce36b85 merge: doc tickets #24/#43 depuis develop 2026-04-16 22:56:47 +02:00
jmartin b0594c32e7 docs(tickets): #24 livré backend, #43 alignement API verify-token/create-password
Made-with: Cursor
2026-04-16 22:56:42 +02:00
jmartin 81ac8860f1 feat(auth): durcir la création du mot de passe via token
Squash de develop vers master pour intégrer verify-token/create-password avec TTL strict, usage unique atomique et invalidation explicite du token.

Made-with: Cursor
2026-04-16 11:33:49 +02:00
jmartin 8105c01501 Merge branch 'feature/123-durcissement-token-creation-mdp' into develop 2026-04-16 11:30:09 +02:00
jmartin 2fa1e4a981 feat(auth): durcir le flux de création de mot de passe par token
Ajoute les endpoints verify-token/create-password avec garde-fous TTL, usage unique atomique et invalidation explicite du token pour sécuriser l'activation initiale des comptes.

Made-with: Cursor
2026-04-16 11:30:09 +02:00
jmartin 0cc6fa3a9d docs: ticket #120 fermé — liste tickets + script clôture Gitea
- 23_LISTE-TICKETS : #120 marqué fermé.
- backend/scripts/close-gitea-issue-120.js : commentaire de livraison + PATCH state closed (réutilisable / traçabilité).

Made-with: Cursor
2026-04-13 17:40:58 +02:00
jmartin 0befd4eb2a feat(admin): validation AM/famille, À valider au survol, nettoyage logs
- À valider : ligne avec survol type AdminUserCard, icône Ouvrir centrée et plus grande (iconSize 34).
- Wizard AM : titres Identité et coordonnées / Dossier professionnel / Présentation ; photo à gauche, grille droite [2,2,2,2] (NIR, naissance, agrément, capacité) ; AppUser champs naissance ; dates dd/MM/yyyy via formatIsoDateFr ; ValidationDetailSection titre optionnel.
- Wizard famille : titre étape 4 « Présentation ».
- Suppression des debugPrint liés médias / images / dossier (api_config, auth_network_image, dossier_unifie, user_service, validation_family).

Made-with: Cursor
2026-04-13 17:22:08 +02:00
jmartin f442c85cb6 fix(auth): persister places_available depuis places_disponibles (inscription AM)
Made-with: Cursor
2026-04-13 16:08:20 +02:00
jmartin ab7845347b merge: résolution conflits frontend + scripts parent (#120)
- Conserver le parcours AM complet (photo obligatoire, consentement, MIME, lieux naissance, focus Tab).

- Scripts parent : chemin tests/ressources/photos unifié.

Made-with: Cursor
2026-04-13 16:02:03 +02:00
jmartin 011ab26828 merge: branche backend inscription AM photo (#120) 2026-04-13 15:59:35 +02:00
jmartin a9ce4a6756 fix(am): ordre Tab formulaire pro et chevrons accessibles
- Ordre de focus explicite (NumericFocusOrder) pour l’inscription AM étape 2 : chaîne des champs jusqu’aux chevrons, Pays → NIR, places → Suivant (10) puis Précédent (11).

- FocusTraversalGroup avec OrderedTraversalPolicy sur le Scaffold pour respecter cet ordre (desktop / web).

- Champ pays : TextInputAction.next, onFieldSubmitted et onEditingComplete vers le NIR ; CustomAppTextField expose onEditingComplete.

- NirTextField : focusNode et focusTraversalOrder optionnels.

Made-with: Cursor
2026-04-13 14:09:21 +02:00
jmartin d550e57cb8 fix(inscription-am): étape 1/2 obligatoires alignées API (min 2 car., photo)
- Étape 1 : prénom/nom min 2 caractères (minPersonNameLength sur PersonalInfoFormScreen pour AM)

- Étape 2 : ville/pays naissance min 2 ; photo réelle + consentement obligatoires ; isStep2Complete strict

- registerAM : lieu naissance trim + toujours envoyer photo_base64 ; lieu_* non null si valide

- Étape 4 : blocage envoi si isRegistrationComplete faux

Made-with: Cursor
2026-04-13 13:32:25 +02:00
jmartin b4b44cb1b9 feat(inscription-am): places disponibles, UI étape 2, plafond capacité 4 côté app
- POST /auth/register/am : places_disponibles, enregistrement place_disponible, contrôle ≤ capacité

- Formulaire pro AM : places sur la ligne capacité, mobile un champ par ligne, carte desktop plus compacte (espacements, polices, labelFieldSpacing)

- Capacité et places validées jusqu’à 4 dans l’app (kAmCapaciteAccueilMax) ; hint NIR « 13 chiffres + clé »

- Scripts d’inscription AM (Node + smoke shell) : places_disponibles

Made-with: Cursor
2026-04-13 13:20:26 +02:00
jmartin 58607cdbc9 feat(inscription AM #120): UI étape pro, photo unifiée, scripts et données test
- Panneau AM : validation NIR alignée API, date d’agrément requise côté app,
  capacité 1–10, n° agrément + date sur une ligne, ville/pays formatés au blur.
- Widget RegistrationPhotoSlot (cadre, croix) partagé avec les cartes enfant.
- AuthService : MIME PNG/JPEG pour la photo ; payload date_agrement.
- Scripts register-am-dubois / mansouri ; chemins tests/ressources/photos ;
  doc test-data + seed ; smoke curl inscription AM.

Made-with: Cursor
2026-04-13 13:19:32 +02:00
jmartin 12bf85b7bd chore: déplacer les scripts d'inscription parent vers tests/scripts
repoRoot corrigé (../..) ; usage: node tests/scripts/register-parent-*.mjs

Made-with: Cursor
2026-04-13 13:19:31 +02:00
jmartin 01c1be8f16 feat(am): inscription photo (picker, bytes web, photo_filename) (#120)
- ProfessionalInfoFormScreen : ImagePicker par défaut, MemoryImage, consentement si photo réelle
- AmRegistrationData : photoBytes + photoFilename ; récap étape 4
- registerAM : base64 + filename, erreurs réseau/JSON comme parents
- docs: 23_LISTE-TICKETS — issue #120

Made-with: Cursor
2026-04-13 13:19:31 +02:00
jmartin cdae9b5f7c feat(am): alignement inscription/validation + BDD init
- Lieu naissance obligatoire (DTO), persistance Users + migration SQL
- BDD.sql: colonnes lieu_naissance_*, tables dossier_famille / dossier_famille_enfants
- inscrireAMComplet: places_available = capacite_accueil
- GET dossier AM: user.lieu_naissance_*, consentement_photo
- docs/TEMP_FRONT_alignement_AM.md pour équipe Flutter
- Parité: email accusé AM, RegisterAmResponseDto (numero_dossier)

Made-with: Cursor
2026-04-13 13:14:38 +02:00
jmartin 73efac482f chore: déplacer les scripts d'inscription parent vers tests/scripts
repoRoot corrigé (../..) ; usage: node tests/scripts/register-parent-*.mjs

Made-with: Cursor
2026-04-13 11:40:41 +02:00
jmartin 62b2466cce feat(am): inscription photo (picker, bytes web, photo_filename) (#120)
- ProfessionalInfoFormScreen : ImagePicker par défaut, MemoryImage, consentement si photo réelle
- AmRegistrationData : photoBytes + photoFilename ; récap étape 4
- registerAM : base64 + filename, erreurs réseau/JSON comme parents
- docs: 23_LISTE-TICKETS — issue #120

Made-with: Cursor
2026-04-12 21:41:19 +02:00
jmartin d69768806c fix(auth): inscription AM — persister photo si photo_base64 sans photo_filename (#120)
- Défaut nom fichier photo_am.jpg ; extension finale dérivée du data-URL
- Swagger DTO + description endpoint register/am

Made-with: Cursor
2026-04-12 21:37:47 +02:00
jmartin eac108c9f4 Merge branch 'master' into develop — alignement avec correctifs master (withOpacity, formatters, script Gitea)
Made-with: Cursor
2026-04-12 21:15:07 +02:00
jmartin 4dedd9b87a fix(web): withOpacity à la place de withValues (Flutter 3.19 / dart2js)
Made-with: Cursor
2026-04-12 21:11:06 +02:00
jmartin 518b3f84df Revert "chore(master): alignement post-squash avec develop (téléphone, script Gitea)"
This reverts commit dc04e04598.
2026-04-11 18:08:36 +02:00
jmartin dc04e04598 chore(master): alignement post-squash avec develop (téléphone, script Gitea)
Made-with: Cursor
2026-04-11 18:08:04 +02:00
jmartin fdd1e06e77 [#101] [Frontend] Inscription parent — API, soumission et validation
Squash merge de develop vers master.

Livrables principaux (ticket #101 et mise au point associée) :
- Branchement du formulaire d'inscription parent sur POST /api/v1/auth/register/parent
- Payload DTO (parents, enfants, photos base64, CGU) et services Auth
- Parcours gestionnaire : cartes dossiers, wizard validation famille, images authentifiées
- Scripts d'inscription test (Martin, Durand/Rousseau, Lecomte) ; .gitignore .cursor/

Inclut également les ajustements develop fusionnés dans ce lot (inscription AM, champs relais, etc.).

Closes #101

Made-with: Cursor
2026-04-11 18:07:24 +02:00
jmartin f1c9db2e7b fix(admin): vignettes enfants du wizard validation (cover, sans fond blanc)
- BoxFit.cover pour remplir le cadre photo
- Suppression du fond blanc du slot, rayon factorisé

Made-with: Cursor
2026-04-11 18:02:52 +02:00
jmartin ccfcfa3287 chore: ignorer le dossier .cursor (config IDE Cursor)
Made-with: Cursor
2026-04-11 18:02:52 +02:00
jmartin dbca12d5d1 scripts: ajout des inscriptions parent test (Martin, Durand/Rousseau, Lecomte)
Scripts Node POST /api/v1/auth/register/parent alignés sur docs/test-data et le seed, avec photos ressources/Photos (réduction JPEG via sharp-cli pour Durand/Rousseau).

Made-with: Cursor
2026-04-11 18:02:52 +02:00
jmartin 4fe29799cd fix(api): CORS + CORP sur réponses statiques /uploads (Flutter web)
- enableCors avant les statics
- Access-Control-Allow-Origin * + Cross-Origin-Resource-Policy sur fichiers
- OPTIONS préflight pour /uploads et /api/v1/uploads

Made-with: Cursor
2026-04-11 17:11:23 +02:00
jmartin 3bfdadd301 fix(front): images validation dossier, traces debug, baseUrl via Env
- ApiConfig.baseUrl dérivé de Env.apiBaseUrl (alignement API / origine médias).
- AuthNetworkImage : uploads publics sans Bearer (CORS web) ; log erreurs en debug.
- debugPrint kDebugMode : GET dossier, EnfantDossier.fromJson, absoluteMediaUrl, carte enfant.
- ParentDossier id depuis user_id ; fallbacks présentation / photo.

Made-with: Cursor
2026-04-11 17:08:34 +02:00
jmartin 09289882d0 fix: photos admin — URL via /api/v1/uploads + static Express
- Nest: servir le volume uploads aussi sous /api/v1/uploads (Traefik /api)
- Flutter: _fullPhotoUrl = base API + chemin /uploads/... (plus d’origine site seul)
- EnfantDossier: tolérer photoUrl camelCase

Made-with: Cursor
2026-04-11 16:47:29 +02:00
jmartin e7f0037ac3 fix: motivation dossier famille à l’inscription + servir /uploads + Traefik
- Inscription parent: créer dossier_famille + dossier_famille_enfants (presentation_dossier)
- Nest: useStaticAssets sur le volume uploads (chemins /uploads/photos/…)
- docker-compose: route Traefik PathPrefix /uploads → API (priorité 18)
- Front: ParentDossier.id depuis user_id (aligné API)

Made-with: Cursor
2026-04-11 16:37:44 +02:00
jmartin 4969be5809 feat(inscription): payload enfant/photo et limite JSON 15 Mo côté API
- Express/Nest : body parser json et urlencoded à 15 Mo pour les photos base64.
- Front : envoi d’inscription parent (authService, payload, étape 3).

Made-with: Cursor
2026-04-11 16:36:15 +02:00
jmartin d7ccbfbe35 feat(api): dossier famille — photo_url et consent_photo par enfant
- DossierFamilleEnfantDto: champs optionnels Swagger
- Mapping getDossierFamilleByNumero + fallback dossier_famille_enfants
- GET dossiers/:num (type family) enrichi via délégation existante

Made-with: Cursor
2026-04-11 16:31:11 +02:00
jmartin 2b3128ce4a fix(api): inscription parent — body 15mb, uploads persistants, 409 si unique PG
- NestExpressApplication + useBodyParser json/urlencoded 15mb
- UPLOAD_PHOTOS_DIR: résolution chemin absolu/relatif + doc .env.example / proxy
- docker-compose: volume backend_uploads + UPLOAD_PHOTOS_DIR par défaut
- Map erreur PostgreSQL 23505 → ConflictException (parent + AM)
- Logs Nest si échec SMTP après inscription (email déjà géré)

Made-with: Cursor
2026-04-10 17:40:23 +02:00
jmartin 077a1ca3ed fix(bdd): genre_type inclut Autre (alignement API inscription enfants)
Made-with: Cursor
2026-03-31 00:08:41 +02:00
jmartin bf05b1d7d7 feat(inscription): email accusé réception, photos enfants, formulaires identité
- Backend: mail après inscription parent avec n° dossier, UPLOAD_PHOTOS_DIR, réponse API
- Frontend: imageBytes + payload photo, utils email/code postal/téléphone, champs dédiés
- Formulaires admin, login (focus), personal_info, child_card, custom_app_text_field

Made-with: Cursor
2026-03-31 00:04:38 +02:00
jmartin 110240b682 feat(frontend): inscription — UI cartes enfant, formatage noms, focus Tab
- Formulaire infos perso : ordre de tabulation explicite, Checkbox Material
  pour le consentement photo, formatage nom/prénom/ville à la perte de focus
  et à la soumission (name_format_utils).
- Carte enfant : cadre photo, croix, ombres, consentement ; formatage
  prénom/nom enfant ; normalisation avant passage étape 4.
- Asset photo_frame.png ; HoverReliefWidget clipBehavior.
- Ajustements payload / modèle / étapes inscription liés au parcours.

Made-with: Cursor
2026-03-28 20:40:27 +01:00
jmartin a723412043 revert(ui): ChildCardWidget identique au commit b18d5c8 (7 fév. 2026)
- Restauration fichier tel que dans feat(frontend): Refonte multi-modes (#78)
- Retrait onGenreChanged (step3/step5) ; genre API: défaut Autre si non renseigné
  jusqu’à nouvelle UI genre

Made-with: Cursor
2026-03-28 18:22:22 +01:00
jmartin 1e21fe25f3 fix(front): genre enfant — pastilles au lieu du dropdown sur fond beige
Le commit cd2bf63 ajoutait un Dropdown dans un bloc bg_beige, lourd sur
les cartes aquarellées. Remplacement par trois choix tactiles discrets
(Garçon / Fille / Autre), espacements alignés sur prénom/nom.

Made-with: Cursor
2026-03-28 18:15:02 +01:00
jmartin cd2bf63b8a feat(front): inscription parent — genre obligatoire H/F/Autre (#101)
- ChildData.genre + copyWith ; liste déroulante sur la carte enfant
- Validation et payload API alignés sur GenreType
- Étape 3 : mises à jour via copyWith (préservation du genre)

Made-with: Cursor
2026-03-28 18:08:32 +01:00
jmartin de60a001cc feat(front): inscription parent — soumission réelle vers POST /auth/register/parent (#101)
- ParentRegistrationPayload : validation client et mapping UserRegistrationData → JSON API
- AuthService.registerParent
- Étape 5 : appel API, chargement, erreurs en dialogue, succès inchangé puis /login

Made-with: Cursor
2026-03-28 18:07:52 +01:00
jmartin 481fa66630 chore(front): supprimer le préremplissage et les données aléatoires à l’inscription
- Parcours AM : plus de jeu de test Marie DUBOIS ni photo factice
- Parcours parent : plus de DataGenerator ; enfants vides à l’ajout
- Suppression de data_generator.dart (inutilisé)

Made-with: Cursor
2026-03-28 18:07:46 +01:00
jmartin cde676c4f9 feat: alignement master sur develop (squash)
- Dossiers unifiés #119, pending-families enrichi, validation admin (wizards)
- Front: modèles dossier_unifie / pending_family, NIR, auth
- Migrations dossier_famille, scripts de test API
- Résolution conflits: parents.*, docs tickets, auth_service, nir_utils

Made-with: Cursor
2026-03-26 00:20:47 +01:00
jmartin 0a07fade40 docs: liste tickets — fermeture #106 #107 #109 #119 (Gitea)
Made-with: Cursor
2026-03-26 00:20:32 +01:00
jmartin 0e8a999e41 Merge branch 'feature/107-onglet-a-valider' into develop
Conflits pending-families : conservation du DTO/SQL develop (date_soumission, emails, parents).

Made-with: Cursor
2026-03-26 00:16:53 +01:00
jmartin 2e02f60601 feat(admin): onglet À valider, dossier unifié et modales de validation
- Onglet « À valider » (AM + familles), pending-families et détail dossier par numéro.
- Wizards validation AM et famille, modale commune, chargement via GET /dossiers/:num.
- UI : cartes enfants, photo AM (cadre uniforme, ratio), NIR affiché formaté (espaces autour du tiret).
- Backend : DTO / routes parents pending ; scripts de test et mise à jour issue Gitea.

Tickets #107, #119.

Made-with: Cursor
2026-03-26 00:15:18 +01:00
jmartin 0e6e473e84 feat: pending-families ajoute parents[] (email/tel/ville/cp) + ordre stable
Made-with: Cursor
2026-03-25 10:58:29 +01:00
jmartin cc3639b19a fix: dossier AM expose texte_motivation (alias biographie) pour le front
Made-with: Cursor
2026-03-23 23:43:00 +01:00
jmartin cf9cd495bf feat: enrichissement GET pending-families (date_soumission, nombre_enfants, emails)
Made-with: Cursor
2026-03-23 23:37:50 +01:00
jmartin 76ba28a7ce fix: dossier famille - un seul texte_motivation, plus de tableau presentation
Made-with: Cursor
2026-03-18 01:56:58 +01:00
jmartin 1772744c81 feat(#119): dossier famille = 1 par famille, N enfants; retrait repas/type_contrat/budget; adresse dans API parents
Made-with: Cursor
2026-03-18 01:52:28 +01:00
jmartin 5465117238 fix: renvoyer le genre des enfants dans le dossier famille (GET dossiers/parents)
Made-with: Cursor
2026-03-18 01:35:27 +01:00
jmartin 6e2343087e feat(#119): GET /dossiers/:numeroDossier unifié AM ou famille (type + dossier)
Made-with: Cursor
2026-03-17 23:11:53 +01:00
jmartin f6fabc521e feat(#119): GET /parents/dossier-famille/:numeroDossier - dossier famille complet (admin/gestionnaire)
Made-with: Cursor
2026-03-17 22:40:48 +01:00
jmartin 5390276ecd fix: ParentsModule - import JwtModule pour AuthGuard (évite restart loop au déploiement)
Made-with: Cursor
2026-03-13 16:49:39 +01:00
jmartin 7e9306de01 fix: GET /parents/pending-families 500 + #113 doublons inscription
- parents.service: normaliser parentIds (array ou string PG) pour éviter 500
- auth.service: doublons à l'inscription (#113) - parent/co-parent même email, NIR et numéro agrément AM
- docs: mise à jour statuts tickets

Made-with: Cursor
2026-03-13 16:30:12 +01:00
jmartin d832559027 Merge branch 'develop' of https://git.ptits-pas.fr/jmartin/petitspas into develop 2026-03-13 10:52:28 +01:00
jmartin 54ad1d2aa1 docs: retirer référence aux scripts Gitea validation supprimés
Made-with: Cursor
2026-03-13 10:52:15 +01:00
jmartin 060e610a75 Merge branch 'develop' (squash) – Reprise après refus #111
Made-with: Cursor
2026-03-12 23:06:04 +01:00
jmartin 86b28abe51 feat(#111): Reprise après refus – backend
- GET /auth/reprise-dossier?token= : dossier pour préremplir (token seul)
- PATCH /auth/reprise-resoumettre : token + champs modifiables → en_attente, token invalidé
- POST /auth/reprise-identify : numero_dossier + email → type + token
- UserService: findByTokenReprise, resoumettreReprise, findByNumeroDossierAndEmailForReprise

Made-with: Cursor
2026-03-12 23:04:45 +01:00
jmartin 7e32eef0a7 Merge branch 'develop' (squash) – Refus sans suppression #110
Made-with: Cursor
2026-03-12 22:57:45 +01:00
jmartin 86d8189038 feat(#110): Refus sans suppression – token reprise + email
- Colonnes token_reprise, token_reprise_expire_le (migration + BDD.sql)
- refuser: génère token (7j), enregistre, trace validations, envoie email (template refus + lien reprise)
- MailService.sendRefusEmail ; échec email ne bloque pas le refus

Made-with: Cursor
2026-03-12 22:56:27 +01:00
jmartin aa4e240ad1 Merge branch 'develop' (squash) – Validation dossier famille #108
Made-with: Cursor
2026-03-12 22:47:41 +01:00
jmartin dbcb3611d4 feat(#108): Validation dossier famille – POST /parents/:parentId/valider-dossier
- getFamilyUserIds(parentId) : tous les user_id de la famille (co_parent + enfants partagés)
- Valide en une fois tous les comptes en_attente/refuse de la famille (validateUser)
- Réponse : liste des Users validés

Made-with: Cursor
2026-03-12 22:46:17 +01:00
jmartin a92447aaf0 Merge branch 'develop' (squash) – Liste familles en attente #106
Made-with: Cursor
2026-03-12 22:37:41 +01:00
jmartin 1fa70f4052 feat(#106): Liste familles en attente – GET /parents/pending-families
- Une entrée par famille (co_parent + enfants partagés, même logique que backfill #103)
- libelle, parentIds, numero_dossier ; filtre statut en_attente
- AuthGuard + RolesGuard sur controller parents

Made-with: Cursor
2026-03-12 22:36:16 +01:00
jmartin 94c8a0d97a Merge branch 'develop' (squash) – Statut refusé #105, script Gitea fallback ~/.bashrc
Made-with: Cursor
2026-03-12 22:28:13 +01:00
jmartin 393a527c37 feat(#105): Statut « refusé » – enum, migration, pending/reprise, refuser, connexion
- Enum statut_utilisateur_type + valeur 'refuse' (migration + BDD.sql)
- GET /users/reprise, PATCH /users/:id/refuser (refus_compte en validations)
- PATCH /users/:id/valider accepte en_attente et refuse (reprise)
- Connexion refusée si statut refuse

Made-with: Cursor
2026-03-12 22:21:12 +01:00
jmartin af489f39b4 Merge branch 'develop' (squash) – Numéro de dossier #103 et autres avancements
Made-with: Cursor
2026-03-12 22:14:21 +01:00
jmartin dfd58d9b6c feat(#103): Numéro de dossier – backend
- Colonne numero_dossier (utilisateurs, assistantes_maternelles, parents)
- Table numero_dossier_sequence, format AAAA-NNNNNN, séquence par année
- Génération à la soumission AM et parent (famille)
- Backfill existants (famille = co_parent ou enfants partagés)
- API PATCH /users/:id/numero-dossier (gestionnaire/admin)
- Garde-fous: max 2 parents/dossier, pas de mélange AM/parent

Made-with: Cursor
2026-03-12 22:12:33 +01:00
jmartin 34a36b069e docs: liste tickets complète (23_LISTE-TICKETS) + script gitea-close-issue
Made-with: Cursor
2026-03-11 22:19:33 +01:00
jmartin aefe590d2c Squash merge develop into master (câblage inscription AM #91)
Made-with: Cursor
2026-02-26 21:21:17 +01:00
jmartin 2fa546e6b7 fix(inscription AM): format NIR Corse + extraction message erreur API
Made-with: Cursor
2026-02-26 21:02:40 +01:00
jmartin 8636b16659 feat(inscription AM): câblage API step 4 + AuthService.registerAM avec nir_utils
Made-with: Cursor
2026-02-26 20:52:37 +01:00
jmartin 7e17e5ff8d Merge branch 'feature/91-cablage-inscription-am' into develop
Made-with: Cursor
2026-02-26 19:11:38 +01:00
jmartin f749484731 test(inscription AM): Préremplissage données de test Marie DUBOIS (squash develop)
Étapes 1 à 3 du formulaire d'inscription AM : données du jeu de test
officiel (03_seed_test_data.sql) au lieu du générateur aléatoire.

Made-with: Cursor
2026-02-26 19:10:58 +01:00
jmartin e8b6d906e6 Merge branch 'feature/prefill-am-marie-dubois' into develop
Made-with: Cursor
2026-02-26 19:10:47 +01:00
jmartin ae0be04964 test(inscription AM): Préremplissage données de test Marie DUBOIS
Étapes 1 à 3 du formulaire d'inscription AM : remplacer les données
aléatoires par le jeu de test officiel (03_seed_test_data.sql).

Made-with: Cursor
2026-02-26 19:10:04 +01:00
jmartin ca98821b3e Merge develop into master (squash): ticket #102 NIR harmonisation
- Backend: DTO NIR 15 car 2A/2B, validation format+clé, warning cohérence
- BDD: nir_chiffre NOT NULL, migration pour bases existantes
- Seeds: 02 nir_chiffre, 03 Marie 2A / Fatima 99
- Frontend: nir_utils, nir_text_field, formulaire pro, mock inscription AM

Made-with: Cursor
2026-02-26 13:55:42 +01:00
jmartin 447f3d4137 fix(#102): 02_seed - ajouter nir_chiffre à l'INSERT assistantes_maternelles (NOT NULL)
Made-with: Cursor
2026-02-26 13:53:41 +01:00
jmartin 721f40599b feat(frontend): NIR 15 car., formatage, validation, widget dédié (#102)
- nir_utils: normalizeNir, formatNir, validateNir (format + clé), Corse 2A/2B
- NirInputFormatter: formatage auto à la saisie (espaces + tiret)
- NirTextField: widget réutilisable pour champ NIR
- professional_info_form_screen: NIR 15 car., affichage formaté à l'init
- custom_app_text_field: paramètre inputFormatters

Refs: #102
Made-with: Cursor
2026-02-26 13:49:57 +01:00
jmartin a9c6b9e15b feat(#102): BDD nir_chiffre NOT NULL + migration pour bases existantes
Made-with: Cursor
2026-02-26 12:56:15 +01:00
jmartin 38c003ef6f feat(#102): mock préremplissage AM étape 2 - NIR Marie Dubois 2A (Ajaccio)
Made-with: Cursor
2026-02-26 11:24:27 +01:00
jmartin 3dbddbb8c4 feat(#102): seed NIR Marie 2A (Corse Ajaccio), Fatima 99 (étranger), doc
Made-with: Cursor
2026-02-26 11:23:59 +01:00
jmartin f46740c6ab feat(#102): validation NIR (format + clé 2A/2B) + warning cohérence
Made-with: Cursor
2026-02-26 11:23:22 +01:00
jmartin 85bfef7a6b feat(#102): DTO NIR - accepter 2A/2B Corse (15 caractères)
Made-with: Cursor
2026-02-26 11:21:45 +01:00
jmartin b1a80f85c9 Squash merge develop into master (feat #25 API users/pending, dashboards, login)
Made-with: Cursor
2026-02-26 10:44:04 +01:00
jmartin 3c2ecdff7a Merge branch 'feature/25-backend-pending-users' - feat(#25): API GET /users/pending 2026-02-26 10:43:09 +01:00
jmartin 8b83702bd2 feat(#25): API GET /users/pending - liste comptes en attente
- UserController: endpoint GET /users/pending (rôles SUPER_ADMIN, ADMINISTRATEUR, GESTIONNAIRE)
- UserService: findPendingUsers(role?) avec filtre statut EN_ATTENTE
- GestionnairesService: retrait date_consentement_photo (non présent dans DTO)

Made-with: Cursor
2026-02-26 10:37:22 +01:00
jmartin 19b8be684f test(inscription AM): Données de test Marie DUBOIS pour le parcours d'inscription
En vue du câblage de l'inscription AM sur l'API (#91), remplacement des
données aléatoires par le jeu de test officiel (Marie DUBOIS, seed
03_seed_test_data.sql / docs/test-data) dans les étapes 1 à 3 du
formulaire. Facilite les tests manuels et la recette.

- Étape 1 : identité (Marie DUBOIS, 25 Rue de la République, Bezons)
- Étape 2 : infos pro (NIR, agrément AGR-2019-095001, capacité 4)
- Étape 3 : texte de présentation (biographie du seed)

Autres mises à jour : scripts Gitea, doc tickets, dashboards.

Refs: #91
Made-with: Cursor
2026-02-26 10:30:40 +01:00
jmartin e713c05da1 feat: Bandeau générique, dashboards et doc (squash develop, Closes #100)
- Bandeau générique (DashboardBandeau) pour Parent, Admin, Gestionnaire, AM
- ParentDashboardScreen, AdminDashboardScreen, GestionnaireDashboardScreen, AM dashboard
- AppFooter responsive, scripts Gitea (create/list issues parent API)
- Doc: ticket #101 Inscription Parent API, mise à jour 23_LISTE-TICKETS
- User.fromJson robustesse (nullable id/email/role)
- Suppression dashboard_app_bar.dart au profit de dashboard_bandeau.dart

Refs: #100, #101
Made-with: Cursor
2026-02-25 21:48:38 +01:00
jmartin 5950d85876 feat(frontend): Bandeau générique, footer et doc (#100)
- ParentDashboardScreen : utilisation de DashboardBandeau et AppFooter
- app_footer : footer responsive (desktop / mobile)
- docs/23_LISTE-TICKETS.md : mise à jour liste des tickets
- docs/POINT_TICKETS_FRONT_API.txt : point tickets frontend/API
- backend/scripts : create-gitea-issue-parent-api.js, list-gitea-issues.js

Refs: #100
Made-with: Cursor
2026-02-25 21:44:59 +01:00
jmartinandCursor 4339e1e53d feat(100): bandeau dashboard générique, icônes rôle/email, footer go_router, user fromJson défensif
- Bandeau générique DashboardBandeau (logo | onglets | capsule utilisateur)
- Capsule: icône rôle (admin/gestionnaire/parent/AM) + Prénom Nom + menu (email avec icône, Profil, Paramètres, Déconnexion)
- Migration admin, gestionnaire, parent, AM vers DashboardBandeau
- Écran AM (page blanche), route /am-dashboard
- Routes /privacy et /legal, footer avec context.push
- AppUser.fromJson: id/email/role null-safe
- Suppression DashboardAppBarAdmin et dashboard_app_bar.dart

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 19:50:53 +01:00
jmartinandCursor 51d279e341 docs: fermeture ticket #44 (Dashboard Gestionnaire - Structure)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 16:37:54 +01:00
jmartinandCursor fffe8cd202 merge: squash develop into master (#44 Dashboard Gestionnaire - Structure)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 16:37:15 +01:00
jmartinandCursor defa438edf feat(#44): UserManagementPanel + masquer onglet Administrateurs (dashboard gestionnaire)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 16:35:35 +01:00
jmartinandCursor e990d576cf feat(#44): dashboard gestionnaire + redirection login rôle gestionnaire
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 16:20:59 +01:00
jmartinandCursor 619e39219f merge: squash develop into master (login autofill + clavier #98)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 12:00:51 +01:00
jmartinandCursor e8c6665a06 fix(#98): corriger la compatibilité Flutter du bouton image
Supprime le paramètre mouseCursor de TextButton.styleFrom (non supporté par la version Flutter du projet) et conserve le curseur pointeur via MouseRegion.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-25 11:10:35 +01:00
jmartinandCursor a4e6cfc50e feat(#98): améliorer le login avec autofill natif et navigation clavier
Active l’autofill navigateur/OS sur le formulaire de connexion et complète l’accessibilité clavier (Tab jusqu’au bouton, Entrée sur le mot de passe) sans stockage local custom des identifiants.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 23:27:04 +01:00
jmartinandCursor 6749f2025a fix(backend): remove date_consentement_photo from gestionnaire update
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 23:15:29 +01:00
jmartinandCursor 119edbcfb4 merge: squash develop into master
Intègre en un seul commit les évolutions récentes de develop vers master, incluant la modale admin/gestionnaire, les protections super admin, les ajustements API associés et la mise à jour documentaire des tickets/spec.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 22:58:40 +01:00
jmartinandCursor 80d69a5463 docs: aligner tickets Gitea et figer la spec admin
Synchronise les statuts des tickets #93/#95/#96/#97 avec l'API Gitea et finalise la SSS-001 avec le contrat de gestion des comptes d'administration.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 22:55:22 +01:00
jmartinandCursor 0579fda553 merge: intégrer feature/96-creation-admin-modale dans develop
Fusionne le ticket #96 avec résolution des conflits sur la modale partagée, les droits admin/super admin et l’harmonisation visuelle des listes utilisateurs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 22:36:58 +01:00
jmartinandCursor d14550a1cf feat(#96): harmoniser les icones de rôles en liste et modale
Uniformise l'identité visuelle des rôles (admin, super admin, gestionnaire, parent) avec icônes dédiées dans les listes et la modale, et affiche le téléphone dans la ligne admin en retirant le rôle redondant.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 22:31:23 +01:00
jmartinandCursor 2645cf1cd6 fix(#96): protéger le super admin en édition et suppression
Empêche la suppression d'un super administrateur et fige son identité (nom/prénom) côté API, avec alignement de la modale frontend pour masquer la suppression et verrouiller ces champs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 22:05:17 +01:00
jmartinandCursor e2ebc6a0a1 feat(#96): différencier la consultation admin et le mode édition
Affiche une identité visuelle dédiée pour les super admins et adapte l’action par ligne (oeil en lecture seule, crayon en édition) avec modale strictement read-only quand l’utilisateur n’a pas les droits.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 21:48:11 +01:00
jmartinandCursor 090ce6e13b docs: close ticket #96
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 18:45:37 +01:00
jmartinandCursor d66bdd04be feat: admin creation modal and backend fixes for user updates (#96)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 18:39:01 +01:00
jmartinandCursor d8572e7fd6 feat(#96): finaliser la modale admin/gestionnaire et les règles d’édition
Unifie la modale utilisateur pour création/édition admin et gestionnaire, fiabilise la saisie/normalisation (téléphone, nom/prénom) et corrige la mise à jour backend pour accepter le rattachement relais sans erreur 400.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 17:25:15 +01:00
jmartinandCursor 33cc7a9191 feat(backend): add create admin API and update docs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 11:07:14 +01:00
jmartinandCursor 222d7c702f docs: close ticket #97
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 11:04:21 +01:00
jmartinandCursor 537c46127f feat(backend): implement create admin API (ticket #97)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 11:03:53 +01:00
jmartinandCursor 10ebc77ba1 feat(backend): update gestionnaire creation logic and clean up DTOs
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 10:26:24 +01:00
jmartinandCursor ed18dcab10 fix(backend): simplify gestionnaire creation and set default status to ACTIF
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 10:25:42 +01:00
jmartinandCursor f9477d3fbe feat: livrer ticket #35 et synchroniser les évolutions admin
Intègre en un seul commit les évolutions de develop, avec la création/édition/suppression de gestionnaires via modale unifiée (#35) et les correctifs associés sur la gestion admin.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 00:20:33 +01:00
jmartinandCursor bb92f010bd feat(#35): unifier la modale gestionnaire en création et édition
Branche la modale sur l'action Modifier, supprime l'action dédiée de rattachement relais, ajoute la suppression avec confirmation et sécurise le dropdown relais en édition.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 00:19:32 +01:00
jmartinandCursor 42bb872c41 feat(#35): créer un gestionnaire via modale avec sélection de relais
Implémente la création de gestionnaire directement depuis le dashboard admin avec formulaire validé, appel API dédié et rattachement optionnel à un relais depuis une combobox.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-24 00:08:31 +01:00
jmartin 4d37131301 Merge branch 'master' of https://git.ptits-pas.fr/jmartin/petitspas 2026-02-24 00:08:30 +01:00
jmartin fac3ae9baa Merge branch 'develop' of https://git.ptits-pas.fr/jmartin/petitspas into develop 2026-02-24 00:08:28 +01:00
jmartinandCursor 04b910295c fix(backend): fix UpdateGestionnaireDto compilation error (#35)
- Changed UpdateGestionnaireDto to inherit from PartialType(CreateUserDto) instead of CreateGestionnaireDto
- Ensures all fields (like date_consentement_photo) are available for update logic

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 23:53:21 +01:00
jmartinandCursor 5c28981ac5 fix(backend): fix UpdateGestionnaireDto inheritance to include all user fields
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 23:52:56 +01:00
jmartin 57ce5af0f4 Merge branch 'develop' of https://git.ptits-pas.fr/jmartin/petitspas into develop 2026-02-23 23:24:37 +01:00
jmartinandCursor c136f28f12 fix(backend): remove address from gestionnaire creation (#35)
- Updated CreateGestionnaireDto to omit address field
- Updated GestionnairesService to not map address on creation

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 23:21:26 +01:00
jmartin c1204a3050 fix(backend): merge fix/remove-adresse-gestionnaire 2026-02-23 23:19:50 +01:00
jmartinandCursor 9d4363b2a7 fix(backend): remove adresse from CreateGestionnaireDto and service
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 23:19:43 +01:00
jmartinandCursor 4b176b7083 feat: livrer ticket #93 et finaliser #17 avec gestion des Relais (#95)
Homogénéise le dashboard admin (onglets/listes/cartes/états) via composants réutilisables, finalise la création gestionnaire côté backend, et intègre la gestion des Relais avec rattachement gestionnaire.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 23:07:04 +01:00
jmartin af06ab1e66 Merge branch 'feature/93-homogeneisation-onglets-admin' into develop 2026-02-23 23:05:34 +01:00
jmartin aa148354ec Merge branch 'develop' of https://git.ptits-pas.fr/jmartin/petitspas into develop 2026-02-23 23:01:53 +01:00
jmartinandCursor 00c42c7bee feat(release): Backend Gestionnaire Creation (#17)
- Implemented MailModule and MailService
- Updated GestionnairesService to send welcome email
- Forced password change on first login for new gestionnaires

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 22:50:13 +01:00
jmartinandCursor a10dc5a195 docs: mark ticket #17 as completed
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 22:47:15 +01:00
jmartin 04c0b05aae feat(backend): merge feature/17-backend-create-gestionnaire 2026-02-23 22:46:36 +01:00
jmartinandCursor d0b730c8ab feat(backend): implement gestionnaire creation with email notification (#17)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 22:46:30 +01:00
jmartinandCursor bc8362bdb7 refactor(#93): extraire un widget UserList réutilisable
Centralise le pattern d'affichage des listes utilisateurs pour garantir une UI homogène entre gestionnaires, parents, assistantes maternelles et administrateurs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 17:59:03 +01:00
jmartinandCursor ac3178903d docs(#93): tracer l'évolution RBAC intra-RPE dans le CDC
Documente la future gouvernance par rôles au sein d'un même relais pour cadrer les évolutions ultérieures sans l'intégrer au périmètre des tickets backend/frontend actuels.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 17:32:09 +01:00
jmartinandCursor aec1990ec9 refactor(#93): uniformiser la ligne utilisateur et afficher Modifier au survol
Met le rendu des lignes sur une seule ligne (icone, nom, infos) et n’affiche l’action Modifier qu’au hover pour alléger visuellement les listes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 17:32:09 +01:00
jmartinandCursor 5da2ab9005 feat(#93): optimiser l’affichage Parents/AM avec modale de détails
Intègre un bandeau unique (onglets à gauche, recherche/filtre en pilule, bouton Ajouter à droite) et compacte les cartes Parents/AM avec ouverture d’une modale complète sur Modifier (croix, actions Modifier/Supprimer).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 17:32:03 +01:00
jmartinandCursor b2d6414fab refactor(#93): homogénéiser la présentation des onglets admin
Uniformise les 4 onglets de gestion admin avec des composants UI partagés (header, états de liste, carte utilisateur) pour garantir une expérience cohérente sans changement backend.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 17:31:50 +01:00
jmartinandCursor fbafef8f2c feat(#95): implémenter la gestion Relais admin et le rattachement gestionnaire
Ajoute la section Paramètres territoriaux avec CRUD Relais, modale de saisie structurée, états visuels harmonisés, et rattachement d'un relais principal aux gestionnaires via l'API.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 20:06:17 +01:00
jmartinandCursor 42c569e491 feat(release): Backend Relais Module (#94)
- Implemented Relais entity and CRUD API
- Added relation between Users (Gestionnaires) and Relais
- Updated database initialization script
- Documentation updates

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 14:40:32 +01:00
jmartinandCursor 135c7c2255 docs: mark ticket #94 as completed
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 14:39:17 +01:00
jmartin 9cce326046 feat(backend): merge feature/94-backend-relais (Relais module and database update) 2026-02-21 14:38:44 +01:00
jmartinandCursor d697083f54 feat(database): add Relais table to initialization script
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 14:34:45 +01:00
jmartinandCursor ae786426fd feat(backend): implement Relais module and relation with Gestionnaire (Ticket #94)
- Create Relais entity
- Create Relais module, controller, service with CRUD
- Update Users entity with ManyToOne relation to Relais
- Update GestionnairesService to handle relaisId

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 14:27:14 +01:00
jmartinandCursor d32d956b0e feat(dashboard-admin): connect admin dashboard to real API data (Ticket #92)
- Frontend:
  - Create UserService to handle user-related API calls (gestionnaires, parents, AMs, admins)
  - Update AdminDashboardScreen to use dynamic widgets
  - Implement dynamic management widgets:
    - GestionnaireManagementWidget
    - ParentManagementWidget
    - AssistanteMaternelleManagementWidget
    - AdminManagementWidget
  - Add data models: ParentModel, AssistanteMaternelleModel
  - Update AppUser model
  - Update ApiConfig

- Backend:
  - Update controllers (Parents, AMs, Gestionnaires, Users) to allow ADMINISTRATEUR role to list users
  - Fix: Activate endpoint GET /gestionnaires (import GestionnairesModule in UserModule)

- Docs:
  - Add note about backend fix for Gestionnaires module
  - Update .cursorrules to forbid worktrees

- Seed:
  - Add test data seed script (reset-and-seed-db.sh)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-17 22:17:51 +01:00
jmartinandCursor e4f7a35f0f fix(#92): activer endpoint GET /gestionnaires (note backend)
- UserModule: importer GestionnairesModule
- GestionnairesModule: importer AuthModule (AuthGuard/JwtService)
- user_service: appeler /gestionnaires au lieu de /users + filtre

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 22:46:11 +01:00
jmartinandCursor 8a6768b316 feat(dashboard-admin): connect admin dashboard to real API data (Ticket #92)
- Frontend:
  - Create UserService to handle user-related API calls (gestionnaires, parents, AMs, admins)
  - Update AdminDashboardScreen to use dynamic widgets
  - Implement dynamic management widgets:
    - GestionnaireManagementWidget
    - ParentManagementWidget
    - AssistanteMaternelleManagementWidget
    - AdminManagementWidget
  - Add data models: ParentModel, AssistanteMaternelleModel
  - Update AppUser model
  - Update ApiConfig

- Backend:
  - Update controllers (Parents, AMs, Gestionnaires, Users) to allow ADMINISTRATEUR role to list users
  - Note: Gestionnaires endpoint is currently bypassed in frontend (using /users filter) due to module import issue (documented in docs/92_NOTE-BACKEND-GESTIONNAIRES.md)

- Docs:
  - Add note about backend fix for Gestionnaires module
  - Update .cursorrules to forbid worktrees

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 21:43:27 +01:00
jmartinandCursor 3892a8beab feat(#92): seed données de test dashboard admin + script reset BDD
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 17:27:26 +01:00
jmartinandCursor d39bc55be3 chore(backend): variables LOG_API_REQUESTS et CONFIG_ENCRYPTION_KEY dans docker-compose
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 17:26:19 +01:00
jmartinandCursor e0debf0394 docs: README BDD seed/reset, liste tickets #92, procédure API Gitea, statut application
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 17:26:13 +01:00
jmartin 1fca0cf132 Merge branch 'master' of https://git.ptits-pas.fr/jmartin/petitspas 2026-02-16 16:22:16 +01:00
jmartinandCursor b16dd4b55c merge: resolution conflits develop -> master (ticket #14)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 16:22:04 +01:00
jmartinandCursor 8682421453 Merge develop into master (squash)
- feat(#90): API Inscription AM - POST /auth/register/am
- Suppression legacy register/parent/legacy
- BDD assistantes_maternelles alignée entité
- Script test register AM

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 16:19:23 +01:00
jmartinandCursor 31bd8c3175 fix(#90): BDD assistantes_maternelles alignée entité + script test curl
- BDD.sql: ville_residence, annee_experience, specialite, date_agrement nullable
- scripts/test-register-am.sh pour tester POST /auth/register/am

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 16:18:06 +01:00
jmartinandCursor c94f2cf0d5 feat(#90): API Inscription AM - POST /auth/register/am
- DTO RegisterAMCompletDto (identité, photo, infos pro, CGU)
- Endpoint POST /auth/register/am + inscrireAMComplet() (transaction User + AssistanteMaternelle)
- Photo base64, token création MDP, consentement photo
- Suppression legacy: route register/parent/legacy, registerParent(), RegisterParentDto
- Frontend: ApiConfig.registerAM pour ticket #91

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-16 00:05:23 +01:00
jmartinandCursor dfe7daed14 Merge squash develop into master (incl. #14 première config setup/complete)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-15 23:20:15 +01:00
jmartin 111935e451 Merge branch 'feature/14-premiere-connexion-config' into develop (#14) 2026-02-15 23:20:00 +01:00
jmartinandCursor ae3292a7fc fix(backend): setup/complete accepte userId null pour éviter erreur UUID (#14)
- completeSetup: userId = req.user?.id ?? null (plus de fallback 'system')
- markSetupCompleted(userId: string | null), set(..., userId ?? undefined)
- Corrige 'invalid input syntax for type uuid: "system"' au clic Sauvegarder

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-15 23:19:18 +01:00
jmartinandCursor 8e8c6d79b1 feat(#14): finalisation redirection et nettoyage
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-15 23:08:02 +01:00
jmartinandCursor 6752dc97b4 feat(#14): redirection première connexion config
- Redirection vers /login après première config réussie
- Gestion défensive des réponses API (200/201, bool/string)
- Force l'onglet Paramètres si setup non terminé

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-15 23:02:12 +01:00
jmartinandCursor 31857ec891 docs(#14): note back config/setup + frontend parsing erreurs
- docs/14_NOTE-BACKEND-CONFIG-SETUP.md : modifs à faire côté back (UUID system)
- configuration_service : parsing défensif des réponses d'erreur (évite JSNull)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 16:10:04 +01:00
jmartinandCursor ca7ef862da feat(admin): première connexion → panneau Paramètres, reste grisé jusqu'à Sauvegarder (#14)
- Au chargement admin: appel getSetupStatus(), si non terminé → onglet Paramètres par défaut
- Onglet Gestion des utilisateurs grisé et inaccessible tant que setup non complété
- Sauvegarder: updateBulk + completeSetup + déblocage des panneaux
- Tester SMTP: saveBulkOnly puis test (sans completeSetup, panneaux restent verrouillés)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 15:54:47 +01:00
jmartinandCursor 11aa66feff Merge squash develop into master (incl. #89 log API requests debug)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 15:26:06 +01:00
jmartin 358eefdab3 Merge branch 'feature/89-log-api-requests' into develop (#89) 2026-02-13 15:25:56 +01:00
jmartinandCursor d23f3c9f4f feat(admin): panneau Paramètres - sauvegarde config + test SMTP (#15)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 15:23:00 +01:00
jmartinandCursor 1834eb8c79 feat(admin): panneau Paramètres - sauvegarde config + test SMTP
- Onglet Paramètres dans l'admin avec 3 sections (Email, Personnalisation, Avancé)
- Service ConfigurationService (GET config, PATCH bulk, POST test-smtp)
- Bouton Sauvegarder et bouton Tester SMTP (sauvegarde avant test)
- Endpoints api_config pour configuration

Closes #15

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 11:16:37 +01:00
jmartinandCursor 0386785f81 feat(backend): log des appels API en mode debug (#89)
- Ajout LogRequestInterceptor (méthode, URL, query, body)
- Activé via LOG_API_REQUESTS=true
- Masquage des champs sensibles (password, smtp_password, token...)
- Enregistrement global dans main.ts, doc dans .env.example

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 11:15:41 +01:00
jmartinandCursor c43f55bed6 Merge origin/develop (conflit 23_LISTE-TICKETS résolu, garde numéros = Gitea)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-10 00:20:59 +01:00
jmartinandCursor 0c48a5c06f Merge branch 'develop' (docs concept v1.3, conflit résolu)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-10 00:20:32 +01:00
jmartinandCursor be8b1f23ed docs: concept v1.3 config (panneau Paramètres 3 sections, numéros = Gitea)
- 21_CONFIGURATION-SYSTEME: workflow sans /admin/setup, 3 sections, panneau unique
- 23_LISTE-TICKETS: numéros de section = numéros Gitea, tickets #14/#15 alignés
- 24_DECISIONS-PROJET: config initiale = panneau + navigation bloquée
- BRIEFING-FRONTEND: tickets #12/#13 remplacés par panneau Paramètres
- Suppression login_screen.dart.bak

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-10 00:19:35 +01:00
jmartinandCursor 18b270eaa3 Merge origin/develop: résolution conflits doc tickets
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 23:55:16 +01:00
jmartinandCursor 68e4f54814 Merge develop (squash): correctifs modale MDP (champs lavande/jaune), doc tickets 84/85
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 23:53:58 +01:00
jmartinandCursor 6794190916 chore(login): retrait du lien Test modale MDP
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 23:52:51 +01:00
jmartinandCursor 790761d576 fix(modale): champs MDP actuel lavande, nouveau et confirmation jaune
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 23:52:25 +01:00
jmartinandCursor 930097f87d Merge develop: fix auth connexion admin (#84)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 22:37:48 +01:00
jmartinandCursor 18af5c9034 fix(auth): connexion admin - token snake_case, routes GoRouter, profil (Closes #84)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 22:36:52 +01:00
jmartinandCursor 10bf2553e7 fix(ui): renforcer ombre des boutons Parents/AM sur mobile (écran choix)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 19:15:10 +01:00
jmartinandCursor 678f4219b5 docs: ticket #82 fermé (écran Login mobile)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 18:56:07 +01:00
jmartinandCursor 5295e8ec72 Merge develop (squash): login mobile, formulaire sous slogan par ratio
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 18:54:16 +01:00
jmartinandCursor 480f4a9396 fix(login): position formulaire sous slogan par ratio image (river_logo_mobile)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 18:53:37 +01:00
jmartinandCursor 6bf0932da8 docs: Index, doc API Gitea et script fermeture issue
- docs/00_INDEX.md : entrée pour 26_GITEA-API
- docs/26_GITEA-API.md : procédure API Gitea (auth, issues, PR, branches, dépannage)
- scripts/gitea-close-issue.sh : fermer une issue via l'API (GITEA_TOKEN / .gitea-token)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:39:47 +01:00
jmartinandCursor 2f1740b35f docs: Ajouter ticket #83 RegisterChoiceScreen Mobile (terminé)
Mise à jour de la liste des tickets pour documenter le ticket #83 complété :
- Adaptation responsive RegisterChoiceScreen (mobile/desktop)
- Extraction ChoiceCardWidget réutilisable
- Bouton Précédent stylisé avec CustomNavigationButton
- Tailles icônes augmentées (140px/170px)

Total: 65 tickets (~184h)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 12:37:04 +01:00
jmartinandCursor fd97e68dd9 feat: Adapter RegisterChoiceScreen pour mobile (Closes #83)
- Implémentation responsive avec LayoutBuilder pour détecter mobile/desktop
- Mode mobile : titre au-dessus, carte pleine largeur avec ratio 2/3, boutons verticaux
- Mode desktop : chevron en haut à gauche, layout texte/carte côte à côte
- Extraction de la logique de carte dans ChoiceCardWidget réutilisable
- Bouton "Précédent" stylisé avec CustomNavigationButton et HoverReliefWidget
- Tailles d'icônes augmentées (140px mobile, 170px desktop)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 12:34:50 +01:00
jmartinandCursor 813fdb8449 fix(#83): Corriger le style du bouton Précédent et ajuster les tailles d'icônes
Utilise CustomNavigationButton avec HoverReliefWidget pour le bouton Précédent en mode mobile, assurant la cohérence visuelle avec les autres écrans. Augmente également la taille des icônes de choix (140px mobile, 170px desktop).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 12:33:48 +01:00
jmartinandCursor 155c6ca4d5 chore(deps): Update pubspec.lock
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 11:44:47 +01:00
jmartinandCursor 030ef81038 feat(frontend): Adapt RegisterChoiceScreen for mobile (Closes #83)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 17:43:17 +01:00
jmartinandCursor 0d88597bb6 test
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 17:42:09 +01:00
jmartinandCursor 9b007fe490 docs: Mark tickets #38, #39, #40, #41, #42 as completed (Closes #38, Closes #39, Closes #40, Closes #41, Closes #42)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 17:15:15 +01:00
jmartinandCursor 7ecb99963c docs: Update ticket list with #81 status (Closes #81)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 17:10:47 +01:00
jmartinandCursor 39814c76b1 docs: Update ticket list and cleanup obsolete files (Closes #79, Closes #80)
- Delete obsolete nanny_registration_data.dart and nanny_register_confirmation_screen.dart
- Update 23_LISTE-TICKETS.md with #79 and #80 status

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 17:09:29 +01:00
jmartin b956f94ad2 Merge branch 'develop' 2026-02-07 14:52:47 +01:00
jmartinandCursor b18d5c8a9e feat(frontend): Refonte infrastructure formulaires multi-modes
- Support des modes Desktop/Mobile et Édition/Lecture seule
- Refactoring des widgets de formulaire (PersonalInfo, ProfessionalInfo, Presentation, ChildCard)
- Mise à jour des écrans de récapitulatif (ParentStep5, AmStep4)
- Ajout de navigation (Précédent/Soumettre) sur mobile

Closes #78

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 14:51:33 +01:00
jmartinandCursor 6ad88cbbc6 docs: Update ticket list with #78 (Multi-mode forms refactoring)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 14:46:34 +01:00
jmartinandCursor dfe91ed772 Add navigation buttons to mobile recap screens and fix child card width
- Add 'Previous' and 'Submit' buttons to mobile recap screens (Parent & AM)
- Fix imports for navigation buttons and widgets
- Adjust ChildCardWidget width to fill available space on mobile editing

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 14:44:17 +01:00
jmartinandCursor 08612c455d Fix recap screens layout (desktop/mobile) and widget styles
- Restore horizontal 2:1 layout for desktop readonly cards
- Implement adaptive height for mobile readonly cards
- Fix spacing and margins on mobile recap screens
- Update field styles to use beige background
- Adjust ChildCardWidget width for mobile editing
- Fix compilation errors and duplicate methods

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 13:29:11 +01:00
jmartinandCursor 6452706680 fix(#78): Ajustements UI ChildCardWidget et ParentStep3
- Réduction de la taille des polices et des champs dans la carte enfant (Mobile/Desktop) pour éviter l'overflow.
- Restauration de la taille du bouton "+" en mode Desktop (100px).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 14:33:04 +01:00
jmartinandCursor eea94769bf feat(#78): Migrer ParentRegisterStep3Screen (Enfants) vers infrastructure multi-modes
Adaptation responsive du formulaire "Informations Enfants" (Parent Step 3) :
- Desktop : Conservation du layout horizontal avec scroll et effets de fondu
- Mobile : Layout vertical avec cartes empilées
  - Header fixe
  - Bouton "+" carré (50px) centré à la fin de la liste
  - Boutons navigation intégrés au scroll
  - Cartes enfants adaptées (scale 0.9, polices réduites)
- Mise à jour DisplayConfig (mode optionnel par défaut)
- Mise à jour AppCustomCheckbox (paramètre fontSize)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 11:43:26 +01:00
jmartinandCursor bdecbc2c1d feat(#78): Migrer PresentationFormScreen vers infrastructure multi-modes
Adaptation responsive du formulaire de présentation (AM Step 3) :
- Desktop : Layout horizontal avec scroll global (format 2:1)
- Mobile : Layout plein écran sans scroll global
  - Header fixe (titre + étape)
  - Carte occupe tout l'espace vertical disponible
  - Seul le champ texte interne est scrollable
  - Boutons fixes en bas
- Checkbox CGU adaptée (texte raccourci + scale 0.85 en mobile)
- Chevrons uniquement en mode desktop

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 11:19:43 +01:00
jmartinandCursor f8bd911c02 feat(#78): Migrer ProfessionalInfoFormScreen vers infrastructure multi-modes
Adaptation responsive du formulaire d'informations professionnelles (AM Step 2) :
- Desktop : photo gauche (270px) + champs droite
- Mobile : layout vertical avec photo 200px + champs empilés
- Boutons CustomNavigationButton (violet/vert) sous la carte en mobile
- Chevrons uniquement en mode desktop
- Espacement adapté (12px mobile vs 32px desktop)
- Tailles de police adaptées

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 11:12:42 +01:00
jmartinandCursor b79f8c7e64 refactor(#78): Renommer assets images pour usage générique
Renommage des assets pour permettre leur utilisation
aussi bien pour les boutons que pour les champs :

**Images renommées :**
- input_field_bg.png → bg_beige.png
- input_field_jaune.png → bg_yellow.png
- input_field_lavande.png → bg_lavender.png
- btn_green.png → bg_green.png

**Fichiers mis à jour (8) :**
- custom_app_text_field.dart (champs de formulaire)
- custom_navigation_button.dart (nouveau widget boutons)
- base_form_screen.dart (structure de page)
- login_screen.dart
- change_password_dialog.dart
- am_register_step4_screen.dart
- parent_register_step5_screen.dart
- summary_screen.dart

**Avantages :**
 Noms génériques et cohérents
 Réutilisabilité boutons ET champs
 Maintenance facilitée

Référence: #78

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 11:01:54 +01:00
jmartinandCursor a57993a90f feat(#78): Finaliser layout mobile responsive de PersonalInfoFormScreen
Layout mobile complètement repensé avec séparation desktop/mobile :

**Layout Desktop (_buildDesktopFields) :**
- Champs par paires horizontales (Row avec Expanded)
- Code Postal + Ville avec ratio flex 2:5
- Espacement 32px entre lignes
- Taille police : 22px labels, 20px input

**Layout Mobile (_buildMobileFields) :**
- Tous les champs empilés verticalement (Column pure)
- Chaque champ prend toute la largeur
- Espacement 12px entre champs (compact)
- Taille police : 15px labels, 14px input
- Hauteur champs réduite : 45px

**Nouveau widget CustomNavigationButton :**
- Widget réutilisable pour boutons navigation
- Enum NavigationButtonStyle (green/purple)
- Utilise assets images comme fond
- Bouton "Précédent" : fond lavande, texte violet foncé
- Bouton "Suivant" : fond vert, texte vert foncé

**Boutons mobile :**
- Positionnés sous la carte (dans le scroll)
- Aligned avec les marges de la carte (5% de chaque côté)
- Prennent toute la largeur avec Expanded
- Écart de 16px entre les deux
- Utilisation de CustomNavigationButton

**Optimisations mobile :**
- Padding carte réduit : 20px vertical (vs 40px initial)
- Toggles compacts (Switch scale 0.85)
- Titre : 18px (vs 24px desktop)
- Étape : 13px (vs 16px desktop)

Référence: #78

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 11:01:43 +01:00
jmartinandCursor 1d774f29eb feat(#78): Migrer PersonalInfoFormScreen vers infrastructure multi-modes
Migration du widget PersonalInfoFormScreen pour utiliser la nouvelle
infrastructure générique :

**Modifications PersonalInfoFormScreen:**
- Ajout paramètre DisplayMode mode (editable/readonly)
- Utilisation de DisplayConfig pour détecter mobile/desktop
- Utilisation de FormFieldRow pour layout responsive
- Adaptation automatique carte vertical/horizontal
- Boutons navigation adaptés mobile/desktop
- Conservation de toutes les fonctionnalités (toggles, validation, etc.)

**Corrections infrastructure:**
- base_form_screen.dart: Correction paramètres ImageButton (bg, textColor)
- form_field_wrapper.dart: Correction paramètres CustomAppTextField
- Gestion correcte des types nullables (TextInputType)

**Résultat:**
 Compilation sans erreurs
 Layout responsive fonctionnel
 Mode editable opérationnel
 Prêt pour mode readonly (récaps)

Référence: #78

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 17:38:54 +01:00
jmartinandCursor 890619ff59 feat(#78): Créer infrastructure générique pour formulaires multi-modes
Nouvelle architecture centralisée pour tous les formulaires :

**Configuration centrale (display_config.dart):**
- DisplayMode enum (editable/readonly)
- LayoutType enum (mobile/desktop)
- DisplayConfig class pour configuration complète
- LayoutHelper avec utilitaires (détection, spacing, etc.)
- Breakpoint: 600px (mobile < 600px reste toujours vertical)

**Widgets génériques (form_field_wrapper.dart):**
- FormFieldWrapper: champ auto-adaptatif (TextField ou Text readonly)
- FormFieldRow: ligne responsive (horizontal desktop, vertical mobile)

**Structure de page (base_form_screen.dart):**
- BaseFormScreen: layout complet avec carte, boutons, navigation
- Gestion auto des assets carte (horizontal/vertical selon layout)

**Avantages:**
 Code unique pour editable + readonly + mobile + desktop
 Logique centralisée (aucune duplication)
 Héritage automatique via DisplayConfig propagé
 API simple et cohérente

Prochaine étape: Migration des widgets existants

Référence: #78

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 17:33:29 +01:00
jmartinandCursor 5d7eb9eb36 fix(#79): Supprimer toutes les références obsolètes à Nanny
Corrections suite au merge master:
- Suppression imports nanny_register_step*.dart (fichiers supprimés)
- Suppression variable nannyRegistrationDataNotifier
- Suppression section Nanny Registration Flow complète
- Ajout import go_router dans login_screen.dart

L'application compile maintenant sans erreurs.

Référence: #79

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 16:17:49 +01:00
jmartin 45bd8a9ef1 Sync master with develop 2026-02-03 16:12:58 +01:00
jmartinandCursor acb8e72a7c Merge remote-tracking branch 'origin/master' into master
Résolution conflit: Suppression de frontend/lib/navigation/app_router.dart
(fichier obsolète remplacé par frontend/lib/config/app_router.dart)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 16:12:29 +01:00
jmartin b6c70a52ac Merge develop into master
Synchronisation complète de develop vers master.

Inclut:
- Backend: API auth, config, documents légaux (#3-#9, #31, #75-#77)
- Database: Schéma unifié, tables config/documents (#3-#7)
- Documentation: Architecture, déploiement, tickets (#61-#62)
- Frontend: Inscription parents/AM, dashboard (#36-#42)
- Frontend: Renommage Nanny→AM (#79)
- Frontend: Widgets génériques réutilisables (#80)
- Frontend: Corrections post-refactoring (#81)

80 commits mergés - master à jour avec develop
2026-02-03 16:10:47 +01:00
jmartin 96794919a8 refactor(widgets): Extraire ProfessionalInfoFormScreen en widget réutilisable
Nouveau widget professional_info_form_screen.dart :
- Formulaire complet d'infos professionnelles pour AM
- Gestion de la photo avec sélection et consentement
- Champs : ville/pays/date de naissance, NIR, agrément, capacité
- Validations intégrées (NIR 13 chiffres, capacité > 0, etc.)

AM Step 2 refactorisé :
- Utilise le nouveau ProfessionalInfoFormScreen
- Code réduit de ~280 lignes à ~75 lignes
- Logique de génération de données de test préservée
- Préparé pour réutilisation dans les récapitulatifs

Impact : -205 lignes de code
2026-01-28 17:09:41 +01:00
jmartin 271dc713a3 feat(widgets): Créer composants réutilisables pour écrans de récapitulatif
Nouveau fichier summary_screen.dart avec :
- Widget SummaryScreen : Layout générique pour récapitulatif
- Widget SummaryCard : Carte de récapitulatif avec AspectRatio et bouton Edit
- Fonction buildDisplayFieldValue : Champ en lecture seule stylisé

Ces composants permettront de simplifier et unifier les écrans
de récapitulatif parent et AM.
2026-01-28 17:03:10 +01:00
jmartin 13741b0430 chore(auth): Supprimer les fichiers nanny obsolètes
Suppression des 4 fichiers nanny_register_step*.dart qui sont obsolètes
après le renommage complet en "am" (Assistante Maternelle).

Les nouveaux fichiers correspondants sont :
- am_register_step1_screen.dart
- am_register_step2_screen.dart
- am_register_step3_screen.dart
- am_register_step4_screen.dart
2026-01-28 17:00:52 +01:00
jmartin 8e3af711e5 refactor(widgets): Extraire ChildCardWidget dans un fichier séparé
Extraction du widget _ChildCardWidget de parent_register_step3_screen.dart
vers un fichier réutilisable child_card_widget.dart

Améliorations :
- Widget désormais public (ChildCardWidget au lieu de _ChildCardWidget)
- Réutilisable dans d'autres écrans (ex: récapitulatifs détaillés)
- Imports nettoyés et simplifiés
- Meilleure organisation du code

Le widget gère :
- Photo de l'enfant avec sélection d'image
- Toggle "Enfant à naître"
- Champs: Prénom, Nom, Date de naissance
- Checkboxes: Consentement photo, Naissance multiple
- Bouton de suppression (si > 1 enfant)
2026-01-28 17:00:40 +01:00
jmartin e700e50924 fix(widgets): Ajouter 2 toggles côte à côte comme l'ancien design
Structure correcte pour Parent Step 2 :
- Toggle gauche : "Ajouter Parent 2 ?" avec icône person_add_alt_1
- Toggle droit : "Même Adresse ?" avec icône home_work_outlined
- Les 2 toggles sont dans une Row (flex: 12 chacun)
- Toggle "Même Adresse" grisé si Parent 2 désactivé
- Suppression de l'ancienne checkbox en bas

Conforme à l'ancien code testé et validé.
2026-01-28 16:52:04 +01:00
jmartin 36ef0f8d5c fix(widgets): Repositionner le toggle et checkbox dans la carte
- Toggle "Il y a un 2ème parent ?" maintenant DANS la carte (pas au-dessus)
- Checkbox "Même adresse que parent 1" reste dans la carte
- Taille du texte du toggle ajustée à 20px pour cohérence
- Espacement de 25px après le toggle

Position correcte conforme à l'ancien design.
2026-01-28 16:51:02 +01:00
jmartin f09deb5efc fix(auth): Correction des erreurs de compilation
Corrections des appels de méthodes et des types :

1. Parent Steps 1-2 : Passer des objets ParentData complets
   - updateParent1(ParentData(...)) au lieu de paramètres nommés
   - updateParent2(ParentData(...)) ou null pour supprimer

2. AM Step 1 : Utiliser la bonne méthode
   - updateIdentityInfo() au lieu de updatePersonalInfo()

3. personal_info_form_screen : Corrections widgets
   - Accès correct à widget.stepText
   - Gestion du nullable sur onChanged de AppCustomCheckbox

Ces corrections permettent la compilation sans erreur.
2026-01-28 16:47:10 +01:00
jmartin 26a0e31b32 fix(router): Mise à jour du routeur principal
- Ajout des imports pour les nouveaux écrans AM
- Mise à jour des routes /am-register-step1 à step4
- Suppression de la route /am-register-confirmation (obsolète)
- Configuration du Provider AmRegistrationData
- Nettoyage des imports inutilisés

Les routes AM sont maintenant complètes et fonctionnelles.
2026-01-28 16:43:55 +01:00
jmartin 21430dca41 refactor(auth): Refactoring écrans avec widgets génériques
Refactorisation des écrans d'inscription pour utiliser les nouveaux widgets :

Parent Step 1 (227 → 65 lignes, -71%)
- Utilise personal_info_form_screen
- Conserve préremplissage des données de test
- Couleur : peach

Parent Step 2 (273 → 90 lignes, -67%)
- Utilise personal_info_form_screen
- Toggle "Il y a un 2ème parent"
- Checkbox "Même adresse que parent 1"
- Couleur : blue

Parent Step 4 (247 → 42 lignes, -83%)
- Utilise presentation_form_screen
- Formulaire de motivation
- Couleur : green

AM Step 1 (209 → 65 lignes, -69%)
- Utilise personal_info_form_screen
- Conserve préremplissage des données de test
- Couleur : blue

AM Step 3 (195 → 45 lignes, -77%)
- Utilise presentation_form_screen
- Formulaire de présentation
- Couleur : peach

Total : -709 lignes de code maintenable !
2026-01-28 16:43:47 +01:00
jmartin dcb81d3feb feat(widgets): Création de widgets génériques réutilisables
Création de 2 nouveaux widgets génériques pour réduire la duplication :

1. presentation_form_screen.dart
   - Widget pour formulaires de présentation/motivation
   - Paramétrable : titre, couleur, hint, routes
   - Utilisé par Parent Step 4 et AM Step 3
   - Réduction de ~350 lignes de code dupliqué

2. personal_info_form_screen.dart
   - Widget pour formulaires d'informations personnelles
   - Gère nom, prénom, téléphone, email, adresse
   - Options : toggle "2ème parent", checkbox "même adresse"
   - Utilisé par Parent Steps 1-2 et AM Step 1
   - Réduction de ~460 lignes de code dupliqué

Avantages :
- Maintenance simplifiée (1 seul fichier à modifier)
- Cohérence visuelle garantie entre tous les écrans
- Extensibilité facile pour nouveaux types d'utilisateurs
2026-01-28 16:43:36 +01:00
jmartin 7c86feeb78 chore(auth): Suppression des fichiers obsolètes et doublons
- Suppression de l'ancien routeur navigation/app_router.dart
- Suppression du dossier /parent/ (versions dupliquées)
- Suppression du dossier /am/ (versions de travail temporaires)

Ces fichiers sont remplacés par les versions actives dans auth/
2026-01-28 16:43:25 +01:00
jmartin df87abbb85 feat(auth): Renommer "Nanny" en "Assistante Maternelle" (AM)
- Création du modèle am_registration_data.dart
- Création des 4 écrans d'inscription AM (steps 1-4)
- Mise à jour du bouton "Assistante Maternelle" dans register_choice
- Conformité CDC : pas de champs mot de passe dans les formulaires
- Préremplissage des données de test pour faciliter le développement

Ref: Ticket #XX - Renommage workflow inscription AM
2026-01-28 16:43:16 +01:00
jmartin bd81561e41 Merge branch 'master' into develop 2026-01-27 16:51:02 +01:00
jmartin cc96ef20e1 Merge branch 'master' of https://git.ptits-pas.fr/jmartin/petitspas 2026-01-27 16:49:44 +01:00
jmartin a4ac65a5db Merge feature/47: Modale de changement de mot de passe obligatoire 2026-01-27 16:49:37 +01:00
jmartin 3d13eb5b2e [Doc] Ajout tâche amélioration libellé consentement photo (#62)
Ajout d'une tâche dans le suivi des évolutions du CDC :
- Note pour améliorer le libellé de la checkbox de consentement photo
  sur l'écran d'inscription des nounous (étape 2)

Le libellé actuel 'J'accepte l'utilisation de ma photo' devra être
rendu plus explicite et conforme RGPD.

Refs: #62 (Amendement CDC)
2026-01-27 16:44:23 +01:00
jmartin 5b37d09fa9 [Doc] Guide d'architecture technique et déploiement (#61 #16)
Ajout d'une documentation technique complète pour l'infrastructure
et le déploiement de l'application P'titsPas.

Contenu du guide :
- Vue d'ensemble de l'architecture (Flutter frontend + Node.js backend)
- Prérequis serveur (Node.js, PostgreSQL, ressources recommandées)
- Instructions d'installation pas à pas
- Configuration de la base de données PostgreSQL
- Déploiement du backend (NestJS)
- Build et déploiement du frontend Flutter Web
- Configuration NGINX comme reverse proxy
- Sécurisation SSL/TLS avec Let's Encrypt
- Monitoring et maintenance
- Sauvegarde et restauration
- Troubleshooting des problèmes courants

Ce document est essentiel pour le déploiement on-premise de l'application
par les collectivités locales.

Refs: #61 (Guide installation & configuration), #16 (Doc config on-premise)
2026-01-27 16:44:23 +01:00
jmartin 53f3af9794 [Frontend] Ajout nouvelles icônes SVG de l'application
Ajout de deux versions d'icônes SVG pour l'application :
- icon.svg : Icône standard de l'application
- icon_improved.svg : Version améliorée de l'icône

Ces icônes seront utilisées pour le branding de l'application
et les différentes tailles d'affichage.
2026-01-27 16:44:23 +01:00
jmartin 105cf53e7b [Frontend] Parcours complet inscription Assistantes Maternelles (#40 #41 #42)
Implémentation du parcours d'inscription des assistantes maternelles en 4 étapes
+ écran de confirmation, en utilisant Provider pour la gestion d'état.

Fonctionnalités implémentées :
- Étape 1 : Identité (nom, prénom, adresse, email, mot de passe)
- Étape 2 : Infos professionnelles (photo, agrément, NIR, capacité d'accueil)
- Étape 3 : Présentation personnelle et acceptation CGU
- Étape 4 : Récapitulatif et validation finale
- Écran de confirmation post-inscription

Fichiers ajoutés :
- models/nanny_registration_data.dart : Modèle de données avec Provider
- screens/auth/nanny_register_step1_screen.dart : Identité
- screens/auth/nanny_register_step2_screen.dart : Infos pro
- screens/auth/nanny_register_step3_screen.dart : Présentation
- screens/auth/nanny_register_step4_screen.dart : Récapitulatif
- screens/auth/nanny_register_confirmation_screen.dart : Confirmation
- screens/unknown_screen.dart : Écran pour routes inconnues
- config/app_router.dart : Copie du routeur (à intégrer)

Refs: #40 (Panneau 1 Identité), #41 (Panneau 2 Infos pro), #42 (Finalisation)
2026-01-27 16:44:23 +01:00
jmartin 29bee9fa80 [Frontend] Refactorisation inscription Parents avec Provider (#38 #39)
Refactorisation complète du parcours d'inscription des parents pour utiliser
Provider au lieu du passage de données par paramètres de navigation.

Modifications principales :
- Utilisation de Provider pour partager UserRegistrationData entre les étapes
- Simplification du routeur (suppression des paramètres)
- Amélioration de la persistance des données entre les étapes
- Meilleure expérience utilisateur lors de la navigation

Fichiers modifiés :
- models/user_registration_data.dart : Modèle avec ChangeNotifier
- screens/auth/parent_register_step1-5_screen.dart : Intégration Provider
- navigation/app_router.dart : Simplification du routing
- main.dart : Configuration du Provider
- login_screen.dart : Ajout navigation vers inscription
- register_choice_screen.dart : Navigation vers parcours parent/AM
- utils/data_generator.dart : Génération de données de test

Refs: #38 (Étape 3 Enfants), #39 (Étapes 4-6 Finalisation)
2026-01-27 16:44:23 +01:00
Julien Martin dbd56637e1 chore: ignore les fichiers générés par Flutter pour Android et Windows 2026-01-27 16:44:23 +01:00
jmartin 264e0d49ae fix: Configuration CORS explicite pour dev localhost 2026-01-27 16:36:53 +01:00
jmartin fe71fdf28e feat(#47): Ajout de la modale de changement de mot de passe obligatoire
Implémentation complète du ticket #47 :
- Mise à jour de l'URL API vers app.ptits-pas.fr
- Ajout du champ changement_mdp_obligatoire au modèle AppUser
- Ajout des endpoints /auth/me et /auth/change-password-required
- Implémentation de la vraie logique de connexion dans AuthService
- Création de la modale ChangePasswordDialog non-dismissible
- Connexion du bouton de connexion avec gestion de la modale
- Ajout des routes admin-dashboard et parent-dashboard

La modale s'affiche automatiquement après connexion si
changement_mdp_obligatoire = true et bloque l'utilisateur jusqu'au
changement de mot de passe.
2026-01-27 16:30:15 +01:00
jmartin b3ec1b94ea docs: Ajout briefing développement frontend 2026-01-27 16:21:22 +01:00
jmartin 95d1c3741b feat(#31): API Changement MDP obligatoire première connexion (#77)
Co-authored-by: Julien Martin <julien.martin@ptits-pas.fr>
Co-committed-by: Julien Martin <julien.martin@ptits-pas.fr>
2026-01-27 15:13:45 +00:00
jmartin c5028c3b22 feat(#75): Seed Super Administrateur par défaut (#76)
Co-authored-by: Julien Martin <julien.martin@ptits-pas.fr>
Co-committed-by: Julien Martin <julien.martin@ptits-pas.fr>
2026-01-27 15:07:22 +00:00
jmartin cef197d133 Merge master into develop - Synchronisation des branches
Fusion des travaux :
- Backend complet (ConfigService, DocumentsLegaux, Auth, etc.)
- Frontend étapes inscription 1-2
- Infrastructure Docker
- Documentation technique

Résolution des conflits :
- Images déplacées vers frontend/assets/images/
- Dossier Archives supprimé
- Backend : version master conservée
- Frontend : améliorations UI de develop conservées
2026-01-27 14:56:49 +01:00
jmartin c934466e47 Merge pull request 'feat(#37): Inscription Parent - Étape 2 (Parent 2)' (#74) from feature/37-frontend-inscription-parent-step2 into master
feat(#37): Inscription Parent - Étape 2 (Parent 2)
2025-12-01 22:36:31 +00:00
jmartin 90d8fa8669 feat(#37): Inscription Parent - Étape 2 (Parent 2)
Frontend Step2:
- Suppression des champs mot de passe et confirmation
- Correction de l'indicateur d'étape: 2/5 → 2/6
- Améliorations visuelles (mêmes que Step1):
  * Taille des labels: 18 → 22px
  * Taille de police des champs: 18 → 20px
  * Espacement entre champs: 20 → 32px
  * Meilleure répartition verticale avec spaceEvenly

Note: Le champ password est conservé dans le modèle ParentData pour compatibilité
2025-12-01 23:36:02 +01:00
jmartin 90cdf16709 docs: Correction numérotation tickets et ajout statuts terminés
- Correction numérotation pour correspondre à Gitea (#36-#63)
- Ajout tickets #34 et #35 (réservés)
- Marquage tickets terminés avec :
  * #3, #4, #7 (BDD)
  * #18, #19, #20, #21 (Backend API Parent)
  * #36 (Frontend Step1)
- Correction doublons (#38, #39, #41, #42, #47, #48)
- Renumération tickets Frontend et Tests
2025-12-01 23:28:08 +01:00
jmartin bde97c24db Merge pull request 'feat(#36): Inscription Parent - Étape 1 (Parent 1)' (#73) from feature/36-frontend-inscription-parent-step1 into master
feat(#36): Inscription Parent - Étape 1 (Parent 1)
2025-12-01 22:21:02 +00:00
jmartin 9ae6533b4d feat(#36): Inscription Parent - Étape 1 (Parent 1)
Backend:
- Retrait des champs non-CDC: profession, situation_familiale, date_naissance
- Nettoyage des DTOs RegisterParentCompletDto et RegisterParentDto
- Mise à jour de la logique dans auth.service.ts (inscrireParentComplet et legacy)

Frontend Step1:
- Suppression des champs mot de passe et confirmation
- Correction de l'indicateur d'étape: 1/5 → 1/6
- Améliorations visuelles:
  * Taille des labels: 18 → 22px
  * Taille de police des champs: 18 → 20px
  * Espacement entre champs: 20 → 32px
  * Meilleure répartition verticale avec spaceEvenly

Note: Le champ password est conservé dans le modèle ParentData pour compatibilité avec Step2
2025-12-01 23:19:58 +01:00
jmartin 579b6cae90 [Backend] API Inscription Parent - REFONTE Workflow 6 etapes (#72)
Co-authored-by: Julien Martin <julien.martin@ptits-pas.fr>
Co-committed-by: Julien Martin <julien.martin@ptits-pas.fr>
2025-12-01 21:43:36 +00:00
jmartin 9aea26805d Merge pull request '[Backend] Endpoint inscription parent + Nettoyage code #9' (#71) from feature/9-endpoints-inscription-parent into master 2025-11-30 15:10:21 +00:00
jmartin 40c7f40d12 feat(backend): endpoint inscription parent + nettoyage code #9
🧹 NETTOYAGE CODE ÉTUDIANT:
- Suppression console.log/error (4 occurrences)
- Suppression code commenté inutile
- Suppression champs obsolètes (mobile, telephone_fixe)
- Correction password nullable dans Users entity

 NOUVELLES FONCTIONNALITÉS:
- Ajout champs token_creation_mdp dans Users entity
- Création RegisterParentDto (validation complète)
- Endpoint POST /auth/register/parent
- Méthode registerParent() avec transaction
- Gestion Parent 1 + Parent 2 (co-parent optionnel)
- Génération tokens UUID pour création MDP
- Lecture durée token depuis AppConfigService
- Création automatique entités Parents
- Statut EN_ATTENTE par défaut
- Intégration AppConfigModule dans AuthModule
- Amélioration méthode login() (vérif statut + password null)

📋 WORKFLOW CDC CONFORME:
- Inscription SANS mot de passe
- Token envoyé par email (TODO)
- Validation gestionnaire requise
- Support co-parent avec même adresse

Réf: docs/20_WORKFLOW-CREATION-COMPTE.md
Ticket: #9 (ou #16)
2025-11-30 16:09:54 +01:00
jmartin 61b45cd830 Merge pull request '[Backend] API Documents Légaux #31' (#70) from feature/31-api-documents-legaux into master 2025-11-30 15:00:52 +00:00
jmartin f53fd903e5 feat(backend): API documents légaux #31
- Création DTOs (UploadDocumentDto, DocumentsActifsResponseDto, DocumentVersionDto)
- Création DocumentsLegauxController avec 6 endpoints:
  * GET /documents-legaux/actifs (public)
  * GET /documents-legaux/:type/versions (admin)
  * POST /documents-legaux (upload, admin)
  * PATCH /documents-legaux/:id/activer (admin)
  * GET /documents-legaux/:id/download (public)
  * GET /documents-legaux/:id/verifier-integrite (admin)
- Support upload multipart/form-data avec FileInterceptor
- Validation des types (cgu/privacy)
- Stream PDF pour téléchargement
- Intégration dans DocumentsLegauxModule
- Compilation OK

TODO: Ajouter guards auth (JwtAuthGuard, RolesGuard)

Réf: docs/22_DOCUMENTS-LEGAUX.md
2025-11-30 16:00:12 +01:00
jmartin 98082187b5 Merge pull request '[Backend] Service gestion documents légaux #8' (#69) from feature/8-service-documents-legaux into master
Merge pull request #69: Service gestion documents légaux

Ticket #8 complété
2025-11-30 14:51:36 +00:00
jmartin 1fb8c33cbf feat(backend): service gestion documents légaux #8
- Création entité DocumentLegal
- Création entité AcceptationDocument
- Création DocumentsLegauxService avec méthodes:
  * getDocumentsActifs()
  * uploadNouvelleVersion() (avec hash SHA-256)
  * activerVersion() (transaction)
  * listerVersions()
  * telechargerDocument()
  * verifierIntegrite()
  * enregistrerAcceptation()
  * getAcceptationsUtilisateur()
- Création DocumentsLegauxModule
- Intégration dans AppModule
- Ajout dépendances multer + @types/multer

Réf: docs/22_DOCUMENTS-LEGAUX.md
2025-11-30 15:50:38 +01:00
jmartin 6ceb0f0ea9 Merge feature/7-tables-documents-legaux into master
Ticket #7: Ajout tables documents légaux versionnés
- Table documents_legaux
- Table acceptations_documents
- Colonnes CGU dans utilisateurs
- Seed documents v1
2025-11-30 15:36:31 +01:00
jmartin bebd3c74da feat(bdd): ajout tables documents_legaux et acceptations_documents #7
- Création table documents_legaux (versioning + hash SHA-256)
- Création table acceptations_documents (traçabilité RGPD)
- Ajout colonnes dans utilisateurs (cgu_version_acceptee, etc.)
- Seed documents génériques v1 (CGU + Privacy)
- Index pour performance

Réf: docs/22_DOCUMENTS-LEGAUX.md
2025-11-30 15:34:28 +01:00
jmartin e1628da9cb Merge pull request '[Backend] API admin configuration avec test SMTP' (#67) from feature/6-admin-configuration into master
Merge pull request #67: [Backend] API admin configuration avec test SMTP

Implémentation de l'API REST pour la gestion de la configuration système avec test SMTP intégré.

Closes #6
2025-11-28 16:24:57 +00:00
jmartin eb1583b35b feat(backend): API admin configuration avec test SMTP (#6)
Implémentation de l'API REST pour la gestion de la configuration
système par les administrateurs.

Nouveaux fichiers :
- modules/config/config.controller.ts : Controller REST
- modules/config/dto/update-config.dto.ts : DTO mise à jour
- modules/config/dto/test-smtp.dto.ts : DTO test SMTP

Endpoints créés :
 GET /api/v1/configuration/setup/status
   → Vérifier si la configuration initiale est terminée

 POST /api/v1/configuration/setup/complete
   → Marquer la configuration comme terminée

 POST /api/v1/configuration/test-smtp
   → Tester la connexion SMTP + envoi email de test

 PATCH /api/v1/configuration/bulk
   → Mise à jour multiple des configurations

 GET /api/v1/configuration
   → Récupérer toutes les configurations (admin)

 GET /api/v1/configuration/:category
   → Récupérer par catégorie (email/app/security)

Fonctionnalités :
- Validation des données avec class-validator
- Test SMTP avec Nodemailer
- Envoi d'email de test HTML
- Gestion d'erreurs complète
- Rechargement automatique du cache
- Traçabilité des modifications

Sécurité :
- Guards commentés (à activer avec JWT)
- Validation des catégories
- Mots de passe masqués dans les réponses

Dépendances ajoutées :
- nodemailer ^6.9.16
- @types/nodemailer ^6.4.16

Tests effectués :
 GET /setup/status → {setupCompleted: false}
 GET /email → 8 configurations email
 Build Docker réussi
 Toutes les routes mappées correctement

Ref: #6
2025-11-28 17:00:55 +01:00
jmartin ec485b5a3e Merge pull request '[Backend] Service de configuration avec cache et encryption' (#66) from feature/5-service-configuration into master
Merge pull request #66: [Backend] Service de configuration avec cache et encryption

Implémentation du service de configuration dynamique avec cache en mémoire et chiffrement AES-256-CBC.

Closes #5
2025-11-28 15:33:20 +00:00
jmartin 80afe2fa2f feat(backend): service de configuration avec cache et encryption (#5)
Implémentation du service de configuration dynamique pour
le déploiement on-premise de l'application.

Nouveaux fichiers :
- entities/configuration.entity.ts : Entité TypeORM
- modules/config/config.service.ts : Service avec cache et encryption
- modules/config/config.module.ts : Module NestJS
- modules/config/index.ts : Export centralisé

Fonctionnalités :
 Cache en mémoire au démarrage (16 configurations)
 Chiffrement AES-256-CBC pour valeurs sensibles
 Conversion automatique de types (string/number/boolean/json)
 Méthodes get/set avec traçabilité
 Récupération par catégorie (email/app/security)
 Masquage automatique des mots de passe
 Support setup wizard (isSetupCompleted)

Sécurité :
- Clé de chiffrement depuis CONFIG_ENCRYPTION_KEY
- Format iv:encrypted pour AES-256-CBC
- Mots de passe masqués dans les API

Intégration :
- AppConfigModule ajouté à app.module.ts
- Service global exporté pour utilisation dans toute l'app
- Chargement automatique au démarrage (OnModuleInit)

Tests :
 Build Docker réussi
 16 configurations chargées en cache
 Service démarré sans erreur

Ref: #5
2025-11-28 16:29:33 +01:00
jmartin 4149d0147f Merge pull request '[BDD] Ajout table configuration système' (#65) from feature/4-table-configuration into master
Merge pull request #65: [BDD] Ajout table configuration système

Ajout de la table configuration pour la gestion dynamique de la configuration on-premise.

Closes #4
2025-11-28 15:23:31 +00:00
jmartin 47dbe94b02 feat(bdd): ajout table configuration système (#4)
Ajout de la table configuration pour la gestion dynamique
de la configuration on-premise de l'application.

Structure :
- Table configuration (clé/valeur avec types)
- Index sur cle et categorie pour performance
- Contrainte UNIQUE sur cle
- Référence vers utilisateurs pour traçabilité

Données initiales (seed) :
- Configuration Email (SMTP) : 8 paramètres
- Configuration Application : 4 paramètres
- Configuration Sécurité : 4 paramètres

Types supportés :
- string : chaînes de caractères
- number : nombres entiers/décimaux
- boolean : true/false
- json : objets JSON
- encrypted : valeurs chiffrées AES-256

Catégories :
- email : Configuration SMTP
- app : Paramètres application
- security : Paramètres de sécurité

Base de données recréée et testée 
16 configurations insérées par défaut 

Ref: #4
2025-11-28 16:19:46 +01:00
jmartin fd4f5e6b12 Merge pull request '[BDD] Conformité CDC v1.3 - Schéma unifié' (#64) from feature/3-ajout-champs-bdd into master
Merge pull request #64: [BDD] Conformité CDC v1.3 - Schéma unifié

Modifications du schéma BDD.sql pour conformité CDC v1.3.

Closes #3
2025-11-28 15:03:43 +00:00
jmartin 40b1eb2192 feat(bdd): conformité CDC v1.3 - schéma unifié (#3)
Modifications du schéma BDD.sql :

Table utilisateurs :
- password devient NULLABLE (créé après validation via token)
- Ajout token_creation_mdp + token_creation_mdp_expire_le
- telephone unifié (suppression mobile/telephone_fixe)
- Ajout index sur token_creation_mdp

Table assistantes_maternelles :
- date_agrement devient NOT NULL (obligatoire)
- Suppression annee_experience
- Suppression specialite

Table enfants :
- genre devient NOT NULL (obligatoire H/F)

Autres modifications :
- docker-compose.yml : pointage vers BDD.sql unifié
- Suppression des anciens fichiers de migration (01-07)
- Base de données recréée et testée 

Ref: #3
2025-11-28 16:00:17 +01:00
jmartin 933793aad8 docs: ajout roadmap générale (Phases 1-5) et mise à jour index 2025-11-28 14:54:29 +01:00
jmartin 2285069a52 docs: ajout ligne vide finale + ignore token Gitea 2025-11-28 10:31:11 +01:00
jmartin 2e3139b5fc docs: mise à jour contrats API (backend-database et frontend-backend) 2025-11-26 14:33:46 +01:00
jmartin 93306d287b docs: mise à jour workflow création compte (corrections cohérence CDC v1.3) 2025-11-26 14:33:20 +01:00
jmartin d0827a119e docs: ajout documentation technique complète (configuration, documents légaux, tickets, décisions, backlog Phase 2) 2025-11-26 14:33:04 +01:00
jmartin a5dae7a017 docs: Workflow création de compte + refonte documentation
- Ajout Cahier des Charges v1.3
- Ajout Workflow technique création de compte (v1.0)
- Réorganisation docs avec préfixes numériques (00_, 01_, etc.)
- Ajout données de test CSV
- Modifications principales :
  * Champ téléphone unique (suppression mobile/fixe)
  * Inscription sans mot de passe (Parents + AM)
  * Création MDP par email après validation (7j)
  * Genre enfant obligatoire (H/F)
  * Date agrément obligatoire pour AM
2025-11-25 00:28:35 +01:00
jmartin 48b01ed3fe docs: Ajout de la documentation complète (API, Database, Audit)
- Création du dossier docs/ pour centraliser la documentation
- Ajout de API.md : documentation complète de tous les endpoints
- Ajout de DATABASE.md : schéma complet de la base de données
- Ajout de AUDIT.md : audit du projet YNOV
- Déplacement des README-ARCHITECTURE.md et README-DEPLOYMENT.md vers docs/
- Ajout d'un README.md index dans docs/
2025-11-24 21:39:01 +01:00
jmartin aa61831878 feat: Création de la structure api-contracts
- Contrats d'API Frontend ↔ Backend (OpenAPI 3.0)
- Contrats Backend ↔ Database (Prisma/SQL)
- Documentation complète pour génération de code
- Permet l'interchangeabilité des composants
2025-11-24 15:45:07 +01:00
jmartin ad81a2f4f4 feat: Configuration Docker Compose à 3 services
- Frontend: Flutter web (app.ptits-pas.fr)
- Backend: NestJS API (app.ptits-pas.fr/api)
- Database: PostgreSQL 17 + PgAdmin (app.ptits-pas.fr/pgadmin)
- Réseau: ptitspas_network + proxy_network (Traefik)
- Documentation architecture et déploiement
2025-11-24 15:44:58 +01:00
jmartin bbf73458cb feat: Intégration de la base de données PostgreSQL depuis YNOV
- Structure complète: utilisateurs, parents, assmat, enfants, contrats
- Migrations SQL avec enums et contraintes
- Seed: 1 super_admin (admin@ptits-pas.fr)
- Mot de passe: 4dm1n1strateur (hash bcrypt)
2025-11-24 15:44:39 +01:00
jmartin 9cb4162165 feat: Intégration du frontend Flutter depuis YNOV
- Framework: Flutter web
- Pages: Login, inscription, dashboards
- Services: API client, authentification, gestion d'état
- Intégration avec backend NestJS
- Dockerfile pour déploiement web
2025-11-24 15:44:15 +01:00
jmartin 33d6e7b0c3 feat: Intégration du backend NestJS depuis YNOV
- Framework: NestJS avec TypeORM
- Authentification: JWT (access + refresh tokens)
- Gestion utilisateurs: CRUD complet avec validation
- Routes: auth, users, parents, assistantes maternelles
- Dockerfile pour conteneurisation
2025-11-24 15:44:07 +01:00
jmartin 49f0684ad3 nettoyage: Suppression des dossiers de la maquette initiale 2025-11-24 15:43:53 +01:00
Julien Martin 1bbdab03d0 suppression d'un reliquat 2025-05-12 22:47:30 +02:00
Julien Martin 7f78617561 Merge branch 'develop' of https://github.com/MonkeyJLuffy/petitspas into develop 2025-05-12 16:15:29 +02:00
Julien Martin 7707b99773 feat: harmonisation de la taille de police dans les champs de motivation (étape4 et 5) - Ajout du paramètre fontSize au CustomDecoratedTextField - Taille de police fixée à 18px pour une meilleure lisibilité 2025-05-12 16:11:12 +02:00
Julien Martin 760f4feca3 feat(ui): Amélioration des cartes de récapitulatif parents et enfants\n\n- Labes plus grands et espacement augmenté pour les cartes parents\n- Labels plus grands pour les champs enfants\n- Titre enfant intégré et centré dans la carte\n- Image enfant sans cadre blanc, occupant toute la hauteur\n- Bouton de modification bien positionné\n- Affichage des consentements en lecture seule sous la fiche enfant 2025-05-12 13:04:29 +02:00
Julien Martin 03712bd99b feat(ui): Ajout des icônes de croix rouge et grise pour la suppression des cartes enfants 2025-05-12 12:21:05 +02:00
Julien Martin 1496f7f174 refactor(inscription): Refonte complète du processus d'inscription - Modèles etdonnées: Suppression de placeholder_registration_data.dart, ajout de user_registration_data.dart, data_generator.dart et card_assets.dart - Interface utilisateur: Refonte des écrans d'inscription, amélioration des widgets, ajout de cartes colorées - Assets: Ajout de nouvelles cartes colorées - Configuration: Mise à jour de pubspec.yaml et app_router.dart 2025-05-12 12:00:49 +02:00
Julien Martin acb602643a feat: Avancée majeure parcours inscription parent et refactorisation widgets UI
Ce commit comprend plusieurs améliorations significatives :

Inscription Parent - Étape 5 (Récapitulatif) :
- Initialisation de l'écran pour l'étape 5/5 du parcours d'inscription parent.
- Mise en place de la structure de base de l'écran de récapitulatif (titre, fond, bouton de soumission initial, modale de confirmation).
- Intégration de la navigation vers l'étape 5 depuis l'étape 4, incluant le passage (actuellement factice) des données d'inscription.
- Correction des erreurs de navigation et de typage liées à l'introduction de `PlaceholderRegistrationData` pour cette nouvelle étape.

Refactorisation des Widgets UI :
- `CustomAppTextField` :
    - Évolution majeure pour supporter différents styles de fond (beige, lavande, jaune) via un nouvel enum `CustomAppTextFieldStyle`.
    - Les images de fond pour les styles lavande et jaune (`input_field_lavande.png`, `input_field_jaune.png`) ont été renommées et sont maintenant utilisées.
    - Mise à jour de l'écran de login pour utiliser ce `CustomAppTextField` stylisé, remplaçant l'ancien widget privé `_ImageTextField`.
    - Réintégration des paramètres `isRequired`, `enabled`, `readOnly`, `onTap`, et `suffixIcon` qui avaient été omis lors d'une refactorisation précédente, assurant la compatibilité avec l'étape 3.
- `ImageButton` :
    - Extraction du widget privé `_ImageButton` de l'écran de login en un widget public `ImageButton` (dans `widgets/image_button.dart`) pour une réutilisation globale.
    - Mise à jour de l'écran de login pour utiliser ce nouveau widget public.
    - Utilisation du nouveau `ImageButton` pour le bouton "Soumettre ma demande" sur l'écran de l'étape 5.

Corrections :
- Correction d'une erreur de `RenderFlex overflowed` dans la carte enfant (`_ChildCardWidget`) de l'étape 3 de l'inscription parent, en ajustant les espacements internes.
- Résolution de diverses erreurs de compilation qui sont apparues pendant ces refactorisations.
2025-05-07 17:43:07 +02:00
Julien Martin 0772f83369 feat(auth): amélioration UI et UX étape 4 inscription parent 2025-05-07 17:09:06 +02:00
Julien Martin 42d147c273 feat(auth): Amélioration UI/UX étape 3 inscription enfants
- Corrige le débordement visuel (RenderFlex overflow) dans les cartes enfants.

- Augmente les marges latérales du sélecteur d'enfants pour un meilleur centrage.

- Ajoute un défilement automatique vers la droite lors de l'ajout d'un enfant.

- Intègre une barre de défilement horizontale et un effet de fondu dynamique (fading edges) au sélecteur d'enfants.

- Ajuste le padding vertical dans CustomAppTextField pour un meilleur centrage du hintText.

- Met à jour index.html :

  - Utilise le token {{flutter_service_worker_version}}.

  - Ajoute la balise meta mobile-web-app-capable.

  - Rétablit temporairement loadEntrypoint pour éviter un écran blanc (avertissement de dépréciation en attente de correction).
2025-05-07 10:42:52 +02:00
Julien Martin df56ba11df feat(auth): Amélioration UI et logique inscription parent étape 3
- Ajout du switch "Enfant à naître" et ajustement du champ prénom.

- Amélioration de la gestion de l'affichage des photos (placeholder, kIsWeb).

- Refactorisation des boutons avec HoverReliefWidget.

- Localisation du DatePicker en français.

- Nettoyage de l'intégration (annulée) de image_cropper.

- Mise à jour de EVOLUTIONS_CDC.md.
2025-05-06 23:44:10 +02:00
Julien Martin bbdacd68aa feat(auth): Supprime l'ancien workflow d'inscription parent et ajoute les assets pour le nouveau workflow 2025-05-05 12:51:32 +02:00
Julien Martin 7f831f363e chore: mise à jour du .gitignore et nettoyage du cache 2025-05-02 21:27:29 +02:00
Julien Martin 009d42ece8 chore: mise à jour du .gitignore et nettoyage des fichiers inutiles 2025-05-02 21:24:28 +02:00
Julien Martin e6d3c41ecc refactor: suppression des fichiers de thème non utilisés 2025-05-02 20:41:00 +02:00
Julien Martin c7ac3d9ebe docs: mise à jour des règles et évolutions du CDC 2025-05-02 19:54:18 +02:00
Julien Martin c8b8ad9318 feat(login): ajout du lien 'Mot de passe oublié ?' dans l'interface de connexion\n\n- Ajout du lien dans la page de connexion\n- Mise à jour du document d'évolution avec les spécifications de récupération de compte\n- Ajustements mineurs dans l'interface 2025-05-02 19:44:52 +02:00
Julien Martin 482040ba55 fix: mise à jour des chemins de l'icône pour utiliser icon.png 2025-05-01 16:51:20 +02:00
Julien Martin 2bcb0b1e54 J'ajoute tout ce que cursor a oublié... 2025-05-01 16:43:03 +02:00
Julien Martin 30e72242a8 style(login): � Ajustement de la mise en page du formulaire de connexion - Alignement des labels et des champs - Ajustement de la taille de police 2025-05-01 16:34:23 +02:00
Julien Martin aaf7070757 feat(login): Ajoutdes champs de formulaire et du bouton de connexion - Images field_email, field_password et btn_green 2025-04-30 18:38:04 +02:00
Julien Martin f4c211e0dd feat(login): � Refote visuelle du login - Fond paper2 et image river_logo_desktop positionnée à 1/4 de la largeur restante - Séparation desktop/mobile 2025-04-30 18:26:40 +02:00
Julien Martin 9519fafe3a feat: ajout d'un sélecteur de thèmes avec trois options (P'titsPas, Pastel, Sombre) 2025-04-30 11:01:15 +02:00
Julien Martin 9321430818 feat(init): mise en place initiale de P'titsPas - Documentation: CDC complet, sécifications techniques SSS-001, charte graphique, évolutions - Backend: structure NestJS avec controllers/services/routes, config Prisma - Frontend: app Flutter avec structure MVC, thème et Firebase - Changement de nom: SuperNounou devient P'titsPas 2025-04-30 10:38:47 +02:00
507 changed files with 72306 additions and 3963 deletions
+18
View File
@@ -0,0 +1,18 @@
# Fins de ligne : toujours LF dans le dépôt (évite les conflits Linux/Windows)
* text=auto eol=lf
# Fichiers binaires : pas de conversion
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.webp binary
*.pdf binary
*.woff binary
*.woff2 binary
*.ttf binary
*.eot binary
# Scripts shell : toujours LF
*.sh text eol=lf
+7
View File
@@ -12,6 +12,7 @@ dist/
.env.*.local .env.*.local
# IDE # IDE
.cursor/
.idea/ .idea/
.vscode/ .vscode/
*.swp *.swp
@@ -32,11 +33,16 @@ yarn-error.log*
*.sqlite3 *.sqlite3
# Flutter # Flutter
**/android/local.properties
.flutter-plugins .flutter-plugins
.flutter-plugins-dependencies .flutter-plugins-dependencies
.pub-cache/ .pub-cache/
.pub/ .pub/
/build/ /build/
**/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
**/windows/flutter/generated_plugin_registrant.cc
**/windows/flutter/generated_plugin_registrant.h
**/windows/flutter/generated_plugins.cmake
# Coverage # Coverage
coverage/ coverage/
@@ -52,3 +58,4 @@ Xcf/**
# Release notes # Release notes
CHANGELOG.md CHANGELOG.md
Ressources/ Ressources/
.gitea-token
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 MiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 501 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 563 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+87
View File
@@ -0,0 +1,87 @@
# 📜 API Contracts - PtitsPas
Ce dossier contient les **contrats d'API** qui définissent les interfaces entre les différentes couches de l'application.
## 🎯 Objectif
Garantir que **Frontend**, **Backend** et **Database** respectent des contrats stricts, permettant de les rendre **interchangeables** sans casser l'application.
---
## 📁 Structure
```
api-contracts/
├── frontend-backend/ # Contrat Frontend ↔ Backend (HTTP REST)
│ ├── openapi.yaml # Spécification OpenAPI 3.0 (source de vérité)
│ └── generated/ # Code généré automatiquement
│ ├── dart/ # Client API pour Flutter
│ └── typescript/ # Types pour NestJS
└── backend-database/ # Contrat Backend ↔ Database (ORM/SQL)
├── schema.prisma # Schéma Prisma (ou TypeORM entities)
└── migrations/ # Migrations SQL versionnées
```
---
## 🔄 Workflow de Génération
### 1. Frontend ↔ Backend
**Source de vérité :** `frontend-backend/openapi.yaml`
**Génération du client Dart (Flutter) :**
```bash
cd api-contracts/frontend-backend
docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli generate \
-i /local/openapi.yaml \
-g dart-dio \
-o /local/generated/dart
```
**Génération des types TypeScript (NestJS) :**
```bash
cd api-contracts/frontend-backend
npx openapi-typescript openapi.yaml --output generated/typescript/api.types.ts
```
---
### 2. Backend ↔ Database
**Source de vérité :** `backend-database/schema.prisma`
**Génération du client Prisma :**
```bash
cd api-contracts/backend-database
npx prisma generate
```
**Génération des migrations SQL :**
```bash
cd api-contracts/backend-database
npx prisma migrate dev --name <nom_migration>
```
---
## ✅ Avantages
- **Frontend interchangeable** : React, Vue, Angular → il suffit de régénérer le client API
- **Backend interchangeable** : Python, Go, Java → tant qu'il respecte `openapi.yaml`
- **Database read-only en prod** : User PostgreSQL `app_user` (pas de DDL)
- **Cohérence garantie** : Types générés = pas d'erreur de typage
- **Documentation auto** : OpenAPI = documentation interactive (Swagger UI)
---
## 📚 Documentation
- [OpenAPI 3.0 Spec](https://swagger.io/specification/)
- [Prisma Schema](https://www.prisma.io/docs/concepts/components/prisma-schema)
- [openapi-generator](https://openapi-generator.tech/)
- [openapi-typescript](https://github.com/drwpow/openapi-typescript)
+27
View File
@@ -0,0 +1,27 @@
# 💾 Backend ↔ Database Contract
Ce dossier contient le **contrat de données** entre le Backend et la Base de Données.
## 📋 Contenu
- **`schema.prisma`** : Schéma de base de données (à créer)
- **`migrations/`** : Migrations SQL versionnées (actuellement dans `/database/migrations/`)
## 🔄 Migration Future
À terme, les migrations SQL de `/database/migrations/` seront gérées ici avec Prisma :
```bash
# Générer une migration
npx prisma migrate dev --name add_user_phone
# Appliquer en production
npx prisma migrate deploy
```
## 📚 Référence
- [Prisma Migrate](https://www.prisma.io/docs/concepts/components/prisma-migrate)
+179
View File
@@ -0,0 +1,179 @@
openapi: 3.0.0
info:
title: PtitsPas API
version: 1.0.0
description: |
API REST pour l'application PtitsPas.
Ce contrat définit l'interface entre le Frontend (Flutter) et le Backend (NestJS).
contact:
name: PtitsPas Team
email: admin@ptits-pas.fr
servers:
- url: https://app.ptits-pas.fr/api
description: Production
- url: http://localhost:3000/api
description: Développement local
paths:
/auth/login:
post:
summary: Authentification utilisateur
operationId: loginUser
tags:
- Authentication
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- email
- password
properties:
email:
type: string
format: email
example: admin@ptits-pas.fr
password:
type: string
format: password
example: "4dm1n1strateur"
responses:
'200':
description: Authentification réussie
content:
application/json:
schema:
type: object
properties:
access_token:
type: string
description: Token JWT d'accès
refresh_token:
type: string
description: Token JWT de rafraîchissement
user:
$ref: '#/components/schemas/User'
'401':
description: Identifiants invalides
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/users:
get:
summary: Liste des utilisateurs
operationId: listUsers
tags:
- Users
security:
- bearerAuth: []
parameters:
- name: role
in: query
schema:
$ref: '#/components/schemas/RoleType'
- name: statut
in: query
schema:
$ref: '#/components/schemas/StatutUtilisateurType'
responses:
'200':
description: Liste des utilisateurs
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/User'
'401':
description: Non authentifié
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
components:
schemas:
User:
type: object
required:
- id
- email
- role
- statut
properties:
id:
type: string
format: uuid
example: "550e8400-e29b-41d4-a716-446655440000"
email:
type: string
format: email
example: "parent@ptits-pas.fr"
prenom:
type: string
example: "Jean"
nom:
type: string
example: "Dupont"
role:
$ref: '#/components/schemas/RoleType'
statut:
$ref: '#/components/schemas/StatutUtilisateurType'
telephone:
type: string
example: "0612345678"
adresse:
type: string
photo_url:
type: string
format: uri
cree_le:
type: string
format: date-time
RoleType:
type: string
enum:
- parent
- assistante_maternelle
- gestionnaire
- administrateur
- super_admin
StatutUtilisateurType:
type: string
enum:
- en_attente
- actif
- suspendu
Error:
type: object
required:
- message
- statusCode
properties:
message:
type: string
example: "Identifiants invalides"
statusCode:
type: integer
example: 401
error:
type: string
example: "Unauthorized"
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: Token JWT obtenu via /auth/login
+34
View File
@@ -0,0 +1,34 @@
# Fichier: .env.example
# Copier ce fichier vers .env et adapter les valeurs selon votre environnement
# Configuration de la base de données PostgreSQL
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
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
# Configuration de l'API
API_PORT=3000
# Secrets pour l'authentification JWT
JWT_SECRET=dev-jwt-secret-key-change-me
JWT_EXPIRATION_TIME=7d
# Environnement
NODE_ENV=development
# Photos inscription (fichiers écrits depuis base64). Préférer un chemin ABSOLU.
# Local : laisser vide → ./uploads/photos (relatif au cwd du processus).
# Docker (docker-compose) : UPLOAD_PHOTOS_DIR=/app/uploads/photos + volume nommé (voir docker-compose.yml).
# UPLOAD_PHOTOS_DIR=
#
# Reverse proxy : si Nginx devant lAPI, augmenter la taille du corps (ex. client_max_body_size 16m;).
# Traefik en reverse proxy simple ne limite en général pas le corps ; si middleware buffering, prévoir ~16 Mo+.
# Log de chaque appel API (mode debug) — mettre à true pour tracer les requêtes front
# LOG_API_REQUESTS=true
+64
View File
@@ -0,0 +1,64 @@
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
.env
# Tests bdd
.vscode/
BDD.sql
migrations/
src/seed/
+4
View File
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}
+43
View File
@@ -0,0 +1,43 @@
FROM node:22-alpine AS builder
WORKDIR /app
# Copier les fichiers de configuration
COPY package*.json ./
COPY tsconfig*.json ./
COPY nest-cli.json ./
# Installer TOUTES les dépendances (dev + prod pour le build)
RUN npm install && npm cache clean --force
# Copier le code source
COPY src ./src
# Builder l'application
RUN npm run build
# Stage production
FROM node:22-alpine AS production
WORKDIR /app
# Installer seulement les dépendances de production
COPY package*.json ./
RUN npm install --only=production && npm cache clean --force
# Copier le build depuis le stage builder
COPY --from=builder /app/dist ./dist
# Créer un utilisateur non-root
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nestjs -u 1001
# Dossiers écriture runtime (nestjs non-root) : photos + documents légaux (PDF)
RUN mkdir -p /app/uploads/photos /app/documents/legaux && \
chown -R nestjs:nodejs /app/uploads /app/documents
USER nestjs
EXPOSE 3000
CMD ["node", "dist/main"]
+63
View File
@@ -0,0 +1,63 @@
# 🚀 Guide de développement local
## Prérequis
- Docker et Docker Compose installés
- Git
## 🏃‍♂️ Démarrage rapide
### 1. Cloner le projet
```bash
git clone <url-du-depot-backend>
cd ptitspas-backend
```
### 2. Configuration de l'environnement
```bash
# Copier le fichier d'exemple
cp .env.example .env
# Optionnel : adapter les valeurs dans .env selon vos besoins
```
### 3. Lancer l'application
```bash
# Démarrer tous les services (PostgreSQL + PgAdmin + Backend)
docker compose -f docker-compose.dev.yml up -d
# Voir les logs
docker compose -f docker-compose.dev.yml logs -f
```
## 🌐 Accès aux services
- **Backend API** : http://localhost:3000
- **PgAdmin** : http://localhost:8080
- Email : admin@localhost
- Mot de passe : admin123
- **PostgreSQL** : localhost:5432
- Utilisateur : admin
- Mot de passe : admin123
- Base : ptitpas_db
## 🛠️ Commandes utiles
```bash
# Arrêter les services
docker compose -f docker-compose.dev.yml down
# Rebuild le backend après modification du Dockerfile
docker compose -f docker-compose.dev.yml up --build backend
# Voir l'état des services
docker compose -f docker-compose.dev.yml ps
# Accéder au container backend
docker exec -it ptitspas-backend-dev sh
```
## 📝 Notes de développement
- Les modifications du code source sont automatiquement prises en compte (hot reload)
- Les données PostgreSQL sont persistantes via le volume `postgres_dev_data`
- Le fichier `.env` n'est pas versionné pour des raisons de sécurité
+158
View File
@@ -0,0 +1,158 @@
# P'titsPas API Backend ✨
Ce dépôt contient le code source de l'API backend pour la plateforme **P'titsPas**. L'API est construite avec NestJS et est responsable de toute la logique métier, de la gestion des données et de l'authentification des utilisateurs.
---
## 📚 Table des matières
- [Technologies utilisées](#-technologies-utilisées)
- [Prérequis](#-prérequis)
- [Installation](#-installation)
- [Lancement de l'application](#-lancement-de-lapplication)
- [Scripts principaux](#-scripts-principaux)
- [Tests](#-tests)
- [Gestion des migrations](#-gestion-des-migrations)
- [Documentation de l'API](#-documentation-de-lapi)
---
## 🛠️ Technologies utilisées
- **Framework**: [NestJS](https://nestjs.com/) (TypeScript)
- **Base de données**: [PostgreSQL](https://www.postgresql.org/)
- **ORM**: [TypeORM](https://typeorm.io/)
- **Authentification**: JWT avec [Passport.js](http://www.passportjs.org/)
- **Stockage Fichiers**: [MinIO](https://min.io/) (Compatible S3)
- **Tâches Asynchrones**: [Redis](https://redis.io/) avec [BullMQ](https://bullmq.io/)
- **Conteneurisation**: [Docker](https://www.docker.com/)
---
## 📋 Prérequis
Avant de commencer, assurez-vous d'avoir installé les outils suivants sur votre machine :
- [Node.js](https://nodejs.org/) (v18 ou supérieure)
- [npm](https://www.npmjs.com/) ou [pnpm](https://pnpm.io/)
- [Docker](https://www.docker.com/products/docker-desktop/) et Docker Compose
- [Git](https://git-scm.com/)
---
## 🚀 Installation
1. **Clonez le dépôt :**
```bash
git clone https://github.com/votre-username/ptitspas-backend.git
cd ptitspas-backend
```
2. **Créez le fichier d'environnement :**
Copiez le fichier d'exemple `.env.example` et renommez-le en `.env`. Ce fichier est ignoré par Git et contiendra vos secrets locaux.
```bash
cp .env.example .env
```
➡️ **Important :** Ouvrez le fichier `.env` et remplissez les variables (identifiants de la base de données, secrets JWT, etc.).
3. **Installez les dépendances du projet :**
```bash
npm install
```
---
## ▶️ Lancement de l'application
### Méthode recommandée : avec Docker
Cette méthode lance l'ensemble des services nécessaires (API, base de données, MinIO, Redis) dans des conteneurs isolés.
```bash
docker-compose up --build
```
L'API sera accessible à l'adresse `http://localhost:3000` (ou le port que vous avez configuré dans votre `.env`).
### Méthode locale (pour le développement)
Cette méthode ne lance que le serveur NestJS. Assurez-vous que les autres services (PostgreSQL, Redis, etc.) sont déjà en cours d'exécution (par exemple, via Docker).
```
npm run start:dev
```
Le serveur redémarrera automatiquement à chaque modification de fichier.
---
## ⚙️ Scripts principaux
| Commande | Description |
| :------------------ | :-------------------------------------------------------------------- |
| `npm run start:dev` | Lance le serveur en mode développement avec rechargement automatique. |
| `npm run build` | Compile le projet TypeScript en JavaScript. |
| `npm start` | Lance l'application depuis les fichiers compilés (mode production). |
| `npm run lint` | Analyse le code pour détecter les erreurs de style et de syntaxe. |
---
## 🧪 Tests
Pour lancer les tests, utilisez les commandes suivantes :
| Commande | Description |
| :----------------- | :--------------------------------------------------------------- |
| `npm test` | Lance les tests unitaires. |
| `npm run test:e2e` | Lance les tests de bout en bout (end-to-end). |
| `npm run test:cov` | Lance tous les tests et génère un rapport de couverture de code. |
---
## 🗄️ Gestion des migrations
La structure de la base de données est gérée par des fichiers de migration TypeORM.
1. **Générer une nouvelle migration :**
Après avoir modifié une entité TypeORM, générez automatiquement le fichier de migration correspondant.
```bash
npm run migration:generate -- src/database/migrations/NomDeLaMigration
```
2. **Appliquer les migrations :**
Pour mettre à jour le schéma de votre base de données avec les nouvelles migrations.
```bash
npm run migration:run
```
---
## 📖 Documentation de l'API
Une fois l'application lancée, la documentation de l'API générée avec **Swagger (OpenAPI)** est disponible à l'adresse suivante :
➡️ **[http://localhost:3000/api-docs](http://localhost:3000/api-docs)**
Cette interface vous permet d'explorer et de tester toutes les routes de l'API directement depuis votre navigateur.
Excellente idée. C'est un élément crucial qui définit les droits et les devoirs liés à votre code.
En me basant sur la section `10.5 Propriété intellectuelle et licence de lapplication` de votre cahier des charges, j'ai rédigé une section "Licence" qui reflète précisément le statut propriétaire de votre projet.
Voici le `README.md` complet et mis à jour.
---
## 📜 Licence
Ce projet est distribué sous une **licence propriétaire**.
Le code source, la marque "P'titsPas" et la documentation associée sont la propriété exclusive de l'éditeur, Julien MARTIN.
Toute reproduction, distribution, modification ou utilisation du code source est strictement interdite sans un accord écrit préalable de l'auteur. Les clients et partenaires autorisés disposent d'une licence d'utilisation non-exclusive et non-transférable, conformément aux termes de leur contrat.
Pour toute question relative à l'utilisation ou à l'acquisition d'une licence, veuillez contacter l'auteur.
+72
View File
@@ -0,0 +1,72 @@
# Docker Compose pour développement local
# Usage: docker compose -f docker-compose.dev.yml up -d
services:
# Base de données PostgreSQL
postgres:
image: postgres:17
container_name: ptitspas-postgres-dev
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-admin}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-admin123}
POSTGRES_DB: ${POSTGRES_DB:-ptitpas_db}
ports:
- "5433:5432"
volumes:
# Si le fichier d'init existe dans le dépôt database
- ./migrations/01_init.sql:/docker-entrypoint-initdb.d/01_init.sql
- postgres_dev_data:/var/lib/postgresql/data
networks:
- ptitspas_dev
# Interface d'administration DB
pgadmin:
image: dpage/pgadmin4
container_name: ptitspas-pgadmin-dev
restart: unless-stopped
environment:
PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-admin@ptits-pas.fr}
PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin123}
ports:
- "8080:80"
depends_on:
- postgres
networks:
- ptitspas_dev
# Backend NestJS
backend:
build:
context: .
dockerfile: Dockerfile
container_name: ptitspas-backend-dev
restart: unless-stopped
environment:
POSTGRES_HOST: ${POSTGRES_HOST:-postgres}
POSTGRES_PORT: ${POSTGRES_PORT:-5432}
POSTGRES_USER: ${POSTGRES_USER:-admin}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-admin123}
POSTGRES_DB: ${POSTGRES_DB:-ptitpas_db}
API_PORT: ${API_PORT:-3000}
JWT_SECRET: ${JWT_SECRET:-dev-jwt-secret-key}
JWT_EXPIRATION_TIME: ${JWT_EXPIRATION_TIME:-7d}
NODE_ENV: ${NODE_ENV:-development}
ports:
- "3000:3000"
depends_on:
- postgres
volumes:
# Pour le hot reload en développement
- ./src:/app/src
- /app/node_modules
networks:
- ptitspas_dev
volumes:
postgres_dev_data:
networks:
ptitspas_dev:
driver: bridge
+34
View File
@@ -0,0 +1,34 @@
// @ts-check
import eslint from '@eslint/js';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import globals from 'globals';
import tseslint from 'typescript-eslint';
export default tseslint.config(
{
ignores: ['eslint.config.mjs'],
},
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
eslintPluginPrettierRecommended,
{
languageOptions: {
globals: {
...globals.node,
...globals.jest,
},
sourceType: 'commonjs',
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
{
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-floating-promises': 'warn',
'@typescript-eslint/no-unsafe-argument': 'warn'
},
},
);
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true,
"assets": [
{
"include": "**/*.hbs",
"watchAssets": true
}
]
}
}
+10392 -1348
View File
File diff suppressed because it is too large Load Diff
+91 -26
View File
@@ -1,36 +1,101 @@
{ {
"name": "petitspas-backend", "name": "ptitspas-ynov-back",
"version": "1.0.0", "version": "0.0.1",
"description": "Backend pour l'application P'titsPas", "description": "",
"main": "dist/index.js", "author": "",
"private": true,
"license": "UNLICENSED",
"scripts": { "scripts": {
"start": "node dist/index.js", "typeorm": "typeorm-ts-node-commonjs",
"dev": "ts-node-dev --respawn --transpile-only src/index.ts", "migration:run": "npm run typeorm migration:run",
"build": "tsc", "build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest", "test": "jest",
"init-admin": "ts-node src/scripts/initAdmin.ts" "test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"test:api-dossiers": "node scripts/test-api-dossiers.js"
}, },
"dependencies": { "dependencies": {
"@nestjs/common": "^11.1.0", "@nestjs/common": "^11.1.6",
"@prisma/client": "^6.7.0", "@nestjs/config": "^4.0.2",
"@types/jsonwebtoken": "^9.0.9", "@nestjs/core": "^11.0.1",
"bcrypt": "^5.1.1", "@nestjs/jwt": "^11.0.0",
"cors": "^2.8.5", "@nestjs/mapped-types": "^2.1.0",
"express": "^4.18.2", "@nestjs/platform-express": "^11.0.1",
"helmet": "^7.1.0", "@nestjs/swagger": "^11.2.0",
"jsonwebtoken": "^9.0.2", "@nestjs/typeorm": "^11.0.0",
"morgan": "^1.10.0" "@sentry/nestjs": "^10.10.0",
"bcrypt": "^6.0.0",
"bcryptjs": "^3.0.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"handlebars": "^4.7.8",
"joi": "^18.0.0",
"mapped-types": "^0.0.1",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.16",
"passport-jwt": "^4.0.1",
"pg": "^8.16.3",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"swagger-ui-express": "^5.0.1",
"typeorm": "^0.3.26"
}, },
"devDependencies": { "devDependencies": {
"@types/bcrypt": "^5.0.2", "@eslint/eslintrc": "^3.2.0",
"@types/cors": "^2.8.17", "@eslint/js": "^9.18.0",
"@types/express": "^4.17.21", "@nestjs/cli": "^11.0.10",
"@types/helmet": "^4.0.0", "@nestjs/schematics": "^11.0.0",
"@types/morgan": "^1.9.9", "@nestjs/testing": "^11.0.1",
"@types/node": "^20.11.19", "@types/bcrypt": "^6.0.0",
"prisma": "^6.7.0", "@types/express": "^5.0.0",
"@types/handlebars": "^4.1.0",
"@types/jest": "^30.0.0",
"@types/multer": "^1.4.12",
"@types/node": "^22.10.7",
"@types/nodemailer": "^6.4.16",
"@types/passport-jwt": "^4.0.1",
"@types/supertest": "^6.0.2",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-prettier": "^5.2.2",
"globals": "^16.0.0",
"jest": "^30.0.0",
"prettier": "^3.4.2",
"source-map-support": "^0.5.21",
"supertest": "^7.0.0",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"ts-node-dev": "^2.0.0", "tsconfig-paths": "^4.2.0",
"typescript": "^5.3.3" "typescript": "^5.7.3",
"typescript-eslint": "^8.20.0"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/$1"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
} }
} }
+107
View File
@@ -0,0 +1,107 @@
/**
* Commentaire de clôture + fermeture issue Gitea #120.
* Usage: node backend/scripts/close-gitea-issue-120.js
* Token : .gitea-token (racine), GITEA_TOKEN, ou docs/27_BRIEFING-FRONTEND.md
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const repoRoot = path.join(__dirname, '../..');
const ISSUE = 120;
const REPO = 'jmartin/petitspas';
const body = `## Fermeture ticket #120 — livré sur \`develop\`
Branche **\`feature/120-inscription-am-photo\`** mergée dans **\`develop\`** (livraison : inscription AM alignée parents + panneau validation gestionnaire).
### Inscription AM (alignement parents)
- Photo, consentement, lieux de naissance : parcours et API alignés sur le modèle parents (DTO, entité user, migration SQL, écrans inscription AM, \`registration_photo_slot\`, scripts de test Node).
### Panneau gestionnaire — onglet « À valider »
- Bouton **Ouvrir** : visible au **survol** (même principe que les cartes admin), **icône centrée** sur la ligne et **taille doublée** (\`iconSize\` 34).
### Wizard validation dossier AM
- Titres : **Identité et coordonnées** · **Dossier professionnel** (au-dessus des champs à droite, pas de la photo) · **Présentation**.
- **Photo** à gauche (ratio identité 35×45) ; **grille droite** \`[2,2,2,2]\` : NIR | date de naissance, ville | pays de naissance, n° agrément | date d'agrément, capacité | places.
- **\`AppUser\`** : \`date_naissance\`, \`lieu_naissance_ville\`, \`lieu_naissance_pays\` ; affichage dates **\`dd/MM/yyyy\`** (\`formatIsoDateFr\`).
- **\`ValidationDetailSection\`** : titre **optionnel** (wizard AM).
### Wizard validation famille
- Étape 4 : titre **« Présentation »** (plus « Présentation / Motivation »).
### Nettoyage
- Suppression des **\`debugPrint\`** liés aux médias / images / chargement dossier (\`api_config\`, \`auth_network_image\`, \`dossier_unifie\`, \`user_service\`, carte enfant validation).
---
*Issue fermée après merge sur \`develop\` et recette.*`;
let token = process.env.GITEA_TOKEN;
if (!token) {
try {
const tokenFile = path.join(repoRoot, '.gitea-token');
if (fs.existsSync(tokenFile)) token = fs.readFileSync(tokenFile, 'utf8').trim();
} catch (_) {}
}
if (!token) {
try {
const briefing = fs.readFileSync(
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
'utf8',
);
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
if (m) token = m[1].trim();
} catch (_) {}
}
if (!token) {
console.error('Token non trouvé : .gitea-token ou GITEA_TOKEN');
process.exit(1);
}
function request(method, apiPath, payloadObj) {
const payload = payloadObj ? JSON.stringify(payloadObj) : null;
return new Promise((resolve, reject) => {
const opts = {
hostname: 'git.ptits-pas.fr',
path: `/api/v1/repos/${REPO}${apiPath}`,
method,
headers: {
Authorization: 'token ' + token,
'Content-Type': 'application/json',
...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}),
},
};
const req = https.request(opts, (res) => {
let d = '';
res.on('data', (c) => (d += c));
res.on('end', () => {
if (res.statusCode !== 200 && res.statusCode !== 201) {
reject(new Error(`HTTP ${res.statusCode}: ${d}`));
return;
}
try {
resolve(d ? JSON.parse(d) : {});
} catch (_) {
resolve({});
}
});
});
req.on('error', reject);
if (payload) req.write(payload);
req.end();
});
}
(async () => {
try {
console.log(`POST commentaire issue #${ISSUE}...`);
await request('POST', `/issues/${ISSUE}/comments`, { body });
console.log('Commentaire publié.');
console.log(`PATCH fermeture issue #${ISSUE}...`);
await request('PATCH', `/issues/${ISSUE}`, { state: 'closed' });
console.log(`Issue #${ISSUE} fermée.`);
} catch (e) {
console.error(e.message || e);
process.exit(1);
}
})();
+123
View File
@@ -0,0 +1,123 @@
/**
* Commentaire de clôture + fermeture issue Gitea #131.
* Usage: node backend/scripts/close-gitea-issue-131.js
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const repoRoot = path.join(__dirname, '../..');
const ISSUE = 131;
const REPO = 'jmartin/petitspas';
const body = `## Fermeture ticket #131 — livré sur \`develop\` et \`master\`
Branche **\`feature/131\`** mergée dans **\`develop\`**, puis squash merge **\`develop\`\`master\`** (déploiement production).
### Fiche parent (dashboard admin)
- Modale **\`AdminParentEditModal\`** éditable dès l'ouverture
- En-tête dynamique : **nom/prénom** + sous-titre **co-parent** (si connu)
- Gélule **statut** modifiable
- **\`PATCH /api/v1/parents/:id/fiche\`** — identité + statut
- **\`GET /api/v1/parents\`** — liste parents (fix décorateur \`@Get()\` manquant)
- Réponses API : \`co_parent\` peuplé, secrets user masqués (\`sanitizeUserForApi\`)
- Liste enfants en bas de fiche + rattachement/détachement
### Fiche AM (dashboard admin)
- Modale **\`AdminAmEditModal\`** — 3 onglets : Identité | Fiche pro | Enfants accueillis
- **\`PATCH /api/v1/assistantes-maternelles/:id/fiche\`** — identité + champs pro (NIR, date/lieu naissance, date agrément, places, disponibilité)
- **\`POST/DELETE …/enfants/:enfantId\`** — rattacher / détacher un enfant
- Grille capacité **\`AdminAmChildrenCapacityGrid\`** (2×2)
- Rattachement enfants **différé jusqu'à Sauvegarder** (pas d'appel API immédiat)
### Back — placement AM ↔ enfant
- Table **\`enfants_assistantes_maternelles\`** (placement temporel, 1 garde active/enfant)
- Enum enfant : \`a_naitre\`, \`garde\`, \`sans_garde\`, \`scolarise\` — **plus \`actif\`**
- Rattachement → statut enfant \`garde\` ; détachement → \`sans_garde\`
- Migration : \`database/migrations/2026_enfants_assistantes_maternelles.sql\`
- Schéma canonique : \`database/BDD.sql\`
### Front — statuts & polish
- **\`enfant_status_utils.dart\`** — libellés/couleurs \`garde\`/\`sans_garde\`
- Mise à jour cartes enfants, modale détail, filtres dashboard
### Correctifs recette
- \`GET /parents\` 404 → ajout \`@Get()\` sur \`getAll()\`
- Sauvegarde AM 400 → alignement DTO \`UpdateAmFicheAdminDto\` sur payload front
- Crash NIR → fix \`toISOString\` + pas d'effacement NIR (colonne NOT NULL)
### Hors périmètre #131 (tickets voisins)
- **#137** onglet Enfants global · **#138** fiche enfant complète · **#115/#116** affiliation avancée
---
*Issue fermée après merge sur \`develop\` + \`master\` et déploiement production.*`;
let token = process.env.GITEA_TOKEN;
if (!token) {
try {
const tokenFile = path.join(repoRoot, '.gitea-token');
if (fs.existsSync(tokenFile)) token = fs.readFileSync(tokenFile, 'utf8').trim();
} catch (_) {}
}
if (!token) {
try {
const briefing = fs.readFileSync(
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
'utf8',
);
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
if (m) token = m[1].trim();
} catch (_) {}
}
if (!token) {
console.error('Token non trouvé : .gitea-token ou GITEA_TOKEN');
process.exit(1);
}
function request(method, apiPath, payloadObj) {
const payload = payloadObj ? JSON.stringify(payloadObj) : null;
return new Promise((resolve, reject) => {
const opts = {
hostname: 'git.ptits-pas.fr',
path: `/api/v1/repos/${REPO}${apiPath}`,
method,
headers: {
Authorization: 'token ' + token,
'Content-Type': 'application/json',
...(payload ? { 'Content-Length': Buffer.byteLength(payload) } : {}),
},
};
const req = https.request(opts, (res) => {
let d = '';
res.on('data', (c) => (d += c));
res.on('end', () => {
if (res.statusCode !== 200 && res.statusCode !== 201) {
reject(new Error(`HTTP ${res.statusCode}: ${d}`));
return;
}
try {
resolve(d ? JSON.parse(d) : {});
} catch (_) {
resolve({});
}
});
});
req.on('error', reject);
if (payload) req.write(payload);
req.end();
});
}
(async () => {
try {
console.log(`POST commentaire issue #${ISSUE}...`);
await request('POST', `/issues/${ISSUE}/comments`, { body });
console.log('Commentaire publié.');
console.log(`PATCH fermeture issue #${ISSUE}...`);
await request('PATCH', `/issues/${ISSUE}`, { state: 'closed' });
console.log(`Issue #${ISSUE} fermée.`);
} catch (e) {
console.error(e.message || e);
process.exit(1);
}
})();
@@ -0,0 +1,111 @@
/**
* Crée l'issue Gitea — gestionnaire ne doit pas pouvoir se supprimer.
* Usage: node backend/scripts/create-gitea-issue-gestionnaire-self-delete.js
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const repoRoot = path.join(__dirname, '../..');
const MILESTONE_0_1_0 = 10;
let token = process.env.GITEA_TOKEN;
if (!token) {
try {
const tokenFile = path.join(repoRoot, '.gitea-token');
if (fs.existsSync(tokenFile)) token = fs.readFileSync(tokenFile, 'utf8').trim();
} catch (_) {}
}
if (!token) {
try {
const briefing = fs.readFileSync(
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
'utf8',
);
const m = briefing.match(/Token:\s*(gitebu_[a-f0-9]+)/);
if (m) token = m[1].trim();
} catch (_) {}
}
if (!token) {
console.error('Token non trouvé : .gitea-token ou GITEA_TOKEN');
process.exit(1);
}
const body = `## Contexte
Quand un **gestionnaire** connecté ouvre sa propre fiche dans l'onglet **Gestionnaires** (Gestion des utilisateurs), la modale **Modifier un "Gestionnaire"** affiche le bouton **Supprimer**.
Comportement actuel : le gestionnaire voit et peut tenter de supprimer son propre compte.
## Comportement attendu
Comme pour le **super administrateur** (bouton Supprimer masqué sur la fiche super admin) :
- **Pas de bouton Supprimer** quand l'utilisateur édite **sa propre fiche**
- **Modification** des informations (prénom, nom, email, téléphone, relais, mot de passe) **toujours autorisée**
## Périmètre
- Frontend : \`AdminUserFormDialog\` (\`gestionnaires_create.dart\`)
- Comparer \`initialUser.id\` avec l'utilisateur connecté (\`AuthService.getCurrentUser\`)
- Masquer Supprimer si édition de soi-même ; conserver garde existante super admin
## Critères d'acceptation
- [ ] Gestionnaire connecté → ouvre sa fiche → **pas** de bouton Supprimer
- [ ] Gestionnaire connecté → peut **Modifier** ses informations
- [ ] Super admin / admin → peut toujours supprimer **un autre** gestionnaire (si droits API)
- [ ] Pas de régression sur fiche super administrateur (Supprimer toujours masqué)
## Fichiers clés
- \`frontend/lib/screens/administrateurs/creation/gestionnaires_create.dart\`
- \`frontend/lib/widgets/admin/gestionnaire_management_widget.dart\`
## Milestone
**0.1.0** — correction UX / sécurité gestion utilisateurs.`;
const payloadClean = JSON.stringify({
title: '[Bug] Gestionnaire peut voir Supprimer sur sa propre fiche',
body,
milestone: MILESTONE_0_1_0,
});
const opts = {
hostname: 'git.ptits-pas.fr',
path: '/api/v1/repos/jmartin/petitspas/issues',
method: 'POST',
headers: {
Authorization: 'token ' + token,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payloadClean),
},
};
const req = https.request(opts, (res) => {
let d = '';
res.on('data', (c) => (d += c));
res.on('end', () => {
try {
const o = JSON.parse(d);
if (o.number) {
console.log('NUMBER:', o.number);
console.log('URL:', o.html_url);
console.log('MILESTONE:', o.milestone?.title ?? '(aucun)');
} else {
console.error('Réponse:', d);
process.exit(1);
}
} catch (e) {
console.error(d);
process.exit(1);
}
});
});
req.on('error', (e) => {
console.error(e);
process.exit(1);
});
req.write(payloadClean);
req.end();
@@ -0,0 +1,95 @@
/**
* Crée l'issue Gitea "[Frontend] Inscription Parent Branchement soumission formulaire à l'API"
* Usage: node backend/scripts/create-gitea-issue-parent-api.js
* Token : .gitea-token (racine du dépôt), sinon GITEA_TOKEN, sinon
* docs/27_BRIEFING-FRONTEND.md (voir docs/26_GITEA-API.md)
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const repoRoot = path.join(__dirname, '../..');
let token = process.env.GITEA_TOKEN;
if (!token) {
try {
const tokenFile = path.join(repoRoot, '.gitea-token');
if (fs.existsSync(tokenFile)) {
token = fs.readFileSync(tokenFile, 'utf8').trim();
}
} catch (_) {}
}
if (!token) {
try {
const briefing = fs.readFileSync(
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
'utf8',
);
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
if (m) token = m[1].trim();
} catch (_) {}
}
if (!token) {
console.error(
'Token non trouvé : créer .gitea-token à la racine ou export GITEA_TOKEN (voir docs/26_GITEA-API.md)',
);
process.exit(1);
}
const body = `## Description
Branchement du formulaire d'inscription parent (étape 5, récapitulatif) à l'endpoint d'inscription. Aujourd'hui la soumission n'appelle pas l'API : elle affiche uniquement une modale puis redirige vers le login.
**Estimation** : 4h | **Labels** : frontend, p3, auth, cdc
## Tâches
- [ ] Créer un service ou méthode (ex. AuthService.registerParent) appelant POST /api/v1/auth/register/parent
- [ ] Construire le body (DTO) à partir de UserRegistrationData (parent1, parent2, children, motivationText, CGU) en cohérence avec le backend (#18)
- [ ] Dans ParentRegisterStep5Screen, au clic « Soumettre » : appel API puis modale + redirection ou message d'erreur
- [ ] Gestion des photos enfants (base64 ou multipart selon API)
## Référence
20_WORKFLOW-CREATION-COMPTE.md § Étape 3 Inscription d'un parent, backend #18`;
const payload = JSON.stringify({
title: "[Frontend] Inscription Parent Branchement soumission formulaire à l'API",
body,
});
const opts = {
hostname: 'git.ptits-pas.fr',
path: '/api/v1/repos/jmartin/petitspas/issues',
method: 'POST',
headers: {
Authorization: 'token ' + token,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload),
},
};
const req = https.request(opts, (res) => {
let d = '';
res.on('data', (c) => (d += c));
res.on('end', () => {
try {
const o = JSON.parse(d);
if (o.number) {
console.log('NUMBER:', o.number);
console.log('URL:', o.html_url);
} else {
console.error('Erreur API:', o.message || d);
process.exit(1);
}
} catch (e) {
console.error('Réponse:', d);
process.exit(1);
}
});
});
req.on('error', (e) => {
console.error(e);
process.exit(1);
});
req.write(payload);
req.end();
+67
View File
@@ -0,0 +1,67 @@
/**
* Liste toutes les issues Gitea (ouvertes + fermées) pour jmartin/petitspas.
* Token : .gitea-token (racine), GITEA_TOKEN, ou docs/27_BRIEFING-FRONTEND.md
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const repoRoot = path.join(__dirname, '../..');
let token = process.env.GITEA_TOKEN;
if (!token) {
try {
const tokenFile = path.join(repoRoot, '.gitea-token');
if (fs.existsSync(tokenFile)) token = fs.readFileSync(tokenFile, 'utf8').trim();
} catch (_) {}
}
if (!token) {
try {
const briefing = fs.readFileSync(
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
'utf8',
);
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
if (m) token = m[1].trim();
} catch (_) {}
}
if (!token) {
console.error('Token non trouvé');
process.exit(1);
}
function get(path) {
return new Promise((resolve, reject) => {
const opts = { hostname: 'git.ptits-pas.fr', path, method: 'GET', headers: { Authorization: 'token ' + token } };
const req = https.request(opts, (res) => {
let d = '';
res.on('data', (c) => (d += c));
res.on('end', () => {
try { resolve(JSON.parse(d)); } catch (e) { reject(e); }
});
});
req.on('error', reject);
req.end();
});
}
async function main() {
const seen = new Map();
for (const state of ['open', 'closed']) {
for (let page = 1; ; page++) {
const raw = await get('/api/v1/repos/jmartin/petitspas/issues?state=' + state + '&limit=50&page=' + page + '&type=issues');
if (raw && raw.message && !Array.isArray(raw)) {
console.error('API:', raw.message);
process.exit(1);
}
const list = Array.isArray(raw) ? raw : [];
for (const i of list) {
if (!i.pull_request) seen.set(i.number, { number: i.number, title: i.title, state: i.state });
}
if (list.length < 50) break;
}
}
const all = [...seen.values()].sort((a, b) => a.number - b.number);
console.log(JSON.stringify(all, null, 2));
}
main().catch((e) => { console.error(e); process.exit(1); });
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env node
/**
* Test API GET /dossiers/:numeroDossier (dossier unifié AM ou famille).
*
* Prérequis : backend démarré (npm run start:dev dans backend/).
*
* Usage:
* node scripts/test-api-dossiers.js
* NUMERO_DOSSIER=2026-000001 node scripts/test-api-dossiers.js
* BASE_URL=https://app.ptits-pas.fr/api/v1 TEST_EMAIL=xxx TEST_PASSWORD=yyy NUMERO_DOSSIER=2026-000001 node scripts/test-api-dossiers.js
*
* Sans TEST_EMAIL/TEST_PASSWORD : 401 sur les routes protégées.
* NUMERO_DOSSIER : optionnel ; si absent, utilise le premier numero_dossier de pending-families (avec token).
*/
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000/api/v1';
const TEST_EMAIL = process.env.TEST_EMAIL;
const TEST_PASSWORD = process.env.TEST_PASSWORD;
const NUMERO_DOSSIER = process.env.NUMERO_DOSSIER;
async function request(method, path, body = null, token = null) {
const url = path.startsWith('http') ? path : `${BASE_URL}${path}`;
const opts = {
method,
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
};
if (token) opts.headers.Authorization = `Bearer ${token}`;
if (body) opts.body = JSON.stringify(body);
const res = await fetch(url, opts);
const text = await res.text();
let data = null;
try {
data = text ? JSON.parse(text) : null;
} catch (_) {
data = text;
}
return { status: res.status, data };
}
async function main() {
console.log('Base URL:', BASE_URL);
console.log('Numéro dossier (env):', NUMERO_DOSSIER ?? '(sera déduit si token fourni)');
console.log('');
let token = null;
if (TEST_EMAIL && TEST_PASSWORD) {
console.log('1. Login...');
const loginRes = await request('POST', '/auth/login', {
email: TEST_EMAIL,
password: TEST_PASSWORD,
});
if (loginRes.status !== 200 && loginRes.status !== 201) {
console.log(' Échec login:', loginRes.status, loginRes.data);
process.exit(1);
}
token = loginRes.data?.access_token ?? loginRes.data?.accessToken ?? null;
if (!token) {
console.log(' Réponse login sans token:', JSON.stringify(loginRes.data, null, 2));
process.exit(1);
}
console.log(' OK, token reçu.');
console.log('');
} else {
console.log('TEST_EMAIL / TEST_PASSWORD non définis : GET /dossiers/:numero nécessite un token (401 attendu).');
console.log('');
}
let numeroDossier = NUMERO_DOSSIER;
if (!numeroDossier && token) {
console.log('2. Récupération d\'un numéro de dossier (GET /parents/pending-families)...');
const pendingRes = await request('GET', '/parents/pending-families', null, token);
if (pendingRes.status === 200 && Array.isArray(pendingRes.data) && pendingRes.data.length > 0) {
numeroDossier = pendingRes.data[0].numero_dossier || null;
console.log(' Premier numero_dossier:', numeroDossier);
} else {
console.log(' Aucune famille en attente ou erreur. Utilisez NUMERO_DOSSIER=2026-000001');
}
console.log('');
}
if (!numeroDossier) {
numeroDossier = '2026-000001';
console.log('2. Pas de numéro fourni, test avec numéro par défaut:', numeroDossier);
} else {
console.log('2. GET /dossiers/' + encodeURIComponent(numeroDossier));
}
const dossierRes = await request(
'GET',
'/dossiers/' + encodeURIComponent(numeroDossier),
null,
token
);
console.log(' Status:', dossierRes.status);
if (dossierRes.status === 200 && dossierRes.data) {
const d = dossierRes.data;
console.log(' type:', d.type);
console.log(' dossier (clés):', d.dossier ? Object.keys(d.dossier) : '-');
if (d.dossier && Array.isArray(d.dossier.enfants)) {
console.log(' enfants:', d.dossier.enfants.length);
d.dossier.enfants.forEach((e, i) => {
console.log(
` [${i + 1}] id=${e.id} first_name=${e.first_name} last_name=${e.last_name} birth_date=${e.birth_date} gender=${e.gender} genre=${e.genre} status=${e.status}`
);
});
}
console.log('');
console.log('Réponse brute (dossier):');
console.log(JSON.stringify(d.dossier, null, 2));
} else {
console.log(' Réponse:', JSON.stringify(dossierRes.data, null, 2));
}
console.log('');
console.log('Fin du test.');
}
main().catch((err) => {
console.error('Erreur:', err.message || err);
process.exit(1);
});
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env node
/**
* Test des endpoints "comptes en attente" (ticket #107).
*
* Prérequis : backend démarré (npm run start:dev dans backend/).
*
* Usage:
* node scripts/test-pending-api.js
* TEST_EMAIL=xxx TEST_PASSWORD=yyy node scripts/test-pending-api.js
* BASE_URL=https://app.ptits-pas.fr/api/v1 TEST_EMAIL=xxx TEST_PASSWORD=yyy node scripts/test-pending-api.js
*
* Sans TEST_EMAIL/TEST_PASSWORD : les GET protégés renverront 401 (normal).
* Avec un compte gestionnaire ou admin : affiche les listes en attente.
*/
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000/api/v1';
const TEST_EMAIL = process.env.TEST_EMAIL;
const TEST_PASSWORD = process.env.TEST_PASSWORD;
async function request(method, path, body = null, token = null) {
const url = path.startsWith('http') ? path : `${BASE_URL}${path}`;
const opts = {
method,
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
};
if (token) opts.headers.Authorization = `Bearer ${token}`;
if (body) opts.body = JSON.stringify(body);
const res = await fetch(url, opts);
const text = await res.text();
let data = null;
try {
data = text ? JSON.parse(text) : null;
} catch (_) {
data = text;
}
return { status: res.status, data };
}
async function main() {
console.log('Base URL:', BASE_URL);
console.log('');
let token = null;
if (TEST_EMAIL && TEST_PASSWORD) {
console.log('1. Login...');
const loginRes = await request('POST', '/auth/login', {
email: TEST_EMAIL,
password: TEST_PASSWORD,
});
if (loginRes.status !== 200 && loginRes.status !== 201) {
console.log(' Échec login:', loginRes.status, loginRes.data);
process.exit(1);
}
token = loginRes.data?.access_token ?? loginRes.data?.accessToken ?? null;
if (!token) {
console.log(' Réponse login sans token:', JSON.stringify(loginRes.data, null, 2));
process.exit(1);
}
console.log(' OK, token reçu.');
console.log('');
} else {
console.log('TEST_EMAIL / TEST_PASSWORD non définis : les appels protégés vont renvoyer 401.');
console.log('Exemple: TEST_EMAIL=admin@example.com TEST_PASSWORD=xxx node scripts/test-pending-api.js');
console.log('');
}
console.log('2. GET /users/pending?role=assistante_maternelle');
const pendingUsersRes = await request(
'GET',
'/users/pending?role=assistante_maternelle',
null,
token
);
console.log(' Status:', pendingUsersRes.status);
if (pendingUsersRes.status === 200) {
const list = Array.isArray(pendingUsersRes.data) ? pendingUsersRes.data : [];
console.log(' Nombre d\'utilisateurs en attente (AM):', list.length);
list.forEach((u, i) => {
console.log(
` [${i + 1}] id=${u.id} email=${u.email} role=${u.role} statut=${u.statut} numero_dossier=${u.numero_dossier ?? '-'}`
);
});
} else {
console.log(' Réponse:', JSON.stringify(pendingUsersRes.data, null, 2));
}
console.log('');
console.log('3. GET /parents/pending-families');
const pendingFamiliesRes = await request('GET', '/parents/pending-families', null, token);
console.log(' Status:', pendingFamiliesRes.status);
if (pendingFamiliesRes.status === 200) {
const list = Array.isArray(pendingFamiliesRes.data) ? pendingFamiliesRes.data : [];
console.log(' Nombre de familles en attente:', list.length);
list.forEach((f, i) => {
console.log(
` [${i + 1}] libelle=${f.libelle} parentIds=${JSON.stringify(f.parentIds)} numero_dossier=${f.numero_dossier ?? '-'}`
);
});
} else {
console.log(' Réponse:', JSON.stringify(pendingFamiliesRes.data, null, 2));
}
console.log('');
console.log('Fin du test.');
}
main().catch((err) => {
console.error('Erreur:', err.message || err);
process.exit(1);
});
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
# Inscription AM complète : jeux officiels alignés sur database/seed/03_seed_test_data.sql
# node tests/scripts/register-am-dubois-test.mjs [BASE_URL]
# node tests/scripts/register-am-mansouri-test.mjs [BASE_URL]
#
# Smoke curl (email + NIR jetables, hors seed — ne pas mélanger avec Marie / Fatima) :
# Usage: ./scripts/test-register-am.sh [BASE_URL]
# Exemple: ./scripts/test-register-am.sh http://localhost:3000/api/v1
BASE_URL="${1:-http://localhost:3000/api/v1}"
echo "POST $BASE_URL/auth/register/am (profil smoke, pas les AM du seed)"
echo "---"
curl -s -w "\n\nHTTP %{http_code}\n" -X POST "$BASE_URL/auth/register/am" \
-H "Content-Type: application/json" \
-d '{
"email": "smoke.am.curl@ptits-pas.fr",
"prenom": "Smoke",
"nom": "CURLTEST",
"telephone": "0612345678",
"adresse": "1 rue Smoke",
"code_postal": "95870",
"ville": "Bezons",
"consentement_photo": false,
"date_naissance": "1986-12-15",
"nir": "186127500100279",
"numero_agrement": "AGR-SMOKE-CURL-001",
"capacite_accueil": 3,
"places_disponibles": 2,
"acceptation_cgu": true,
"acceptation_privacy": true
}'
@@ -0,0 +1,100 @@
/**
* Met à jour l'issue Gitea #119 : endpoint unifié GET /dossiers/:numeroDossier (option A)
* Usage: node backend/scripts/update-gitea-issue-119-dossiers.js
* Token : .gitea-token (racine), GITEA_TOKEN, ou docs/27_BRIEFING-FRONTEND.md
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const repoRoot = path.join(__dirname, '../..');
let token = process.env.GITEA_TOKEN;
if (!token) {
try {
const tokenFile = path.join(repoRoot, '.gitea-token');
if (fs.existsSync(tokenFile)) token = fs.readFileSync(tokenFile, 'utf8').trim();
} catch (_) {}
}
if (!token) {
try {
const briefing = fs.readFileSync(
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
'utf8',
);
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
if (m) token = m[1].trim();
} catch (_) {}
}
if (!token) {
console.error('Token non trouvé');
process.exit(1);
}
const body = `## Besoin
Un **seul** endpoint **GET par numéro de dossier** qui renvoie le dossier complet, **AM ou famille** selon le numéro. Clé unique = numéro de dossier (usage : modale de validation, consultation gestionnaire, reprise, etc.).
**Option A Endpoint unifié**
- **Route** : \`GET /api/v1/dossiers/:numeroDossier\` (ou \`GET /dossiers/:numeroDossier\` selon préfixe API).
- Le backend détermine si le numéro appartient à une **AM** ou à une **famille** (ex. lookup \`users\` / \`parents\` / \`assistantes_maternelles\`).
- **Réponse** avec discriminent :
- \`{ type: 'family', dossier: { numero_dossier, parents, enfants, presentation } }\`
- \`{ type: 'am', dossier: { numero_dossier, user, ... } }\` (fiche AM complète, champs utiles sans secrets)
- **Rôles** : SUPER_ADMIN, ADMINISTRATEUR, GESTIONNAIRE.
- **Réponses** : 200 (dossier), 403, 404 (numéro inconnu).
Aucun filtre par statut : on renvoie le dossier s'il existe ; le front affiche Valider/Refuser selon le statut.
**Labels suggérés** : backend, api, dossiers, gestionnaire
---
## Implémentation
- **Nouveau module ou route** : \`GET /dossiers/:numeroDossier\`.
- **Service** : trouver qui possède ce \`numero_dossier\` (famille → \`parents\`, AM → \`users\` + \`assistantes_maternelles\`). Appeler la logique existante dossier-famille ou construire le payload AM, puis retourner \`{ type, dossier }\`.
- **Réutiliser** : la logique actuelle \`GET /parents/dossier-famille/:numeroDossier\` peut être appelée en interne pour \`type: 'family'\` ; ajouter une branche \`type: 'am'\` avec un DTO « dossier AM complet ».
- DTO(s) : garder \`DossierFamilleCompletDto\` pour la famille ; ajouter un DTO pour le dossier AM (user sans secrets + infos AM). Réponse unifiée : \`{ type: 'am' | 'family', dossier: ... }\`.`;
const payload = JSON.stringify({
title: 'Endpoint unifié GET /dossiers/:numeroDossier (AM ou famille)',
body,
});
const opts = {
hostname: 'git.ptits-pas.fr',
path: '/api/v1/repos/jmartin/petitspas/issues/119',
method: 'PATCH',
headers: {
Authorization: 'token ' + token,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload),
},
};
const req = https.request(opts, (res) => {
let d = '';
res.on('data', (c) => (d += c));
res.on('end', () => {
try {
const o = JSON.parse(d);
if (o.number || o.id) {
console.log('Issue #119 mise à jour.');
console.log('URL:', o.html_url || 'https://git.ptits-pas.fr/jmartin/petitspas/issues/119');
} else {
console.error('Erreur API:', o.message || d);
process.exit(1);
}
} catch (e) {
console.error('Réponse:', d);
process.exit(1);
}
});
});
req.on('error', (e) => {
console.error(e);
process.exit(1);
});
req.write(payload);
req.end();
@@ -0,0 +1,153 @@
/**
* Met à jour l'issue Gitea #140 — epic dashboard admin ch.6 famille.
* Usage: node backend/scripts/update-gitea-issue-140-ch6-famille.js
* Token : .gitea-token (racine), GITEA_TOKEN, ou docs/27_BRIEFING-FRONTEND.md
*/
const https = require('https');
const fs = require('fs');
const path = require('path');
const repoRoot = path.join(__dirname, '../..');
let token = process.env.GITEA_TOKEN;
if (!token) {
try {
const tokenFile = path.join(repoRoot, '.gitea-token');
if (fs.existsSync(tokenFile)) token = fs.readFileSync(tokenFile, 'utf8').trim();
} catch (_) {}
}
if (!token) {
try {
const briefing = fs.readFileSync(
path.join(repoRoot, 'docs/27_BRIEFING-FRONTEND.md'),
'utf8',
);
const m = briefing.match(/Token:\s*(giteabu_[a-f0-9]+)/);
if (m) token = m[1].trim();
} catch (_) {}
}
if (!token) {
console.error('Token non trouvé : .gitea-token ou GITEA_TOKEN (voir docs/26_GITEA-API.md)');
process.exit(1);
}
const body = `## Rôle de ce ticket
**#140 est un ticket epic / livraison** : il regroupe la mise en œuvre du **chapitre 6** du doc [28_EVOLUTION-FAMILLE-ET-RESPONSABLES.md](../docs/28_EVOLUTION-FAMILLE-ET-RESPONSABLES.md) (§6.1 et §6.2) sur la branche \`feature/140-dashboard-admin-ch6-famille\`.
Il **ne remplace pas** les tickets détaillés ci-dessous : il sert de **fil de livraison** (PR, recette, fermeture coordonnée). Chaque sous-ticket garde son périmètre propre ; #140 est clos quand l'ensemble est **fonctionnel et homogène en UI**.
---
## Tickets couverts (périmètres embarqués)
| Ticket | Sujet | Rôle dans #140 |
|--------|--------|----------------|
| **#115** | Rattachement parent — **backend** | API \`POST/DELETE …/enfants/:enfantId\` |
| **#116** | Rattachement parent — **frontend** | UI rattacher / détacher depuis la fiche parent |
| **#130** | \`UserService\` — APIs admin | Appels \`getParents\`, \`getParent\`, \`getEnfants\`, \`updateParentFiche\`, etc. |
| **#131** | Fiche parent éditable | Modale parent (dashboard) — *hors fiche AM* |
| **#137** | Onglet **Enfants** | Liste globale admin + accès fiche enfant |
| **#138** | Fiche enfant + liste dans fiche parent | \`AdminChildDetailModal\` + liste enfants en bas de fiche parent |
> **Note :** fermer #140 peut entraîner la fermeture **partielle ou totale** de ces tickets selon ce qui est réellement livré et recetté dans la PR.
---
## Hors scope #140
- **§6.3** — Création dossier admin sans numéro → **#129**
- Fiche **AM** éditable (dashboard) → reste **#131** (partie AM)
- Parcours gestionnaire « famille complexe » → **#139**
- Qualification responsable légal (combobox sur lien) → ticket à créer
---
## Backend (attendu / livré)
- [x] \`PATCH /parents/:id/fiche\` — édition fiche parent (admin/gestionnaire)
- [x] \`POST /parents/:id/enfants/:enfantId\` — rattacher un enfant existant
- [x] \`DELETE /parents/:id/enfants/:enfantId\` — détacher (garde-fou : ≥1 responsable / enfant)
- [x] \`GET /enfants\` enrichi ; \`PATCH /enfants/:id\` pour gestionnaire
---
## Frontend — fait
- [x] Modale **fiche parent** éditable (shell aligné validation, statut gélule, téléphone formaté, \`IdentityBlock\`)
- [x] Onglet **Enfants** — liste globale (\`EnfantManagementWidget\`)
- [x] Liste enfants dans fiche parent — cartes (photo, nom, âge ans/mois, statut), cadre blanc, scroll 2,5 lignes
- [x] Rattacher / détacher un enfant **existant** (API branchée)
- [x] Parsing robuste \`parentChildren\` + URLs médias \`/uploads\` en Flutter web
- [x] \`UserService\` — APIs parents / enfants / affiliation
---
## Frontend — reste à faire (bloquant clôture)
### Fiche enfant — #138 (UI)
- [ ] Reprendre \`AdminChildDetailModal\` : même **look & feel** que fiche parent / modales validation (largeur ~930 px, grille champs, photo enfant)
- [ ] Aligner dates, genre, statut sur les wizards validation famille
### Modale **rattacher** un enfant — #116 (UI)
- [ ] Remplacer le \`SimpleDialog\` actuel par une modale cohérente : liste type \`AdminEnfantUserCard\` (photo, nom, âge), recherche éventuelle
### Création d'un **nouvel** enfant depuis la fiche parent — doc §6.2
- [ ] **Non implémenté** aujourd'hui (seul le rattachement d'un enfant déjà en base existe)
- [ ] À trancher : inclus dans #140 si le back expose un \`POST\` admin depuis le contexte parent, sinon ticket dédié / extension #129
---
## Recette avant merge
1. Parent avec 0 / 1 / 3+ enfants — liste, scroll, compteur
2. Rattacher puis détacher (message si dernier responsable)
3. Clic enfant → fiche enfant (après refonte UI)
4. Onglet Enfants — même rendu cartes
5. Avatars photos (URLs absolues web)
---
## Branche
\`feature/140-dashboard-admin-ch6-famille\`
## Références
- Doc produit : \`docs/28_EVOLUTION-FAMILLE-ET-RESPONSABLES.md\` §6.1, §6.2`;
const payload = JSON.stringify({ body });
const req = https.request(
{
hostname: 'git.ptits-pas.fr',
path: '/api/v1/repos/jmartin/petitspas/issues/140',
method: 'PATCH',
headers: {
Authorization: `token ${token}`,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(payload),
},
},
(res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode < 300) {
const json = JSON.parse(data);
console.log('Issue #140 mise à jour :', json.html_url);
console.log('updated_at:', json.updated_at);
} else {
console.error('Erreur', res.statusCode, data);
process.exit(1);
}
});
},
);
req.on('error', (err) => {
console.error(err);
process.exit(1);
});
req.write(payload);
req.end();
-18
View File
@@ -1,18 +0,0 @@
import { Controller, Post, Body, UseGuards, Req } from '@nestjs/common';
import { AdminService } from './admin.service';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
@Controller('admin')
export class AdminController {
constructor(private readonly adminService: AdminService) {}
@Post('change-password')
@UseGuards(JwtAuthGuard)
async changePassword(
@Req() req,
@Body('oldPassword') oldPassword: string,
@Body('newPassword') newPassword: string,
) {
return this.adminService.changePassword(req.user.id, oldPassword, newPassword);
}
}
-18
View File
@@ -1,18 +0,0 @@
import { Module } from '@nestjs/common';
import { AdminController } from './admin.controller';
import { AdminService } from './admin.service';
import { PrismaModule } from '../prisma/prisma.module';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [
PrismaModule,
JwtModule.register({
secret: process.env.JWT_SECRET,
signOptions: { expiresIn: '1d' },
}),
],
controllers: [AdminController],
providers: [AdminService],
})
export class AdminModule {}
-40
View File
@@ -1,40 +0,0 @@
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../prisma/prisma.service';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AdminService {
constructor(
private prisma: PrismaService,
private jwtService: JwtService,
) {}
async changePassword(adminId: string, oldPassword: string, newPassword: string) {
// Récupérer l'administrateur
const admin = await this.prisma.admin.findUnique({
where: { id: adminId },
});
if (!admin) {
throw new UnauthorizedException('Administrateur non trouvé');
}
// Vérifier l'ancien mot de passe
const isPasswordValid = await bcrypt.compare(oldPassword, admin.password);
if (!isPasswordValid) {
throw new UnauthorizedException('Ancien mot de passe incorrect');
}
// Hasher le nouveau mot de passe
const hashedPassword = await bcrypt.hash(newPassword, 10);
// Mettre à jour le mot de passe
await this.prisma.admin.update({
where: { id: adminId },
data: { password: hashedPassword },
});
return { message: 'Mot de passe modifié avec succès' };
}
}
+22
View File
@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});
+17
View File
@@ -0,0 +1,17 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getOverView() {
return this.appService.getOverView();
}
@Get('hello')
getHello(): string {
return this.appService.getHello();
}
}
+65 -7
View File
@@ -1,17 +1,75 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule, ConfigService } from '@nestjs/config';
import { PrismaModule } from './prisma/prisma.module'; import { AppController } from './app.controller';
import { AuthModule } from './auth/auth.module'; import { AppService } from './app.service';
import { AdminModule } from './admin/admin.module'; import appConfig from './config/app.config';
import databaseConfig from './config/database.config';
import jwtConfig from './config/jwt.config';
import { configValidationSchema } from './config/validation.schema';
import { UserModule } from './routes/user/user.module';
import { TypeOrmModule } from '@nestjs/typeorm';
import { APP_FILTER } from '@nestjs/core';
import { ParentsModule } from './routes/parents/parents.module';
import { AuthModule } from './routes/auth/auth.module';
import { SentryGlobalFilter } from '@sentry/nestjs/setup';
import { AllExceptionsFilter } from './common/filters/all_exceptions.filters';
import { EnfantsModule } from './routes/enfants/enfants.module';
import { AppConfigModule } from './modules/config/config.module';
import { DocumentsLegauxModule } from './modules/documents-legaux';
import { RelaisModule } from './routes/relais/relais.module';
import { DossiersModule } from './routes/dossiers/dossiers.module';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot({ ConfigModule.forRoot({
// Gestion dynamique des fichiers .env
envFilePath: [`.env.${process.env.NODE_ENV || 'development'}`, '.env'],
// envFilePath: '.env',
// Chargement de configurations typées
load: [appConfig, databaseConfig, jwtConfig],
isGlobal: true, isGlobal: true,
validationSchema: configValidationSchema,
}), }),
PrismaModule, TypeOrmModule.forRootAsync({
imports: [ConfigModule,
],
inject: [ConfigService],
useFactory: (config: ConfigService) => ({
type: 'postgres',
host: config.get('database.host'),
port: config.get<number>('database.port'),
username: config.get('database.username'),
password: config.get('database.password'),
database: config.get('database.database'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: false,
migrations: [__dirname + '/migrations/**/*{.ts,.js}'],
logging: true,
}),
}),
UserModule,
ParentsModule,
EnfantsModule,
AuthModule, AuthModule,
AdminModule, AppConfigModule,
DocumentsLegauxModule,
RelaisModule,
DossiersModule,
],
controllers: [AppController],
providers: [
AppService,
{
provide: APP_FILTER,
useClass: SentryGlobalFilter
},
{
provide: APP_FILTER,
useClass: AllExceptionsFilter,
}
], ],
}) })
export class AppModule {} export class AppModule { }
+62
View File
@@ -0,0 +1,62 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello Test!!!';
}
getOverView() {
return {
name: "P'titsPas API",
version: "1.0",
description: "Documentation rapide des endpoints disponibles",
authentication: "JWT Bearer Token requis",
endpoints: [
{
method: "GET",
path: "/parents",
description: "Liste tous les parents",
roles: ["SUPER_ADMIN", "GESTIONNAIRE"]
},
{
method: "GET",
path: "/parents/:id",
description: "Récupère un parent par ID utilisateur",
roles: ["SUPER_ADMIN", "GESTIONNAIRE"],
params: ["id (UUID)"]
},
{
method: "POST",
path: "/parents",
description: "Crée un parent",
roles: ["SUPER_ADMIN", "GESTIONNAIRE"],
body: {
user_id: "UUID",
co_parent_id: "UUID (optionnel)"
}
},
{
method: "PATCH",
path: "/parents/:id",
description: "Met à jour un parent",
roles: ["SUPER_ADMIN", "GESTIONNAIRE"],
params: ["id (UUID)"],
body: {
user_id: "UUID (optionnel)",
co_parent_id: "UUID (optionnel)"
}
},
{
method: "DELETE",
path: "/parents/:id",
description: "Supprime un parent",
roles: ["SUPER_ADMIN"],
params: ["id (UUID)"]
}
],
docs: "/api/docs"
};
}
}
@@ -0,0 +1,4 @@
import { SetMetadata } from "@nestjs/common";
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
@@ -0,0 +1,3 @@
import { SetMetadata } from "@nestjs/common";
export const Roles = (...roles: string[]) => SetMetadata("roles", roles);
@@ -0,0 +1,7 @@
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
export const User = createParamDecorator((data: string | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user?.[data] : user;
});
@@ -0,0 +1,11 @@
import { IsDateString, IsOptional } from "class-validator";
export class DateRangeQueryDto {
@IsOptional()
@IsDateString()
start?: string;
@IsOptional()
@IsDateString()
end?: string;
}
+6
View File
@@ -0,0 +1,6 @@
import { IsUUID } from "class-validator";
export class IdParamDto {
@IsUUID()
id: string;
}
@@ -0,0 +1,11 @@
import { IsOptional, IsPositive } from "class-validator";
export class PaginationQueryDto {
@IsOptional()
@IsPositive()
offset?: number;
@IsOptional()
@IsPositive()
limit?: number;
}
@@ -0,0 +1,8 @@
import { IsOptional, IsString, MinLength } from "class-validator";
export class SearchQueryDto {
@IsOptional()
@IsString()
@MinLength(2)
q?: string;
}
@@ -0,0 +1,27 @@
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from "@nestjs/common";
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const message =
exception instanceof HttpException
? exception.getResponse()
: { message: 'Internal server error' };
response.status(status).json({
success: false,
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
message,
});
}
}
+49
View File
@@ -0,0 +1,49 @@
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { JwtService } from "@nestjs/jwt";
import { Request } from 'express';
import { IS_PUBLIC_KEY } from "../decorators/public.decorator";
import { ConfigService } from "@nestjs/config";
@Injectable()
export class AuthGuard implements CanActivate {
constructor(
private readonly jwtService: JwtService,
private readonly reflector: Reflector,
private readonly configService: ConfigService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
const request = context.switchToHttp().getRequest<Request>();
if (request.path.startsWith('/api-docs')) {
return true;
}
const authHeader = request.headers['authorization'] as string | undefined;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new UnauthorizedException('Token manquant ou invalide');
}
const token = authHeader.split(' ')[1];
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: this.configService.get<string>('jwt.accessSecret'),
});
request.user = {
...payload,
id: payload.sub,
};
return true;
} catch (error) {
throw new UnauthorizedException('Token invalide ou expiré');
}
}
}
+26
View File
@@ -0,0 +1,26 @@
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { Reflector } from "@nestjs/core";
import { Observable } from "rxjs";
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles || requiredRoles.length === 0) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user || !user.role) {
return false;
}
return requiredRoles.includes(user.role);
}
}
@@ -0,0 +1,69 @@
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
import { Request } from 'express';
/** Clés à masquer dans les logs (corps de requête) */
const SENSITIVE_KEYS = [
'password',
'smtp_password',
'token',
'accessToken',
'refreshToken',
'secret',
];
function maskBody(body: unknown): unknown {
if (body === null || body === undefined) return body;
if (typeof body !== 'object') return body;
const out: Record<string, unknown> = {};
for (const [key, value] of Object.entries(body)) {
const lower = key.toLowerCase();
const isSensitive = SENSITIVE_KEYS.some((s) => lower.includes(s));
out[key] = isSensitive ? '***' : value;
}
return out;
}
@Injectable()
export class LogRequestInterceptor implements NestInterceptor {
private readonly enabled: boolean;
constructor() {
this.enabled =
process.env.LOG_API_REQUESTS === 'true' ||
process.env.LOG_API_REQUESTS === '1';
}
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
if (!this.enabled) return next.handle();
const http = context.switchToHttp();
const req = http.getRequest<Request>();
const { method, url, body, query } = req;
const hasBody = body && Object.keys(body).length > 0;
const logLine = [
`[API] ${method} ${url}`,
Object.keys(query || {}).length ? `query=${JSON.stringify(query)}` : '',
hasBody ? `body=${JSON.stringify(maskBody(body))}` : '',
]
.filter(Boolean)
.join(' ');
console.log(logLine);
return next.handle().pipe(
tap({
next: () => {
// Optionnel: log du statut en fin de requête (si besoin plus tard)
},
}),
);
}
}
@@ -0,0 +1,15 @@
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from "@nestjs/common";
import { map, Observable, timestamp } from "rxjs";
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) => ({
success: true,
timestamp: new Date().toISOString(),
data
})),
);
}
}
+109
View File
@@ -0,0 +1,109 @@
/**
* Utilitaire de validation du NIR (numéro de sécurité sociale français).
* - Format 15 caractères (chiffres ou 2A/2B pour la Corse).
* - Clé de contrôle : 97 - (NIR13 mod 97). Pour 2A/2B, conversion temporaire (INSEE : 2A→19, 2B→20).
* - En cas d'incohérence avec les données (sexe, date, lieu) : warning uniquement, pas de rejet.
*/
const NIR_CORSE_2A = '19';
const NIR_CORSE_2B = '20';
/** Regex 15 caractères : sexe (1-3) + 4 chiffres + (2A|2B|2 chiffres) + 6 chiffres + 2 chiffres clé */
const NIR_FORMAT = /^[1-3]\d{4}(?:2A|2B|\d{2})\d{6}\d{2}$/i;
/**
* Convertit le NIR en chaîne de 13 chiffres pour le calcul de la clé (2A→19, 2B→20).
*/
export function nirTo13Digits(nir: string): string {
const n = nir.toUpperCase().replace(/\s/g, '');
if (n.length !== 15) return '';
const dept = n.slice(5, 7);
let deptNum: string;
if (dept === '2A') deptNum = NIR_CORSE_2A;
else if (dept === '2B') deptNum = NIR_CORSE_2B;
else deptNum = dept;
return n.slice(0, 5) + deptNum + n.slice(7, 13);
}
/**
* Vérifie que le format NIR est valide (15 caractères, 2A/2B acceptés).
*/
export function isNirFormatValid(nir: string): boolean {
if (!nir || typeof nir !== 'string') return false;
const n = nir.replace(/\s/g, '').toUpperCase();
return NIR_FORMAT.test(n);
}
/**
* Calcule la clé de contrôle attendue (97 - (NIR13 mod 97)).
* Retourne un nombre entre 1 et 97.
*/
export function computeNirKey(nir13: string): number {
const num = parseInt(nir13, 10);
if (Number.isNaN(num) || nir13.length !== 13) return -1;
return 97 - (num % 97);
}
/**
* Vérifie la clé de contrôle du NIR (15 caractères).
* Retourne true si le NIR est valide (format + clé).
*/
export function isNirKeyValid(nir: string): boolean {
const n = nir.replace(/\s/g, '').toUpperCase();
if (n.length !== 15) return false;
const nir13 = nirTo13Digits(n);
if (nir13.length !== 13) return false;
const expectedKey = computeNirKey(nir13);
const actualKey = parseInt(n.slice(13, 15), 10);
return expectedKey === actualKey;
}
export interface NirValidationResult {
valid: boolean;
error?: string;
warning?: string;
}
/**
* Valide le NIR (format + clé). En cas d'incohérence avec date de naissance ou sexe, ajoute un warning sans invalider.
*/
export function validateNir(
nir: string,
options?: { dateNaissance?: string; genre?: 'H' | 'F' },
): NirValidationResult {
const n = (nir || '').replace(/\s/g, '').toUpperCase();
if (n.length === 0) return { valid: false, error: 'Le NIR est requis' };
if (!isNirFormatValid(n)) {
return { valid: false, error: 'Le NIR doit contenir 15 caractères (chiffres, ou 2A/2B pour la Corse)' };
}
if (!isNirKeyValid(n)) {
return { valid: false, error: 'Clé de contrôle du NIR invalide' };
}
let warning: string | undefined;
if (options?.genre) {
const sexNir = n[0];
const expectedSex = options.genre === 'F' ? '2' : '1';
if (sexNir !== expectedSex) {
warning = 'Le NIR ne correspond pas au genre indiqué (position 1 du NIR).';
}
}
if (options?.dateNaissance) {
try {
const d = new Date(options.dateNaissance);
if (!Number.isNaN(d.getTime())) {
const year2 = d.getFullYear() % 100;
const month = d.getMonth() + 1;
const nirYear = parseInt(n.slice(1, 3), 10);
const nirMonth = parseInt(n.slice(3, 5), 10);
if (nirYear !== year2 || nirMonth !== month) {
warning = warning
? `${warning} Le NIR ne correspond pas à la date de naissance (positions 2-5).`
: 'Le NIR ne correspond pas à la date de naissance indiquée (positions 2-5).';
}
}
} catch {
// ignore
}
}
return { valid: true, warning };
}
@@ -0,0 +1,32 @@
import { sanitizeUserForApi } from './sanitize-user-for-api';
import { RoleType, StatutUtilisateurType, Users } from '../../entities/users.entity';
describe('sanitizeUserForApi', () => {
const base: Users = {
id: 'u1',
email: 'a@b.fr',
prenom: 'Paul',
nom: 'Parent',
role: RoleType.PARENT,
statut: StatutUtilisateurType.ACTIF,
password: 'hash',
token_creation_mdp: 'tok',
token_creation_mdp_expire_le: new Date(),
password_reset_token: 'rst',
password_reset_expires: new Date(),
} as Users;
it('retire password et tokens', () => {
const out = sanitizeUserForApi(base)!;
expect(out.prenom).toBe('Paul');
expect(out.nom).toBe('Parent');
expect(out.password).toBeUndefined();
expect(out.token_creation_mdp).toBeUndefined();
expect(out.password_reset_token).toBeUndefined();
});
it('retourne undefined si user absent', () => {
expect(sanitizeUserForApi(null)).toBeUndefined();
expect(sanitizeUserForApi(undefined)).toBeUndefined();
});
});
@@ -0,0 +1,23 @@
import { Users } from 'src/entities/users.entity';
/** Champs sensibles exclus des réponses API (ticket #131 — user / co_parent). */
const SENSITIVE_USER_KEYS: (keyof Users)[] = [
'password',
'token_creation_mdp',
'token_creation_mdp_expire_le',
'password_reset_token',
'password_reset_expires',
];
/**
* Retourne une copie utilisateur sans secrets (hash MDP, tokens).
* Utilisé pour `user` et `co_parent` dans les réponses Parents.
*/
export function sanitizeUserForApi(user?: Users | null): Users | undefined {
if (!user) return undefined;
const safe = { ...user } as Users;
for (const key of SENSITIVE_USER_KEYS) {
delete safe[key];
}
return safe;
}
+6
View File
@@ -0,0 +1,6 @@
import { registerAs } from '@nestjs/config';
export default registerAs('', () => ({
port: process.env.PORT,
env: process.env.NODE_ENV,
}));
+9
View File
@@ -0,0 +1,9 @@
import { registerAs } from '@nestjs/config';
export default registerAs('database', () => ({
host: process.env.POSTGRES_HOST,
port: process.env.POSTGRES_PORT,
username: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
database: process.env.POSTGRES_DB,
}));
+8
View File
@@ -0,0 +1,8 @@
import { registerAs } from '@nestjs/config';
export default registerAs('jwt', () => ({
accessSecret: process.env.JWT_ACCESS_SECRET,
accessExpiresIn: process.env.JWT_ACCESS_EXPIRES,
refreshSecret: process.env.JWT_REFRESH_SECRET,
refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES,
}));
+15
View File
@@ -0,0 +1,15 @@
import { DataSource } from 'typeorm';
import { config } from 'dotenv';
config();
export default new DataSource({
type: 'postgres',
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT || '5432', 10),
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
database: process.env.DATABASE_NAME,
entities: ['src/**/*.entity.ts'],
migrations: ['src/migrations/*.ts'],
});
+21
View File
@@ -0,0 +1,21 @@
import * as Joi from 'joi';
export const configValidationSchema = Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production', 'test')
.default('development'),
PORT: Joi.number().optional(),
// Base de données
POSTGRES_HOST: Joi.string().required(),
POSTGRES_PORT: Joi.number().required(),
POSTGRES_USER: Joi.string().required(),
POSTGRES_PASSWORD: Joi.string().required(),
POSTGRES_DB: Joi.string().required(),
// JWT
JWT_ACCESS_SECRET: Joi.string().required(),
JWT_ACCESS_EXPIRES: Joi.string().required(),
JWT_REFRESH_SECRET: Joi.string().required(),
JWT_REFRESH_EXPIRES: Joi.string().required(),
});
@@ -1,72 +0,0 @@
import { Request, Response } from 'express';
import { ThemeService, ThemeData } from '../services/theme.service';
export class ThemeController {
// Créer un nouveau thème
static async createTheme(req: Request, res: Response) {
try {
const themeData: ThemeData = req.body;
const theme = await ThemeService.createTheme(themeData);
res.status(201).json(theme);
} catch (error) {
res.status(500).json({ error: 'Erreur lors de la création du thème' });
}
}
// Récupérer tous les thèmes
static async getAllThemes(req: Request, res: Response) {
try {
const themes = await ThemeService.getAllThemes();
res.json(themes);
} catch (error) {
res.status(500).json({ error: 'Erreur lors de la récupération des thèmes' });
}
}
// Récupérer le thème actif
static async getActiveTheme(req: Request, res: Response) {
try {
const theme = await ThemeService.getActiveTheme();
if (!theme) {
return res.status(404).json({ error: 'Aucun thème actif trouvé' });
}
res.json(theme);
} catch (error) {
res.status(500).json({ error: 'Erreur lors de la récupération du thème actif' });
}
}
// Activer un thème
static async activateTheme(req: Request, res: Response) {
try {
const { themeId } = req.params;
const theme = await ThemeService.activateTheme(themeId);
res.json(theme);
} catch (error) {
res.status(500).json({ error: 'Erreur lors de l\'activation du thème' });
}
}
// Mettre à jour un thème
static async updateTheme(req: Request, res: Response) {
try {
const { themeId } = req.params;
const themeData: Partial<ThemeData> = req.body;
const theme = await ThemeService.updateTheme(themeId, themeData);
res.json(theme);
} catch (error) {
res.status(500).json({ error: 'Erreur lors de la mise à jour du thème' });
}
}
// Supprimer un thème
static async deleteTheme(req: Request, res: Response) {
try {
const { themeId } = req.params;
await ThemeService.deleteTheme(themeId);
res.status(204).send();
} catch (error) {
res.status(500).json({ error: 'Erreur lors de la suppression du thème' });
}
}
}
@@ -0,0 +1,40 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
CreateDateColumn,
} from 'typeorm';
import { Users } from './users.entity';
import { DocumentLegal } from './document-legal.entity';
@Entity('acceptations_documents')
export class AcceptationDocument {
@PrimaryGeneratedColumn('uuid')
id: string;
@ManyToOne(() => Users, { nullable: false, onDelete: 'CASCADE' })
@JoinColumn({ name: 'id_utilisateur' })
utilisateur: Users;
@ManyToOne(() => DocumentLegal, { nullable: true })
@JoinColumn({ name: 'id_document' })
document: DocumentLegal | null;
@Column({ type: 'varchar', length: 50, nullable: false })
type_document: 'cgu' | 'privacy';
@Column({ type: 'integer', nullable: false })
version_document: number;
@CreateDateColumn({ name: 'accepte_le', type: 'timestamptz' })
accepteLe: Date;
@Column({ type: 'inet', nullable: true })
ip_address: string | null;
@Column({ type: 'text', nullable: true })
user_agent: string | null;
}
@@ -0,0 +1,47 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
CreateDateColumn,
} from 'typeorm';
import { AssistanteMaternelle } from './assistantes_maternelles.entity';
import { Children } from './children.entity';
import { Users } from './users.entity';
@Entity('enfants_assistantes_maternelles', { schema: 'public' })
export class AmChildren {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ name: 'id_am', type: 'uuid' })
amId: string;
@Column({ name: 'id_enfant', type: 'uuid' })
enfantId: string;
@Column({ name: 'date_debut', type: 'date' })
date_debut: Date;
@Column({ name: 'date_fin', type: 'date', nullable: true })
date_fin?: Date;
@CreateDateColumn({ name: 'cree_le', type: 'timestamptz' })
cree_le: Date;
@Column({ name: 'cree_par', type: 'uuid', nullable: true })
cree_par?: string;
@ManyToOne(() => AssistanteMaternelle, (am) => am.amChildren, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'id_am', referencedColumnName: 'user_id' })
am: AssistanteMaternelle;
@ManyToOne(() => Children, (c) => c.amLinks, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'id_enfant', referencedColumnName: 'id' })
child: Children;
@ManyToOne(() => Users, { nullable: true, onDelete: 'SET NULL' })
@JoinColumn({ name: 'cree_par', referencedColumnName: 'id' })
createdBy?: Users;
}
@@ -0,0 +1,58 @@
import { Entity, PrimaryColumn, Column, OneToOne, OneToMany, JoinColumn } from 'typeorm';
import { Users } from './users.entity';
import { AmChildren } from './am_children.entity';
@Entity('assistantes_maternelles')
export class AssistanteMaternelle {
// PK = FK vers utilisateurs.id
@PrimaryColumn('uuid', { name: 'id_utilisateur' })
user_id: string;
@OneToOne(() => Users, (user) => user.assistanteMaternelle, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'id_utilisateur', referencedColumnName: 'id' })
user: Users;
@Column({ name: 'numero_agrement', length: 50, nullable: true })
approval_number?: string;
@Column({ name: 'nir_chiffre', length: 15, nullable: true })
nir?: string;
@Column({ name: 'nb_max_enfants', type: 'int', nullable: true })
max_children?: number;
@Column({ name: 'biographie', type: 'text', nullable: true })
biography?: string;
@Column({
name: 'disponible',
type: 'boolean',
default: true,
nullable: true,
})
available?: boolean;
@Column({ name: 'ville_residence', length: 100, nullable: true })
residence_city?: string;
@Column( { name: 'date_agrement', type: 'date', nullable: true })
agreement_date?: Date;
@Column( { name: 'annee_experience', type: 'smallint', nullable: true })
years_experience?: number;
@Column( { name: 'specialite', length: 100, nullable: true })
specialty?: string;
@Column( { name: 'place_disponible', type: 'integer', nullable: true })
places_available?: number;
/** Numéro de dossier (format AAAA-NNNNNN), même valeur que sur utilisateurs (ticket #103) */
@Column({ name: 'numero_dossier', length: 20, nullable: true })
numero_dossier?: string;
@OneToMany(() => AmChildren, (ac) => ac.am)
amChildren: AmChildren[];
}
@@ -0,0 +1,42 @@
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
import { Contrat } from "./contrats.entity";
import { Users } from "./users.entity";
export enum StatutAvenantType {
PROPOSE = 'propose',
ACCEPTE = 'accepte',
REFUSE = 'refuse',
}
@Entity('avenants_contrats')
export class AvenantContrat {
// Define your columns and relationships here
@PrimaryGeneratedColumn('uuid')
id: string;
@ManyToOne(() => Contrat, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'id_contrat' })
contrat: Contrat;
@Column({ type: 'jsonb', nullable: true, name: 'modifications' })
modifications?: any;
@ManyToOne(() => Users, { nullable: true })
@JoinColumn({ name: 'initie_par', referencedColumnName: 'id' })
initiator?: Users;
@Column({
type: 'enum',
enum: StatutAvenantType,
enumName: 'statut_avenant_type',
default: StatutAvenantType.PROPOSE,
name: 'statut'
})
statut: StatutAvenantType;
@CreateDateColumn({ name: 'cree_le', type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ name: 'modifie_le', type: 'timestamptz' })
updatedAt: Date;
}
+79
View File
@@ -0,0 +1,79 @@
import {
Entity, PrimaryGeneratedColumn, Column,
OneToMany, ManyToMany, CreateDateColumn, JoinTable
} from 'typeorm';
import { Parents } from './parents.entity';
import { ParentsChildren } from './parents_children.entity';
import { AmChildren } from './am_children.entity';
import { Dossier } from './dossiers.entity';
export enum StatutEnfantType {
A_NAITRE = 'a_naitre',
SCOLARISE = 'scolarise',
GARDE = 'garde',
SANS_GARDE = 'sans_garde',
}
export enum GenreType {
H = 'H',
F = 'F',
AUTRE = 'Autre',
}
@Entity('enfants')
export class Children {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({
type: 'enum',
enum: StatutEnfantType,
enumName: 'statut_enfant_type',
name: 'statut'
})
status: StatutEnfantType;
@Column({ name: 'prenom', length: 100, nullable: true })
first_name?: string;
@Column({ name: 'nom', length: 100, nullable: true })
last_name?: string;
@Column({
type: 'enum',
enum: GenreType,
enumName: 'genre_type',
nullable: true,
name: 'genre'
})
gender?: GenreType;
@Column({ type: 'date', nullable: true, name: 'date_naissance' })
birth_date?: Date;
@Column({ type: 'date', nullable: true, name: 'date_prevue_naissance' })
due_date?: Date;
@Column({ nullable: true, name: 'photo_url', type: 'text' })
photo_url?: string;
@Column({ default: false, name: 'consentement_photo', type: 'boolean' })
consent_photo: boolean;
@Column({ type: 'timestamptz', nullable: true, name: 'date_consentement_photo' })
consent_photo_at?: Date;
@Column({ default: false, name: 'est_multiple', type: 'boolean' })
is_multiple: boolean;
// Lien via table de jointure enfants_parents
@OneToMany(() => ParentsChildren, pc => pc.child)
parentLinks: ParentsChildren[];
@OneToMany(() => AmChildren, (ac) => ac.child)
amLinks: AmChildren[];
// Relation avec Dossier
@OneToMany(() => Dossier, d => d.child)
dossiers: Dossier[];
}
@@ -0,0 +1,39 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';
import { Users } from './users.entity';
@Entity('configuration')
export class Configuration {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 100, unique: true, nullable: false })
cle: string;
@Column({ type: 'text', nullable: true })
valeur: string | null;
@Column({ type: 'varchar', length: 50, nullable: false })
type: 'string' | 'number' | 'boolean' | 'json' | 'encrypted';
@Column({ type: 'varchar', length: 50, nullable: true })
categorie: 'email' | 'app' | 'security' | null;
@Column({ type: 'text', nullable: true })
description: string | null;
@UpdateDateColumn({ name: 'modifie_le' })
modifieLe: Date;
@ManyToOne(() => Users, { nullable: true })
@JoinColumn({ name: 'modifie_par' })
modifiePar: Users | null;
}
+57
View File
@@ -0,0 +1,57 @@
import { Column, CreateDateColumn, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
import { Dossier } from "./dossiers.entity";
export enum StatutContratType {
BROUILLON = 'brouillon',
EN_ATTENTE_SIGNATURE = 'en_attente_signature',
VALIDE = 'valide',
RESILIE = 'resilie',
}
@Entity('contrats')
export class Contrat {
// Define your columns and relationships here
@PrimaryGeneratedColumn('uuid')
id: string;
@OneToOne(() => Dossier, {onDelete: 'CASCADE'} )
@JoinColumn({ name: 'id_dossier'})
dossier: Dossier;
@Column({type: 'jsonb', nullable: true, name: 'planning'})
planning?: any;
@Column({type: 'numeric', precision: 6, scale: 2, nullable: true, name: 'tarif_horaire'})
hourly_rate?: string;
@Column({type: 'numeric', precision: 6, scale: 2, nullable: true, name: 'indemnites_repas'})
meal_indemnity?: string;
@Column( { name: 'date_debut', type: 'date', nullable: true })
start_date?: Date;
@Column({
type: 'enum',
enum: StatutContratType,
enumName: 'statut_contrat_type',
default: StatutContratType.BROUILLON,
name: 'statut'
})
statut: StatutContratType;
@Column({type: 'boolean', default: false, name: 'signe_parent'})
signed_by_parent: boolean;
@Column({type: 'boolean', default: false, name: 'signe_am'})
signed_by_am: boolean;
@Column({type: 'timestamptz', nullable: true, name: 'finalise_le'})
finalized_at?: Date;
@CreateDateColumn({ name: 'cree_le', type: 'timestamptz' })
created_at: Date;
@UpdateDateColumn({ name: 'modifie_le', type: 'timestamptz' })
updated_at: Date;
}
@@ -0,0 +1,44 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
CreateDateColumn,
} from 'typeorm';
import { Users } from './users.entity';
@Entity('documents_legaux')
export class DocumentLegal {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 50, nullable: false })
type: 'cgu' | 'privacy';
@Column({ type: 'integer', nullable: false })
version: number;
@Column({ type: 'varchar', length: 255, nullable: false })
fichier_nom: string;
@Column({ type: 'varchar', length: 500, nullable: false })
fichier_path: string;
@Column({ type: 'varchar', length: 64, nullable: false })
fichier_hash: string;
@Column({ type: 'boolean', default: false })
actif: boolean;
@ManyToOne(() => Users, { nullable: true })
@JoinColumn({ name: 'televerse_par' })
televersePar: Users | null;
@CreateDateColumn({ name: 'televerse_le', type: 'timestamptz' })
televerseLe: Date;
@Column({ name: 'active_le', type: 'timestamptz', nullable: true })
activeLe: Date | null;
}
@@ -0,0 +1,65 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
OneToMany,
CreateDateColumn,
UpdateDateColumn,
JoinColumn,
} from 'typeorm';
import { Parents } from './parents.entity';
import { Children } from './children.entity';
import { StatutDossierType } from './dossiers.entity';
/** Un dossier = une famille, N enfants (texte de motivation unique, liste d'enfants). */
@Entity('dossier_famille')
export class DossierFamille {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ name: 'numero_dossier', length: 20 })
numero_dossier: string;
@ManyToOne(() => Parents, { onDelete: 'CASCADE', nullable: false })
@JoinColumn({ name: 'id_parent', referencedColumnName: 'user_id' })
parent: Parents;
@Column({ type: 'text', nullable: true })
presentation?: string;
@Column({
type: 'enum',
enum: StatutDossierType,
enumName: 'statut_dossier_type',
default: StatutDossierType.ENVOYE,
name: 'statut',
})
statut: StatutDossierType;
@CreateDateColumn({ name: 'cree_le', type: 'timestamptz' })
cree_le: Date;
@UpdateDateColumn({ name: 'modifie_le', type: 'timestamptz' })
modifie_le: Date;
@OneToMany(() => DossierFamilleEnfant, (dfe) => dfe.dossier_famille)
enfants: DossierFamilleEnfant[];
}
@Entity('dossier_famille_enfants')
export class DossierFamilleEnfant {
@Column({ name: 'id_dossier_famille', primary: true })
id_dossier_famille: string;
@Column({ name: 'id_enfant', primary: true })
id_enfant: string;
@ManyToOne(() => DossierFamille, (df) => df.enfants, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'id_dossier_famille' })
dossier_famille: DossierFamille;
@ManyToOne(() => Children, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'id_enfant' })
enfant: Children;
}
+61
View File
@@ -0,0 +1,61 @@
import {
Entity, PrimaryGeneratedColumn, Column,
ManyToOne, OneToMany, CreateDateColumn, UpdateDateColumn, JoinColumn
} from 'typeorm';
import { Parents } from './parents.entity';
import { Children } from './children.entity';
import { Message } from './messages.entity';
export enum StatutDossierType {
ENVOYE = 'envoye',
ACCEPTE = 'accepte',
REFUSE = 'refuse',
CLOTURE = 'cloture',
}
@Entity('dossiers')
export class Dossier {
@PrimaryGeneratedColumn('uuid')
id: string;
@ManyToOne(() => Parents, p => p.dossiers, { onDelete: 'CASCADE', nullable: false })
@JoinColumn({ name: 'id_parent', referencedColumnName: 'user_id' })
parent: Parents;
@ManyToOne(() => Children, c => c.dossiers, { onDelete: 'CASCADE', nullable: false })
@JoinColumn({ name: 'id_enfant', referencedColumnName: 'id' })
child: Children;
@Column({ type: 'text', nullable: true, name: 'presentation' })
presentation?: string;
@Column({ type: 'varchar', length: 50, nullable: true, name: 'type_contrat' })
type_contrat?: string;
@Column({ type: 'boolean', default: false, name: 'repas' })
meals: boolean;
@Column({ type: 'numeric', precision: 10, scale: 2, nullable: true, name: 'budget' })
budget?: number;
@Column({ type: 'jsonb', nullable: true, name: 'planning_souhaite' })
desired_schedule?: any;
@Column({
type: 'enum',
enum: StatutDossierType,
enumName: 'statut_dossier_type',
default: StatutDossierType.ENVOYE,
name: 'statut'
})
status: StatutDossierType;
@CreateDateColumn({ name: 'cree_le', type: 'timestamptz' })
created_at: Date;
@UpdateDateColumn({ name: 'modifie_le', type: 'timestamptz' })
updated_at: Date;
@OneToMany(() => Message, m => m.dossier)
messages: Message[];
}
+79
View File
@@ -0,0 +1,79 @@
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;
}
+29
View File
@@ -0,0 +1,29 @@
import {
Entity, PrimaryGeneratedColumn, Column,
ManyToOne, JoinColumn, CreateDateColumn
} from 'typeorm';
import { Dossier } from './dossiers.entity';
import { Users } from './users.entity';
@Entity('messages')
export class Message {
@PrimaryGeneratedColumn('uuid')
id: string;
@ManyToOne(() => Dossier, d => d.messages, { onDelete: 'CASCADE', nullable: false })
@JoinColumn({ name: 'id_dossier' })
dossier: Dossier;
@ManyToOne(() => Users, u => u.messages, { onDelete: 'CASCADE', nullable: false })
@JoinColumn({ name: 'id_expediteur' })
sender: Users;
@Column({ type: 'text', name: 'contenu' })
content: string;
@Column({ type: 'boolean', name: 're_redige_par_ia', default: false })
reRedigeParIA: boolean;
@CreateDateColumn({ name: 'cree_le', type: 'timestamptz' })
created_at: Date;
}
@@ -0,0 +1,23 @@
import { Column, CreateDateColumn, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
import { Users } from "./users.entity";
@Entity('notifications')
export class Notification {
// Define your columns and relationships here
@PrimaryGeneratedColumn('uuid')
id: string;
@ManyToOne(() => Users, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'id_utilisateur', referencedColumnName: 'id' })
user: Users;
@Column({ type: 'text', name: 'contenu' })
content: string;
@Column({type: 'boolean', name: 'lu', default: false})
read: boolean;
@CreateDateColumn({ name: 'cree_le', type: 'timestamptz' })
created_at: Date;
}

Some files were not shown because too many files have changed in this diff Show More