From 393a527c379509a4b3c0a68d529c9a0fad8b362d Mon Sep 17 00:00:00 2001 From: Julien Martin Date: Thu, 12 Mar 2026 22:21:12 +0100 Subject: [PATCH] =?UTF-8?q?feat(#105):=20Statut=20=C2=AB=20refus=C3=A9=20?= =?UTF-8?q?=C2=BB=20=E2=80=93=20enum,=20migration,=20pending/reprise,=20re?= =?UTF-8?q?fuser,=20connexion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Enum statut_utilisateur_type + valeur 'refuse' (migration + BDD.sql) - GET /users/reprise, PATCH /users/:id/refuser (refus_compte en validations) - PATCH /users/:id/valider accepte en_attente et refuse (reprise) - Connexion refusée si statut refuse Made-with: Cursor --- backend/src/entities/users.entity.ts | 1 + backend/src/routes/auth/auth.service.ts | 6 +++ backend/src/routes/user/user.controller.ts | 22 ++++++++++ backend/src/routes/user/user.service.ts | 41 ++++++++++++++++++- database/BDD.sql | 2 +- .../2026_statut_utilisateur_refuse.sql | 4 ++ 6 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 database/migrations/2026_statut_utilisateur_refuse.sql diff --git a/backend/src/entities/users.entity.ts b/backend/src/entities/users.entity.ts index ca5d2ef..d44726e 100644 --- a/backend/src/entities/users.entity.ts +++ b/backend/src/entities/users.entity.ts @@ -29,6 +29,7 @@ export enum StatutUtilisateurType { EN_ATTENTE = 'en_attente', ACTIF = 'actif', SUSPENDU = 'suspendu', + REFUSE = 'refuse', } export enum SituationFamilialeType { diff --git a/backend/src/routes/auth/auth.service.ts b/backend/src/routes/auth/auth.service.ts index 45e7ee5..edaba73 100644 --- a/backend/src/routes/auth/auth.service.ts +++ b/backend/src/routes/auth/auth.service.ts @@ -96,6 +96,12 @@ export class AuthService { throw new UnauthorizedException('Votre compte a été suspendu. Contactez un administrateur.'); } + if (user.statut === StatutUtilisateurType.REFUSE) { + throw new UnauthorizedException( + 'Votre compte a été refusé. Vous pouvez corriger votre dossier et le soumettre à nouveau ; un gestionnaire pourra le réexaminer.', + ); + } + return this.generateTokens(user.id, user.email, user.role); } diff --git a/backend/src/routes/user/user.controller.ts b/backend/src/routes/user/user.controller.ts index d7df010..90b8535 100644 --- a/backend/src/routes/user/user.controller.ts +++ b/backend/src/routes/user/user.controller.ts @@ -50,6 +50,16 @@ export class UserController { return this.userService.findPendingUsers(role); } + // Lister les comptes refusés (à corriger / reprise) + @Get('reprise') + @Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE) + @ApiOperation({ summary: 'Lister les comptes refusés (reprise)' }) + findRefusedUsers( + @Query('role') role?: RoleType + ) { + return this.userService.findRefusedUsers(role); + } + // Lister tous les utilisateurs (super_admin uniquement) @Get() @Roles(RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR) @@ -112,6 +122,18 @@ export class UserController { return this.userService.validateUser(id, currentUser, comment); } + @Patch(':id/refuser') + @Roles(RoleType.SUPER_ADMIN, RoleType.GESTIONNAIRE, RoleType.ADMINISTRATEUR) + @ApiOperation({ summary: 'Refuser un compte (à corriger)' }) + @ApiParam({ name: 'id', description: "UUID de l'utilisateur" }) + refuse( + @Param('id') id: string, + @User() currentUser: Users, + @Body('comment') comment?: string, + ) { + return this.userService.refuseUser(id, currentUser, comment); + } + @Patch(':id/suspendre') @Roles(RoleType.SUPER_ADMIN, RoleType.GESTIONNAIRE, RoleType.ADMINISTRATEUR) @ApiOperation({ summary: 'Suspendre un compte utilisateur' }) diff --git a/backend/src/routes/user/user.service.ts b/backend/src/routes/user/user.service.ts index 3ef45b3..e6c270c 100644 --- a/backend/src/routes/user/user.service.ts +++ b/backend/src/routes/user/user.service.ts @@ -140,6 +140,15 @@ export class UserService { return this.usersRepository.find({ where }); } + /** Comptes refusés (à corriger) : liste pour reprise par le gestionnaire */ + async findRefusedUsers(role?: RoleType): Promise { + const where: any = { statut: StatutUtilisateurType.REFUSE }; + if (role) { + where.role = role; + } + return this.usersRepository.find({ where }); + } + async findAll(): Promise { return this.usersRepository.find(); } @@ -214,7 +223,7 @@ export class UserService { return this.usersRepository.save(user); } - // Valider un compte utilisateur + // Valider un compte utilisateur (en_attente ou refuse -> actif) async validateUser(user_id: string, currentUser: Users, comment?: string): Promise { if (![RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE].includes(currentUser.role)) { throw new ForbiddenException('Accès réservé aux super admins, administrateurs et gestionnaires'); @@ -222,7 +231,11 @@ export class UserService { const user = await this.usersRepository.findOne({ where: { id: user_id } }); if (!user) throw new NotFoundException('Utilisateur introuvable'); - + + if (user.statut !== StatutUtilisateurType.EN_ATTENTE && user.statut !== StatutUtilisateurType.REFUSE) { + throw new BadRequestException('Seuls les comptes en attente ou refusés (à corriger) peuvent être validés.'); + } + user.statut = StatutUtilisateurType.ACTIF; const savedUser = await this.usersRepository.save(user); if (user.role === RoleType.PARENT) { @@ -270,6 +283,30 @@ export class UserService { await this.validationRepository.save(suspend); return savedUser; } + + /** Refuser un compte (en_attente -> refuse) ; tracé dans validations */ + async refuseUser(user_id: string, currentUser: Users, comment?: string): Promise { + if (![RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE].includes(currentUser.role)) { + throw new ForbiddenException('Accès réservé aux super admins, administrateurs et gestionnaires'); + } + const user = await this.usersRepository.findOne({ where: { id: user_id } }); + if (!user) throw new NotFoundException('Utilisateur introuvable'); + if (user.statut !== StatutUtilisateurType.EN_ATTENTE) { + throw new BadRequestException('Seul un compte en attente peut être refusé.'); + } + user.statut = StatutUtilisateurType.REFUSE; + const savedUser = await this.usersRepository.save(user); + const validation = this.validationRepository.create({ + user: savedUser, + type: 'refus_compte', + status: StatutValidationType.REFUSE, + validated_by: currentUser, + comment, + }); + await this.validationRepository.save(validation); + return savedUser; + } + /** * Affecter ou modifier le numéro de dossier d'un utilisateur (parent ou AM). * Permet au gestionnaire/admin de rapprocher deux dossiers (même numéro pour plusieurs personnes). diff --git a/database/BDD.sql b/database/BDD.sql index fc04af8..57d67ee 100644 --- a/database/BDD.sql +++ b/database/BDD.sql @@ -11,7 +11,7 @@ DO $$ BEGIN CREATE TYPE genre_type AS ENUM ('H', 'F'); END IF; IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_utilisateur_type') THEN - CREATE TYPE statut_utilisateur_type AS ENUM ('en_attente','actif','suspendu'); + CREATE TYPE statut_utilisateur_type AS ENUM ('en_attente','actif','suspendu','refuse'); END IF; IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'statut_enfant_type') THEN CREATE TYPE statut_enfant_type AS ENUM ('a_naitre','actif','scolarise'); diff --git a/database/migrations/2026_statut_utilisateur_refuse.sql b/database/migrations/2026_statut_utilisateur_refuse.sql new file mode 100644 index 0000000..0ce2b78 --- /dev/null +++ b/database/migrations/2026_statut_utilisateur_refuse.sql @@ -0,0 +1,4 @@ +-- Migration #105 : Statut utilisateur « refusé » (à corriger) +-- Ajout de la valeur 'refuse' à l'enum statut_utilisateur_type. + +ALTER TYPE statut_utilisateur_type ADD VALUE IF NOT EXISTS 'refuse';