From 93912d137477e237764923fef1f00c28fbfdbce0 Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Fri, 17 Jul 2026 19:47:13 +0200 Subject: [PATCH] feat(#158): attach/detach enfant au niveau foyer (pivot + co-parent). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST/DELETE /parents/:id/enfants/:enfantId propagent les liens à tous les responsables du foyer (co-parent bidirectionnel + même dossier). Co-authored-by: Cursor --- backend/src/routes/parents/parents.service.ts | 89 +++++++++++++++---- 1 file changed, 72 insertions(+), 17 deletions(-) diff --git a/backend/src/routes/parents/parents.service.ts b/backend/src/routes/parents/parents.service.ts index ac00f0a..15c0581 100644 --- a/backend/src/routes/parents/parents.service.ts +++ b/backend/src/routes/parents/parents.service.ts @@ -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 (A→B et B→A) + même numero_dossier. Ticket #158. */ - async attachEnfant(parentUserId: string, enfantId: string): Promise { - await this.findOne(parentUserId); + private async resolveFoyerParentUserIds(parent: Parents): Promise { + const ids = new Set([parent.user_id]); - const existing = await this.parentsChildrenRepository.findOne({ - where: { parentId: parentUserId, enfantId }, - }); - if (existing) { - throw new ConflictException('Cet enfant est déjà rattaché à ce parent'); + if (parent.co_parent?.id) { + ids.add(parent.co_parent.id); } - 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 { + const parent = await this.findOne(parentUserId); + + const child = await this.parentsRepository.manager.findOne(Children, { + where: { id: enfantId }, + }); if (!child) { throw new NotFoundException('Enfant introuvable'); } - await this.parentsChildrenRepository.save( - this.parentsChildrenRepository.create({ parentId: parentUserId, enfantId }), - ); + const foyerIds = await this.resolveFoyerParentUserIds(parent); + 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); } /** - * Détacher un enfant d'un parent sans supprimer l'enfant. - * Autorise le détachement du dernier responsable (#157) — l'enfant reste listé - * via GET /enfants avec parentLinks vides (alerte front). + * Détacher un enfant du foyer du parent (tous les responsables). Ticket #158. + * Si plus aucun lien ensuite → enfant orphelin (#157). */ async detachEnfant(parentUserId: string, enfantId: string): Promise { - await this.findOne(parentUserId); + const parent = await this.findOne(parentUserId); const link = await this.parentsChildrenRepository.findOne({ where: { parentId: parentUserId, enfantId }, @@ -152,7 +202,12 @@ export class ParentsService { 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); }