Compare commits

..
33 changed files with 781 additions and 1116 deletions
+12
View File
@@ -5,6 +5,7 @@ import {
import { Users } from './users.entity';
import { ParentsChildren } from './parents_children.entity';
import { Dossier } from './dossiers.entity';
import { AmChildren } from './am_children.entity';
@Entity('parents', { schema: 'public' })
export class Parents {
@@ -25,6 +26,17 @@ export class Parents {
@Column({ name: 'numero_dossier', length: 20, nullable: true })
numero_dossier?: string;
/**
* Placement AM↔enfant sélectionné sur le TdB parent (couple actif) — ticket #168.
* Null = le front prend le premier couple actif retourné par lAPI.
*/
@Column({ name: 'id_placement_garde_courant', type: 'uuid', nullable: true })
id_placement_garde_courant?: string | null;
@ManyToOne(() => AmChildren, { nullable: true, onDelete: 'SET NULL' })
@JoinColumn({ name: 'id_placement_garde_courant', referencedColumnName: 'id' })
placement_garde_courant?: AmChildren | null;
// Lien vers enfants via la table enfants_parents
@OneToMany(() => ParentsChildren, pc => pc.parent)
parentChildren: ParentsChildren[];
@@ -0,0 +1,61 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
/** Identité minimale enfant pour le bandeau couple — ticket #168 */
export class CoupleGardeEnfantDto {
@ApiProperty({ format: 'uuid' })
id: string;
@ApiPropertyOptional()
prenom?: string | null;
@ApiPropertyOptional()
nom?: string | null;
@ApiPropertyOptional()
photo_url?: string | null;
}
/** Identité minimale AM pour le bandeau couple — ticket #168 */
export class CoupleGardeAmDto {
@ApiProperty({ format: 'uuid', description: 'UUID utilisateur de lAM' })
id: string;
@ApiPropertyOptional()
prenom?: string | null;
@ApiPropertyOptional()
nom?: string | null;
@ApiPropertyOptional()
photo_url?: string | null;
}
/** Un couple de garde = placement actif enfant ↔ AM */
export class CoupleGardeDto {
@ApiProperty({
format: 'uuid',
description: 'Id du placement (enfants_assistantes_maternelles.id)',
})
id: string;
@ApiProperty({ type: CoupleGardeEnfantDto })
enfant: CoupleGardeEnfantDto;
@ApiProperty({ type: CoupleGardeAmDto })
am: CoupleGardeAmDto;
@ApiProperty({ description: 'True si cest le couple actuellement sélectionné' })
courant: boolean;
}
export class CouplesGardeResponseDto {
@ApiProperty({ type: [CoupleGardeDto] })
couples: CoupleGardeDto[];
@ApiPropertyOptional({
format: 'uuid',
nullable: true,
description: 'Id du couple courant (null si aucun / premier couple implicite côté client)',
})
couple_courant_id: string | null;
}
@@ -0,0 +1,13 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsUUID } from 'class-validator';
/** Corps PUT couple de garde courant — ticket #168 */
export class DefinirCoupleGardeCourantDto {
@ApiProperty({
format: 'uuid',
description: 'Id du placement (enfants_assistantes_maternelles.id) à sélectionner',
})
@IsUUID()
@IsNotEmpty()
couple_id: string;
}
@@ -13,7 +13,10 @@ describe('ParentsController', () => {
createParentDossierStaff: jest.fn(),
addCoParentStaff: jest.fn(),
};
const parentsServiceMock = {};
const parentsServiceMock = {
listerCouplesGarde: jest.fn(),
definirCoupleGardeCourant: jest.fn(),
};
const userServiceMock = {};
beforeEach(async () => {
@@ -39,6 +42,31 @@ describe('ParentsController', () => {
expect(controller).toBeDefined();
});
it('listerCouplesGarde délègue au service (#168)', async () => {
parentsServiceMock.listerCouplesGarde.mockResolvedValue({
couples: [],
couple_courant_id: null,
});
const res = await controller.listerCouplesGarde('parent-uuid');
expect(parentsServiceMock.listerCouplesGarde).toHaveBeenCalledWith('parent-uuid');
expect(res.couples).toEqual([]);
});
it('definirCoupleGardeCourant délègue au service (#168)', async () => {
parentsServiceMock.definirCoupleGardeCourant.mockResolvedValue({
couples: [{ id: 'pl-1', courant: true }],
couple_courant_id: 'pl-1',
});
const res = await controller.definirCoupleGardeCourant('parent-uuid', {
couple_id: 'pl-1',
});
expect(parentsServiceMock.definirCoupleGardeCourant).toHaveBeenCalledWith(
'parent-uuid',
'pl-1',
);
expect(res.couple_courant_id).toBe('pl-1');
});
it('createDossier delegates to authService.createParentDossierStaff with CGU accepted', async () => {
authServiceMock.createParentDossierStaff.mockResolvedValue({
message: 'Dossier famille créé et validé. Un e-mail de création de mot de passe a été envoyé.',
@@ -8,6 +8,7 @@ import {
Param,
Patch,
Post,
Put,
UseGuards,
} from '@nestjs/common';
import { ParentsService } from './parents.service';
@@ -39,6 +40,8 @@ import { User } from 'src/common/decorators/user.decorator';
import { PendingFamilyDto } from './dto/pending-family.dto';
import { DossierFamilleCompletDto } from './dto/dossier-famille-complet.dto';
import { mapParentForApi, mapParentsForApi } from './parents.mapper';
import { CouplesGardeResponseDto } from './dto/couples-garde.dto';
import { DefinirCoupleGardeCourantDto } from './dto/definir-couple-garde-courant.dto';
@ApiTags('Parents')
@ApiBearerAuth('access-token')
@@ -51,6 +54,39 @@ export class ParentsController {
private readonly authService: AuthService,
) {}
@Get('me/couples-garde')
@Roles(RoleType.PARENT)
@ApiOperation({
summary: 'Lister les couples de garde du parent connecté — ticket #168',
description:
'Retourne les placements actifs enfant↔AM rattachés au parent, ' +
'avec indication du couple courant (bandeau TdB quotidien).',
})
@ApiResponse({ status: 200, type: CouplesGardeResponseDto })
@ApiResponse({ status: 403, description: 'Réservé au rôle parent' })
@ApiResponse({ status: 404, description: 'Parent introuvable' })
listerCouplesGarde(@User('id') userId: string): Promise<CouplesGardeResponseDto> {
return this.parentsService.listerCouplesGarde(userId);
}
@Put('me/couples-garde/courant')
@Roles(RoleType.PARENT)
@ApiOperation({
summary: 'Définir le couple de garde courant — ticket #168',
description:
'Persiste la préférence de couple actif (enfant|nounou) pour contextualiser le TdB.',
})
@ApiBody({ type: DefinirCoupleGardeCourantDto })
@ApiResponse({ status: 200, type: CouplesGardeResponseDto })
@ApiResponse({ status: 400, description: 'Couple hors périmètre du parent' })
@ApiResponse({ status: 404, description: 'Parent ou couple introuvable' })
definirCoupleGardeCourant(
@User('id') userId: string,
@Body() dto: DefinirCoupleGardeCourantDto,
): Promise<CouplesGardeResponseDto> {
return this.parentsService.definirCoupleGardeCourant(userId, dto.couple_id);
}
@Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE)
@Post('dossier')
@HttpCode(HttpStatus.CREATED)
+9 -1
View File
@@ -5,6 +5,7 @@ import { JwtModule } from '@nestjs/jwt';
import { Parents } from 'src/entities/parents.entity';
import { DossierFamille, DossierFamilleEnfant } from 'src/entities/dossier_famille.entity';
import { ParentsChildren } from 'src/entities/parents_children.entity';
import { AmChildren } from 'src/entities/am_children.entity';
import { ParentsController } from './parents.controller';
import { ParentsService } from './parents.service';
import { Users } from 'src/entities/users.entity';
@@ -13,7 +14,14 @@ import { AuthModule } from '../auth/auth.module';
@Module({
imports: [
TypeOrmModule.forFeature([Parents, Users, DossierFamille, DossierFamilleEnfant, ParentsChildren]),
TypeOrmModule.forFeature([
Parents,
Users,
DossierFamille,
DossierFamilleEnfant,
ParentsChildren,
AmChildren,
]),
forwardRef(() => UserModule),
forwardRef(() => AuthModule),
JwtModule.registerAsync({
@@ -1,12 +1,43 @@
import { BadRequestException, NotFoundException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { ParentsService } from './parents.service';
import { Parents } from 'src/entities/parents.entity';
import { Users } from 'src/entities/users.entity';
import { DossierFamille } from 'src/entities/dossier_famille.entity';
import { ParentsChildren } from 'src/entities/parents_children.entity';
import { AmChildren } from 'src/entities/am_children.entity';
describe('ParentsService', () => {
describe('ParentsService — couples de garde (#168)', () => {
let service: ParentsService;
const parentsRepository = {
findOne: jest.fn(),
update: jest.fn(),
};
const parentsChildrenRepository = {
find: jest.fn(),
findOne: jest.fn(),
};
const amChildrenRepository = {
find: jest.fn(),
findOne: jest.fn(),
};
beforeEach(async () => {
jest.clearAllMocks();
const module: TestingModule = await Test.createTestingModule({
providers: [ParentsService],
providers: [
ParentsService,
{ provide: getRepositoryToken(Parents), useValue: parentsRepository },
{ provide: getRepositoryToken(Users), useValue: {} },
{ provide: getRepositoryToken(DossierFamille), useValue: {} },
{
provide: getRepositoryToken(ParentsChildren),
useValue: parentsChildrenRepository,
},
{ provide: getRepositoryToken(AmChildren), useValue: amChildrenRepository },
],
}).compile();
service = module.get<ParentsService>(ParentsService);
@@ -15,4 +46,107 @@ describe('ParentsService', () => {
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('listerCouplesGarde', () => {
it('retourne une liste vide si le parent na pas denfant', async () => {
parentsRepository.findOne.mockResolvedValue({
user_id: 'p1',
id_placement_garde_courant: null,
});
parentsChildrenRepository.find.mockResolvedValue([]);
const res = await service.listerCouplesGarde('p1');
expect(res).toEqual({ couples: [], couple_courant_id: null });
expect(amChildrenRepository.find).not.toHaveBeenCalled();
});
it('mappe les placements actifs en couples et marque le courant', async () => {
parentsRepository.findOne.mockResolvedValue({
user_id: 'p1',
id_placement_garde_courant: 'pl-2',
});
parentsChildrenRepository.find.mockResolvedValue([
{ enfantId: 'e1' },
{ enfantId: 'e2' },
]);
amChildrenRepository.find.mockResolvedValue([
{
id: 'pl-1',
amId: 'am-1',
child: { id: 'e1', first_name: 'Léo', last_name: 'M', photo_url: null },
am: { user: { prenom: 'Marie', nom: 'N', photo_url: '/a.jpg' } },
},
{
id: 'pl-2',
amId: 'am-2',
child: { id: 'e2', first_name: 'Léa', last_name: 'M', photo_url: null },
am: { user: { prenom: 'Sophie', nom: 'P', photo_url: null } },
},
]);
const res = await service.listerCouplesGarde('p1');
expect(res.couples).toHaveLength(2);
expect(res.couple_courant_id).toBe('pl-2');
expect(res.couples[1].courant).toBe(true);
expect(res.couples[0].am.prenom).toBe('Marie');
expect(res.couples[0].enfant.prenom).toBe('Léo');
});
it('404 si parent inconnu', async () => {
parentsRepository.findOne.mockResolvedValue(null);
await expect(service.listerCouplesGarde('x')).rejects.toBeInstanceOf(
NotFoundException,
);
});
});
describe('definirCoupleGardeCourant', () => {
it('persiste le couple si lenfant est rattaché au parent', async () => {
parentsRepository.findOne.mockResolvedValue({
user_id: 'p1',
id_placement_garde_courant: null,
});
amChildrenRepository.findOne.mockResolvedValue({
id: 'pl-1',
enfantId: 'e1',
date_fin: null,
});
parentsChildrenRepository.findOne.mockResolvedValue({
parentId: 'p1',
enfantId: 'e1',
});
parentsRepository.update.mockResolvedValue({ affected: 1 });
// second call via listerCouplesGarde
parentsChildrenRepository.find.mockResolvedValue([{ enfantId: 'e1' }]);
amChildrenRepository.find.mockResolvedValue([
{
id: 'pl-1',
amId: 'am-1',
child: { id: 'e1', first_name: 'Léo', last_name: null, photo_url: null },
am: { user: { prenom: 'Marie', nom: null, photo_url: null } },
},
]);
const res = await service.definirCoupleGardeCourant('p1', 'pl-1');
expect(parentsRepository.update).toHaveBeenCalledWith(
{ user_id: 'p1' },
{ id_placement_garde_courant: 'pl-1' },
);
expect(res.couple_courant_id).toBe('pl-1');
expect(res.couples[0].courant).toBe(true);
});
it('400 si le couple nappartient pas au parent', async () => {
parentsRepository.findOne.mockResolvedValue({ user_id: 'p1' });
amChildrenRepository.findOne.mockResolvedValue({
id: 'pl-1',
enfantId: 'e99',
});
parentsChildrenRepository.findOne.mockResolvedValue(null);
await expect(
service.definirCoupleGardeCourant('p1', 'pl-1'),
).rejects.toBeInstanceOf(BadRequestException);
});
});
});
+109 -1
View File
@@ -5,7 +5,7 @@ import {
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { In, IsNull, Repository } from 'typeorm';
import { Parents } from 'src/entities/parents.entity';
import { DossierFamille } from 'src/entities/dossier_famille.entity';
import { RoleType, Users } from 'src/entities/users.entity';
@@ -20,6 +20,8 @@ import {
import { ParentsChildren } from 'src/entities/parents_children.entity';
import { Children } from 'src/entities/children.entity';
import { UpdateParentFicheAdminDto } from './dto/update-parent-fiche-admin.dto';
import { AmChildren } from 'src/entities/am_children.entity';
import { CouplesGardeResponseDto } from './dto/couples-garde.dto';
@Injectable()
export class ParentsService {
@@ -32,6 +34,8 @@ export class ParentsService {
private readonly dossierFamilleRepository: Repository<DossierFamille>,
@InjectRepository(ParentsChildren)
private readonly parentsChildrenRepository: Repository<ParentsChildren>,
@InjectRepository(AmChildren)
private readonly amChildrenRepository: Repository<AmChildren>,
) {}
// Création dun parent
@@ -505,4 +509,108 @@ export class ParentsService {
}
return raw.map((r: { id: string }) => r.id);
}
/**
* Liste les couples de garde (enfant ↔ AM) du parent connecté — ticket #168.
* Un couple = un placement actif dans enfants_assistantes_maternelles pour un enfant du parent.
*/
async listerCouplesGarde(parentUserId: string): Promise<CouplesGardeResponseDto> {
const parent = await this.parentsRepository.findOne({
where: { user_id: parentUserId },
});
if (!parent) {
throw new NotFoundException('Parent introuvable');
}
const liensEnfants = await this.parentsChildrenRepository.find({
where: { parentId: parentUserId },
select: ['enfantId'],
});
const enfantIds = liensEnfants.map((l) => l.enfantId);
if (enfantIds.length === 0) {
return { couples: [], couple_courant_id: null };
}
const placements = await this.amChildrenRepository.find({
where: { enfantId: In(enfantIds), date_fin: IsNull() },
relations: ['child', 'am', 'am.user'],
order: { date_debut: 'ASC' },
});
let idCourant = parent.id_placement_garde_courant ?? null;
const idsValides = new Set(placements.map((p) => p.id));
if (idCourant && !idsValides.has(idCourant)) {
idCourant = null;
await this.parentsRepository.update(
{ user_id: parentUserId },
{ id_placement_garde_courant: null },
);
}
if (!idCourant && placements.length === 1) {
idCourant = placements[0].id;
}
const couples = placements.map((p) => {
const amUser = p.am?.user;
return {
id: p.id,
enfant: {
id: p.child.id,
prenom: p.child.first_name ?? null,
nom: p.child.last_name ?? null,
photo_url: p.child.photo_url ?? null,
},
am: {
id: p.amId,
prenom: amUser?.prenom ?? null,
nom: amUser?.nom ?? null,
photo_url: amUser?.photo_url ?? null,
},
courant: idCourant != null && p.id === idCourant,
};
});
return {
couples,
couple_courant_id: idCourant,
};
}
/**
* Persiste le couple de garde actif pour le parent — ticket #168.
*/
async definirCoupleGardeCourant(
parentUserId: string,
coupleId: string,
): Promise<CouplesGardeResponseDto> {
const parent = await this.parentsRepository.findOne({
where: { user_id: parentUserId },
});
if (!parent) {
throw new NotFoundException('Parent introuvable');
}
const placement = await this.amChildrenRepository.findOne({
where: { id: coupleId, date_fin: IsNull() },
});
if (!placement) {
throw new NotFoundException('Couple de garde introuvable ou inactif');
}
const lien = await this.parentsChildrenRepository.findOne({
where: { parentId: parentUserId, enfantId: placement.enfantId },
});
if (!lien) {
throw new BadRequestException(
'Ce couple ne concerne pas un enfant rattaché à ce parent',
);
}
await this.parentsRepository.update(
{ user_id: parentUserId },
{ id_placement_garde_courant: coupleId },
);
return this.listerCouplesGarde(parentUserId);
}
}
+14 -1
View File
@@ -154,7 +154,10 @@ CREATE INDEX idx_assistantes_maternelles_numero_dossier
CREATE TABLE parents (
id_utilisateur UUID PRIMARY KEY REFERENCES utilisateurs(id) ON DELETE CASCADE,
id_co_parent UUID REFERENCES utilisateurs(id),
numero_dossier VARCHAR(20)
numero_dossier VARCHAR(20),
-- Préférence couple de garde actif (TdB quotidien) — ticket #168
-- FK ajoutée après création de enfants_assistantes_maternelles (voir ALTER plus bas)
id_placement_garde_courant UUID
);
CREATE INDEX idx_parents_numero_dossier
@@ -206,6 +209,16 @@ CREATE UNIQUE INDEX uq_enfant_garde_active
ON enfants_assistantes_maternelles (id_enfant)
WHERE date_fin IS NULL;
-- FK couple courant parent → placement (#168) — après table enfants_assistantes_maternelles
ALTER TABLE parents
ADD CONSTRAINT fk_parents_placement_garde_courant
FOREIGN KEY (id_placement_garde_courant)
REFERENCES enfants_assistantes_maternelles(id) ON DELETE SET NULL;
CREATE INDEX idx_parents_placement_garde_courant
ON parents(id_placement_garde_courant)
WHERE id_placement_garde_courant IS NOT NULL;
-- ==========================================================
-- Table : dossier_famille (inscription parent — ticket #119)
-- ==========================================================
@@ -0,0 +1,10 @@
-- Ticket #168 — Couple de garde courant (préférence parent)
-- Idempotent : safe à rejouer.
ALTER TABLE parents
ADD COLUMN IF NOT EXISTS id_placement_garde_courant UUID
REFERENCES enfants_assistantes_maternelles(id) ON DELETE SET NULL;
CREATE INDEX IF NOT EXISTS idx_parents_placement_garde_courant
ON parents(id_placement_garde_courant)
WHERE id_placement_garde_courant IS NOT NULL;
+8
View File
@@ -10,6 +10,8 @@ Index de navigation du dépôt. Dernière révision : **septembre 2026** (CDC V1
| [12 — SRS gestion utilisateurs](./12_SRS-GESTION-UTILISATEURS.md) | Spécification **technique** du domaine users / dossiers |
| [05 — Versions & milestones](./05_VERSIONS-ET-MILESTONES.md) | Semver Gitea + bilans |
| [29 — Bilan version 0.1.0](./29_BILAN-VERSION-0.1.0.md) | Tickets livrés 0.1.0 |
| [30 — Découpage tickets quotidien](./30_DECOUPAGE-TICKETS-QUOTIDIEN.md) | Brouillon / backlog tickets parent/AM |
| [31 — Mini-spec quotidien parent/AM](./31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md) | Besoin figé atelier sept. 2026 |
| [04 — Roadmap générale](./04_ROADMAP-GENERALE.md) | Vision phases long terme |
| [28 — Évolution famille / responsables](./28_EVOLUTION-FAMILLE-ET-RESPONSABLES.md) | Limites modèle foyer / contournements |
@@ -30,7 +32,13 @@ Index de navigation du dépôt. Dernière révision : **septembre 2026** (CDC V1
|-----|---------|
| [20 — Workflow création de compte](./20_WORKFLOW-CREATION-COMPTE.md) | Inscription / validation (détail historique) |
| [juridique/](./juridique/README.md) | CGU / CGC / privacy + [22 technique](./juridique/22_DOCUMENTS-LEGAUX.md) |
## Charte & maquettes
| Doc | Contenu |
|-----|---------|
| [CHARTE_GRAPHIQUE.md](./CHARTE_GRAPHIQUE.md) | Charte UI |
| [maquettes/](./maquettes/README.md) | Maquettes TdB quotidien (réf. v4 + historique) |
## Projet & outillage
+8 -6
View File
@@ -12,12 +12,12 @@ Ce fichier remplace, pour le **semver / milestones**, les anciennes tables figé
| Milestone | Rôle | Statut |
|-----------|------|--------|
| **0.1.0** | MVP opérable (auth, inscription, dashboard dossiers/fiches, suppressions, cleanups) | **Terminée** — [bilan](./29_BILAN-VERSION-0.1.0.md) |
| **0.2.0** | Suite produit (ex. recherche / échanges — sans contrat) | Ouverte |
| **0.3.0** | Contrat + planning | Ouverte |
| **0.4.0** | Carnet de liaison | Ouverte |
| **0.9.0** | Hors périmètre cleanup 0.1.0 (doublons, upload, tech auth/photos, UX erreurs…) | Ouverte |
| **1.0.0** | Release majeure Phase 1 (critères PO) | Réserve |
| **Backlog transverse** | Doc étendue, CI/tests, RGPD avancé, monitoring — hors semver dédié | Ouverte |
| **0.2.0** | **Quotidien parent / AM** (TdB 3 colonnes, cartes, blog, messagerie) — [#165](https://git.ptits-pas.fr/jmartin/petitspas/issues/165)… | **Ouverte** — [mini-spec](./31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md) · [découpage](./30_DECOUPAGE-TICKETS-QUOTIDIEN.md) |
| **0.3.0** | Contrat + planning | Fermée (vide) — à réouvrir au besoin |
| **0.4.0** | Carnet de liaison | Fermée (vide) — à réouvrir au besoin |
| **0.9.0** | Hors périmètre cleanup 0.1.0 | Fermée (vide) |
| **1.0.0** | Release majeure (critères PO) | Fermée (réserve) |
| **Backlog transverse** | Doc / CI / RGPD / monitoring | Fermée (vide) |
Liens Gitea : [milestones](https://git.ptits-pas.fr/jmartin/petitspas/milestones).
@@ -35,6 +35,8 @@ Liens Gitea : [milestones](https://git.ptits-pas.fr/jmartin/petitspas/milestones
|-----|------|
| [01_CAHIER-DES-CHARGES.md](./01_CAHIER-DES-CHARGES.md) | CDC **complet** V1.4 (users mis à jour ; reste = cible) |
| [12_SRS-GESTION-UTILISATEURS.md](./12_SRS-GESTION-UTILISATEURS.md) | SRS technique domaine utilisateurs |
| [31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md](./31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md) | Besoin quotidien parent/AM (0.2.0) |
| [30_DECOUPAGE-TICKETS-QUOTIDIEN.md](./30_DECOUPAGE-TICKETS-QUOTIDIEN.md) | Mapping tickets #165#189 |
| Archive CDC V1.3 | [archive/obsolete/01_CAHIER-DES-CHARGES-v1.3.md](./archive/obsolete/01_CAHIER-DES-CHARGES-v1.3.md) |
---
+2
View File
@@ -8,5 +8,7 @@ Ne plus maintenir de catalogue exhaustif des tickets dans le dépôt : l’état
Pour une **version livrée**, lire le bilan correspondant (ex. [29_BILAN-VERSION-0.1.0.md](./29_BILAN-VERSION-0.1.0.md)).
**Backlog en cours `0.2.0` (quotidien)** : epic [#165](https://git.ptits-pas.fr/jmartin/petitspas/issues/165) · [découpage #166#189](./30_DECOUPAGE-TICKETS-QUOTIDIEN.md) · [mini-spec](./31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md).
Archive historique (liste Phase 1 figée, avril 2026) :
[archive/obsolete/23_LISTE-TICKETS.md](./archive/obsolete/23_LISTE-TICKETS.md).
+201
View File
@@ -0,0 +1,201 @@
# Découpage tickets — Quotidien parent / AM
> **Statut :** backlog Gitea créé (**milestone `0.2.0`**, **#165#189**) — *création anticipée avant validation PO* ; **descriptions enrichies** ensuite à partir de la mini-spec / découpage.
> **Réf. visuelle :** [maquettes/courantes/maquette-dashboard-parent-quotidien-v4.png](./maquettes/courantes/maquette-dashboard-parent-quotidien-v4.png)
> **Mini-spec :** [31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md](./31_MINI-SPEC-QUOTIDIEN-PARENT-AM.md)
> **Règle :** tickets **front** / **back** séparés · widgets partagés parent↔AM · **pas dimplé code** tant que le PO na pas validé le backlog.
## Intention produit (rappel)
- TdB PC en **3 colonnes** : **cartes** | **blog** (défaut) | **messagerie**
- Contexte actif = **couple** enfantnounou (parent) / enfantparent(s) (AM)
- Blog **indispensable** (AM + RPE) ; messagerie type WhatsApp (BABA, 2 parents = même fil AM)
- Look **papier / pastel / lignée inscription** (pas le dashboard staff)
- **Widgets partagés** parent ↔ AM (blog, messagerie, cartes, coquille, couple paramétré) — pas de double implémentation
- « Effet démo » = logiciel peuplé + sync live multi-écrans — **pas** un sous-MVP jetable
- **Hors ce découpage** (jalons suivants) : page Contrat riche (Pajemploi, CP), notifs CDC fourre-tout, masquage messages, posts blog parent, Agenda « riche » (entrée bandeau possible en stub)
## Contrainte technique front (dictée 23/09)
Les panneaux parent et AM étant **quasiment identiques**, le découpage impose des **briques réutilisables** :
| Widget / module | Tickets concernés |
|-----------------|-------------------|
| Coquille TdB 3 colonnes + bandeau | A1, B1 — B1 **réutilise** la coquille dA1 |
| Sélecteur couple (mode parent vs AM) | A2, B2 — **un** composant, 2 modes |
| Flux de cartes | C2, C3 — **un** widget + config rôles |
| Blog (fil + composeur) | D2, D3, D4 — même brique |
| Messagerie (chat + onglets) | E4, E5, E6 — même brique |
Les tickets **[Front] AM** (B1, B2, C3, D3, E5) = **intégration / mode rôle**, pas une 2ᵉ copie du code.
## Proposition de milestone
| Champ | Valeur |
|-------|--------|
| Titre | **`0.2.0`** (rouvert) |
| Description | Quotidien parent / AM — TdB 3 colonnes, cartes, blog, messagerie |
| Label | `v0.2.0` |
## Mapping Gitea
| Id | Issue | Titre |
|----|-------|-------|
| Epic | [#165](https://git.ptits-pas.fr/jmartin/petitspas/issues/165) | Epic quotidien |
| A1 | [#166](https://git.ptits-pas.fr/jmartin/petitspas/issues/166) | [Front] Coquille TdB parent |
| A2 | [#167](https://git.ptits-pas.fr/jmartin/petitspas/issues/167) | [Front] Couple enfantnounou |
| A3 | [#168](https://git.ptits-pas.fr/jmartin/petitspas/issues/168) | [Backend] API couples parent |
| B1 | [#169](https://git.ptits-pas.fr/jmartin/petitspas/issues/169) | [Front] Coquille TdB AM |
| B2 | [#170](https://git.ptits-pas.fr/jmartin/petitspas/issues/170) | [Front] Couple enfantparent(s) |
| B3 | [#171](https://git.ptits-pas.fr/jmartin/petitspas/issues/171) | [Backend] API enfants AM |
| C1 | [#172](https://git.ptits-pas.fr/jmartin/petitspas/issues/172) | [Backend] API cartes |
| C2 | [#173](https://git.ptits-pas.fr/jmartin/petitspas/issues/173) | [Front] Cartes parent |
| C3 | [#174](https://git.ptits-pas.fr/jmartin/petitspas/issues/174) | [Front] Cartes AM |
| C4 | [#175](https://git.ptits-pas.fr/jmartin/petitspas/issues/175) | [Front] Déclarer absence |
| C5 | [#176](https://git.ptits-pas.fr/jmartin/petitspas/issues/176) | [Front] Formulaires AM cartes |
| D1 | [#177](https://git.ptits-pas.fr/jmartin/petitspas/issues/177) | [Backend] API blog |
| D2 | [#178](https://git.ptits-pas.fr/jmartin/petitspas/issues/178) | [Front] Blog parent |
| D3 | [#179](https://git.ptits-pas.fr/jmartin/petitspas/issues/179) | [Front] Blog AM |
| D4 | [#180](https://git.ptits-pas.fr/jmartin/petitspas/issues/180) | [Front] Blog gestionnaire |
| E1 | [#181](https://git.ptits-pas.fr/jmartin/petitspas/issues/181) | [Backend] Socle messagerie |
| E2 | [#182](https://git.ptits-pas.fr/jmartin/petitspas/issues/182) | [Backend] Mess. AM |
| E3 | [#183](https://git.ptits-pas.fr/jmartin/petitspas/issues/183) | [Backend] Mess. RPE |
| E4 | [#184](https://git.ptits-pas.fr/jmartin/petitspas/issues/184) | [Front] Messagerie parent |
| E5 | [#185](https://git.ptits-pas.fr/jmartin/petitspas/issues/185) | [Front] Messagerie AM |
| E6 | [#186](https://git.ptits-pas.fr/jmartin/petitspas/issues/186) | [Front] Mess. RPE gestionnaire |
| F1 | [#187](https://git.ptits-pas.fr/jmartin/petitspas/issues/187) | [Front] Stubs Agenda / Contrat |
| F2 | [#188](https://git.ptits-pas.fr/jmartin/petitspas/issues/188) | [Front] Swipe mobile |
| G1 | [#189](https://git.ptits-pas.fr/jmartin/petitspas/issues/189) | [Backend] Seeds quotidien |
---
## Epic A — Coquille TdB parent
### A1 — [Front] Coquille TdB parent 3 colonnes + bandeau → [#166](https://git.ptits-pas.fr/jmartin/petitspas/issues/166)
Bandeau TdB / Agenda / Contrat + menu user ; corps en 3 colonnes (cartes | blog | messagerie) ; fond papier / pastel. **Livrer la coquille comme widget/layout réutilisable** (lAM B1 sen branche). Remplace / refactor `ParentDashboardScreen` + `dashbord_parent/`.
### A2 — [Front] Sélecteur couple enfantnounou → [#167](https://git.ptits-pas.fr/jmartin/petitspas/issues/167)
Composant **paramétrable** (mode parent : enfant|nounou). Dropdown si plusieurs gardes ; informatif si une seule. B2 = même widget en mode AM.
### A3 — [Back] API contexte de garde actif (couples parent)
Endpoint(s) listant les couples enfantAM du parent connecté + couple « courant » (persistance session / préférence). Données mini : ids, noms, photos.
---
## Epic B — Coquille TdB AM (miroir)
### B1 — [Front] Coquille TdB AM 3 colonnes + bandeau → [#169](https://git.ptits-pas.fr/jmartin/petitspas/issues/169)
**Réutilise** la coquille dA1/#166 ; point de départ `am_dashboard_screen.dart`.
### B2 — [Front] Sélecteur couple enfantparent(s) → [#170](https://git.ptits-pas.fr/jmartin/petitspas/issues/170)
**Même widget** quA2 en mode AM : gauche photo enfant ; droite parent1+parent2 empilés ou un parent centré.
### B3 — [Back] API contexte enfants accueillis (AM)
Liste des enfants / foyers pour lAM + contexte courant (symétrique A3).
---
## Epic C — Cartes (file du quotidien)
### C1 — [Back] Modèle + API cartes / événements de garde
Types V1 : absence enfant, congé AM, maladie AM (« arrêt »), sortie à valider. CRUD / transitions de statut ; liaison couple / agenda (hook minimal). Règles : absence enfant sans veto AM ; congé AM accepter/refuser (**1 parent suffit**) ; maladie AM accusé parent ; sortie **1 parent suffit**. Aucun doc médical stocké.
### C2 — [Front] Flux de cartes colonne gauche (parent)
Liste scroll pastel (palette `assets/cards/` — 7 couleurs) ; ouverture détail ; actions valider / refuser / accusé selon type. Bouton **Déclarer une absence** (+ autres actions TBD plus tard).
### C3 — [Front] Flux de cartes colonne gauche (AM)
Miroir : création congé / maladie / sortie ; lecture absences enfants du jour / à venir.
### C4 — [Front] Formulaire déclarer une absence (parent)
Saisie période (+ motif léger si besoin) → crée une carte côté AM.
### C5 — [Front] Formulaires AM congé / maladie / sortie
Création des cartes correspondantes + ciblage enfants si sortie.
---
## Epic D — Blog
### D1 — [Back] Modèle + API blog (posts + médias)
Posts par auteur AM ou gestionnaire (RPE) ; texte + photos ; ciblage enfants (AM) / audience (RPE) ; fil visible parents (et AM selon règles). Sync temps réel ou polling acceptable V1.
### D2 — [Front] Colonne Blog parent (défaut)
Fil de posts ; auteur **distinct** AM vs RPE ; miniatures ; pas de bouton « Écrire un post » parent en V1 (reporté).
### D3 — [Front] Colonne Blog AM — lecture + écrire un post
Composer texte + photos + sélection enfants concernés ; publication.
### D4 — [Front] Publication blog gestionnaire (point dentrée staff)
UI minimale côté dashboard gestionnaire (ou réutilisation) pour publier une annonce RPE visible sur les TdB parent/AM.
---
## Epic E — Messagerie
### E1 — [Back] Socle messagerie (choix lib / protocole) + API
Évaluer brique existante (style chat WhatsApp : texte, emoji, images). Conversations, participants, pièces jointes images. Temps réel (WS ou équivalent) souhaité pour leffet multi-écrans.
### E2 — [Back] Mess. AM — conversation foyer ↔ AM
Une conversation par couple/foyer ; **les deux parents** voient le **même** fil. Pas de masquage V1.
### E3 — [Back] Mess. RPE — privée + ajout de participants
Fil 1↔1 par défaut (parent↔RPE ou AM↔RPE) ; possibilité d**ajouter** 2ᵉ parent / AM / parent pour médiation.
### E4 — [Front] Colonne messagerie parent (onglets Mess. AM | Mess. RPE)
UI chat + saisie + PJ ; défaut = Mess. AM.
### E5 — [Front] Colonne messagerie AM (onglets équivalents)
Miroir parent ; Mess. AM = foyer courant ; Mess. RPE = canal relais.
### E6 — [Front] Mess. RPE côté gestionnaire (entrée minimale)
Liste / réponse aux fils RPE + ajout de participants pour médiation.
---
## Epic F — Navigation & mobile (socle)
### F1 — [Front] Navigation Agenda / Contrat (stubs ou routes)
Entrées bandeau : pages **placeholder** ou squelette (Agenda / Contrat pleine page) pour ne pas bloquer le TdB — contenu métier = jalons suivants.
### F2 — [Front] Adaptation mobile — swipe 3 panneaux
Sur téléphone : swipe Blog ↔ Cartes ↔ Messagerie ; navigation TdB/Agenda/Contrat hors swipe. Contrat peut rester limité (« mieux sur PC ») en V1 si besoin.
---
## Epic G — Données de démo / peuplement
### G1 — [Back/Outillage] Jeu de données peuplé quotidien
Scripts / seeds : foyer 2 parents, AM, enfant(s), quelques cartes, posts blog, fils messagerie — pour enchaîner une démo multi-écrans sans saisie manuelle lourde.
---
## Hors découpage (volontairement)
| Sujet | Motif |
|-------|--------|
| Page Contrat (Pajemploi, CP, avenants) | Module valué — jalon dédié |
| Agenda calendrier riche | Entrée bandeau OK en stub ; métier plus tard |
| Notifs CDC (contrat/paiement/dossier) | Plus tard |
| Masquage messages / privé parent↔AM | Plus tard |
| Parent auteur de posts blog | Hypothétique / plus tard |
| Photo parent à linscription | Non ; éventuel menu profil plus tard |
---
## Ordre de réalisation suggéré
1. **A1 → A2 → A3** (coquille + contexte parent)
2. **C1 → C2 / C4** (premières cartes utiles)
3. **D1 → D2 / D3** (blog central)
4. **E1 → E2 → E4** (messagerie AM)
5. **B1 → B2 → B3** + miroirs C3/C5/D3/E5 (AM)
6. **E3 / E5 / E6 / D4** (RPE)
7. **F1 → F2** + **G1** (nav, mobile, seeds)
---
## Validation
Backlog créé sur Gitea (**#165#189**, milestone `0.2.0`).
**Implémentation code** : seulement après relecture PO / ajustements éventuels sur le découpage.
+104
View File
@@ -0,0 +1,104 @@
# Mini-spec — Quotidien parent / AM
> **Statut :** figée pour backlog (sept. 2026)
> **Réf. visuelle :** [maquettes/courantes/maquette-dashboard-parent-quotidien-v4.png](./maquettes/courantes/maquette-dashboard-parent-quotidien-v4.png)
> **Découpage tickets :** [30_DECOUPAGE-TICKETS-QUOTIDIEN.md](./30_DECOUPAGE-TICKETS-QUOTIDIEN.md)
> **CDC :** vision dorigine conservée ; priorités UX évoluées (blog central, couple enfantnounou)
## 1. Objectif
Construire les **espaces parent et AM du quotidien** : tableau de bord 3 colonnes, file de cartes (absences / congés / sorties), **blog** (cœur du jour), **messagerie** AM et RPE. Logiciel réel peuplé de données ; une démo = sync live multi-écrans (parent / AM / gestionnaire).
## 2. UI — contrainte non négociable
- Look **papier / pastel**, lignée **login / inscription** (`paper2.png`), pas le dashboard staff violet Material
- PC paysage : **3 colonnes** égales
- Mobile : **swipe** entre les 3 panneaux ; TdB / Agenda / Contrat via nav dédiée
### Architecture front — maximiser les widgets partagés
Parent et AM sont **quasi identiques** : **ne pas dupliquer** les colonnes métier.
| Brique | Usage |
|--------|--------|
| Widget **Blog** | Colonne milieu parent **et** AM (+ entrée publication gestionnaire qui réutilise le composeur) |
| Widget **Messagerie** | Colonne droite parent **et** AM (+ vue gestionnaire RPE) |
| Widget **flux de cartes** | Colonne gauche parent **et** AM (actions / droits selon rôle) |
| Widget **bandeau couple** | Variante *enfant\|nounou* (parent) vs *enfant\|parent(s)* (AM) — même composant paramétré |
| Coquille TdB 3 colonnes | Layout / bandeau navig commun ; le rôle injecte le couple + droits |
Principe : **briques correctement encapsulées** (API claire props / callbacks), branchées sur le même back ; seules les **différences de rôle** (qui crée quoi, libellés daction) restent hors du widget partagé.
## 3. Layout TdB
| Colonne | Contenu |
|---------|---------|
| **Gauche** | Couple actif · flux de **cartes** · actions (ex. Déclarer une absence) |
| **Milieu** | **Blog** — affichage **par défaut** à larrivée |
| **Droite** | **Messagerie** seule — onglets Mess. AM (défaut) \| Mess. RPE |
Bandeau : **TdB** · **Agenda** · **Contrat** · menu user (recherche AM, paramètres…).
### Parent — couple
- Un bandeau **enfant | nounou** (photos + noms)
- Plusieurs gardes → dropdown unique (bascule de couple)
- Une seule garde → affichage informatif (pas de chevron)
### AM — couple (miroir)
- Bandeau **enfant | parent(s)**
- 2 parents : noms empilés à droite ; 1 parent : nom centré
- Pas de photo parent obligatoire (option profil plus tard)
## 4. Canaux (ne pas mélanger)
| Canal | Rôle |
|-------|------|
| **Cartes** | Décisions / futur proche : absences, congés, maladie AM, sorties à valider |
| **Blog** | Mémoire / actus du quotidien (texte + photos) — **indispensable**, plus un module optionnel CDC §9 |
| **Mess. AM** | Chat foyer ↔ AM (style WhatsApp : texte, emoji, images) |
| **Mess. RPE** | Privée par défaut ; ajout de participants pour médiation / conflit |
## 5. Règles métier V1
| Type | Qui initie | Validation |
|------|------------|------------|
| Absence enfant | Parent | Pas de veto AM |
| Congé AM | AM | **1 parent** accepte ou refuse |
| Maladie AM | AM | Parent accuse réception (pas de doc médical in-app) |
| Sortie | AM / RPE | **1 parent** suffit (présence / absence) ; explication aussi via blog |
- Mess. AM : les **2 parents** voient le **même** fil — **pas** de masquage V1
- Blog auteurs V1 : **AM** + **gestionnaire (RPE)** — parent auteur = plus tard
- Couple actif filtre cartes + messagerie + blog (hypothèse produit : **oui**)
## 6. Hors périmètre de ce jalon
- Page **Contrat** riche (Pajemploi, CP…) — stub bandeau OK
- **Agenda** calendrier riche — stub bandeau OK
- Notifications CDC fourre-tout (paiement, dossier…)
- Masquage messages ; posts blog parent
- Carnet repas/sieste (roadmap Phase 4)
## 7. Critères « effet démo » (acceptation)
Avec données peuplées, sur au moins 2 supports :
1. Parent et AM voient le **même contexte de garde** (couple / enfant)
2. Parent déclare une **absence** → carte visible côté AM
3. AM publie un **post blog** (texte + photo) → visible colonne blog parent
4. Gestionnaire publie une **annonce RPE** → visible sur les TdB concernés
5. Message **Mess. AM** dun côté → apparaît de lautre (près temps réel)
6. Look conforme charte papier / pastel
## 8. Point de départ code
- Parent : `frontend/lib/screens/home/parent_screen/ParentDashboardScreen.dart` + `dashbord_parent/`
- AM : `frontend/lib/screens/am/am_dashboard_screen.dart` (placeholder)
- Cartes couleurs : `frontend/assets/cards/` (7 teintes max)
## 9. Ordre de build suggéré
Voir [30_DECOUPAGE-TICKETS-QUOTIDIEN.md](./30_DECOUPAGE-TICKETS-QUOTIDIEN.md) § ordre — A (coquille parent) → C (cartes) → D (blog) → E (messagerie) → B (miroir AM) → RPE → F/G (mobile + seeds).
+29
View File
@@ -0,0 +1,29 @@
# Maquettes — espaces parent / AM (quotidien)
Références visuelles issues de latelier de cadrage (sept. 2026).
## Organisation
| Dossier | Contenu |
|---------|---------|
| [`courantes/`](./courantes/) | **Référence active** — à utiliser pour tickets / implémentation |
| [`historique/`](./historique/) | Itérations précédentes + croquis manuscrit (conservées pour traçabilité) |
## Référence active
| Fichier | Description |
|---------|-------------|
| [`courantes/maquette-dashboard-parent-quotidien-v4.png`](./courantes/maquette-dashboard-parent-quotidien-v4.png) | TdB parent PC — **3 colonnes** : cartes + couple · blog (défaut) · messagerie (AM / RPE) |
## Historique
| Fichier | Note |
|---------|------|
| [`historique/croquis-dashboard-parent-2026-09-23.jpg`](./historique/croquis-dashboard-parent-2026-09-23.jpg) | Wireframe manuscrit (WhatsApp) — point de départ layout |
| `…-v1.png` | Première génération IA (~50/50) |
| `…-v2.png` | Recadrage 1/3 + 2/3 (onglets Mess/Blog à droite) |
| `…-v3.png` | Couple enfantnounou (encore 2 colonnes) |
## Suite éventuelle
- Maquette **TdB AM** (miroir : bandeau enfant \| parent(s)) — pas encore générée.
Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 149 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

-89
View File
@@ -1,89 +0,0 @@
# Mini-spec API — POST /parents/dossier (#129)
Contrat pour le **plan front** (wizard création dossier famille staff).
Miroir de **#156** (`POST /assistantes-maternelles/dossier`).
## Endpoint
| | |
|--|--|
| **Méthode** | `POST` |
| **URL** | `{base}/parents/dossier` |
| **Auth** | Bearer JWT |
| **Rôles** | `gestionnaire`, `administrateur`, `super_admin` |
| **Content-Type** | `application/json` |
Ne **pas** appeler `POST /auth/register/parent` depuis le dashboard.
## Body (JSON)
Aligné `RegisterParentCompletDto`, **sans** CGU/privacy obligatoires (acceptées serveur).
### Parent 1 (obligatoire)
| Champ | Type | Obligatoire | Notes |
|-------|------|-------------|--------|
| `email` | string | oui | unique |
| `prenom` | string | oui | |
| `nom` | string | oui | |
| `telephone` | string | oui | `0X…` ou `+33…` |
| `adresse` | string | non | |
| `code_postal` | string | non | |
| `ville` | string | non | |
### Co-parent (optionnel)
`co_parent_email`, `co_parent_prenom`, `co_parent_nom`, `co_parent_telephone`,
`co_parent_meme_adresse`, `co_parent_adresse`, `co_parent_code_postal`, `co_parent_ville`.
Si co-parent fourni : e-mail distinct ; mêmes règles téléphone / adresse que register.
### Enfants (≥ 1)
| Champ | Type | Notes |
|-------|------|--------|
| `enfants` | `EnfantInscriptionDto[]` | `prenom`, `nom`, `date_naissance` / `date_previsionnelle_naissance`, `genre`, `photo_base64`, `photo_filename`, etc. |
### Présentation
| Champ | Type | Obligatoire |
|-------|------|-------------|
| `presentation_dossier` | string | non (max 2000) |
## Réponses
### 201 Created
```json
{
"message": "Dossier famille créé et validé. Un e-mail de création de mot de passe a été envoyé.",
"numero_dossier": "2026-000043",
"parent_user_id": "uuid-pivot",
"co_parent_user_id": "uuid-ou-null",
"statut": "actif",
"enfant_ids": ["uuid", "..."]
}
```
Effets serveur : user(s) parent **actif**, fiches `parents`, enfants + foyer, n° dossier,
**e-mail création MDP** pour chaque compte sans MDP (pas daccusé « en attente »).
### Erreurs
| Code | Cas |
|------|-----|
| 400 | Validation DTO / métier (enfants vides, dates, etc.) |
| 401 | Token manquant / invalide |
| 403 | Rôle non staff |
| 409 | Conflit e-mail (pivot et/ou co-parent) |
## Front
- `UserService.createParentDossier(body)` → cet endpoint
- Wizard create basé sur `ValidationFamilyWizard`
- Ne pas envoyer `acceptation_cgu` / `acceptation_privacy` (optionnels)
## Branche
`feature/129-creation-dossier-parent`
@@ -1,73 +0,0 @@
# Mini-spec API — POST /parents/:id/co-parent (#135)
Contrat back pour lajout dun **2ᵉ parent** sur un foyer mono-parent (staff).
## Endpoint
| | |
|--|--|
| **Méthode** | `POST` |
| **URL** | `{base}/api/v1/parents/{parentUserId}/co-parent` |
| **Auth** | Bearer JWT |
| **Rôles** | `gestionnaire`, `administrateur`, `super_admin` |
| **Succès** | **201** |
`parentUserId` = UUID du **parent pivot** (déjà dans le dossier).
Ne **pas** appeler `POST /auth/register/parent` ni `POST /parents/dossier`.
---
## Body (JSON)
| Champ | Type | Obligatoire | Notes |
|-------|------|-------------|--------|
| `email` | string | oui | unique |
| `prenom` | string | oui | |
| `nom` | string | oui | |
| `telephone` | string | oui | `0X…` ou `+33…` |
| `meme_adresse` | bool | non | défaut **true** → copie adresse du pivot |
| `adresse` | string | si `meme_adresse=false` | |
| `code_postal` | string | si `meme_adresse=false` | |
| `ville` | string | si `meme_adresse=false` | |
---
## Comportement 201
- User co-parent **actif** + token création MDP
- Fiche `parents` + liens pivot ↔ co-parent + même `numero_dossier`
- Enfants du foyer rattachés au co-parent
- E-mail **création MDP** (pas mail « en attente »)
```json
{
"message": "Co-parent ajouté au foyer. Un e-mail de création de mot de passe a été envoyé.",
"numero_dossier": "2026-000043",
"parent_user_id": "uuid-pivot",
"co_parent_user_id": "uuid-co",
"statut": "actif"
}
```
## Erreurs
| Code | Cas |
|------|-----|
| 400 | Déjà un co-parent / 2 responsables / validation adresse |
| 401 | Token invalide |
| 403 | Rôle non staff |
| 404 | Pivot introuvable |
| 409 | Email déjà pris |
## Réemploi édition identité
| Endpoint | Usage |
|----------|--------|
| `GET /dossiers/:numero` | Préremplir wizard edit |
| `PATCH /parents/:id/fiche` | Sauver identité pivot / co-parent existant |
| `PATCH /assistantes-maternelles/:id/fiche` | Édition AM |
## Branche
`feature/135-edition-dossier`
@@ -1,83 +0,0 @@
# Mini-spec front — Mode édition dossier + ajout 2ᵉ parent (#135)
Branche : `feature/135-edition-dossier`
Ticket : **#135** (full-stack)
Prérequis : **#153** (liste Dossiers) livré.
---
## Objectif
1. Clic sur un dossier (liste #153) → ouvrir le wizard en mode **`edit`**
2. Foyer **mono-parent** : page co-parent → **switch** ajouter un 2ᵉ parent
3. Sauvegarder les champs via APIs existantes + nouvel endpoint co-parent
---
## Modes wizard
| Mode | Famille | AM |
|------|---------|-----|
| `review` | déjà | déjà |
| `create` | déjà (#129) | déjà (#156) |
| **`edit`** | **à faire** | **à faire** |
Factories : `ParentDossierWizard.edit(...)` / `AmDossierWizard.edit(...)`
Préremplir via `UserService.getDossierByNumero(numero)`.
---
## APIs
| Action | Endpoint |
|--------|----------|
| Charger | `GET /dossiers/:numero` |
| Sauver parent | `PATCH /parents/:id/fiche` |
| Sauver AM | `PATCH /assistantes-maternelles/:id/fiche` |
| **Ajouter co-parent** | **`POST /parents/:pivotUserId/co-parent`** — voir `docs/tmp/135-contrat-api-ajout-co-parent.md` |
Body co-parent :
```json
{
"email": "thomas@…",
"prenom": "Thomas",
"nom": "MARTIN",
"telephone": "0678456789",
"meme_adresse": true
}
```
`UserService.addCoParent(pivotUserId, body)` → cet endpoint.
---
## UX
- Depuis `DossiersManagementWidget` / carte liste : clic → edit (plus seulement review pending)
- Pending : garder validation (review) ; dossiers actifs → edit
- Mono-parent : switch « Ajouter un co-parent » (comme create) → au save, `POST …/co-parent` si nouveau
- Déjà 2 parents : éditer les deux fiches ; pas de 3ᵉ
- Pas de bouton créer dans longlet Dossiers
---
## Hors scope
- Famille N responsables (#139)
- Suppressions (#154)
- Création dossier initial (#129 / #156)
---
## Critères dacceptation
- [ ] Clic dossier actif → wizard edit prérempli
- [ ] PATCH fiche enregistre les modifs
- [ ] Mono-parent + switch → co-parent créé (actif + mail MDP)
- [ ] review / create inchangés
## Branche
`feature/135-edition-dossier`
@@ -1,65 +0,0 @@
# Mini-spec — Suppression complète grossesse multiple / `est_multiple`
**Ticket** : **#152** — https://git.ptits-pas.fr/jmartin/petitspas/issues/152
**Branche** : `feature/152-remove-est-multiple` (depuis `develop`)
**Périmètre** : **full stack** — BDD + back + front + scripts + docs. **Aucun fantôme.**
---
## Décision
On **supprime tout**. Pas de DTO « ignorés », pas de compat payload.
`forbidNonWhitelisted: true` ⇒ back et front **partent ensemble** (même feature / même déploiement).
---
## Alias retirés
`est_multiple` · `is_multiple` · `grossesse_multiple` · `multipleBirth` · `estMultiple` · `isMultiple` · `jumeau_multiple`
---
## Back / BDD (fait sur la branche)
- [x] Migration `database/migrations/2026_drop_enfants_est_multiple.sql`
- [x] `BDD.sql`, seeds, CSV test
- [x] Entity `Children` sans colonne
- [x] DTO create/inscription/réponse/dossier famille **sans** le champ
- [x] Services auth / enfants / parents : plus de mapping
- [x] Prisma legacy `isMultiple` retiré
## Front (fait sur la branche)
- [x] Modèles admin / dossier / inscription
- [x] Payloads inscription + reprise
- [x] Modale enfant + wizard dossier famille
- [x] Step3 inscription parent
## Scripts / docs
- [x] `tests/scripts/register-parent-*.mjs`
- [x] `docs/10_DATABASE.md`, `docs/99_REGLES-CODAGE.md`
- Docs tmp/archive #112 : mentions historiques OK (archive)
---
## Déploiement
1. Appliquer la migration SQL sur la BDD vivante
2. Deploy back **et** front de cette branche
3. Smoke : création enfant staff, inscription parent, reprise, wizard famille
## Vérif
```bash
rg -n 'est_multiple|is_multiple|grossesse_multiple|multipleBirth|estMultiple|isMultiple|jumeau_multiple' \
backend/src frontend/lib database tests/scripts docs/10_DATABASE.md docs/99_REGLES-CODAGE.md
```
**0** hit (hors ce fichier mini-spec / archives).
## Hors scope
- Métier futur « fratrie / jumeaux » → nouveau ticket
- #155 rename Admin*
- Ticket modales staff
@@ -1,77 +0,0 @@
# Mini-spec API — GET /dossiers (#153)
Contrat pour le **plan front** (onglet permanent Dossiers).
## Endpoint
| | |
|--|--|
| **Méthode** | `GET` |
| **URL** | `{base}/api/v1/dossiers` |
| **Auth** | Bearer JWT |
| **Rôles** | `gestionnaire`, `administrateur`, `super_admin` |
| **Query** | `q` (optionnel) — recherche n° / libellé / email |
Complète `GET /dossiers/:numeroDossier` (#119) déjà existant.
---
## Réponse 200
Tableau de lignes (1 entrée = 1 `numero_dossier`) :
```json
[
{
"type": "famille",
"numero_dossier": "2026-000043",
"libelle": "Claire MARTIN & Thomas MARTIN",
"emails": ["claire@test.fr", "thomas@test.fr"],
"user_ids": ["uuid-pivot", "uuid-co"],
"statut": "actif",
"a_valider": false,
"date_reference": "2026-01-12T10:00:00.000Z"
},
{
"type": "assistante_maternelle",
"numero_dossier": "2026-000042",
"libelle": "Marie DUPONT",
"emails": ["marie@test.fr"],
"user_ids": ["uuid-am"],
"statut": "en_attente",
"a_valider": true,
"date_reference": "2026-02-01T08:00:00.000Z"
}
]
```
### Champs
| Champ | Notes |
|-------|--------|
| `type` | `famille` \| `assistante_maternelle` |
| `numero_dossier` | Clé dunité |
| `libelle` | Noms formatés (foyer : `A & B`) |
| `emails` / `user_ids` | Membres du foyer ou AM |
| `statut` | Agrégé : `en_attente` si au moins un user pending |
| `a_valider` | `true` si pending → section haute UI |
| `date_reference` | `MIN(cree_le)` des users |
**Tri** : `a_valider` dabord, puis `numero_dossier` décroissant.
**Famille** : dédupliquée par `numero_dossier` (pivot + co-parent = 1 ligne).
---
## Front
- `UserService.getDossiers({ q? })` → cet endpoint
- Section haute : filtrer `a_valider == true` **ou** continuer pending APIs existantes
- Section basse : liste complète (ou hors pending selon règle UX)
- Clic → `GET /dossiers/:numero` (détail) / validation review
Composition client `getParents`+`getAM` **plus nécessaire** si cet endpoint est déployé.
## Branche
`feature/153-onglet-dossiers`
-167
View File
@@ -1,167 +0,0 @@
# Mini-spec front — Onglet permanent « Dossiers » (#153)
Branche Git (front + back) : `feature/153-onglet-dossiers`
Ticket Gitea : **#153** (ticket normal, plus epic)
> Suite prévue : **#135** = au clic, mode **édition** wizard + ajout 2ᵉ parent.
> **#153** = onglet + listes + navigation / validation pending. **Pas** de création, **pas** d’édition complète.
---
## Contexte / objectif
Remplacer longlet conditionnel **« À valider »** (apparaît/disparaît selon pending) par un onglet **permanent « Dossiers »** dans le dashboard admin/gestionnaire.
Quand on ouvre **Dossiers** :
1. **En haut** — section **Dossiers à valider** (AM + familles pending)
2. **En dessous** — liste de **tous les dossiers** (familles **et** AM), 1 ligne = 1 `numero_dossier`
3. Différenciation visuelle famille vs AM : **couleur + icône**
4. **Barre de recherche** (n° dossier, nom, email…)
**Pas** de bouton « Créer un dossier » ici (création via **+ Parents** #129 / **+ Asmat** #156).
---
## UX cible
### Onglets dashboard (`UserManagementPanel`)
| Avant (#107) | Après (#153) |
|--------------|--------------|
| « À valider » **conditionnel** si pending | **« Dossiers » toujours visible** (admin + gestionnaire) |
| Contenu = seulement pending | Pending **en haut** + liste complète **en bas** |
Ordre suggéré des onglets :
`Dossiers` | `Parents` | `Enfants` | `Assistantes maternelles` | `Gestionnaires` | (`Administrateurs`)
### Section haute — À valider
- Réutiliser / adapter `PendingValidationWidget` (ou extraire la liste dans un sous-widget).
- Sources déjà branchées :
- `UserService.getPendingUsers(role: 'assistante_maternelle')`
- `UserService.getPendingFamilies()`
- Clic ligne pending → **`ValidationDossierModal`** / wizards `.review` (inchangé).
- Si section vide : ne pas afficher de gros vide ; masquer la section ou message court « Aucun dossier en attente ».
### Section basse — Tous les dossiers
1 ligne = **1 dossier** (`numero_dossier`), type :
| Type | Libellé UI | Couleur (suggestion) |
|------|------------|----------------------|
| `famille` | Famille / Parents | teinte existante parents (ex. violet / rose dashboard) |
| `assistante_maternelle` | AM | teinte existante AM (ex. teal / bleu) |
Colonnes / infos utiles (cartes style `AdminUserCard` ou lignes type pending) :
- n° dossier
- type (pastille couleur + icône)
- libellé (noms parents ou AM)
- email(s) principal(aux)
- statut user / dossier si dispo (`actif`, `en_attente`, …)
- date utile si dispo
**Déduplication** : un foyer (pivot + co-parent) = **une** ligne famille (même `numero_dossier`). Idem AM.
### Recherche
- La search bar du panel (aujourdhui désactivée / hint « pas de recherche » sur À valider) doit **filtrer la liste unifiée** (et idéalement aussi le pending affiché).
- Critères **minimum** : `numero_dossier`, nom, prénom, email.
- Harmoniser le hint : `Rechercher un dossier (n°, nom, email)…`
### État vide liste complète
Aide optionnelle : *« Pour créer un dossier → onglet Parents (+ Parents) ou Assistantes maternelles (+ Asmat) »*.
### Clic sur un dossier de la liste complète (#153)
| Cas | Comportement #153 |
|-----|-------------------|
| Pending | Ouvrir validation (review) — déjà en place |
| Dossier **actif** / non pending | Ouvrir consultation via `GET /dossiers/:numeroDossier` (`UserService.getDossierByNumero`) en **lecture / review** si possible **sans** save édition |
**Ne pas** implémenter le mode `edit` ni le switch 2ᵉ parent → **#135**.
Si louverture « review » dun dossier actif est trop lourde pour ce ticket : clic peut temporairement no-op / snackbar *« Édition dossier : prochainement (#135) »***à éviter** si `getDossierByNumero` + wizard review marche déjà pour les deux types.
---
## Données / APIs (front)
### Déjà disponibles (préférer composer côté front pour #153)
| Besoin | API / service |
|--------|----------------|
| Pending AM | `getPendingUsers(role: assistante_maternelle)` |
| Pending familles | `getPendingFamilies()` |
| Parents (avec `numero_dossier`) | `getParents()` |
| AM (avec `numero_dossier`) | `getAssistantesMaternelles()` |
| Détail unifié | `getDossierByNumero(numero)``GET /dossiers/:numeroDossier` |
**Pas dendpoint `GET /dossiers` liste** aujourdhui. Pour #153 :
- Construire la liste unifiée **côté client** à partir de `getParents()` + `getAssistantesMaternelles()` (group by `numero_dossier`).
- Exclure ou marquer les pending déjà dans la section haute (éviter doublons visuels, ou les laisser dans les deux avec badge « à valider » — **préférence** : pending **uniquement** en haut ; liste basse = tous **hors** pending **ou** tous avec badge ; choisir une règle claire et documenter dans le PR).
**Règle recommandée** :
- Haut = pending only
- Bas = **tous** les dossiers ayant un `numero_dossier` (y compris pending) **OU** bas = non-pending only
**Recommandation produit** : bas = **tous** (vision complète), pending aussi en haut pour action rapide. Si doublon gênant : bas = non-pending only.
### Si le back ajoute plus tard `GET /dossiers`
Brancher `UserService.getDossiers()` — hors scope bloquant #153 front si composition client OK.
---
## Fichiers front probables
| Fichier | Rôle |
|---------|------|
| `frontend/lib/widgets/admin/user_management_panel.dart` | Onglet permanent **Dossiers** ; retirer logique conditionnelle À valider ; search sur cet onglet |
| `frontend/lib/widgets/admin/pending_validation_widget.dart` | Réemploi section haute (ou refactor léger) |
| **Nouveau** `…/dossiers_management_widget.dart` (nom libre) | Shell onglet : pending + liste unifiée + refresh |
| **Nouveau** modèle léger `DossierListItem` (type, numero, libelle, emails, statut…) | Mapping parents/AM → ligne |
| `user_service.dart` / `api_config.dart` | Seulement si helper `getDossiersUnified()` côté client (pas forcément nouvel endpoint) |
| `validation_dossier_modal.dart` | Réemploi ouverture pending / détail |
Réutiliser look & feel cartes / hover « Ouvrir » de `_PendingValidationRow` / `AdminUserCard`.
---
## Hors scope (#153)
- Bouton créer dossier
- Mode `edit` wizard + ajout 2ᵉ parent → **#135**
- Suppressions → **#154**
- Famille N responsables → **#139**
- Changer les onglets Parents / AM / Enfants (restent)
---
## Critères dacceptation front
- [ ] Onglet **Dossiers** toujours visible (même 0 pending)
- [ ] Plus donglet conditionnel **« À valider »**
- [ ] Section haute pending si non vide ; validation au clic OK
- [ ] Liste unifiée familles + AM en dessous ; 1 ligne / `numero_dossier`
- [ ] Couleur + icône différencient famille / AM
- [ ] Recherche filtre (n° + nom + email minimum)
- [ ] **Aucun** bouton créer dans cet onglet
- [ ] Pas de régression validation pending (valider / refuser)
---
## Back (info — Cursor back séparé si besoin)
- Liste unifiée : **pas bloquante** si composition front
- Optionnel : `GET /api/v1/dossiers` (liste) pour perf / pagination plus tard
- `GET /dossiers/:numero` déjà là (#119)
---
## Branche
`feature/153-onglet-dossiers` (depuis `develop`)
-35
View File
@@ -1,35 +0,0 @@
# Matrice suppression — #154 / back **#159** / front **#160**
**Statut** : cadrage PO validé (sept. 2026)
**Milestone** : 0.1.0
**Email** : pas demail de suppression (cas rare)
## Droits
| Cible | Qui peut supprimer |
|-------|-------------------|
| Dossier / parent / enfant / AM | `GESTIONNAIRE`, `ADMINISTRATEUR`, `SUPER_ADMIN` |
| Gestionnaire (user) | `ADMINISTRATEUR`, `SUPER_ADMIN` |
| Administrateur (user) | Autre admin OK ; **self interdit** ; **dernier admin** = `SUPER_ADMIN` only ; `SUPER_ADMIN` non supprimable |
## Matrice métier
| Point dentrée | Action | Effet |
|----------------|--------|--------|
| Dossiers | Delete dossier **famille** | Tous **parents** + tous **enfants** ; clore placements AM des enfants |
| Dossiers / AM | Delete dossier **AM** ou compte AM | **Compte AM + dossier AM** ; enfants **conservés** ; placements **clos** |
| Parents | Co-parent (autre parent reste) | Compte parent seul ; dossier + enfants restent |
| Parents | Dernier parent | Parent + **enfants** rattachés |
| Enfants | Pas dernier | Enfant seul (retiré du dossier) |
| Enfants | Dernier + `deleteDossier=true` | Cascade dossier famille (parents + enfants) |
| Enfants | Dernier + `deleteDossier=false` | Enfant seul ; dossier peut apparaître **`sans_enfant`** |
| Pending / validé | — | **Mêmes règles** (pas de différenciation) |
## Warning
- `sans_enfant` sur liste `GET /dossiers` (dossier famille sans enfant lié).
- Miroir de `sans_responsable` (#157) côté enfants.
## Hors scope
Soft-delete RGPD, audit (#128), famille N (#139), restriction admin-only métier (plus tard).
-169
View File
@@ -1,169 +0,0 @@
# Mini-spec front — Suppressions dashboard
**Ticket front** : **#160** — https://git.ptits-pas.fr/jmartin/petitspas/issues/160
**Ticket back** : **#159** — https://git.ptits-pas.fr/jmartin/petitspas/issues/159
**Epic** : #154 (complète #133)
**Branche back** : `feature/159-suppressions-backend`
**Doc matrice** : [154-matrice-suppression.md](./154-matrice-suppression.md)
Travail **en parallèle** : ce contrat est la source de vérité UI ↔ API.
---
## UX commune
Sur chaque ligne / carte des listes :
- Icône **poubelle** en bout de ligne
- Clic → **dialog de confirmation** (texte dimpact) → DELETE → **refresh** liste
- Pending = **mêmes** règles que validés
- **Pas** demail
| Liste | Poubelle visible si |
|-------|---------------------|
| Dossiers, Parents, Enfants, AM | gestionnaire **ou** admin |
| Gestionnaires | **admin** only |
| Administrateurs | admin+ ; **pas** sur sa propre ligne ; dernier admin : UI warning + réservé super_admin |
---
## Contrat API
Base : auth Bearer. Erreurs : `400` / `403` / `404` / `409` avec `message` FR.
### 1. `DELETE /dossiers/:numeroDossier`
- **Famille** → supprime tous parents + enfants du n° ; clos placements AM des enfants.
- **AM** → compte AM + dossier AM ; enfants gardés ; placements clos.
**Réponse 200** (exemple) :
```json
{
"type": "famille",
"numero_dossier": "2026-000043",
"deleted_user_ids": ["…"],
"deleted_enfant_ids": ["…"],
"message": "Dossier famille supprimé."
}
```
ou
```json
{
"type": "assistante_maternelle",
"numero_dossier": "2026-000015",
"deleted_user_ids": ["…"],
"deleted_enfant_ids": [],
"message": "Dossier assistante maternelle supprimé."
}
```
**Dialog UI** : lister libellé + n° + « X parent(s), Y enfant(s) » (ou « compte AM, enfants conservés »).
---
### 2. `DELETE /users/:id`
Comportement selon le **rôle** de la cible :
| Cible | Effet |
|-------|--------|
| Parent **co-parent** | Delete ce user seul |
| Parent **dernier** du dossier | Delete user + enfants du foyer |
| AM | Delete user AM + dossier AM ; enfants conservés ; placements clos |
| Gestionnaire | Admin only ; self → 403 |
| Administrateur | Self → 403 ; dernier admin → super_admin only sinon 403 ; super_admin → 403 |
**Réponse 200** :
```json
{
"deleted_user_ids": ["…"],
"deleted_enfant_ids": ["…"],
"message": "…"
}
```
**Dialogs** :
- Co-parent : « Ce parent sera retiré / supprimé du dossier {n°}. Les enfants restent avec le co-parent. »
- Dernier parent : « Dernier parent du dossier {n°}. Les enfants rattachés seront aussi supprimés. »
- AM : « Le compte et le dossier AM seront supprimés. Les enfants accueillis ne seront pas supprimés. »
Optionnel (si exposé) : `GET /users/:id/suppression-impact` — sinon calculer depuis données déjà en liste / détail dossier.
---
### 3. `DELETE /enfants/:id?deleteDossier=true|false`
- Pas dernier enfant → delete enfant (`deleteDossier` ignoré ou false).
- Dernier enfant + `deleteDossier=false` → delete enfant ; dossier famille peut passer `sans_enfant`.
- Dernier enfant + `deleteDossier=true` → cascade dossier famille (parents + enfants).
**Réponse 200** :
```json
{
"deleted_enfant_ids": ["…"],
"deleted_user_ids": ["…"],
"dossier_supprime": false,
"message": "…"
}
```
**Dialog** :
- Standard : « Lenfant sera supprimé du dossier de {famille} ({n°}). »
- Dernier : proposer **deux actions** :
1. Supprimer lenfant seulement (`deleteDossier=false`)
2. Supprimer aussi le dossier / parents (`deleteDossier=true`)
Pour savoir si dernier : compter enfants du `numero_dossier` (détail dossier ou champ impact API).
---
### 4. `GET /dossiers` — flag `sans_enfant`
Chaque item famille peut exposer :
```json
"sans_enfant": true
```
- `true` si dossier **famille** sans enfant lié.
- AM : `false` ou omis.
**UI** : badge / warning vigilance (comme `sans_responsable` / alertes AM).
---
## UserService (Flutter) — signatures cibles
```dart
Future<void> deleteDossier(String numeroDossier);
Future<Map<String, dynamic>> deleteUser(String userId);
Future<Map<String, dynamic>> deleteEnfant(String enfantId, {bool deleteDossier = false});
```
(Adapter le parsing au JSON réel une fois le back mergé ; en parallèle, stubber sur ce contrat.)
---
## Fichiers front probables
- Cartes listes : `admin_user_card.dart`, `admin_enfant_user_card.dart`, cartes dossiers
- Listes : `dossiers_management_widget.dart`, `parent_managmant_widget.dart`, `enfant_management_widget.dart`, `assistante_maternelle_management_widget.dart`, `gestionnaire_management_widget.dart`, `admin_management_widget.dart`
- `user_service.dart`
---
## Critères front (#160)
- [ ] Poubelle selon droits
- [ ] Confirmations avec impact
- [ ] Refresh après succès
- [ ] Dernier enfant : choix dossier oui/non
- [ ] Warning `sans_enfant`
- [ ] Self-admin / dernier admin gérés côté UI (masquer ou message 403)
@@ -1,54 +0,0 @@
# Mini-spec — Rename préfixe `Admin*` dashboard partagé (#155)
**Ticket** : **#155**
**Branche** : `feature/155-rename-admin-prefix-dashboard` (depuis `develop`)
**Décision naming** : **option C** — dossier neutre `widgets/dashboard/` + noms **sans** préfixe `Admin`.
---
## Principe
Les widgets partagés **admin + gestionnaire** ne doivent plus sappeler `Admin*`.
On garde `Admin*` seulement là où cest vraiment le rôle administrateur.
## Gardé `Admin*` (hors rename)
| Élément | Raison |
|---------|--------|
| `AdminManagementWidget` | Onglet **Administrateurs** |
| `screens/administrateurs/*` | `AdminDashboardScreen`, `AdminCreateDialog`, `AdminUserFormDialog` |
| `EnfantAdminModel` | Modèle API (pas un widget) — hors scope ticket |
## Renames faits
| Avant | Après |
|-------|--------|
| `widgets/admin/common/admin_child_detail_modal.dart``AdminChildDetailModal` | `widgets/dashboard/child_detail_modal.dart``ChildDetailModal` |
| `admin_am_edit_modal``AdminAmEditModal` | `am_edit_modal``AmEditModal` |
| `admin_parent_edit_modal``AdminParentEditModal` | `parent_edit_modal``ParentEditModal` |
| `admin_user_card``AdminUserCard` | `user_card``UserCard` |
| `admin_enfant_user_card``AdminEnfantUserCard` | `enfant_user_card``EnfantUserCard` |
| `admin_am_photo_frame``AdminAmPhotoFrame` | `am_photo_frame``AmPhotoFrame` |
| `admin_am_children_capacity_grid` | `am_children_capacity_grid``AmChildrenCapacityGrid` |
| `admin_children_affiliation_panel` | `children_affiliation_panel``ChildrenAffiliationPanel` |
| `admin_select_*` / `AdminSelect*` / `AdminFamilleFoyer` | `select_*` / `Select*` / `FamilleFoyer` |
| `admin_status_capsule` | `status_capsule``StatusCapsule` |
| `admin_list_state``AdminListState` | `user_list_state``UserListState` |
| `admin_detail_modal``AdminDetailModal` / `AdminDetailField` | `detail_modal``DetailModal` / `DetailField` |
| `dashboard_admin.dart` | `user_management_sub_bar.dart` (`DashboardUserManagementSubBar` inchangé) |
Dossier `widgets/admin/` conserve encore les panels métier (`user_management_panel`, wizards, etc.) + `AdminManagementWidget`.
**Phase 2** (même ticket #155) : déplacer ces panels → `widgets/dashboard/` — voir [155-suite-move-admin-panels-to-dashboard.md](./155-suite-move-admin-panels-to-dashboard.md).
## Hors scope
- Refonte UX modales (ticket dédié)
- Rename API / back
- Déplacer tout `widgets/admin/``widgets/dashboard/` (panels) — possible follow-up
## Critères
- [x] Plus de préfixe `Admin` sur les composants **partagés** listés
- [ ] Build Flutter / recette dashboard admin + gestionnaire OK
- [x] Pas de changement comportemental (rename mécanique)
@@ -1,203 +0,0 @@
# Mini-spec — Déplacer les panels `widgets/admin/` → `widgets/dashboard/`
**Ticket** : **#155** (phase 2 — même ticket que le rename `Admin*`)
**Phase 1** : widgets `Admin*``widgets/dashboard/` (déjà sur `feature/155-rename-admin-prefix-dashboard`)
**Branche** : poursuivre / rebaser `feature/155-rename-admin-prefix-dashboard` (ou nouvelle branche depuis `develop` après merge phase 1)
**Nature** : rename / move mécanique — **zéro** changement UX / métier
---
## Contexte
Après la phase 1 (#155), la situation est **hybride** :
| Emplacement | Contenu |
|-------------|---------|
| `widgets/dashboard/` | Composants partagés sans préfixe `Admin*` (modales, cartes, selects, sub-bar…) |
| `widgets/admin/` | **Panels** du dashboard staff (listes, wizards, validation, shell `UserManagementPanel`…) + `AdminManagementWidget` |
Le dossier `admin/` laisse encore croire « réservé administrateur », alors que **gestionnaire** consomme les mêmes panels (`GestionnaireDashboardScreen``UserManagementPanel`).
Ce ticket **termine loption C** au niveau dossier : tout le dashboard staff vit sous `widgets/dashboard/`, sauf ce qui est **vraiment** rôle admin.
---
## Objectif
```
frontend/lib/widgets/admin/<panels & common partagés>
↓ git mv + update imports
frontend/lib/widgets/dashboard/…
```
Critère : un nouveau dev ne doit plus ouvrir `widgets/admin/` pour du code partagé admin+gestionnaire.
---
## Cible darborescence (proposée)
```
widgets/dashboard/
├── (déjà là #155) child_detail_modal.dart, am_edit_modal.dart, user_card.dart, …
├── user_management_panel.dart ← shell onglets
├── user_management_sub_bar.dart ← déjà déplacé #155
├── dossiers_management_widget.dart
├── dossier_list_card.dart
├── parent_management_widget.dart ← corriger le typo managmant au passage ?
├── enfant_management_widget.dart
├── assistante_maternelle_management_widget.dart
├── gestionnaire_management_widget.dart
├── pending_validation_widget.dart
├── parent_dossier_create_modal.dart
├── parent_dossier_wizard.dart
├── am_dossier_create_modal.dart
├── am_dossier_wizard.dart
├── validation_*.dart ← family/am wizards, refus, theme, confirm
├── parametres_panel.dart ← utilisé par écran admin (OK dans dashboard)
├── relais_management_panel.dart
├── common/ ← sous-dossier optionnel
│ ├── suppression_confirm_dialog.dart
│ ├── user_list.dart
│ └── validation_detail_section.dart
└── …
widgets/admin/ ← mince, rôle admin seulement
└── admin_management_widget.dart ← onglet Administrateurs
```
### Variante B (plus stricte)
`AdminManagementWidget` + éventuels helpers purement admin →
`screens/administrateurs/widgets/`
et **suppression** du dossier `widgets/admin/`.
**Reco** : **variante A** (garder `widgets/admin/` minimal avec seulement `AdminManagementWidget`) — moins de churn screens, clair.
---
## Inventaire à déplacer (état actuel)
### Racine `widgets/admin/` → `widgets/dashboard/`
| Fichier actuel | Notes |
|----------------|--------|
| `user_management_panel.dart` | Shell partagé admin + gestionnaire |
| `dossiers_management_widget.dart` | |
| `dossier_list_card.dart` | |
| `parent_managmant_widget.dart` | Typo historique `managmant`**option** : renommer → `parent_management_widget.dart` dans le même ticket ou ticket typo séparé |
| `enfant_management_widget.dart` | |
| `assistante_maternelle_management_widget.dart` | |
| `gestionnaire_management_widget.dart` | |
| `pending_validation_widget.dart` | |
| `parent_dossier_create_modal.dart` | |
| `parent_dossier_wizard.dart` | |
| `am_dossier_create_modal.dart` | |
| `am_dossier_wizard.dart` | |
| `validation_am_wizard.dart` | |
| `validation_family_wizard.dart` | |
| `validation_dossier_modal.dart` | |
| `validation_modal_theme.dart` | |
| `validation_refus_form.dart` | |
| `validation_valider_confirm_dialog.dart` | |
| `parametres_panel.dart` | Écran admin seulement, mais pas préfixé Admin — OK dashboard |
| `relais_management_panel.dart` | |
### `widgets/admin/common/` → `widgets/dashboard/common/` (ou plat)
| Fichier | Notes |
|---------|--------|
| `suppression_confirm_dialog.dart` | Partagé (y compris `screens/administrateurs/creation/*`) |
| `user_list.dart` | |
| `validation_detail_section.dart` | |
### **Ne pas** déplacer
| Fichier | Destination |
|---------|-------------|
| `admin_management_widget.dart` | Reste `widgets/admin/` (ou variante B → screens) |
### Déjà fait (#155) — ne pas retraiter
Tout ce qui est déjà sous `widgets/dashboard/` (`child_detail_modal`, `am_edit_modal`, `user_card`, `select_*`, `user_management_sub_bar`, …).
---
## Consommateurs dimports (à mettre à jour)
### Screens
- `screens/administrateurs/admin_dashboardScreen.dart``UserManagementPanel`, `ParametresPanel`
- `screens/gestionnaire/gestionnaire_dashboard_screen.dart``UserManagementPanel`
- `screens/administrateurs/creation/admin_create.dart``suppression_confirm_dialog`
- `screens/administrateurs/creation/gestionnaires_create.dart` — idem
### Widgets déjà en `dashboard/`
- `am_edit_modal`, `child_detail_modal`, `parent_edit_modal`, `select_*` — imports vers `widgets/admin/common/*` ou panels
### Divers
- `widgets/common/identity_block.dart` (si import admin)
- Tous les fichiers **déplacés** entre eux (imports relatifs / package)
### Hors scope rename classes
Sauf décision explicite sur le typo `parent_managmant_widget` → pas de rename de **classes** métier dans ce ticket (seulement chemins de fichiers + imports).
`AdminManagementWidget` **conserve** son nom.
---
## Plan dexécution
1. Partir de `feature/155-rename-admin-prefix-dashboard` (phase 1) **ou** `develop` si phase 1 déjà mergée
2. `git mv` fichiers selon inventaire
3. Remplacer globalement
`package:p_tits_pas/widgets/admin/``package:p_tits_pas/widgets/dashboard/`
**sauf** `…/widgets/admin/admin_management_widget.dart`
4. Corriger imports relatifs cassés
5. Grep de contrôle (ci-dessous)
6. Build Flutter web (Docker) + smoke dashboard admin **et** gestionnaire
7. Merge → squash master si flux habituel
---
## Vérifs
```bash
# Plus de panels partagés sous admin (seul AdminManagement attendu)
find frontend/lib/widgets/admin -name '*.dart'
# Plus dimports panels vers lancien chemin (sauf AdminManagement)
rg -n "widgets/admin/(user_management|dossiers_|parent_|enfant_|assistante|gestionnaire|pending|validation_|parametres|relais|am_dossier|parent_dossier|dossier_list|common/)" frontend/lib
# Screens OK
rg -n "widgets/admin/" frontend/lib/screens
```
Attendu screens : **0** hit vers panels ; éventuellement plus aucun hit `widgets/admin/` sauf si import explicite `AdminManagementWidget` depuis `user_management_panel` (chemin `widgets/admin/admin_management_widget.dart`).
---
## Hors scope
- Refonte UX des modales / panels (ticket dédié annoncé)
- Rename `EnfantAdminModel`
- Rename `screens/administrateurs/`
- Rename `AdminUserFormDialog` / `AdminCreateDialog`
- Changement API / back
- #152 (`est_multiple`) — autre branche
---
## Critères dacceptation
- [ ] Inventaire déplacé selon tableau
- [ ] `widgets/admin/` ne contient plus que `admin_management_widget.dart` (variante A)
- [ ] Imports screens + widgets à jour
- [ ] Build Flutter OK
- [ ] Recette : dashboard **administrateur** et **gestionnaire** (listes, ouverture fiches, validation, création dossier) sans régression
- [ ] Aucun changement comportemental volontaire
---
## Risques / notes
- **Conflits de merge** si dautres features touchent les panels → faire ce ticket quand la surface dashboard est calme (fin 0.1.0 OK)
- Typo `parent_managmant_widget` : soit inclus (bonus), soit ticket cleanup 1-ligne séparé
- Docs darchive citant `widgets/admin/…` : pas obligatoire de mettre à jour ; `docs/27_BRIEFING-FRONTEND.md` oui si encore listé
-75
View File
@@ -1,75 +0,0 @@
# Mini-spec API — POST /assistantes-maternelles/dossier (#156)
Contrat pour le **plan front** (wizard création AM staff).
## Endpoint
| | |
|--|--|
| **Méthode** | `POST` |
| **URL** | `{base}/assistantes-maternelles/dossier` |
| **Auth** | Bearer JWT |
| **Rôles** | `gestionnaire`, `administrateur`, `super_admin` |
| **Content-Type** | `application/json` |
Ne **pas** appeler `POST /auth/register/am` depuis le dashboard.
## Body (JSON)
Aligné inscription AM publique, **sans** CGU/privacy obligatoires (acceptées serveur).
| Champ | Type | Obligatoire | Notes |
|-------|------|-------------|--------|
| `email` | string | oui | unique |
| `prenom` | string | oui | |
| `nom` | string | oui | |
| `telephone` | string | oui | `0X…` ou `+33…` |
| `adresse` | string | non | |
| `code_postal` | string | non | |
| `ville` | string | non | |
| `photo_base64` | string | non | data-URL `data:image/…;base64,…` |
| `photo_filename` | string | non | hint nom fichier |
| `consentement_photo` | bool | oui | |
| `date_naissance` | date ISO | non | `YYYY-MM-DD` |
| `lieu_naissance_ville` | string | oui | |
| `lieu_naissance_pays` | string | oui | |
| `nir` | string | oui | 15 car. (Corse 2A/2B OK) |
| `numero_agrement` | string | oui | unique |
| `date_agrement` | date ISO | non | |
| `capacite_accueil` | int | oui | 110 |
| `places_disponibles` | int | oui | 010, ≤ capacité |
| `biographie` | string | non | max 2000 |
## Réponses
### 201 Created
```json
{
"message": "Dossier AM créé et validé. Un e-mail de création de mot de passe a été envoyé.",
"user_id": "uuid",
"statut": "actif",
"numero_dossier": "2026-000042"
}
```
Effets serveur : user AM **actif**, fiche `assistantes_maternelles`, n° dossier, **e-mail création MDP** (pas daccusé « en attente »).
### Erreurs
| Code | Cas |
|------|-----|
| 400 | Validation / NIR / places > capacité |
| 403 | Rôle non staff |
| 409 | Email, NIR ou agrément déjà pris |
| 401 | Token manquant / invalide |
## Front
- `UserService.createAmDossier(body)` → cet endpoint
- Après 201 : refresh liste AM ; snackbar OK
- Wizard create : ne pas envoyer `acceptation_cgu` / `acceptation_privacy` (optionnels)
## Branche
`feature/156-creation-dossier-am`
@@ -1,14 +0,0 @@
# Mini-spec — Uniformisation modale staff (Gestionnaire / Administrateur)
**Ticket** : **#164** — https://git.ptits-pas.fr/jmartin/petitspas/issues/164
**Branche** : `feature/164-staff-modal-uniformisation` (depuis `develop`)
**Périmètre** : **front only** — pas dAPI / BDD
**Milestone** : **0.1.0**
Voir le corps du ticket #164 pour la spec complète.
## Livré
- `frontend/lib/widgets/dashboard/staff_user_form_modal.dart``StaffUserFormModal`
- Ancien `AdminUserFormDialog` / `gestionnaires_create.dart` retiré
- Imports : `user_management_panel`, `gestionnaire_management_widget`, `admin_management_widget`