feat(#157): autoriser détachement dernier parent + flag sans_responsable.

Supprime la garde totalLinks<=1 ; GET/findOne enfants exposent
sans_responsable pour les orphelins toujours listés.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
MARTIN Julien 2026-07-17 19:31:18 +02:00
parent 2ececa711b
commit ef7512dc1e
2 changed files with 22 additions and 14 deletions

View File

@ -146,16 +146,27 @@ export class EnfantsService {
throw new ForbiddenException('Accès interdit');
}
// Liste des enfants (admin/gestionnaire)
async findAll(): Promise<Children[]> {
return this.childrenRepository.find({
relations: ['parentLinks', 'parentLinks.parent', 'parentLinks.parent.user'],
order: { last_name: 'ASC', first_name: 'ASC' },
/** Flag API #157 — true si aucun lien enfants_parents. */
private withSansResponsable(child: Children): Children & { sans_responsable: boolean } {
return Object.assign(child, {
sans_responsable: !child.parentLinks || child.parentLinks.length === 0,
});
}
// Liste des enfants (admin/gestionnaire) — inclut les orphelins (parentLinks: [])
async findAll(): Promise<Array<Children & { sans_responsable: boolean }>> {
const children = await this.childrenRepository.find({
relations: ['parentLinks', 'parentLinks.parent', 'parentLinks.parent.user'],
order: { last_name: 'ASC', first_name: 'ASC' },
});
return children.map((c) => this.withSansResponsable(c));
}
// Récupérer un enfant par id
async findOne(id: string, currentUser: Users): Promise<Children> {
async findOne(
id: string,
currentUser: Users,
): Promise<Children & { sans_responsable: boolean }> {
const child = await this.childrenRepository.findOne({
where: { id },
relations: ['parentLinks', 'parentLinks.parent', 'parentLinks.parent.user'],
@ -172,14 +183,14 @@ export class EnfantsService {
case RoleType.ADMINISTRATEUR:
case RoleType.SUPER_ADMIN:
case RoleType.GESTIONNAIRE:
// accès complet
// accès complet (y compris orphelins)
break;
default:
throw new ForbiddenException('Accès interdit');
}
return child;
return this.withSansResponsable(child);
}

View File

@ -138,7 +138,9 @@ export class ParentsService {
}
/**
* Détacher un enfant d'un parent sans supprimer l'enfant. Ticket #115 / doc 28 §6.2.
* 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).
*/
async detachEnfant(parentUserId: string, enfantId: string): Promise<Parents> {
await this.findOne(parentUserId);
@ -150,11 +152,6 @@ export class ParentsService {
throw new NotFoundException('Lien parent-enfant introuvable');
}
const totalLinks = await this.parentsChildrenRepository.count({ where: { enfantId } });
if (totalLinks <= 1) {
throw new BadRequestException('Un enfant doit rester rattaché à au moins un responsable');
}
await this.parentsChildrenRepository.delete({ parentId: parentUserId, enfantId });
return this.findOne(parentUserId);
}