Compare commits
63 Commits
master
...
feature/15
| Author | SHA1 | Date | |
|---|---|---|---|
| d247867fa0 | |||
| 93912d1374 | |||
| 925b6d5cd4 | |||
| b0dddd6695 | |||
| ef7512dc1e | |||
| 2ececa711b | |||
| 7a3d997ff9 | |||
| 6b89d7b405 | |||
| f7ac628445 | |||
| 26e9738ec7 | |||
| 0ec3410457 | |||
| c3acf1970d | |||
| 6fe2b89a61 | |||
| d6a9b3fd66 | |||
| 134b9781c8 | |||
| b936d27445 | |||
| 18c1d1eba7 | |||
| f74b14c203 | |||
| f194b5f9e8 | |||
| 5ab8ae3423 | |||
| 3277f77846 | |||
| 2d80ad0d7e | |||
| 09386f8aa6 | |||
| faa50f637c | |||
| 267fe63aec | |||
| 90b185740c | |||
| b903dbf60b | |||
| 291ea26b34 | |||
| 59919834b9 | |||
| cf6acbae7c | |||
| 7951c38d4d | |||
| 77d952a6f7 | |||
| b4b546044d | |||
| 6788351070 | |||
| 7b69f27ca5 | |||
| 8ed68797aa | |||
| 9ad371a342 | |||
| 18718670e9 | |||
| fbf22f2540 | |||
| 43a2cd213b | |||
| c865d11dc3 | |||
| d03a8e6c8b | |||
| 66c7f22280 | |||
| 53721ffbb3 | |||
| 52e40d0001 | |||
| 4985726bc6 | |||
| 479a32b4bf | |||
| 2fd97ddecb | |||
| ce474797c4 | |||
| ebf794e1ac | |||
| d55f240f56 | |||
| 966f4a9c9c | |||
| 8494341b56 | |||
| 1dddc67933 | |||
| df776d8200 | |||
| c26ed00374 | |||
| 9d54d9b19b | |||
| c438009286 | |||
| 9b7231f1da | |||
| f300505225 | |||
| 25c10c885a | |||
| d70577b1c3 | |||
| 671da71752 |
@ -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<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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1277,18 +1277,16 @@ class _AdminChildDetailModalState extends State<AdminChildDetailModal> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Édition orphelin (#157) : rattache immédiatement au foyer.
|
// Édition orphelin (#157/#158) : un seul attach — le back propage au foyer.
|
||||||
final enfantId = widget.enfant?.id;
|
final enfantId = widget.enfant?.id;
|
||||||
if (enfantId == null || enfantId.isEmpty) return;
|
if (enfantId == null || enfantId.isEmpty) return;
|
||||||
|
|
||||||
setState(() => _saving = true);
|
setState(() => _saving = true);
|
||||||
try {
|
try {
|
||||||
for (final parentId in selected.parentUserIds) {
|
await UserService.attachEnfantToParent(
|
||||||
await UserService.attachEnfantToParent(
|
parentUserId: selected.pivotParentUserId,
|
||||||
parentUserId: parentId,
|
enfantId: enfantId,
|
||||||
enfantId: enfantId,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
final refreshed = await UserService.getEnfant(enfantId);
|
final refreshed = await UserService.getEnfant(enfantId);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|||||||
@ -283,7 +283,7 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
|
|||||||
builder: (ctx) => AlertDialog(
|
builder: (ctx) => AlertDialog(
|
||||||
title: const Text('Détacher l\'enfant'),
|
title: const Text('Détacher l\'enfant'),
|
||||||
content: Text(
|
content: Text(
|
||||||
'Retirer ${child.fullName} de la fiche de ce parent ?',
|
'Retirer ${child.fullName} du foyer (tous les responsables) ?',
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@ -308,8 +308,9 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
await _reloadChildren();
|
await _reloadChildren();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
widget.onSaved?.call();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Enfant détaché')),
|
const SnackBar(content: Text('Enfant détaché du foyer')),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
@ -336,8 +337,9 @@ class _AdminParentEditModalState extends State<AdminParentEditModal> {
|
|||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
await _reloadChildren();
|
await _reloadChildren();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
widget.onSaved?.call();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(content: Text('Enfant rattaché')),
|
const SnackBar(content: Text('Enfant rattaché au foyer')),
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user