feat(#158): attach/detach enfant au niveau foyer (pivot + co-parent).

POST/DELETE /parents/:id/enfants/:enfantId propagent les liens à tous
les responsables du foyer (co-parent bidirectionnel + même dossier).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
MARTIN Julien 2026-07-17 19:47:13 +02:00
parent 925b6d5cd4
commit 93912d1374

View File

@ -114,36 +114,86 @@ export class ParentsService {
} }
/** /**
* Rattacher un enfant existant à un parent (enfants_parents). Ticket #115 / doc 28 §6.2. * Membres du foyer (user ids) pour affiliation enfant.
* Pivot + co-parent (AB et BA) + même numero_dossier. Ticket #158.
*/ */
async attachEnfant(parentUserId: string, enfantId: string): Promise<Parents> { private async resolveFoyerParentUserIds(parent: Parents): Promise<string[]> {
await this.findOne(parentUserId); const ids = new Set<string>([parent.user_id]);
const existing = await this.parentsChildrenRepository.findOne({ if (parent.co_parent?.id) {
where: { parentId: parentUserId, enfantId }, ids.add(parent.co_parent.id);
});
if (existing) {
throw new ConflictException('Cet enfant est déjà rattaché à ce parent');
} }
const child = await this.parentsRepository.manager.findOne(Children, { where: { id: enfantId } }); // Sens inverse : parents qui déclarent ce user comme co-parent
const reverseLinks = await this.parentsRepository.find({
where: { co_parent: { id: parent.user_id } },
relations: ['co_parent'],
});
for (const p of reverseLinks) {
ids.add(p.user_id);
if (p.co_parent?.id) ids.add(p.co_parent.id);
}
const dossier = parent.numero_dossier?.trim();
if (dossier) {
const sameDossier = await this.parentsRepository.find({
where: { numero_dossier: dossier },
relations: ['co_parent'],
});
for (const p of sameDossier) {
ids.add(p.user_id);
if (p.co_parent?.id) ids.add(p.co_parent.id);
}
}
return [...ids];
}
/**
* Rattacher un enfant au foyer du parent (tous les responsables). Ticket #158.
* Un seul POST suffit : liens créés pour pivot + co-parent / même dossier.
*/
async attachEnfant(parentUserId: string, enfantId: string): Promise<Parents> {
const parent = await this.findOne(parentUserId);
const child = await this.parentsRepository.manager.findOne(Children, {
where: { id: enfantId },
});
if (!child) { if (!child) {
throw new NotFoundException('Enfant introuvable'); throw new NotFoundException('Enfant introuvable');
} }
await this.parentsChildrenRepository.save( const foyerIds = await this.resolveFoyerParentUserIds(parent);
this.parentsChildrenRepository.create({ parentId: parentUserId, enfantId }), let created = 0;
);
for (const memberId of foyerIds) {
const existing = await this.parentsChildrenRepository.findOne({
where: { parentId: memberId, enfantId },
});
if (existing) continue;
await this.parentsChildrenRepository.save(
this.parentsChildrenRepository.create({
parentId: memberId,
enfantId,
}),
);
created += 1;
}
if (created === 0) {
throw new ConflictException('Cet enfant est déjà rattaché à ce foyer');
}
return this.findOne(parentUserId); return this.findOne(parentUserId);
} }
/** /**
* Détacher un enfant d'un parent sans supprimer l'enfant. * Détacher un enfant du foyer du parent (tous les responsables). Ticket #158.
* Autorise le détachement du dernier responsable (#157) l'enfant reste listé * Si plus aucun lien ensuite enfant orphelin (#157).
* via GET /enfants avec parentLinks vides (alerte front).
*/ */
async detachEnfant(parentUserId: string, enfantId: string): Promise<Parents> { async detachEnfant(parentUserId: string, enfantId: string): Promise<Parents> {
await this.findOne(parentUserId); const parent = await this.findOne(parentUserId);
const link = await this.parentsChildrenRepository.findOne({ const link = await this.parentsChildrenRepository.findOne({
where: { parentId: parentUserId, enfantId }, where: { parentId: parentUserId, enfantId },
@ -152,7 +202,12 @@ export class ParentsService {
throw new NotFoundException('Lien parent-enfant introuvable'); throw new NotFoundException('Lien parent-enfant introuvable');
} }
await this.parentsChildrenRepository.delete({ parentId: parentUserId, enfantId }); const foyerIds = await this.resolveFoyerParentUserIds(parent);
await this.parentsChildrenRepository.delete({
parentId: In(foyerIds),
enfantId,
});
return this.findOne(parentUserId); return this.findOne(parentUserId);
} }