From da71803852b1d4fa17848fa7f7645c1c892c483a Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Thu, 24 Sep 2026 12:14:16 +0200 Subject: [PATCH] feat(#171): API couples-garde AM (liste + courant) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symétrique #168 : GET/PUT /assistantes-maternelles/me/couples-garde avec parents[] pour le bandeau enfant|foyer. Colonne id_placement_garde_courant sur assistantes_maternelles. Co-authored-by: Cursor --- .../assistantes_maternelles.entity.ts | 22 ++- ...assistantes_maternelles.controller.spec.ts | 30 +++- .../assistantes_maternelles.controller.ts | 33 ++++ .../assistantes_maternelles.module.ts | 14 +- .../assistantes_maternelles.service.spec.ts | 144 +++++++++++++++++- .../assistantes_maternelles.service.ts | 109 ++++++++++++- .../dto/couples-garde-am.dto.ts | 67 ++++++++ .../definir-couple-garde-courant-am.dto.ts | 13 ++ database/BDD.sql | 15 +- .../2026_am_placement_garde_courant.sql | 10 ++ 10 files changed, 439 insertions(+), 18 deletions(-) create mode 100644 backend/src/routes/assistantes_maternelles/dto/couples-garde-am.dto.ts create mode 100644 backend/src/routes/assistantes_maternelles/dto/definir-couple-garde-courant-am.dto.ts create mode 100644 database/migrations/2026_am_placement_garde_courant.sql diff --git a/backend/src/entities/assistantes_maternelles.entity.ts b/backend/src/entities/assistantes_maternelles.entity.ts index 65d46b8..24d57f8 100644 --- a/backend/src/entities/assistantes_maternelles.entity.ts +++ b/backend/src/entities/assistantes_maternelles.entity.ts @@ -1,4 +1,4 @@ -import { Entity, PrimaryColumn, Column, OneToOne, OneToMany, JoinColumn } from 'typeorm'; +import { Entity, PrimaryColumn, Column, OneToOne, OneToMany, JoinColumn, ManyToOne } from 'typeorm'; import { Users } from './users.entity'; import { AmChildren } from './am_children.entity'; @@ -37,22 +37,32 @@ export class AssistanteMaternelle { @Column({ name: 'ville_residence', length: 100, nullable: true }) residence_city?: string; - @Column( { name: 'date_agrement', type: 'date', nullable: true }) + @Column({ name: 'date_agrement', type: 'date', nullable: true }) agreement_date?: Date; - @Column( { name: 'annee_experience', type: 'smallint', nullable: true }) + @Column({ name: 'annee_experience', type: 'smallint', nullable: true }) years_experience?: number; - - @Column( { name: 'specialite', length: 100, nullable: true }) + + @Column({ name: 'specialite', length: 100, nullable: true }) specialty?: string; - @Column( { name: 'place_disponible', type: 'integer', nullable: true }) + @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; + /** + * Placement AM↔enfant sélectionné sur le TdB AM (couple actif) — ticket #171. + */ + @Column({ name: 'id_placement_garde_courant', type: 'uuid', nullable: true }) + id_placement_garde_courant?: string; + + @ManyToOne(() => AmChildren, { nullable: true, onDelete: 'SET NULL' }) + @JoinColumn({ name: 'id_placement_garde_courant', referencedColumnName: 'id' }) + placement_garde_courant?: AmChildren; + @OneToMany(() => AmChildren, (ac) => ac.am) amChildren: AmChildren[]; } diff --git a/backend/src/routes/assistantes_maternelles/assistantes_maternelles.controller.spec.ts b/backend/src/routes/assistantes_maternelles/assistantes_maternelles.controller.spec.ts index 5a7322d..0216da3 100644 --- a/backend/src/routes/assistantes_maternelles/assistantes_maternelles.controller.spec.ts +++ b/backend/src/routes/assistantes_maternelles/assistantes_maternelles.controller.spec.ts @@ -10,7 +10,10 @@ describe('AssistantesMaternellesController', () => { const authServiceMock = { createAmDossierStaff: jest.fn(), }; - const amServiceMock = {}; + const amServiceMock = { + listerCouplesGarde: jest.fn(), + definirCoupleGardeCourant: jest.fn(), + }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -66,4 +69,29 @@ describe('AssistantesMaternellesController', () => { ); expect(res.numero_dossier).toBe('2026-000001'); }); + + it('listerCouplesGarde délègue au service (#171)', async () => { + amServiceMock.listerCouplesGarde.mockResolvedValue({ + couples: [], + couple_courant_id: null, + }); + const res = await controller.listerCouplesGarde('am-uuid'); + expect(amServiceMock.listerCouplesGarde).toHaveBeenCalledWith('am-uuid'); + expect(res.couple_courant_id).toBeNull(); + }); + + it('definirCoupleGardeCourant délègue au service (#171)', async () => { + amServiceMock.definirCoupleGardeCourant.mockResolvedValue({ + couples: [{ id: 'pl-1', courant: true }], + couple_courant_id: 'pl-1', + }); + const res = await controller.definirCoupleGardeCourant('am-uuid', { + couple_id: 'pl-1', + }); + expect(amServiceMock.definirCoupleGardeCourant).toHaveBeenCalledWith( + 'am-uuid', + 'pl-1', + ); + expect(res.couple_courant_id).toBe('pl-1'); + }); }); diff --git a/backend/src/routes/assistantes_maternelles/assistantes_maternelles.controller.ts b/backend/src/routes/assistantes_maternelles/assistantes_maternelles.controller.ts index 7d5609e..8442c70 100644 --- a/backend/src/routes/assistantes_maternelles/assistantes_maternelles.controller.ts +++ b/backend/src/routes/assistantes_maternelles/assistantes_maternelles.controller.ts @@ -2,6 +2,7 @@ import { Controller, Get, Post, + Put, Body, Patch, Param, @@ -20,6 +21,8 @@ import { UpdateAssistanteDto } from '../user/dto/update_assistante.dto'; import { UpdateAmFicheAdminDto } from './dto/update-am-fiche-admin.dto'; import { StaffCreateAmDossierDto } from './dto/staff-create-am-dossier.dto'; import { StaffCreateAmDossierResponseDto } from './dto/staff-create-am-dossier-response.dto'; +import { CouplesGardeAmResponseDto } from './dto/couples-garde-am.dto'; +import { DefinirCoupleGardeCourantAmDto } from './dto/definir-couple-garde-courant-am.dto'; import { RolesGuard } from 'src/common/guards/roles.guard'; import { AuthGuard } from 'src/common/guards/auth.guard'; import { User } from 'src/common/decorators/user.decorator'; @@ -83,6 +86,36 @@ export class AssistantesMaternellesController { return mapAmsForApi(ams); } + @Get('me/couples-garde') + @Roles(RoleType.ASSISTANTE_MATERNELLE) + @ApiOperation({ + summary: 'Couples de garde de l’AM connectée — ticket #171', + description: + 'Placements actifs (enfant + parents) pour peupler le bandeau couple TdB AM, ' + + 'avec indication du couple courant.', + }) + @ApiResponse({ status: 200, type: CouplesGardeAmResponseDto }) + listerCouplesGarde( + @User('id') userId: string, + ): Promise { + return this.assistantesMaternellesService.listerCouplesGarde(userId); + } + + @Put('me/couples-garde/courant') + @Roles(RoleType.ASSISTANTE_MATERNELLE) + @ApiOperation({ summary: 'Définir le couple de garde courant (AM) — ticket #171' }) + @ApiBody({ type: DefinirCoupleGardeCourantAmDto }) + @ApiResponse({ status: 200, type: CouplesGardeAmResponseDto }) + definirCoupleGardeCourant( + @User('id') userId: string, + @Body() dto: DefinirCoupleGardeCourantAmDto, + ): Promise { + return this.assistantesMaternellesService.definirCoupleGardeCourant( + userId, + dto.couple_id, + ); + } + @Roles(RoleType.SUPER_ADMIN, RoleType.GESTIONNAIRE, RoleType.ADMINISTRATEUR) @Get(':id') @ApiParam({ name: 'id', description: "UUID de la nounou" }) diff --git a/backend/src/routes/assistantes_maternelles/assistantes_maternelles.module.ts b/backend/src/routes/assistantes_maternelles/assistantes_maternelles.module.ts index 54a10ae..4f24c8f 100644 --- a/backend/src/routes/assistantes_maternelles/assistantes_maternelles.module.ts +++ b/backend/src/routes/assistantes_maternelles/assistantes_maternelles.module.ts @@ -4,13 +4,21 @@ import { AssistantesMaternellesController } from './assistantes_maternelles.cont import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity'; import { AmChildren } from 'src/entities/am_children.entity'; import { Children } from 'src/entities/children.entity'; +import { ParentsChildren } from 'src/entities/parents_children.entity'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Users } from 'src/entities/users.entity'; import { AuthModule } from '../auth/auth.module'; @Module({ - imports: [TypeOrmModule.forFeature([AssistanteMaternelle, AmChildren, Children, Users]), - AuthModule + imports: [ + TypeOrmModule.forFeature([ + AssistanteMaternelle, + AmChildren, + Children, + Users, + ParentsChildren, + ]), + AuthModule, ], controllers: [AssistantesMaternellesController], providers: [AssistantesMaternellesService], @@ -19,4 +27,4 @@ import { AuthModule } from '../auth/auth.module'; TypeOrmModule, ], }) -export class AssistantesMaternellesModule { } +export class AssistantesMaternellesModule {} diff --git a/backend/src/routes/assistantes_maternelles/assistantes_maternelles.service.spec.ts b/backend/src/routes/assistantes_maternelles/assistantes_maternelles.service.spec.ts index d4d7d5e..3302136 100644 --- a/backend/src/routes/assistantes_maternelles/assistantes_maternelles.service.spec.ts +++ b/backend/src/routes/assistantes_maternelles/assistantes_maternelles.service.spec.ts @@ -1,18 +1,150 @@ +import { NotFoundException } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; import { AssistantesMaternellesService } from './assistantes_maternelles.service'; +import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity'; +import { Users } from 'src/entities/users.entity'; +import { AmChildren } from 'src/entities/am_children.entity'; +import { Children } from 'src/entities/children.entity'; +import { ParentsChildren } from 'src/entities/parents_children.entity'; -describe('AssistantesMaternellesService', () => { +describe('AssistantesMaternellesService — couples de garde (#171)', () => { let service: AssistantesMaternellesService; - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [AssistantesMaternellesService], - }).compile(); + const amRepo = { findOne: jest.fn(), update: jest.fn() }; + const usersRepo = {}; + const amChildrenRepo = { find: jest.fn(), findOne: jest.fn() }; + const childrenRepo = {}; + const parentsChildrenRepo = { find: jest.fn() }; - service = module.get(AssistantesMaternellesService); + beforeEach(async () => { + jest.clearAllMocks(); + const module: TestingModule = await Test.createTestingModule({ + providers: [ + AssistantesMaternellesService, + { provide: getRepositoryToken(AssistanteMaternelle), useValue: amRepo }, + { provide: getRepositoryToken(Users), useValue: usersRepo }, + { provide: getRepositoryToken(AmChildren), useValue: amChildrenRepo }, + { provide: getRepositoryToken(Children), useValue: childrenRepo }, + { + provide: getRepositoryToken(ParentsChildren), + useValue: parentsChildrenRepo, + }, + ], + }).compile(); + service = module.get(AssistantesMaternellesService); }); it('should be defined', () => { expect(service).toBeDefined(); }); + + describe('listerCouplesGarde', () => { + it('retourne vide si aucun placement', async () => { + amRepo.findOne.mockResolvedValue({ + user_id: 'am-1', + id_placement_garde_courant: null, + }); + amChildrenRepo.find.mockResolvedValue([]); + const res = await service.listerCouplesGarde('am-1'); + expect(res).toEqual({ couples: [], couple_courant_id: null }); + expect(parentsChildrenRepo.find).not.toHaveBeenCalled(); + }); + + it('mappe placements + parents et marque le courant', async () => { + amRepo.findOne.mockResolvedValue({ + user_id: 'am-1', + id_placement_garde_courant: 'pl-2', + }); + amChildrenRepo.find.mockResolvedValue([ + { + id: 'pl-1', + enfantId: 'e1', + child: { id: 'e1', first_name: 'Léa', last_name: 'M', photo_url: null }, + }, + { + id: 'pl-2', + enfantId: 'e2', + child: { id: 'e2', first_name: 'Emma', last_name: 'M', photo_url: null }, + }, + ]); + parentsChildrenRepo.find.mockResolvedValue([ + { + parentId: 'p1', + enfantId: 'e1', + parent: { user: { prenom: 'Claire', nom: 'Martin', photo_url: null } }, + }, + { + parentId: 'p2', + enfantId: 'e1', + parent: { user: { prenom: 'Thomas', nom: 'Martin', photo_url: null } }, + }, + { + parentId: 'p1', + enfantId: 'e2', + parent: { user: { prenom: 'Claire', nom: 'Martin', photo_url: null } }, + }, + ]); + + const res = await service.listerCouplesGarde('am-1'); + expect(res.couples).toHaveLength(2); + expect(res.couple_courant_id).toBe('pl-2'); + expect(res.couples[1].courant).toBe(true); + expect(res.couples[0].parents).toHaveLength(2); + expect(res.couples[0].parents[0].prenom).toBe('Claire'); + expect(res.couples[0].enfant.prenom).toBe('Léa'); + }); + + it('404 si AM inconnue', async () => { + amRepo.findOne.mockResolvedValue(null); + await expect(service.listerCouplesGarde('x')).rejects.toBeInstanceOf( + NotFoundException, + ); + }); + }); + + describe('definirCoupleGardeCourant', () => { + it('persiste si le placement appartient à l’AM', async () => { + amRepo.findOne.mockResolvedValue({ + user_id: 'am-1', + id_placement_garde_courant: null, + }); + amChildrenRepo.findOne.mockResolvedValue({ + id: 'pl-1', + amId: 'am-1', + enfantId: 'e1', + }); + amRepo.update.mockResolvedValue({ affected: 1 }); + amChildrenRepo.find.mockResolvedValue([ + { + id: 'pl-1', + enfantId: 'e1', + child: { id: 'e1', first_name: 'Léa', last_name: null, photo_url: null }, + }, + ]); + parentsChildrenRepo.find.mockResolvedValue([ + { + parentId: 'p1', + enfantId: 'e1', + parent: { user: { prenom: 'Claire', nom: 'M', photo_url: null } }, + }, + ]); + + const res = await service.definirCoupleGardeCourant('am-1', 'pl-1'); + expect(amRepo.update).toHaveBeenCalledWith( + { user_id: 'am-1' }, + { id_placement_garde_courant: 'pl-1' }, + ); + expect(res.couple_courant_id).toBe('pl-1'); + expect(res.couples[0].courant).toBe(true); + }); + + it('404 si placement d’une autre AM', async () => { + amRepo.findOne.mockResolvedValue({ user_id: 'am-1' }); + amChildrenRepo.findOne.mockResolvedValue(null); + await expect( + service.definirCoupleGardeCourant('am-1', 'pl-x'), + ).rejects.toBeInstanceOf(NotFoundException); + }); + }); }); diff --git a/backend/src/routes/assistantes_maternelles/assistantes_maternelles.service.ts b/backend/src/routes/assistantes_maternelles/assistantes_maternelles.service.ts index ced0e0e..4018040 100644 --- a/backend/src/routes/assistantes_maternelles/assistantes_maternelles.service.ts +++ b/backend/src/routes/assistantes_maternelles/assistantes_maternelles.service.ts @@ -5,14 +5,16 @@ import { NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { IsNull, Repository } from 'typeorm'; +import { In, IsNull, Repository } from 'typeorm'; import { RoleType, Users } from 'src/entities/users.entity'; import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity'; import { AmChildren } from 'src/entities/am_children.entity'; import { Children, StatutEnfantType } from 'src/entities/children.entity'; +import { ParentsChildren } from 'src/entities/parents_children.entity'; import { CreateAssistanteDto } from '../user/dto/create_assistante.dto'; import { UpdateAssistanteDto } from '../user/dto/update_assistante.dto'; import { UpdateAmFicheAdminDto } from './dto/update-am-fiche-admin.dto'; +import { CouplesGardeAmResponseDto } from './dto/couples-garde-am.dto'; import { validateNir } from 'src/common/utils/nir.util'; const AM_CHILDREN_RELATIONS = ['user', 'amChildren', 'amChildren.child'] as const; @@ -28,6 +30,8 @@ export class AssistantesMaternellesService { private readonly amChildrenRepository: Repository, @InjectRepository(Children) private readonly childrenRepository: Repository, + @InjectRepository(ParentsChildren) + private readonly parentsChildrenRepository: Repository, ) {} async create(dto: CreateAssistanteDto): Promise { @@ -264,4 +268,107 @@ export class AssistantesMaternellesService { await this.assistantesMaternelleRepository.delete(id); return { message: 'Assistante maternelle supprimée' }; } + + /** + * Liste les couples de garde (enfant ↔ parents) de l’AM connectée — ticket #171. + * Un couple = un placement actif dans enfants_assistantes_maternelles pour cette AM. + */ + async listerCouplesGarde(amUserId: string): Promise { + const am = await this.assistantesMaternelleRepository.findOne({ + where: { user_id: amUserId }, + }); + if (!am) { + throw new NotFoundException('Assistante maternelle introuvable'); + } + + const placements = await this.amChildrenRepository.find({ + where: { amId: amUserId, date_fin: IsNull() }, + relations: ['child'], + order: { date_debut: 'ASC' }, + }); + + if (placements.length === 0) { + return { couples: [], couple_courant_id: null }; + } + + const enfantIds = placements.map((p) => p.enfantId); + const liens = await this.parentsChildrenRepository.find({ + where: { enfantId: In(enfantIds) }, + relations: ['parent', 'parent.user'], + }); + const parentsByEnfant = new Map< + string, + { id: string; prenom: string | null; nom: string | null; photo_url: string | null }[] + >(); + for (const l of liens) { + const list = parentsByEnfant.get(l.enfantId) ?? []; + const u = l.parent?.user; + list.push({ + id: l.parentId, + prenom: u?.prenom ?? null, + nom: u?.nom ?? null, + photo_url: u?.photo_url ?? null, + }); + parentsByEnfant.set(l.enfantId, list); + } + + let idCourant = am.id_placement_garde_courant ?? null; + const idsValides = new Set(placements.map((p) => p.id)); + if (idCourant && !idsValides.has(idCourant)) { + idCourant = null; + await this.assistantesMaternelleRepository.update( + { user_id: amUserId }, + { id_placement_garde_courant: () => 'NULL' }, + ); + } + if (!idCourant && placements.length === 1) { + idCourant = placements[0].id; + } + + const couples = placements.map((p) => ({ + 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, + }, + parents: parentsByEnfant.get(p.enfantId) ?? [], + courant: idCourant != null && p.id === idCourant, + })); + + return { + couples, + couple_courant_id: idCourant, + }; + } + + /** + * Persiste le couple de garde actif pour l’AM — ticket #171. + */ + async definirCoupleGardeCourant( + amUserId: string, + coupleId: string, + ): Promise { + const am = await this.assistantesMaternelleRepository.findOne({ + where: { user_id: amUserId }, + }); + if (!am) { + throw new NotFoundException('Assistante maternelle introuvable'); + } + + const placement = await this.amChildrenRepository.findOne({ + where: { id: coupleId, amId: amUserId, date_fin: IsNull() }, + }); + if (!placement) { + throw new NotFoundException('Couple de garde introuvable ou inactif pour cette AM'); + } + + await this.assistantesMaternelleRepository.update( + { user_id: amUserId }, + { id_placement_garde_courant: coupleId }, + ); + + return this.listerCouplesGarde(amUserId); + } } diff --git a/backend/src/routes/assistantes_maternelles/dto/couples-garde-am.dto.ts b/backend/src/routes/assistantes_maternelles/dto/couples-garde-am.dto.ts new file mode 100644 index 0000000..3c1230c --- /dev/null +++ b/backend/src/routes/assistantes_maternelles/dto/couples-garde-am.dto.ts @@ -0,0 +1,67 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; + +/** Identité minimale enfant — ticket #171 (miroir #168) */ +export class CoupleGardeAmEnfantDto { + @ApiProperty({ format: 'uuid' }) + id: string; + + @ApiPropertyOptional() + prenom?: string | null; + + @ApiPropertyOptional() + nom?: string | null; + + @ApiPropertyOptional() + photo_url?: string | null; +} + +/** Identité minimale parent pour bandeau AM — ticket #171 */ +export class CoupleGardeAmParentDto { + @ApiProperty({ format: 'uuid', description: 'UUID utilisateur du parent' }) + id: string; + + @ApiPropertyOptional() + prenom?: string | null; + + @ApiPropertyOptional() + nom?: string | null; + + @ApiPropertyOptional() + photo_url?: string | null; +} + +/** + * Un couple côté AM = placement actif enfant ↔ foyer parents. + * `id` = enfants_assistantes_maternelles.id (même clé que #168). + */ +export class CoupleGardeAmDto { + @ApiProperty({ + format: 'uuid', + description: 'Id du placement (enfants_assistantes_maternelles.id)', + }) + id: string; + + @ApiProperty({ type: CoupleGardeAmEnfantDto }) + enfant: CoupleGardeAmEnfantDto; + + @ApiProperty({ + type: [CoupleGardeAmParentDto], + description: 'Parents rattachés à l’enfant (0–2 typiquement)', + }) + parents: CoupleGardeAmParentDto[]; + + @ApiProperty({ description: 'True si c’est le couple actuellement sélectionné' }) + courant: boolean; +} + +export class CouplesGardeAmResponseDto { + @ApiProperty({ type: [CoupleGardeAmDto] }) + couples: CoupleGardeAmDto[]; + + @ApiPropertyOptional({ + format: 'uuid', + nullable: true, + description: 'Id du couple courant (null si aucun)', + }) + couple_courant_id: string | null; +} diff --git a/backend/src/routes/assistantes_maternelles/dto/definir-couple-garde-courant-am.dto.ts b/backend/src/routes/assistantes_maternelles/dto/definir-couple-garde-courant-am.dto.ts new file mode 100644 index 0000000..c2ca6d4 --- /dev/null +++ b/backend/src/routes/assistantes_maternelles/dto/definir-couple-garde-courant-am.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsUUID } from 'class-validator'; + +/** Corps PUT couple de garde courant AM — ticket #171 */ +export class DefinirCoupleGardeCourantAmDto { + @ApiProperty({ + format: 'uuid', + description: 'Id du placement (enfants_assistantes_maternelles.id) à sélectionner', + }) + @IsUUID() + @IsNotEmpty() + couple_id: string; +} diff --git a/database/BDD.sql b/database/BDD.sql index a23e4a2..b868b7e 100644 --- a/database/BDD.sql +++ b/database/BDD.sql @@ -143,7 +143,10 @@ CREATE TABLE assistantes_maternelles ( annee_experience SMALLINT, specialite VARCHAR(100), place_disponible INT, - numero_dossier VARCHAR(20) + numero_dossier VARCHAR(20), + -- Préférence couple de garde actif (TdB quotidien AM) — ticket #171 + -- FK ajoutée après création de enfants_assistantes_maternelles (voir ALTER plus bas) + id_placement_garde_courant UUID ); CREATE INDEX idx_assistantes_maternelles_numero_dossier @@ -221,6 +224,16 @@ CREATE INDEX idx_parents_placement_garde_courant ON parents(id_placement_garde_courant) WHERE id_placement_garde_courant IS NOT NULL; +-- FK couple courant AM → placement (#171) +ALTER TABLE assistantes_maternelles + ADD CONSTRAINT fk_am_placement_garde_courant + FOREIGN KEY (id_placement_garde_courant) + REFERENCES enfants_assistantes_maternelles(id) ON DELETE SET NULL; + +CREATE INDEX idx_am_placement_garde_courant + ON assistantes_maternelles(id_placement_garde_courant) + WHERE id_placement_garde_courant IS NOT NULL; + -- ========================================================== -- Table : dossier_famille (inscription parent — ticket #119) -- ========================================================== diff --git a/database/migrations/2026_am_placement_garde_courant.sql b/database/migrations/2026_am_placement_garde_courant.sql new file mode 100644 index 0000000..3faaeaf --- /dev/null +++ b/database/migrations/2026_am_placement_garde_courant.sql @@ -0,0 +1,10 @@ +-- Ticket #171 — Couple de garde courant (préférence AM) +-- Idempotent : safe à rejouer. + +ALTER TABLE assistantes_maternelles + 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_am_placement_garde_courant + ON assistantes_maternelles(id_placement_garde_courant) + WHERE id_placement_garde_courant IS NOT NULL;