feat(#111): Reprise après refus – backend

- GET /auth/reprise-dossier?token= : dossier pour préremplir (token seul)
- PATCH /auth/reprise-resoumettre : token + champs modifiables → en_attente, token invalidé
- POST /auth/reprise-identify : numero_dossier + email → type + token
- UserService: findByTokenReprise, resoumettreReprise, findByNumeroDossierAndEmailForReprise

Made-with: Cursor
This commit is contained in:
2026-03-12 23:04:45 +01:00
parent 86d8189038
commit 86b28abe51
6 changed files with 243 additions and 3 deletions
+47 -1
View File
@@ -1,7 +1,7 @@
import { BadRequestException, ForbiddenException, Injectable, Logger, NotFoundException } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { RoleType, StatutUtilisateurType, Users } from "src/entities/users.entity";
import { In, Repository } from "typeorm";
import { In, MoreThan, Repository } from "typeorm";
import { CreateUserDto } from "./dto/create_user.dto";
import { CreateAdminDto } from "./dto/create_admin.dto";
import { UpdateUserDto } from "./dto/update_user.dto";
@@ -392,6 +392,52 @@ export class UserService {
return savedUser;
}
/** Trouve un user par token reprise valide (non expiré). Ticket #111 */
async findByTokenReprise(token: string): Promise<Users | null> {
return this.usersRepository.findOne({
where: {
token_reprise: token,
statut: StatutUtilisateurType.REFUSE,
token_reprise_expire_le: MoreThan(new Date()),
},
});
}
/** Resoumission reprise : met à jour les champs autorisés, passe en en_attente, invalide le token. Ticket #111 */
async resoumettreReprise(
token: string,
dto: { prenom?: string; nom?: string; telephone?: string; adresse?: string; ville?: string; code_postal?: string; photo_url?: string },
): Promise<Users> {
const user = await this.findByTokenReprise(token);
if (!user) {
throw new NotFoundException('Token reprise invalide ou expiré.');
}
if (dto.prenom !== undefined) user.prenom = dto.prenom;
if (dto.nom !== undefined) user.nom = dto.nom;
if (dto.telephone !== undefined) user.telephone = dto.telephone;
if (dto.adresse !== undefined) user.adresse = dto.adresse;
if (dto.ville !== undefined) user.ville = dto.ville;
if (dto.code_postal !== undefined) user.code_postal = dto.code_postal;
if (dto.photo_url !== undefined) user.photo_url = dto.photo_url;
user.statut = StatutUtilisateurType.EN_ATTENTE;
user.token_reprise = undefined;
user.token_reprise_expire_le = undefined;
return this.usersRepository.save(user);
}
/** Pour modale reprise : numero_dossier + email → user en REFUSE avec token valide. Ticket #111 */
async findByNumeroDossierAndEmailForReprise(numero_dossier: string, email: string): Promise<Users | null> {
const user = await this.usersRepository.findOne({
where: {
email: email.trim().toLowerCase(),
numero_dossier: numero_dossier.trim(),
statut: StatutUtilisateurType.REFUSE,
token_reprise_expire_le: MoreThan(new Date()),
},
});
return user ?? null;
}
async remove(id: string, currentUser: Users): Promise<void> {
if (currentUser.role !== RoleType.SUPER_ADMIN) {
throw new ForbiddenException('Accès réservé aux super admins');