From 7c06fc9c0081eb948edf8aad2dd5997b439055d9 Mon Sep 17 00:00:00 2001 From: sdraris Date: Thu, 25 Sep 2025 10:20:57 +0200 Subject: [PATCH] feat(enfants): ajout de controller et service (refs #01) --- src/routes/enfants/dto/create_enfants.dto.ts | 63 ++++++++++ src/routes/enfants/dto/update_enfants.dto.ts | 4 + src/routes/enfants/enfants.controller.spec.ts | 18 +++ src/routes/enfants/enfants.controller.ts | 63 ++++++++++ src/routes/enfants/enfants.module.ts | 12 ++ src/routes/enfants/enfants.service.spec.ts | 18 +++ src/routes/enfants/enfants.service.ts | 110 ++++++++++++++++++ 7 files changed, 288 insertions(+) create mode 100644 src/routes/enfants/dto/create_enfants.dto.ts create mode 100644 src/routes/enfants/dto/update_enfants.dto.ts create mode 100644 src/routes/enfants/enfants.controller.spec.ts create mode 100644 src/routes/enfants/enfants.controller.ts create mode 100644 src/routes/enfants/enfants.module.ts create mode 100644 src/routes/enfants/enfants.service.spec.ts create mode 100644 src/routes/enfants/enfants.service.ts diff --git a/src/routes/enfants/dto/create_enfants.dto.ts b/src/routes/enfants/dto/create_enfants.dto.ts new file mode 100644 index 0000000..d6cbd8f --- /dev/null +++ b/src/routes/enfants/dto/create_enfants.dto.ts @@ -0,0 +1,63 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsBoolean, IsDateString, IsEnum, IsNotEmpty, IsOptional, IsString, IsUUID, MaxLength, ValidateIf } from "class-validator"; +import { GenreType, StatutEnfantType } from "src/entities/children.entity"; + +export class CreateEnfantsDto { + @ApiProperty({ enum: StatutEnfantType, example: StatutEnfantType.ACTIF }) + @IsEnum(StatutEnfantType) + @IsNotEmpty() + status: StatutEnfantType; + + @ApiProperty({ example: 'Georges', required: false }) + @IsOptional() + @IsString() + @MaxLength(100) + first_name?: string; + + @ApiProperty({ example: 'Lucas', required: false }) + @IsOptional() + @IsString() + @MaxLength(100) + last_name?: string; + + @ApiProperty({ enum: GenreType, required: false }) + @IsOptional() + @IsEnum(GenreType) + gender?: GenreType; + + @ApiProperty({ example: '2018-06-24', required: false }) + @ValidateIf(o => o.status !== StatutEnfantType.A_NAITRE) + @IsOptional() + @IsDateString() + birth_date?: string; + + @ApiProperty({ example: '2024-12-24', required: false }) + @ValidateIf(o => o.status === StatutEnfantType.A_NAITRE) + @IsOptional() + @IsDateString() + due_date?: string; + + @ApiProperty({ example: 'https://monimage.com/photo.jpg', required: false }) + @IsOptional() + @IsString() + photo_url?: string; + + @ApiProperty({ default: false }) + @IsBoolean() + consent_photo: boolean; + + @ApiProperty({ required: false }) + @IsOptional() + @IsDateString() + consent_photo_at?: string; + + @ApiProperty({ default: false }) + @IsBoolean() + is_multiple: boolean; + + @ApiProperty({ example: 'UUID-parent' }) + @IsUUID() + @IsNotEmpty() + id_parent: string; + +} diff --git a/src/routes/enfants/dto/update_enfants.dto.ts b/src/routes/enfants/dto/update_enfants.dto.ts new file mode 100644 index 0000000..dd8c971 --- /dev/null +++ b/src/routes/enfants/dto/update_enfants.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from "@nestjs/swagger"; +import { CreateEnfantsDto } from "./create_enfants.dto"; + +export class UpdateEnfantsDto extends PartialType(CreateEnfantsDto) {} \ No newline at end of file diff --git a/src/routes/enfants/enfants.controller.spec.ts b/src/routes/enfants/enfants.controller.spec.ts new file mode 100644 index 0000000..04e9e0e --- /dev/null +++ b/src/routes/enfants/enfants.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { EnfantsController } from './enfants.controller'; + +describe('EnfantsController', () => { + let controller: EnfantsController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [EnfantsController], + }).compile(); + + controller = module.get(EnfantsController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/routes/enfants/enfants.controller.ts b/src/routes/enfants/enfants.controller.ts new file mode 100644 index 0000000..bd14a24 --- /dev/null +++ b/src/routes/enfants/enfants.controller.ts @@ -0,0 +1,63 @@ +import { Body, Controller, Delete, Get, Param, ParseUUIDPipe, Patch, Post, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { RolesGuard } from 'src/common/guards/roles.guard'; +import { EnfantsService } from './enfants.service'; +import { Roles } from 'src/common/decorators/roles.decorator'; +import { RoleType } from 'src/entities/users.entity'; +import { Children } from 'src/entities/children.entity'; +import { CreateEnfantsDto } from './dto/create_enfants.dto'; + +@ApiBearerAuth() +@ApiTags('Enfants') +@UseGuards(RolesGuard) +@Controller('enfants') +export class EnfantsController { + constructor(private readonly enfantsService: EnfantsService) { } + + @Post() + @ApiOperation({ summary: 'Inscrire un enfant' }) + @ApiResponse({ status: 201, type: Children, description: 'L\'enfant a été inscrit avec succès.' }) + @Roles(RoleType.PARENT, RoleType.SUPER_ADMIN, RoleType.GESTIONNAIRE) + async create(@Body() dto: CreateEnfantsDto): Promise { + return this.enfantsService.create(dto); + } + + // Récupérer tous les enfants avec leurs liens parents + @ApiOperation({ summary: 'Récupérer tous les enfants avec leurs liens parents' }) + @ApiResponse({ status: 200, type: [Children], description: 'Liste de tous les enfants avec leurs liens parents.' }) + @Get() + @Roles(RoleType.GESTIONNAIRE, RoleType.SUPER_ADMIN) + async findAll(): Promise { + return this.enfantsService.findAll(); + } + + // Récupérer un enfant par son ID + @Get(':id') + @ApiOperation({ summary: 'Récupérer un enfant par son ID' }) + @ApiResponse({ status: 200, type: Children, description: 'Détails de l\'enfant avec ses liens parents.' }) + @Roles(RoleType.GESTIONNAIRE, RoleType.SUPER_ADMIN, RoleType.PARENT) + async findOne(@Param('id', new ParseUUIDPipe()) id: string): Promise { + return this.enfantsService.findOne(id); + } + + // Mettre à jour un enfant + @Patch(':id') + @ApiOperation({ summary: 'Mettre à jour un enfant' }) + @ApiResponse({ status: 200, type: Children, description: 'L\'enfant a été mis à jour avec succès.' }) + @Roles(RoleType.GESTIONNAIRE, RoleType.SUPER_ADMIN) + async update( + @Param('id', new ParseUUIDPipe()) id: string, + @Body() dto: Partial, + ): Promise { + return this.enfantsService.update(id, dto); + } + + // Supprimer un enfant + @Delete(':id') + @Roles(RoleType.GESTIONNAIRE, RoleType.SUPER_ADMIN) + @ApiOperation({ summary: 'Supprimer un enfant' }) + @ApiResponse({ status: 204, description: 'L\'enfant a été supprimé avec succès.' }) + async remove(@Param('id', new ParseUUIDPipe()) id: string): Promise { + return this.enfantsService.remove(id); + } +} diff --git a/src/routes/enfants/enfants.module.ts b/src/routes/enfants/enfants.module.ts new file mode 100644 index 0000000..08d357d --- /dev/null +++ b/src/routes/enfants/enfants.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { EnfantsController } from './enfants.controller'; +import { EnfantsService } from './enfants.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Children } from 'src/entities/children.entity'; + +@Module({ + imports: [TypeOrmModule.forFeature([Children])], + controllers: [EnfantsController], + providers: [EnfantsService], +}) +export class EnfantsModule {} diff --git a/src/routes/enfants/enfants.service.spec.ts b/src/routes/enfants/enfants.service.spec.ts new file mode 100644 index 0000000..c1ded59 --- /dev/null +++ b/src/routes/enfants/enfants.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { EnfantsService } from './enfants.service'; + +describe('EnfantsService', () => { + let service: EnfantsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [EnfantsService], + }).compile(); + + service = module.get(EnfantsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/routes/enfants/enfants.service.ts b/src/routes/enfants/enfants.service.ts new file mode 100644 index 0000000..fa0bd4c --- /dev/null +++ b/src/routes/enfants/enfants.service.ts @@ -0,0 +1,110 @@ +import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Children, StatutEnfantType } from 'src/entities/children.entity'; +import { Parents } from 'src/entities/parents.entity'; +import { ParentsChildren } from 'src/entities/parents_children.entity'; +import { Repository } from 'typeorm'; +import { CreateEnfantsDto } from './dto/create_enfants.dto'; + +@Injectable() +export class EnfantsService { + constructor( + @InjectRepository(Children) + private readonly childrenRepository: Repository, + + @InjectRepository(Parents) + private readonly parentsRepository: Repository, + + @InjectRepository(ParentsChildren) + private readonly parentsChildrenRepository: Repository, + ) { } + + // Inscruire un enfant + async create(dto: CreateEnfantsDto): Promise { + + // Vérifier que le parent existe + const parent = await this.parentsRepository.findOne({ + where: { user_id: dto.id_parent }, + }); + // Si le parent n'existe pas, lever une exception + if (!parent) { + throw new NotFoundException(`Id de parent : ${dto.id_parent} introuvable`); + } + + // Evolution future : rendre l'option photo obligatoire ou non configurable + const optionObligatoire = false; + // Si l'enfant est pas a naitre, vérifier qu'une photo est fournie + // Puis si l'option est obligatoire + if (dto.status !== StatutEnfantType.A_NAITRE && !dto.photo_url && optionObligatoire) { + throw new BadRequestException(`Pour un enfant actif, une photo est obligatoire`); + } + + // Créer l'enfant + 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: dto.photo_url, + consent_photo: dto.consent_photo, + consent_photo_at: dto.consent_photo_at ? new Date(dto.consent_photo_at) : undefined, + is_multiple: dto.is_multiple, + }); + await this.childrenRepository.save(child); + + // Créer le lien entre le parent et l'enfant + const parentLink = this.parentsChildrenRepository.create({ + parentId: parent.user_id, + enfantId: child.id, + }); + await this.parentsChildrenRepository.save(parentLink); + return this.findOne(child.id); + } + + // Récupérer tous les enfants avec leurs liens parents + async findAll(): Promise { + const all_children = await this.childrenRepository.find({ + relations: [ + 'parentLinks', + 'parentLinks.parent', + 'parentLinks.parent.user', + ], + order: { last_name: 'ASC', first_name: 'ASC' } + }); + return all_children; + } + + // Récupérer un enfant par son id avec ses liens parents + async findOne(id: string): Promise { + const child = await this.childrenRepository.findOne({ + where: { id }, + relations: [ + 'parentLinks', + 'parentLinks.parent', + 'parentLinks.parent.user', + ], + }); + if (!child) { + throw new NotFoundException(`Id d'enfant : ${id} introuvable`); + } + return child; + } + + // Mettre à jour un enfant + async update(id: string, dto: Partial): Promise { + const child = await this.childrenRepository.findOne({ where: { id } }); + if (!child) { + throw new NotFoundException(`Id d'enfant : ${id} introuvable`); + } + const { id_parent, ...childData } = dto; + await this.childrenRepository.update(id, childData); + return this.findOne(id); + } + + // Supprimer un enfant + async remove(id: string): Promise { + await this.childrenRepository.delete(id); + } +} \ No newline at end of file -- 2.47.2