Compare commits

..
Author SHA1 Message Date
jmartin 4007d7010e Merge branch 'develop' of https://git.ptits-pas.fr/jmartin/petitspas into develop 2026-09-24 12:19:40 +02:00
jmartinandCursor 4261667198 feat(#170): sélecteur couple enfant-parent(s) pour AM
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-24 12:19:26 +02:00
jmartin 15e2d8d850 Merge branch 'feature/171-api-couples-garde-am' into develop
API couples-garde AM (#171).
2026-09-24 12:17:09 +02:00
jmartinandCursor da71803852 feat(#171): API couples-garde AM (liste + courant)
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 <cursoragent@cursor.com>
2026-09-24 12:14:16 +02:00
10 changed files with 439 additions and 18 deletions
@@ -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 { Users } from './users.entity';
import { AmChildren } from './am_children.entity'; import { AmChildren } from './am_children.entity';
@@ -37,22 +37,32 @@ export class AssistanteMaternelle {
@Column({ name: 'ville_residence', length: 100, nullable: true }) @Column({ name: 'ville_residence', length: 100, nullable: true })
residence_city?: string; residence_city?: string;
@Column( { name: 'date_agrement', type: 'date', nullable: true }) @Column({ name: 'date_agrement', type: 'date', nullable: true })
agreement_date?: Date; agreement_date?: Date;
@Column( { name: 'annee_experience', type: 'smallint', nullable: true }) @Column({ name: 'annee_experience', type: 'smallint', nullable: true })
years_experience?: number; years_experience?: number;
@Column( { name: 'specialite', length: 100, nullable: true }) @Column({ name: 'specialite', length: 100, nullable: true })
specialty?: string; specialty?: string;
@Column( { name: 'place_disponible', type: 'integer', nullable: true }) @Column({ name: 'place_disponible', type: 'integer', nullable: true })
places_available?: number; places_available?: number;
/** Numéro de dossier (format AAAA-NNNNNN), même valeur que sur utilisateurs (ticket #103) */ /** Numéro de dossier (format AAAA-NNNNNN), même valeur que sur utilisateurs (ticket #103) */
@Column({ name: 'numero_dossier', length: 20, nullable: true }) @Column({ name: 'numero_dossier', length: 20, nullable: true })
numero_dossier?: string; 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) @OneToMany(() => AmChildren, (ac) => ac.am)
amChildren: AmChildren[]; amChildren: AmChildren[];
} }
@@ -10,7 +10,10 @@ describe('AssistantesMaternellesController', () => {
const authServiceMock = { const authServiceMock = {
createAmDossierStaff: jest.fn(), createAmDossierStaff: jest.fn(),
}; };
const amServiceMock = {}; const amServiceMock = {
listerCouplesGarde: jest.fn(),
definirCoupleGardeCourant: jest.fn(),
};
beforeEach(async () => { beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({ const module: TestingModule = await Test.createTestingModule({
@@ -66,4 +69,29 @@ describe('AssistantesMaternellesController', () => {
); );
expect(res.numero_dossier).toBe('2026-000001'); 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');
});
}); });
@@ -2,6 +2,7 @@ import {
Controller, Controller,
Get, Get,
Post, Post,
Put,
Body, Body,
Patch, Patch,
Param, Param,
@@ -20,6 +21,8 @@ import { UpdateAssistanteDto } from '../user/dto/update_assistante.dto';
import { UpdateAmFicheAdminDto } from './dto/update-am-fiche-admin.dto'; import { UpdateAmFicheAdminDto } from './dto/update-am-fiche-admin.dto';
import { StaffCreateAmDossierDto } from './dto/staff-create-am-dossier.dto'; import { StaffCreateAmDossierDto } from './dto/staff-create-am-dossier.dto';
import { StaffCreateAmDossierResponseDto } from './dto/staff-create-am-dossier-response.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 { RolesGuard } from 'src/common/guards/roles.guard';
import { AuthGuard } from 'src/common/guards/auth.guard'; import { AuthGuard } from 'src/common/guards/auth.guard';
import { User } from 'src/common/decorators/user.decorator'; import { User } from 'src/common/decorators/user.decorator';
@@ -83,6 +86,36 @@ export class AssistantesMaternellesController {
return mapAmsForApi(ams); return mapAmsForApi(ams);
} }
@Get('me/couples-garde')
@Roles(RoleType.ASSISTANTE_MATERNELLE)
@ApiOperation({
summary: 'Couples de garde de lAM 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<CouplesGardeAmResponseDto> {
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<CouplesGardeAmResponseDto> {
return this.assistantesMaternellesService.definirCoupleGardeCourant(
userId,
dto.couple_id,
);
}
@Roles(RoleType.SUPER_ADMIN, RoleType.GESTIONNAIRE, RoleType.ADMINISTRATEUR) @Roles(RoleType.SUPER_ADMIN, RoleType.GESTIONNAIRE, RoleType.ADMINISTRATEUR)
@Get(':id') @Get(':id')
@ApiParam({ name: 'id', description: "UUID de la nounou" }) @ApiParam({ name: 'id', description: "UUID de la nounou" })
@@ -4,13 +4,21 @@ import { AssistantesMaternellesController } from './assistantes_maternelles.cont
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity'; import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';
import { AmChildren } from 'src/entities/am_children.entity'; import { AmChildren } from 'src/entities/am_children.entity';
import { Children } from 'src/entities/children.entity'; import { Children } from 'src/entities/children.entity';
import { ParentsChildren } from 'src/entities/parents_children.entity';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { Users } from 'src/entities/users.entity'; import { Users } from 'src/entities/users.entity';
import { AuthModule } from '../auth/auth.module'; import { AuthModule } from '../auth/auth.module';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([AssistanteMaternelle, AmChildren, Children, Users]), imports: [
AuthModule TypeOrmModule.forFeature([
AssistanteMaternelle,
AmChildren,
Children,
Users,
ParentsChildren,
]),
AuthModule,
], ],
controllers: [AssistantesMaternellesController], controllers: [AssistantesMaternellesController],
providers: [AssistantesMaternellesService], providers: [AssistantesMaternellesService],
@@ -19,4 +27,4 @@ import { AuthModule } from '../auth/auth.module';
TypeOrmModule, TypeOrmModule,
], ],
}) })
export class AssistantesMaternellesModule { } export class AssistantesMaternellesModule {}
@@ -1,18 +1,150 @@
import { NotFoundException } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing'; import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { AssistantesMaternellesService } from './assistantes_maternelles.service'; 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; let service: AssistantesMaternellesService;
beforeEach(async () => { const amRepo = { findOne: jest.fn(), update: jest.fn() };
const module: TestingModule = await Test.createTestingModule({ const usersRepo = {};
providers: [AssistantesMaternellesService], const amChildrenRepo = { find: jest.fn(), findOne: jest.fn() };
}).compile(); const childrenRepo = {};
const parentsChildrenRepo = { find: jest.fn() };
service = module.get<AssistantesMaternellesService>(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', () => { it('should be defined', () => {
expect(service).toBeDefined(); 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 à lAM', 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 dune 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);
});
});
}); });
@@ -5,14 +5,16 @@ import {
NotFoundException, NotFoundException,
} from '@nestjs/common'; } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; 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 { RoleType, Users } from 'src/entities/users.entity';
import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity'; import { AssistanteMaternelle } from 'src/entities/assistantes_maternelles.entity';
import { AmChildren } from 'src/entities/am_children.entity'; import { AmChildren } from 'src/entities/am_children.entity';
import { Children, StatutEnfantType } from 'src/entities/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 { CreateAssistanteDto } from '../user/dto/create_assistante.dto';
import { UpdateAssistanteDto } from '../user/dto/update_assistante.dto'; import { UpdateAssistanteDto } from '../user/dto/update_assistante.dto';
import { UpdateAmFicheAdminDto } from './dto/update-am-fiche-admin.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'; import { validateNir } from 'src/common/utils/nir.util';
const AM_CHILDREN_RELATIONS = ['user', 'amChildren', 'amChildren.child'] as const; const AM_CHILDREN_RELATIONS = ['user', 'amChildren', 'amChildren.child'] as const;
@@ -28,6 +30,8 @@ export class AssistantesMaternellesService {
private readonly amChildrenRepository: Repository<AmChildren>, private readonly amChildrenRepository: Repository<AmChildren>,
@InjectRepository(Children) @InjectRepository(Children)
private readonly childrenRepository: Repository<Children>, private readonly childrenRepository: Repository<Children>,
@InjectRepository(ParentsChildren)
private readonly parentsChildrenRepository: Repository<ParentsChildren>,
) {} ) {}
async create(dto: CreateAssistanteDto): Promise<AssistanteMaternelle> { async create(dto: CreateAssistanteDto): Promise<AssistanteMaternelle> {
@@ -264,4 +268,107 @@ export class AssistantesMaternellesService {
await this.assistantesMaternelleRepository.delete(id); await this.assistantesMaternelleRepository.delete(id);
return { message: 'Assistante maternelle supprimée' }; return { message: 'Assistante maternelle supprimée' };
} }
/**
* Liste les couples de garde (enfant ↔ parents) de lAM connectée — ticket #171.
* Un couple = un placement actif dans enfants_assistantes_maternelles pour cette AM.
*/
async listerCouplesGarde(amUserId: string): Promise<CouplesGardeAmResponseDto> {
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 lAM — ticket #171.
*/
async definirCoupleGardeCourant(
amUserId: string,
coupleId: string,
): Promise<CouplesGardeAmResponseDto> {
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);
}
} }
@@ -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 à lenfant (02 typiquement)',
})
parents: CoupleGardeAmParentDto[];
@ApiProperty({ description: 'True si cest 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;
}
@@ -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;
}
+14 -1
View File
@@ -143,7 +143,10 @@ CREATE TABLE assistantes_maternelles (
annee_experience SMALLINT, annee_experience SMALLINT,
specialite VARCHAR(100), specialite VARCHAR(100),
place_disponible INT, 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 CREATE INDEX idx_assistantes_maternelles_numero_dossier
@@ -221,6 +224,16 @@ CREATE INDEX idx_parents_placement_garde_courant
ON parents(id_placement_garde_courant) ON parents(id_placement_garde_courant)
WHERE id_placement_garde_courant IS NOT NULL; 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) -- Table : dossier_famille (inscription parent — ticket #119)
-- ========================================================== -- ==========================================================
@@ -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;