feat(backend): implement Relais module and relation with Gestionnaire (Ticket #94)

- Create Relais entity
- Create Relais module, controller, service with CRUD
- Update Users entity with ManyToOne relation to Relais
- Update GestionnairesService to handle relaisId

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-21 14:27:14 +01:00
co-authored by Cursor
parent e4f7a35f0f
commit ae786426fd
11 changed files with 290 additions and 14 deletions
+2
View File
@@ -16,6 +16,7 @@ import { AllExceptionsFilter } from './common/filters/all_exceptions.filters';
import { EnfantsModule } from './routes/enfants/enfants.module';
import { AppConfigModule } from './modules/config/config.module';
import { DocumentsLegauxModule } from './modules/documents-legaux';
import { RelaisModule } from './routes/relais/relais.module';
@Module({
imports: [
@@ -53,6 +54,7 @@ import { DocumentsLegauxModule } from './modules/documents-legaux';
AuthModule,
AppConfigModule,
DocumentsLegauxModule,
RelaisModule,
],
controllers: [AppController],
providers: [
+35
View File
@@ -0,0 +1,35 @@
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, OneToMany } from 'typeorm';
import { Users } from './users.entity';
@Entity('relais', { schema: 'public' })
export class Relais {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ name: 'nom' })
nom: string;
@Column({ name: 'adresse' })
adresse: string;
@Column({ type: 'jsonb', name: 'horaires_ouverture', nullable: true })
horaires_ouverture?: any;
@Column({ name: 'ligne_fixe', nullable: true })
ligne_fixe?: string;
@Column({ default: true, name: 'actif' })
actif: boolean;
@Column({ type: 'text', name: 'notes', nullable: true })
notes?: string;
@CreateDateColumn({ name: 'cree_le', type: 'timestamptz' })
cree_le: Date;
@UpdateDateColumn({ name: 'modifie_le', type: 'timestamptz' })
modifie_le: Date;
@OneToMany(() => Users, user => user.relais)
gestionnaires: Users[];
}
+9 -1
View File
@@ -1,11 +1,12 @@
import {
Entity, PrimaryGeneratedColumn, Column,
CreateDateColumn, UpdateDateColumn,
OneToOne, OneToMany
OneToOne, OneToMany, ManyToOne, JoinColumn
} from 'typeorm';
import { AssistanteMaternelle } from './assistantes_maternelles.entity';
import { Parents } from './parents.entity';
import { Message } from './messages.entity';
import { Relais } from './relais.entity';
// Enums alignés avec la BDD PostgreSQL
export enum RoleType {
@@ -147,4 +148,11 @@ export class Users {
@OneToMany(() => Parents, parent => parent.co_parent)
co_parent_in?: Parents[];
@Column({ nullable: true, name: 'relais_id' })
relaisId?: string;
@ManyToOne(() => Relais, relais => relais.gestionnaires, { nullable: true })
@JoinColumn({ name: 'relais_id' })
relais?: Relais;
}
@@ -0,0 +1,34 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsNotEmpty, IsOptional, IsString, IsObject } from 'class-validator';
export class CreateRelaisDto {
@ApiProperty({ example: 'Relais Petite Enfance Centre' })
@IsString()
@IsNotEmpty()
nom: string;
@ApiProperty({ example: '12 rue de la Mairie, 75000 Paris' })
@IsString()
@IsNotEmpty()
adresse: string;
@ApiProperty({ example: { lundi: '09:00-17:00' }, required: false })
@IsOptional()
@IsObject()
horaires_ouverture?: any;
@ApiProperty({ example: '0123456789', required: false })
@IsOptional()
@IsString()
ligne_fixe?: string;
@ApiProperty({ default: true, required: false })
@IsOptional()
@IsBoolean()
actif?: boolean;
@ApiProperty({ example: 'Notes internes...', required: false })
@IsOptional()
@IsString()
notes?: string;
}
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/swagger';
import { CreateRelaisDto } from './create-relais.dto';
export class UpdateRelaisDto extends PartialType(CreateRelaisDto) {}
@@ -0,0 +1,57 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common';
import { RelaisService } from './relais.service';
import { CreateRelaisDto } from './dto/create-relais.dto';
import { UpdateRelaisDto } from './dto/update-relais.dto';
import { ApiBearerAuth, ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { AuthGuard } from 'src/common/guards/auth.guard';
import { RolesGuard } from 'src/common/guards/roles.guard';
import { Roles } from 'src/common/decorators/roles.decorator';
import { RoleType } from 'src/entities/users.entity';
@ApiTags('Relais')
@ApiBearerAuth('access-token')
@UseGuards(AuthGuard, RolesGuard)
@Controller('relais')
export class RelaisController {
constructor(private readonly relaisService: RelaisService) {}
@Post()
@Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR)
@ApiOperation({ summary: 'Créer un relais' })
@ApiResponse({ status: 201, description: 'Le relais a été créé.' })
create(@Body() createRelaisDto: CreateRelaisDto) {
return this.relaisService.create(createRelaisDto);
}
@Get()
@Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR)
@ApiOperation({ summary: 'Lister tous les relais' })
@ApiResponse({ status: 200, description: 'Liste des relais.' })
findAll() {
return this.relaisService.findAll();
}
@Get(':id')
@Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR)
@ApiOperation({ summary: 'Récupérer un relais par ID' })
@ApiResponse({ status: 200, description: 'Le relais trouvé.' })
findOne(@Param('id') id: string) {
return this.relaisService.findOne(id);
}
@Patch(':id')
@Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR)
@ApiOperation({ summary: 'Mettre à jour un relais' })
@ApiResponse({ status: 200, description: 'Le relais a été mis à jour.' })
update(@Param('id') id: string, @Body() updateRelaisDto: UpdateRelaisDto) {
return this.relaisService.update(id, updateRelaisDto);
}
@Delete(':id')
@Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR)
@ApiOperation({ summary: 'Supprimer un relais' })
@ApiResponse({ status: 200, description: 'Le relais a été supprimé.' })
remove(@Param('id') id: string) {
return this.relaisService.remove(id);
}
}
@@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RelaisService } from './relais.service';
import { RelaisController } from './relais.controller';
import { Relais } from 'src/entities/relais.entity';
import { AuthModule } from 'src/routes/auth/auth.module';
@Module({
imports: [
TypeOrmModule.forFeature([Relais]),
AuthModule,
],
controllers: [RelaisController],
providers: [RelaisService],
exports: [RelaisService],
})
export class RelaisModule {}
@@ -0,0 +1,42 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Relais } from 'src/entities/relais.entity';
import { CreateRelaisDto } from './dto/create-relais.dto';
import { UpdateRelaisDto } from './dto/update-relais.dto';
@Injectable()
export class RelaisService {
constructor(
@InjectRepository(Relais)
private readonly relaisRepository: Repository<Relais>,
) {}
create(createRelaisDto: CreateRelaisDto) {
const relais = this.relaisRepository.create(createRelaisDto);
return this.relaisRepository.save(relais);
}
findAll() {
return this.relaisRepository.find({ order: { nom: 'ASC' } });
}
async findOne(id: string) {
const relais = await this.relaisRepository.findOne({ where: { id } });
if (!relais) {
throw new NotFoundException(`Relais #${id} not found`);
}
return relais;
}
async update(id: string, updateRelaisDto: UpdateRelaisDto) {
const relais = await this.findOne(id);
Object.assign(relais, updateRelaisDto);
return this.relaisRepository.save(relais);
}
async remove(id: string) {
const relais = await this.findOne(id);
return this.relaisRepository.remove(relais);
}
}
@@ -1,4 +1,10 @@
import { OmitType } from "@nestjs/swagger";
import { ApiProperty, OmitType } from "@nestjs/swagger";
import { CreateUserDto } from "./create_user.dto";
import { IsOptional, IsUUID } from "class-validator";
export class CreateGestionnaireDto extends OmitType(CreateUserDto, ['role'] as const) {}
export class CreateGestionnaireDto extends OmitType(CreateUserDto, ['role'] as const) {
@ApiProperty({ required: false, description: 'ID du relais de rattachement' })
@IsOptional()
@IsUUID()
relaisId?: string;
}
@@ -41,19 +41,24 @@ export class GestionnairesService {
: undefined,
changement_mdp_obligatoire: dto.changement_mdp_obligatoire ?? false,
role: RoleType.GESTIONNAIRE,
relaisId: dto.relaisId,
});
return this.gestionnaireRepository.save(entity);
}
// Liste des gestionnaires
async findAll(): Promise<Users[]> {
return this.gestionnaireRepository.find({ where: { role: RoleType.GESTIONNAIRE } });
return this.gestionnaireRepository.find({
where: { role: RoleType.GESTIONNAIRE },
relations: ['relais'],
});
}
// Récupérer un gestionnaire par ID
async findOne(id: string): Promise<Users> {
const gestionnaire = await this.gestionnaireRepository.findOne({
where: { id, role: RoleType.GESTIONNAIRE },
relations: ['relais'],
});
if (!gestionnaire) throw new NotFoundException('Gestionnaire introuvable');
return gestionnaire;