feat(#132): POST /enfants staff — parent_user_id + liens foyer.
Autorise GESTIONNAIRE/ADMIN/SUPER_ADMIN à créer un enfant rattaché à un foyer existant (JSON + parent_user_id). Parent multipart inchangé. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,6 +13,12 @@ import { ParentsChildren } from 'src/entities/parents_children.entity';
|
||||
import { RoleType, Users } from 'src/entities/users.entity';
|
||||
import { CreateEnfantsDto } from './dto/create_enfants.dto';
|
||||
|
||||
const STAFF_ROLES: RoleType[] = [
|
||||
RoleType.GESTIONNAIRE,
|
||||
RoleType.ADMINISTRATEUR,
|
||||
RoleType.SUPER_ADMIN,
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class EnfantsService {
|
||||
constructor(
|
||||
@@ -24,20 +30,34 @@ export class EnfantsService {
|
||||
private readonly parentsChildrenRepository: Repository<ParentsChildren>,
|
||||
) { }
|
||||
|
||||
// Création d'un enfant
|
||||
async create(dto: CreateEnfantsDto, currentUser: Users, photoFile?: Express.Multer.File): Promise<Children> {
|
||||
private isStaff(user: Users): boolean {
|
||||
return STAFF_ROLES.includes(user.role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Création d'un enfant.
|
||||
* - PARENT : rattache au parent connecté (multipart photo optionnel).
|
||||
* - Staff : JSON + `parent_user_id` obligatoire (foyer existant). Ticket #132.
|
||||
*/
|
||||
async create(
|
||||
dto: CreateEnfantsDto,
|
||||
currentUser: Users,
|
||||
photoFile?: Express.Multer.File,
|
||||
): Promise<Children> {
|
||||
const pivotUserId = this.resolvePivotParentUserId(dto, currentUser);
|
||||
|
||||
const parent = await this.parentsRepository.findOne({
|
||||
where: { user_id: currentUser.id },
|
||||
where: { user_id: pivotUserId },
|
||||
relations: ['co_parent'],
|
||||
});
|
||||
if (!parent) throw new NotFoundException('Parent introuvable');
|
||||
|
||||
// Vérif métier simple
|
||||
// Vérif métier simple (aligné comportement historique parent)
|
||||
if (dto.status !== StatutEnfantType.A_NAITRE && !dto.birth_date) {
|
||||
throw new BadRequestException('Un enfant né doit avoir une date de naissance');
|
||||
}
|
||||
|
||||
// Vérif doublon éventuel (ex: même prénom + date de naissance pour ce parent)
|
||||
// Vérif doublon éventuel (ex: même prénom + date de naissance)
|
||||
const exist = await this.childrenRepository.findOne({
|
||||
where: {
|
||||
first_name: dto.first_name,
|
||||
@@ -47,37 +67,84 @@ export class EnfantsService {
|
||||
});
|
||||
if (exist) throw new ConflictException('Cet enfant existe déjà');
|
||||
|
||||
// Gestion de la photo uploadée
|
||||
// Gestion de la photo uploadée (parcours parent multipart)
|
||||
let photoUrl = dto.photo_url;
|
||||
let consentAt: Date | undefined;
|
||||
if (photoFile) {
|
||||
dto.photo_url = `/uploads/photos/${photoFile.filename}`;
|
||||
photoUrl = `/uploads/photos/${photoFile.filename}`;
|
||||
if (dto.consent_photo) {
|
||||
dto.consent_photo_at = new Date().toISOString();
|
||||
consentAt = new Date();
|
||||
}
|
||||
} else if (dto.consent_photo) {
|
||||
consentAt = dto.consent_photo_at
|
||||
? new Date(dto.consent_photo_at)
|
||||
: new Date();
|
||||
}
|
||||
|
||||
// Création
|
||||
const child = this.childrenRepository.create(dto);
|
||||
const child = this.childrenRepository.create({
|
||||
status: dto.status,
|
||||
first_name: dto.first_name,
|
||||
last_name: dto.last_name,
|
||||
gender: dto.gender,
|
||||
birth_date: dto.birth_date ? new Date(dto.birth_date) : undefined,
|
||||
due_date: dto.due_date ? new Date(dto.due_date) : undefined,
|
||||
photo_url: photoUrl,
|
||||
consent_photo: !!dto.consent_photo,
|
||||
consent_photo_at: consentAt,
|
||||
is_multiple: !!dto.is_multiple,
|
||||
});
|
||||
await this.childrenRepository.save(child);
|
||||
|
||||
// Lien parent-enfant (Parent 1)
|
||||
const parentLink = this.parentsChildrenRepository.create({
|
||||
parentId: parent.user_id,
|
||||
enfantId: child.id,
|
||||
});
|
||||
await this.parentsChildrenRepository.save(parentLink);
|
||||
// Lien parent-enfant (pivot)
|
||||
await this.parentsChildrenRepository.save(
|
||||
this.parentsChildrenRepository.create({
|
||||
parentId: parent.user_id,
|
||||
enfantId: child.id,
|
||||
}),
|
||||
);
|
||||
|
||||
// Rattachement automatique au co-parent s'il existe
|
||||
if (parent.co_parent) {
|
||||
const coParentLink = this.parentsChildrenRepository.create({
|
||||
parentId: parent.co_parent.id,
|
||||
enfantId: child.id,
|
||||
});
|
||||
await this.parentsChildrenRepository.save(coParentLink);
|
||||
await this.parentsChildrenRepository.save(
|
||||
this.parentsChildrenRepository.create({
|
||||
parentId: parent.co_parent.id,
|
||||
enfantId: child.id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return this.findOne(child.id, currentUser);
|
||||
}
|
||||
|
||||
private resolvePivotParentUserId(
|
||||
dto: CreateEnfantsDto,
|
||||
currentUser: Users,
|
||||
): string {
|
||||
if (this.isStaff(currentUser)) {
|
||||
const id = dto.parent_user_id?.trim();
|
||||
if (!id) {
|
||||
throw new BadRequestException(
|
||||
'parent_user_id est obligatoire pour créer un enfant (staff)',
|
||||
);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
if (currentUser.role === RoleType.PARENT) {
|
||||
if (
|
||||
dto.parent_user_id &&
|
||||
dto.parent_user_id.trim() !== currentUser.id
|
||||
) {
|
||||
throw new ForbiddenException(
|
||||
'Un parent ne peut pas créer un enfant pour un autre compte',
|
||||
);
|
||||
}
|
||||
return currentUser.id;
|
||||
}
|
||||
|
||||
throw new ForbiddenException('Accès interdit');
|
||||
}
|
||||
|
||||
// Liste des enfants (admin/gestionnaire)
|
||||
async findAll(): Promise<Children[]> {
|
||||
return this.childrenRepository.find({
|
||||
@@ -90,7 +157,7 @@ export class EnfantsService {
|
||||
async findOne(id: string, currentUser: Users): Promise<Children> {
|
||||
const child = await this.childrenRepository.findOne({
|
||||
where: { id },
|
||||
relations: ['parentLinks'],
|
||||
relations: ['parentLinks', 'parentLinks.parent', 'parentLinks.parent.user'],
|
||||
});
|
||||
if (!child) throw new NotFoundException('Enfant introuvable');
|
||||
|
||||
@@ -120,7 +187,8 @@ export class EnfantsService {
|
||||
const child = await this.childrenRepository.findOne({ where: { id } });
|
||||
if (!child) throw new NotFoundException('Enfant introuvable');
|
||||
|
||||
const patch: Partial<Children> = { ...dto } as Partial<Children>;
|
||||
const { parent_user_id: _ignored, ...rest } = dto;
|
||||
const patch: Partial<Children> = { ...rest } as Partial<Children>;
|
||||
if (dto.consent_photo !== undefined) {
|
||||
patch.consent_photo = dto.consent_photo;
|
||||
patch.consent_photo_at = dto.consent_photo ? new Date() : null!;
|
||||
|
||||
Reference in New Issue
Block a user