feat: add DCB User Service API - Authentication system with KEYCLOAK - Modular architecture with services for each feature
This commit is contained in:
parent
35f0bcd135
commit
191099d8a5
@ -1,6 +1,7 @@
|
||||
import { Directive, Input, TemplateRef, ViewContainerRef, inject, OnDestroy } from '@angular/core';
|
||||
import { AuthService } from '../services/auth.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { UserRole } from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
@Directive({
|
||||
selector: '[hasRole]',
|
||||
@ -16,7 +17,10 @@ export class HasRoleDirective implements OnDestroy {
|
||||
const requiredRoles = Array.isArray(roles) ? roles : [roles];
|
||||
const userRoles = this.authService.getCurrentUserRoles();
|
||||
|
||||
const hasAccess = requiredRoles.some(role => userRoles.includes(role));
|
||||
const hasAccess = requiredRoles.some(role => userRoles.includes(
|
||||
UserRole.DCB_ADMIN || UserRole.DCB_PARTNER || UserRole.DCB_SUPPORT
|
||||
|| UserRole.DCB_PARTNER_ADMIN || UserRole.DCB_PARTNER_MANAGER || UserRole.DCB_PARTNER_SUPPORT
|
||||
));
|
||||
|
||||
if (hasAccess) {
|
||||
this.viewContainer.createEmbeddedView(this.templateRef);
|
||||
|
||||
@ -10,42 +10,43 @@ export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot, state: R
|
||||
const roleService = inject(RoleService);
|
||||
const router = inject(Router);
|
||||
|
||||
// Attendre que l'initialisation soit terminée
|
||||
// Attendre que l'initialisation du service Auth soit terminée
|
||||
return authService.getInitializedState().pipe(
|
||||
switchMap(initialized => {
|
||||
if (!initialized) {
|
||||
return of(false);
|
||||
}
|
||||
|
||||
// Vérifier l'authentification
|
||||
// 🔒 Étape 1 : Vérifier si déjà authentifié
|
||||
if (authService.isAuthenticated()) {
|
||||
return of(checkRoleAccess(route, roleService, router, state.url));
|
||||
}
|
||||
|
||||
// Tentative de rafraîchissement du token
|
||||
// 🔄 Étape 2 : Tenter un rafraîchissement du token s’il existe
|
||||
const refreshToken = authService.getRefreshToken();
|
||||
if (refreshToken) {
|
||||
|
||||
return authService.refreshToken().pipe(
|
||||
return authService.refreshAccessToken().pipe(
|
||||
tap(() => {
|
||||
// Recharger les rôles après un refresh réussi
|
||||
roleService.refreshRoles();
|
||||
}),
|
||||
map(() => checkRoleAccess(route, roleService, router, state.url)),
|
||||
catchError((error) => {
|
||||
catchError(() => {
|
||||
// En cas d’échec de refresh → déconnexion + redirection login
|
||||
authService.logout().subscribe();
|
||||
return of(redirectToLogin(router, state.url));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Redirection vers login
|
||||
// 🚫 Étape 3 : Aucun token → redirection vers login
|
||||
return of(redirectToLogin(router, state.url));
|
||||
}),
|
||||
catchError(error => {
|
||||
catchError(() => {
|
||||
return of(redirectToLogin(router, state.url));
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Vérifie l'accès basé sur les rôles requis
|
||||
@ -58,6 +59,7 @@ function checkRoleAccess(
|
||||
): boolean {
|
||||
const requiredRoles = route.data?.['roles'] as string[];
|
||||
|
||||
// Si aucun rôle requis → accès autorisé
|
||||
if (!requiredRoles || requiredRoles.length === 0) {
|
||||
return true;
|
||||
}
|
||||
@ -65,11 +67,12 @@ function checkRoleAccess(
|
||||
const hasRequiredRole = roleService.hasAnyRole(requiredRoles);
|
||||
const currentUserRoles = roleService.getCurrentUserRoles();
|
||||
|
||||
// ✅ L’utilisateur possède un des rôles requis
|
||||
if (hasRequiredRole) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Rediriger vers la page non autorisée
|
||||
// ❌ Sinon → rediriger vers une page "non autorisée"
|
||||
router.navigate(['/unauthorized'], {
|
||||
queryParams: {
|
||||
requiredRoles: requiredRoles.join(','),
|
||||
@ -83,19 +86,11 @@ function checkRoleAccess(
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirige vers la page de login avec les paramètres appropriés
|
||||
* Redirige vers la page de login avec un returnUrl
|
||||
*/
|
||||
function redirectToLogin(
|
||||
router: Router,
|
||||
returnUrl: string,
|
||||
): boolean {
|
||||
const queryParams: any = {
|
||||
returnUrl: returnUrl
|
||||
};
|
||||
|
||||
// Message spécifique selon la raison
|
||||
function redirectToLogin(router: Router, returnUrl: string): boolean {
|
||||
router.navigate(['/auth/login'], {
|
||||
queryParams,
|
||||
queryParams: { returnUrl },
|
||||
replaceUrl: true
|
||||
});
|
||||
|
||||
|
||||
@ -8,27 +8,28 @@ export const publicGuard: CanActivateFn = () => {
|
||||
const authService = inject(AuthService);
|
||||
const router = inject(Router);
|
||||
|
||||
// Si l'utilisateur est déjà authentifié, le rediriger vers le dashboard
|
||||
// 🔒 Si l'utilisateur est déjà authentifié → redirection vers le tableau de bord
|
||||
if (authService.isAuthenticated()) {
|
||||
router.navigate(['/dcb-dashboard'], { replaceUrl: true });
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifier si un refresh token est disponible
|
||||
// 🔄 Vérifier si un refresh token est disponible
|
||||
const refreshToken = authService.getRefreshToken();
|
||||
if (refreshToken) {
|
||||
return authService.refreshToken().pipe(
|
||||
return authService.refreshAccessToken().pipe(
|
||||
map(() => {
|
||||
// ✅ Rafraîchissement réussi → redirection vers le dashboard
|
||||
router.navigate(['/dcb-dashboard'], { replaceUrl: true });
|
||||
return false;
|
||||
}),
|
||||
catchError((error) => {
|
||||
// En cas d'erreur, autoriser l'accès à la page publique
|
||||
catchError(() => {
|
||||
// ❌ Rafraîchissement échoué → autoriser l’accès à la page publique
|
||||
return of(true);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// L'utilisateur n'est pas connecté, autoriser l'accès à la page publique
|
||||
// 👤 Aucun token → accès autorisé à la page publique
|
||||
return true;
|
||||
};
|
||||
@ -9,26 +9,31 @@ export const authInterceptor: HttpInterceptorFn = (req, next) => {
|
||||
const authService = inject(AuthService);
|
||||
const router = inject(Router);
|
||||
|
||||
// Exclusion des endpoints d'authentification
|
||||
// Exclure les requêtes d’authentification (login, refresh, logout)
|
||||
if (isAuthRequest(req)) {
|
||||
return next(req);
|
||||
}
|
||||
|
||||
const token = authService.getAccessToken();
|
||||
|
||||
// Si un token existe, l’ajouter aux requêtes API
|
||||
if (token && isApiRequest(req)) {
|
||||
const cloned = addToken(req, token);
|
||||
|
||||
return next(cloned).pipe(
|
||||
catchError((error: HttpErrorResponse) => {
|
||||
// Si le token est expiré → tentative de refresh
|
||||
if (error.status === 401 && !req.url.includes('/auth/refresh')) {
|
||||
return handle401Error(authService, router, req, next);
|
||||
}
|
||||
|
||||
// Autres erreurs → propager
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Si pas de token → requête normale
|
||||
return next(req);
|
||||
};
|
||||
|
||||
@ -42,35 +47,41 @@ function addToken(req: HttpRequest<any>, token: string): HttpRequest<any> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gestion du renouvellement de token en cas d’erreur 401
|
||||
*/
|
||||
function handle401Error(
|
||||
authService: AuthService,
|
||||
router: Router,
|
||||
req: HttpRequest<any>,
|
||||
next: HttpHandlerFn
|
||||
) {
|
||||
return authService.refreshToken().pipe(
|
||||
return authService.refreshAccessToken().pipe(
|
||||
switchMap((response: LoginResponseDto) => {
|
||||
const newRequest = addToken(req, response.access_token);
|
||||
return next(newRequest);
|
||||
}),
|
||||
catchError((refreshError) => {
|
||||
authService.logout().subscribe();
|
||||
router.navigate(['/auth/login']);
|
||||
authService.logout().subscribe(() => {
|
||||
router.navigate(['/auth/login'], { replaceUrl: true });
|
||||
});
|
||||
return throwError(() => refreshError);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Détecte si la requête cible une API de ton backend
|
||||
*/
|
||||
function isApiRequest(req: HttpRequest<any>): boolean {
|
||||
return req.url.includes('/api/') || req.url.includes('/auth/');
|
||||
// Ajuste ici selon ton backend (ex : '/api/', '/v1/', etc.)
|
||||
return req.url.includes('/api/v1/') || req.url.includes('/auth/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclut les endpoints liés à l’authentification
|
||||
*/
|
||||
function isAuthRequest(req: HttpRequest<any>): boolean {
|
||||
const authEndpoints = [
|
||||
'/auth/login',
|
||||
'/auth/refresh',
|
||||
'/auth/logout'
|
||||
];
|
||||
|
||||
const authEndpoints = ['/auth/login', '/auth/refresh', '/auth/logout'];
|
||||
return authEndpoints.some(endpoint => req.url.includes(endpoint));
|
||||
}
|
||||
@ -1,25 +1,62 @@
|
||||
export enum UserRole {
|
||||
DCB_PARTNER_ADMIN = 'DCB_PARTNER_ADMIN',
|
||||
DCB_PARTNER_MANAGER = 'DCB_PARTNER_MANAGER',
|
||||
DCB_PARTNER_SUPPORT = 'DCB_PARTNER_SUPPORT',
|
||||
DCB_PARTNER = 'DCB_PARTNER',
|
||||
DCB_ADMIN = 'DCB_ADMIN',
|
||||
DCB_SUPPORT = 'DCB_SUPPORT'
|
||||
export enum UserType {
|
||||
HUB = 'HUB',
|
||||
MERCHANT = 'MERCHANT',
|
||||
MERCHANT_USER = 'MERCHANT_USER'
|
||||
}
|
||||
|
||||
export interface CreateMerchantUserDto {
|
||||
export enum UserRole {
|
||||
// HUB roles
|
||||
DCB_ADMIN = 'DCB_ADMIN',
|
||||
DCB_SUPPORT = 'DCB_SUPPORT',
|
||||
DCB_PARTNER = 'DCB_PARTNER',
|
||||
|
||||
// MERCHANT roles
|
||||
DCB_PARTNER_ADMIN = 'DCB_PARTNER_ADMIN',
|
||||
DCB_PARTNER_MANAGER = 'DCB_PARTNER_MANAGER',
|
||||
DCB_PARTNER_SUPPORT = 'DCB_PARTNER_SUPPORT'
|
||||
}
|
||||
|
||||
// === BASE USER MODEL ===
|
||||
export interface BaseUserDto {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: UserRole;
|
||||
enabled: boolean;
|
||||
emailVerified: boolean;
|
||||
createdBy: string;
|
||||
createdByUsername: string;
|
||||
createdTimestamp: number;
|
||||
lastLogin?: number;
|
||||
userType: UserType;
|
||||
}
|
||||
|
||||
// === EXTENSIONS ===
|
||||
export interface HubUserDto extends BaseUserDto {
|
||||
userType: UserType.HUB;
|
||||
}
|
||||
|
||||
export interface MerchantUserDto extends BaseUserDto {
|
||||
userType: UserType.MERCHANT;
|
||||
merchantPartnerId: string;
|
||||
}
|
||||
|
||||
// === DTOs CRUD ===
|
||||
export interface CreateUserDto {
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
password: string;
|
||||
role: UserRole.DCB_PARTNER_ADMIN | UserRole.DCB_PARTNER_MANAGER | UserRole.DCB_PARTNER_SUPPORT;
|
||||
role: UserRole;
|
||||
enabled?: boolean;
|
||||
emailVerified?: boolean;
|
||||
merchantPartnerId: string;
|
||||
merchantPartnerId?: string; // obligatoire si MERCHANT
|
||||
}
|
||||
|
||||
export interface UpdateMerchantUserDto {
|
||||
export interface UpdateUserDto {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
@ -27,28 +64,21 @@ export interface UpdateMerchantUserDto {
|
||||
}
|
||||
|
||||
export interface ResetPasswordDto {
|
||||
userId?: string;
|
||||
newPassword: string;
|
||||
temporary?: boolean;
|
||||
}
|
||||
|
||||
export interface MerchantUserResponse {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: UserRole.DCB_PARTNER_ADMIN | UserRole.DCB_PARTNER_MANAGER | UserRole.DCB_PARTNER_SUPPORT;
|
||||
enabled: boolean;
|
||||
emailVerified: boolean;
|
||||
merchantPartnerId: string;
|
||||
createdBy: string;
|
||||
createdByUsername: string;
|
||||
createdTimestamp: number;
|
||||
lastLogin?: number;
|
||||
userType: 'MERCHANT';
|
||||
// === PAGINATION / STATS ===
|
||||
export interface PaginatedUserResponse {
|
||||
users: BaseUserDto[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface MerchantUsersStatsResponse {
|
||||
export interface MerchantPartnerStatsResponse {
|
||||
totalAdmins: number;
|
||||
totalManagers: number;
|
||||
totalSupport: number;
|
||||
@ -57,6 +87,7 @@ export interface MerchantUsersStatsResponse {
|
||||
inactiveUsers: number;
|
||||
}
|
||||
|
||||
// === ROLES ===
|
||||
export interface AvailableRole {
|
||||
value: UserRole;
|
||||
label: string;
|
||||
@ -68,13 +99,15 @@ export interface AvailableRolesResponse {
|
||||
roles: AvailableRole[];
|
||||
}
|
||||
|
||||
export interface SearchMerchantUsersParams {
|
||||
query?: string;
|
||||
role?: UserRole.DCB_PARTNER_ADMIN | UserRole.DCB_PARTNER_MANAGER | UserRole.DCB_PARTNER_SUPPORT;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface RoleOperationResponse {
|
||||
message: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// === SEARCH ===
|
||||
export interface SearchUsersParams {
|
||||
query?: string;
|
||||
role?: UserRole;
|
||||
enabled?: boolean;
|
||||
userType?: UserType;
|
||||
}
|
||||
@ -1,11 +1,19 @@
|
||||
// src/app/core/services/auth.service.ts
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||
import { Router } from '@angular/router';
|
||||
import { environment } from '@environments/environment';
|
||||
import { BehaviorSubject, Observable, throwError, tap, catchError, map, of } from 'rxjs';
|
||||
import { BehaviorSubject, Observable, throwError, tap, catchError } from 'rxjs';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
// Interfaces pour les DTOs de l'API
|
||||
import {
|
||||
UserType,
|
||||
UserRole,
|
||||
BaseUserDto,
|
||||
HubUserDto,
|
||||
MerchantUserDto
|
||||
} from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
// === INTERFACES DTO AUTH ===
|
||||
export interface LoginDto {
|
||||
username: string;
|
||||
password: string;
|
||||
@ -31,23 +39,6 @@ export interface AuthStatusResponseDto {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface UserProfileDto {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
roles: string[];
|
||||
enabled: boolean;
|
||||
emailVerified: boolean;
|
||||
merchantPartnerId: string;
|
||||
createdBy: string;
|
||||
createdByUsername: string;
|
||||
createdTimestamp: number;
|
||||
lastLogin?: number;
|
||||
userType: string;
|
||||
}
|
||||
|
||||
export interface TokenValidationResponseDto {
|
||||
valid: boolean;
|
||||
user: {
|
||||
@ -72,7 +63,7 @@ export class AuthService {
|
||||
private readonly refreshTokenKey = 'refresh_token';
|
||||
|
||||
private authState$ = new BehaviorSubject<boolean>(this.isAuthenticated());
|
||||
private userProfile$ = new BehaviorSubject<UserProfileDto | null>(null);
|
||||
private userProfile$ = new BehaviorSubject<BaseUserDto | null>(null);
|
||||
private initialized$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
// === INITIALISATION DE L'APPLICATION ===
|
||||
@ -95,8 +86,8 @@ export class AuthService {
|
||||
return refreshSuccess;
|
||||
}
|
||||
|
||||
// Token valide, vérifier le profil utilisateur
|
||||
await this.loadUserProfile().toPromise();
|
||||
// Token valide : charger le profil utilisateur
|
||||
await firstValueFrom(this.loadUserProfile());
|
||||
|
||||
this.authState$.next(true);
|
||||
this.initialized$.next(true);
|
||||
@ -121,8 +112,9 @@ export class AuthService {
|
||||
}
|
||||
|
||||
try {
|
||||
// Convertir l'Observable en Promise pour l'initialisation
|
||||
const response = await this.refreshToken().toPromise();
|
||||
const response = await firstValueFrom(this.refreshAccessToken());
|
||||
await firstValueFrom(this.loadUserProfile());
|
||||
this.authState$.next(true);
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.clearAuthData();
|
||||
@ -137,7 +129,7 @@ export class AuthService {
|
||||
return this.initialized$.asObservable();
|
||||
}
|
||||
|
||||
// === MÉTHODES EXISTANTES AVEC AMÉLIORATIONS ===
|
||||
// === MÉTHODES D'AUTHENTIFICATION ===
|
||||
|
||||
/**
|
||||
* Connexion utilisateur
|
||||
@ -149,16 +141,16 @@ export class AuthService {
|
||||
).pipe(
|
||||
tap(response => {
|
||||
this.handleLoginSuccess(response);
|
||||
this.loadUserProfile().subscribe(); // Charger le profil après connexion
|
||||
this.loadUserProfile().subscribe();
|
||||
}),
|
||||
catchError(error => this.handleLoginError(error))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rafraîchissement du token
|
||||
* Rafraîchissement du token d'accès
|
||||
*/
|
||||
refreshToken(): Observable<LoginResponseDto> {
|
||||
refreshAccessToken(): Observable<LoginResponseDto> {
|
||||
const refreshToken = this.getRefreshToken();
|
||||
|
||||
if (!refreshToken) {
|
||||
@ -169,9 +161,7 @@ export class AuthService {
|
||||
`${environment.iamApiUrl}/auth/refresh`,
|
||||
{ refresh_token: refreshToken }
|
||||
).pipe(
|
||||
tap(response => {
|
||||
this.handleLoginSuccess(response);
|
||||
}),
|
||||
tap(response => this.handleLoginSuccess(response)),
|
||||
catchError(error => {
|
||||
this.clearAuthData();
|
||||
return throwError(() => error);
|
||||
@ -187,11 +177,9 @@ export class AuthService {
|
||||
`${environment.iamApiUrl}/auth/logout`,
|
||||
{}
|
||||
).pipe(
|
||||
tap(() => {
|
||||
this.clearAuthData();
|
||||
}),
|
||||
tap(() => this.clearAuthData()),
|
||||
catchError(error => {
|
||||
this.clearAuthData(); // Nettoyer même en cas d'erreur
|
||||
this.clearAuthData();
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
@ -200,22 +188,17 @@ export class AuthService {
|
||||
/**
|
||||
* Chargement du profil utilisateur
|
||||
*/
|
||||
loadUserProfile(): Observable<UserProfileDto> {
|
||||
return this.http.get<UserProfileDto>(
|
||||
loadUserProfile(): Observable<BaseUserDto> {
|
||||
return this.http.get<BaseUserDto>(
|
||||
`${environment.iamApiUrl}/auth/profile`
|
||||
).pipe(
|
||||
tap(profile => {
|
||||
this.userProfile$.next(profile);
|
||||
}),
|
||||
catchError(error => {
|
||||
return throwError(() => error);
|
||||
})
|
||||
tap(profile => this.userProfile$.next(profile)),
|
||||
catchError(error => throwError(() => error))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gestion de la connexion réussie
|
||||
*/
|
||||
// === GESTION DE SESSION ===
|
||||
|
||||
private handleLoginSuccess(response: LoginResponseDto): void {
|
||||
if (response.access_token) {
|
||||
localStorage.setItem(this.tokenKey, response.access_token);
|
||||
@ -228,9 +211,6 @@ export class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Nettoyage des données d'authentification
|
||||
*/
|
||||
private clearAuthData(): void {
|
||||
localStorage.removeItem(this.tokenKey);
|
||||
localStorage.removeItem(this.refreshTokenKey);
|
||||
@ -238,9 +218,8 @@ export class AuthService {
|
||||
this.userProfile$.next(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validation du token
|
||||
*/
|
||||
// === VALIDATION DU TOKEN ===
|
||||
|
||||
validateToken(): Observable<TokenValidationResponseDto> {
|
||||
return this.http.get<TokenValidationResponseDto>(
|
||||
`${environment.iamApiUrl}/auth/validate`
|
||||
@ -253,70 +232,193 @@ export class AuthService {
|
||||
return this.authState$.asObservable();
|
||||
}
|
||||
|
||||
getUserProfile(): Observable<UserProfileDto | null> {
|
||||
getUserProfile(): Observable<BaseUserDto | null> {
|
||||
return this.userProfile$.asObservable();
|
||||
}
|
||||
|
||||
// === GESTION DES RÔLES ET TYPES ===
|
||||
|
||||
getCurrentUserRoles(): UserRole[] {
|
||||
const token = this.getAccessToken();
|
||||
if (!token) return [];
|
||||
|
||||
try {
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
|
||||
// Mapping des rôles Keycloak vers vos rôles DCB
|
||||
const roleMappings: { [key: string]: UserRole } = {
|
||||
// Rôles administrateur
|
||||
'admin': UserRole.DCB_ADMIN,
|
||||
'dcb-admin': UserRole.DCB_ADMIN,
|
||||
'administrator': UserRole.DCB_ADMIN,
|
||||
|
||||
// Rôles support
|
||||
'support': UserRole.DCB_SUPPORT,
|
||||
'dcb-support': UserRole.DCB_SUPPORT,
|
||||
|
||||
// Rôles partenaire
|
||||
'partner': UserRole.DCB_PARTNER,
|
||||
'dcb-partner': UserRole.DCB_PARTNER,
|
||||
|
||||
// Rôles admin partenaire
|
||||
'partner-admin': UserRole.DCB_PARTNER_ADMIN,
|
||||
'dcb-partner-admin': UserRole.DCB_PARTNER_ADMIN,
|
||||
|
||||
// Rôles manager partenaire
|
||||
'partner-manager': UserRole.DCB_PARTNER_MANAGER,
|
||||
'dcb-partner-manager': UserRole.DCB_PARTNER_MANAGER,
|
||||
|
||||
// Rôles support partenaire
|
||||
'partner-support': UserRole.DCB_PARTNER_SUPPORT,
|
||||
'dcb-partner-support': UserRole.DCB_PARTNER_SUPPORT,
|
||||
};
|
||||
|
||||
let allRoles: string[] = [];
|
||||
|
||||
// Collecter tous les rôles du token
|
||||
if (payload.resource_access) {
|
||||
Object.values(payload.resource_access).forEach((client: any) => {
|
||||
if (client?.roles) {
|
||||
allRoles = allRoles.concat(client.roles);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (payload.realm_access?.roles) {
|
||||
allRoles = allRoles.concat(payload.realm_access.roles);
|
||||
}
|
||||
|
||||
const mappedRoles = allRoles
|
||||
.map(role => roleMappings[role.toLowerCase()])
|
||||
.filter(role => role !== undefined);
|
||||
|
||||
return mappedRoles;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les rôles de l'utilisateur courant
|
||||
*/
|
||||
getCurrentUserRoles(): string[] {
|
||||
const token = this.getAccessToken();
|
||||
if (!token) return [];
|
||||
* Récupère le rôle principal de l'utilisateur courant
|
||||
*/
|
||||
getCurrentUserRole(): UserRole | null {
|
||||
const roles = this.getCurrentUserRoles();
|
||||
return roles.length > 0 ? roles[0] : null;
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
const decoded: any = payload;
|
||||
/**
|
||||
* Récupère le type d'utilisateur courant
|
||||
*/
|
||||
getCurrentUserType(): UserType | null {
|
||||
const role = this.getCurrentUserRole();
|
||||
if (!role) return null;
|
||||
|
||||
// Récupérer tous les rôles de tous les clients
|
||||
if (decoded.resource_access) {
|
||||
const allRoles: string[] = [];
|
||||
// Déterminer le type d'utilisateur basé sur le rôle
|
||||
const hubRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER];
|
||||
const merchantRoles = [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT];
|
||||
|
||||
Object.values(decoded.resource_access).forEach((client: any) => {
|
||||
if (client?.roles) {
|
||||
allRoles.push(...client.roles);
|
||||
}
|
||||
});
|
||||
|
||||
return [...new Set(allRoles)];
|
||||
if (hubRoles.includes(role)) {
|
||||
return UserType.HUB;
|
||||
} else if (merchantRoles.includes(role)) {
|
||||
return UserType.MERCHANT;
|
||||
}
|
||||
|
||||
return [];
|
||||
} catch {
|
||||
return [];
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Observable de l'état d'authentification
|
||||
*/
|
||||
onAuthState(): Observable<boolean> {
|
||||
return this.authState$.asObservable();
|
||||
}
|
||||
/**
|
||||
* Vérifie si l'utilisateur courant est un utilisateur Hub
|
||||
*/
|
||||
isHubUser(): boolean {
|
||||
return this.getCurrentUserType() === UserType.HUB;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le profil utilisateur
|
||||
*/
|
||||
getProfile(): Observable<any> {
|
||||
return this.getUserProfile();
|
||||
}
|
||||
/**
|
||||
* Vérifie si l'utilisateur courant est un utilisateur Marchand
|
||||
*/
|
||||
isMerchantUser(): boolean {
|
||||
return this.getCurrentUserType() === UserType.MERCHANT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur a un rôle spécifique
|
||||
*/
|
||||
hasRole(role: string): boolean {
|
||||
return this.getCurrentUserRoles().includes(role);
|
||||
}
|
||||
/**
|
||||
* Vérifie si l'utilisateur courant a un rôle spécifique
|
||||
*/
|
||||
hasRole(role: UserRole): boolean {
|
||||
return this.getCurrentUserRoles().includes(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur a un des rôles spécifiés
|
||||
*/
|
||||
hasAnyRole(roles: string[]): boolean {
|
||||
const userRoles = this.getCurrentUserRoles();
|
||||
return roles.some(role => userRoles.includes(role));
|
||||
}
|
||||
/**
|
||||
* Vérifie si l'utilisateur courant a au moins un des rôles spécifiés
|
||||
*/
|
||||
hasAnyRole(roles: UserRole[]): boolean {
|
||||
const userRoles = this.getCurrentUserRoles();
|
||||
return roles.some(role => userRoles.includes(role));
|
||||
}
|
||||
|
||||
// === GETTERS POUR LES TOKENS ===
|
||||
/**
|
||||
* Vérifie si l'utilisateur courant peut gérer les utilisateurs Hub
|
||||
*/
|
||||
canManageHubUsers(): boolean {
|
||||
const hubAdminRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT];
|
||||
return this.hasAnyRole(hubAdminRoles);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur courant peut gérer les utilisateurs Marchands
|
||||
*/
|
||||
canManageMerchantUsers(): boolean {
|
||||
const allowedRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER];
|
||||
return this.hasAnyRole(allowedRoles);
|
||||
}
|
||||
|
||||
// === MÉTHODES UTILITAIRES ===
|
||||
|
||||
onAuthState(): Observable<boolean> {
|
||||
return this.authState$.asObservable();
|
||||
}
|
||||
|
||||
getProfile(): Observable<BaseUserDto | null> {
|
||||
return this.getUserProfile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère l'ID de l'utilisateur courant
|
||||
*/
|
||||
getCurrentUserId(): string | null {
|
||||
const profile = this.userProfile$.value;
|
||||
return profile?.id || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère le merchantPartnerId de l'utilisateur courant (si marchand)
|
||||
*/
|
||||
getCurrentMerchantPartnerId(): string | null {
|
||||
const profile = this.userProfile$.value;
|
||||
if (profile && 'merchantPartnerId' in profile) {
|
||||
return (profile as MerchantUserDto).merchantPartnerId || null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si le profil fourni est celui de l'utilisateur courant
|
||||
*/
|
||||
isCurrentUserProfile(userId: string): boolean {
|
||||
const currentUserId = this.getCurrentUserId();
|
||||
return currentUserId === userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur peut visualiser tous les marchands
|
||||
*/
|
||||
canViewAllMerchants(): boolean {
|
||||
const hubAdminRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT];
|
||||
return this.hasAnyRole(hubAdminRoles);
|
||||
}
|
||||
|
||||
// === TOKENS ===
|
||||
|
||||
getAccessToken(): string | null {
|
||||
return localStorage.getItem(this.tokenKey);
|
||||
@ -326,7 +428,7 @@ hasAnyRole(roles: string[]): boolean {
|
||||
return localStorage.getItem(this.refreshTokenKey);
|
||||
}
|
||||
|
||||
// === METHODES PRIVEES ===
|
||||
// === GESTION DES ERREURS ===
|
||||
|
||||
private handleLoginError(error: HttpErrorResponse): Observable<never> {
|
||||
let errorMessage = 'Login failed';
|
||||
@ -342,7 +444,7 @@ hasAnyRole(roles: string[]): boolean {
|
||||
return throwError(() => new Error(errorMessage));
|
||||
}
|
||||
|
||||
// === VERIFICATIONS D'ETAT ===
|
||||
// === VERIFICATIONS ===
|
||||
|
||||
isAuthenticated(): boolean {
|
||||
const token = this.getAccessToken();
|
||||
|
||||
@ -135,7 +135,7 @@ export class MenuService {
|
||||
{
|
||||
label: 'Déconnexion',
|
||||
icon: 'tablerLogout2',
|
||||
class: 'fw-semibold text-danger'
|
||||
url: '/auth/logout',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { UserRole } from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
export interface ModulePermission {
|
||||
module: string;
|
||||
@ -9,193 +10,105 @@ export interface ModulePermission {
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class PermissionsService {
|
||||
private readonly permissions: ModulePermission[] = [
|
||||
// Dashboard
|
||||
// Dashboard - Tout le monde
|
||||
{
|
||||
module: 'dcb-dashboard',
|
||||
roles: [
|
||||
'dcb-admin',
|
||||
'dcb-partner',
|
||||
'dcb-support',
|
||||
'dcb-partner-admin',
|
||||
'dcb-partner-manager',
|
||||
'dcb-partner-support'
|
||||
],
|
||||
roles: this.allRoles,
|
||||
},
|
||||
|
||||
{
|
||||
module: 'auth',
|
||||
roles: [
|
||||
'dcb-admin',
|
||||
'dcb-partner',
|
||||
'dcb-support',
|
||||
'dcb-partner-admin',
|
||||
'dcb-partner-manager',
|
||||
'dcb-partner-support'
|
||||
],
|
||||
roles: this.allRoles,
|
||||
},
|
||||
|
||||
// Transactions
|
||||
{
|
||||
module: 'transactions',
|
||||
roles: [
|
||||
'dcb-admin',
|
||||
'dcb-partner',
|
||||
'dcb-support',
|
||||
'dcb-partner-admin',
|
||||
'dcb-partner-manager',
|
||||
'dcb-partner-support'
|
||||
],
|
||||
roles: this.allRoles,
|
||||
},
|
||||
|
||||
// Merchants/Partners
|
||||
{
|
||||
module: 'merchant-partners',
|
||||
roles: [
|
||||
'dcb-admin',
|
||||
'dcb-partner',
|
||||
'dcb-support',
|
||||
'dcb-partner-admin',
|
||||
'dcb-partner-manager',
|
||||
'dcb-partner-support'],
|
||||
roles: this.allRoles,
|
||||
},
|
||||
|
||||
// Operators (Admin only)
|
||||
// Operators - Admin seulement
|
||||
{
|
||||
module: 'operators',
|
||||
roles: ['dcb-admin'],
|
||||
roles: [UserRole.DCB_ADMIN],
|
||||
children: {
|
||||
'config': ['dcb-admin'],
|
||||
'stats': ['dcb-admin']
|
||||
'config': [UserRole.DCB_ADMIN],
|
||||
'stats': [UserRole.DCB_ADMIN]
|
||||
}
|
||||
},
|
||||
|
||||
// Webhooks
|
||||
// Webhooks - Admin et Partner
|
||||
{
|
||||
module: 'webhooks',
|
||||
roles: ['dcb-admin', 'dcb-partner'],
|
||||
roles: [UserRole.DCB_ADMIN, UserRole.DCB_PARTNER],
|
||||
children: {
|
||||
'history': ['dcb-admin', 'dcb-partner'],
|
||||
'status': ['dcb-admin', 'dcb-partner'],
|
||||
'retry': ['dcb-admin']
|
||||
'history': [UserRole.DCB_ADMIN, UserRole.DCB_PARTNER],
|
||||
'status': [UserRole.DCB_ADMIN, UserRole.DCB_PARTNER],
|
||||
'retry': [UserRole.DCB_ADMIN]
|
||||
}
|
||||
},
|
||||
|
||||
// Users (Admin only)
|
||||
// Users - Admin et Support
|
||||
{
|
||||
module: 'users',
|
||||
roles: ['dcb-admin', 'dcb-support']
|
||||
roles: [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT]
|
||||
},
|
||||
|
||||
// Support (All authenticated users)
|
||||
// Settings - Tout le monde
|
||||
{
|
||||
module: 'settings',
|
||||
roles: [
|
||||
'dcb-admin',
|
||||
'dcb-partner',
|
||||
'dcb-support',
|
||||
'dcb-partner-admin',
|
||||
'dcb-partner-manager',
|
||||
'dcb-partner-support'
|
||||
]
|
||||
roles: this.allRoles
|
||||
},
|
||||
|
||||
// Integrations (Admin only)
|
||||
// Integrations - Admin seulement
|
||||
{
|
||||
module: 'integrations',
|
||||
roles: ['dcb-admin']
|
||||
roles: [UserRole.DCB_ADMIN]
|
||||
},
|
||||
|
||||
// Support (All authenticated users)
|
||||
// Modules publics - Tout le monde
|
||||
{
|
||||
module: 'support',
|
||||
roles: [
|
||||
'dcb-admin',
|
||||
'dcb-partner',
|
||||
'dcb-support',
|
||||
'dcb-partner-admin',
|
||||
'dcb-partner-manager',
|
||||
'dcb-partner-support'
|
||||
]
|
||||
roles: this.allRoles
|
||||
},
|
||||
|
||||
// Profile (All authenticated users)
|
||||
{
|
||||
module: 'profile',
|
||||
roles: [
|
||||
'dcb-admin',
|
||||
'dcb-partner',
|
||||
'dcb-support',
|
||||
'dcb-partner-admin',
|
||||
'dcb-partner-manager',
|
||||
'dcb-partner-support'
|
||||
]
|
||||
roles: this.allRoles
|
||||
},
|
||||
|
||||
// Documentation (All authenticated users)
|
||||
{
|
||||
module: 'documentation',
|
||||
roles: [
|
||||
'dcb-admin',
|
||||
'dcb-partner',
|
||||
'dcb-support',
|
||||
'dcb-partner-admin',
|
||||
'dcb-partner-manager',
|
||||
'dcb-partner-support'
|
||||
]
|
||||
roles: this.allRoles
|
||||
},
|
||||
|
||||
// Help (All authenticated users)
|
||||
{
|
||||
module: 'help',
|
||||
roles: [
|
||||
'dcb-admin',
|
||||
'dcb-partner',
|
||||
'dcb-support',
|
||||
'dcb-partner-admin',
|
||||
'dcb-partner-manager',
|
||||
'dcb-partner-support'
|
||||
]
|
||||
roles: this.allRoles
|
||||
},
|
||||
|
||||
// About (All authenticated users)
|
||||
{
|
||||
module: 'about',
|
||||
roles: [
|
||||
'dcb-admin',
|
||||
'dcb-partner',
|
||||
'dcb-support',
|
||||
'dcb-partner-admin',
|
||||
'dcb-partner-manager',
|
||||
'dcb-partner-support'
|
||||
]
|
||||
roles: this.allRoles
|
||||
}
|
||||
];
|
||||
|
||||
// Tous les rôles DCB
|
||||
private get allRoles(): string[] {
|
||||
return Object.values(UserRole);
|
||||
}
|
||||
|
||||
canAccessModule(modulePath: string, userRoles: string[]): boolean {
|
||||
if (!userRoles || userRoles.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (!userRoles?.length) return false;
|
||||
|
||||
const [mainModule, subModule] = modulePath.split('/');
|
||||
const permission = this.findPermission(mainModule);
|
||||
|
||||
if (!permission) {
|
||||
console.warn(`No permission configuration for module: ${mainModule}`);
|
||||
console.warn(`No permission for module: ${mainModule}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check main module access
|
||||
const hasModuleAccess = this.hasAnyRole(permission.roles, userRoles);
|
||||
if (!hasModuleAccess) return false;
|
||||
// Vérifier accès module principal
|
||||
const hasMainAccess = this.hasAnyRole(permission.roles, userRoles);
|
||||
if (!hasMainAccess) return false;
|
||||
|
||||
// Check sub-module access if specified
|
||||
// Vérifier sous-module si nécessaire
|
||||
if (subModule && permission.children) {
|
||||
const subModuleRoles = permission.children[subModule];
|
||||
if (!subModuleRoles) {
|
||||
console.warn(`No permission configuration for submodule: ${mainModule}/${subModule}`);
|
||||
return false;
|
||||
}
|
||||
return this.hasAnyRole(subModuleRoles, userRoles);
|
||||
const subRoles = permission.children[subModule];
|
||||
if (!subRoles) return false;
|
||||
return this.hasAnyRole(subRoles, userRoles);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -206,12 +119,8 @@ export class PermissionsService {
|
||||
}
|
||||
|
||||
private hasAnyRole(requiredRoles: string[], userRoles: string[]): boolean {
|
||||
return requiredRoles.some(role => userRoles.includes(role));
|
||||
}
|
||||
|
||||
getAccessibleModules(userRoles: string[]): string[] {
|
||||
return this.permissions
|
||||
.filter(permission => this.hasAnyRole(permission.roles, userRoles))
|
||||
.map(permission => permission.module);
|
||||
return requiredRoles.some(required =>
|
||||
userRoles.some(user => user.toLowerCase() === required.toLowerCase())
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
// src/app/core/services/role-management.service.ts
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HubUsersService, UserRole } from '../../modules/users/services/users.service';
|
||||
import { HubUsersService } from '../../modules/hub-users/services/hub-users.service';
|
||||
import { BehaviorSubject, Observable, map, tap, of, catchError } from 'rxjs';
|
||||
import { UserRole } from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
export interface RolePermission {
|
||||
canCreateUsers: boolean;
|
||||
@ -13,6 +13,7 @@ export interface RolePermission {
|
||||
canAccessAdmin: boolean;
|
||||
canAccessSupport: boolean;
|
||||
canAccessPartner: boolean;
|
||||
assignableRoles: UserRole[]; // Ajout de cette propriété
|
||||
}
|
||||
|
||||
// Interface simplifiée pour la réponse API
|
||||
@ -20,6 +21,7 @@ export interface AvailableRoleResponse {
|
||||
value: UserRole;
|
||||
label: string;
|
||||
description: string;
|
||||
allowedForCreation?: boolean;
|
||||
}
|
||||
|
||||
export interface AvailableRolesResponse {
|
||||
@ -85,6 +87,24 @@ export class RoleManagementService {
|
||||
label: 'DCB Partner',
|
||||
description: 'Merchant partner with access to their own merchant ecosystem',
|
||||
permissions: this.getPermissionsForRole(UserRole.DCB_PARTNER)
|
||||
},
|
||||
{
|
||||
value: UserRole.DCB_PARTNER_ADMIN,
|
||||
label: 'Partner Admin',
|
||||
description: 'Administrateur partenaire marchand',
|
||||
permissions: this.getPermissionsForRole(UserRole.DCB_PARTNER_ADMIN)
|
||||
},
|
||||
{
|
||||
value: UserRole.DCB_PARTNER_MANAGER,
|
||||
label: 'Partner Manager',
|
||||
description: 'Manager partenaire marchand',
|
||||
permissions: this.getPermissionsForRole(UserRole.DCB_PARTNER_MANAGER)
|
||||
},
|
||||
{
|
||||
value: UserRole.DCB_PARTNER_SUPPORT,
|
||||
label: 'Partner Support',
|
||||
description: 'Support partenaire marchand',
|
||||
permissions: this.getPermissionsForRole(UserRole.DCB_PARTNER_SUPPORT)
|
||||
}
|
||||
]
|
||||
};
|
||||
@ -114,13 +134,12 @@ export class RoleManagementService {
|
||||
map(response => response.roles.map(role => ({
|
||||
value: role.value,
|
||||
label: role.label,
|
||||
description: role.description
|
||||
description: role.description,
|
||||
allowedForCreation: role.allowedForCreation
|
||||
})))
|
||||
);
|
||||
}
|
||||
|
||||
// ... (le reste des méthodes reste identique)
|
||||
|
||||
/**
|
||||
* Définit le rôle de l'utilisateur courant
|
||||
*/
|
||||
@ -139,6 +158,10 @@ export class RoleManagementService {
|
||||
* Récupère les permissions détaillées selon le rôle
|
||||
*/
|
||||
getPermissionsForRole(role: UserRole): RolePermission {
|
||||
const allRoles = Object.values(UserRole);
|
||||
const hubRoles = [UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER, UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT];
|
||||
const merchantRoles = [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT];
|
||||
|
||||
switch (role) {
|
||||
case UserRole.DCB_ADMIN:
|
||||
return {
|
||||
@ -150,10 +173,53 @@ export class RoleManagementService {
|
||||
canManageMerchants: true,
|
||||
canAccessAdmin: true,
|
||||
canAccessSupport: true,
|
||||
canAccessPartner: true
|
||||
canAccessPartner: true,
|
||||
assignableRoles: allRoles
|
||||
};
|
||||
|
||||
case UserRole.DCB_SUPPORT:
|
||||
return {
|
||||
canCreateUsers: true,
|
||||
canEditUsers: true,
|
||||
canDeleteUsers: false,
|
||||
canManageRoles: true,
|
||||
canViewStats: true,
|
||||
canManageMerchants: true,
|
||||
canAccessAdmin: false,
|
||||
canAccessSupport: true,
|
||||
canAccessPartner: true,
|
||||
assignableRoles: hubRoles,
|
||||
};
|
||||
|
||||
case UserRole.DCB_PARTNER:
|
||||
return {
|
||||
canCreateUsers: true,
|
||||
canEditUsers: true,
|
||||
canDeleteUsers: true,
|
||||
canManageRoles: true,
|
||||
canViewStats: true,
|
||||
canManageMerchants: true,
|
||||
canAccessAdmin: true,
|
||||
canAccessSupport: true,
|
||||
canAccessPartner: true,
|
||||
assignableRoles: merchantRoles
|
||||
};
|
||||
|
||||
case UserRole.DCB_PARTNER_ADMIN:
|
||||
return {
|
||||
canCreateUsers: true,
|
||||
canEditUsers: true,
|
||||
canDeleteUsers: false,
|
||||
canManageRoles: true,
|
||||
canViewStats: true,
|
||||
canManageMerchants: true,
|
||||
canAccessAdmin: false,
|
||||
canAccessSupport: false,
|
||||
canAccessPartner: true,
|
||||
assignableRoles: [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT]
|
||||
};
|
||||
|
||||
case UserRole.DCB_PARTNER_MANAGER:
|
||||
return {
|
||||
canCreateUsers: true,
|
||||
canEditUsers: true,
|
||||
@ -162,21 +228,23 @@ export class RoleManagementService {
|
||||
canViewStats: true,
|
||||
canManageMerchants: true,
|
||||
canAccessAdmin: false,
|
||||
canAccessSupport: true,
|
||||
canAccessPartner: true
|
||||
canAccessSupport: false,
|
||||
canAccessPartner: true,
|
||||
assignableRoles: []
|
||||
};
|
||||
|
||||
case UserRole.DCB_PARTNER:
|
||||
case UserRole.DCB_PARTNER_SUPPORT:
|
||||
return {
|
||||
canCreateUsers: false,
|
||||
canEditUsers: false,
|
||||
canDeleteUsers: false,
|
||||
canManageRoles: false,
|
||||
canViewStats: false,
|
||||
canViewStats: true,
|
||||
canManageMerchants: false,
|
||||
canAccessAdmin: false,
|
||||
canAccessSupport: false,
|
||||
canAccessPartner: true
|
||||
canAccessPartner: false,
|
||||
assignableRoles: []
|
||||
};
|
||||
|
||||
default:
|
||||
@ -185,11 +253,12 @@ export class RoleManagementService {
|
||||
canEditUsers: false,
|
||||
canDeleteUsers: false,
|
||||
canManageRoles: false,
|
||||
canViewStats: false,
|
||||
canManageMerchants: false,
|
||||
canViewStats: true,
|
||||
canManageMerchants: true,
|
||||
canAccessAdmin: false,
|
||||
canAccessSupport: false,
|
||||
canAccessPartner: false
|
||||
canAccessPartner: false,
|
||||
assignableRoles: []
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -200,17 +269,14 @@ export class RoleManagementService {
|
||||
canAssignRole(currentUserRole: UserRole | null, targetRole: UserRole): boolean {
|
||||
if (!currentUserRole) return false;
|
||||
|
||||
// Seuls les admins peuvent attribuer tous les rôles
|
||||
if (currentUserRole === UserRole.DCB_ADMIN) {
|
||||
// SEUL DCB_PARTNER peut attribuer tous les rôles
|
||||
if (currentUserRole === UserRole.DCB_PARTNER, currentUserRole === UserRole.DCB_ADMIN, currentUserRole === UserRole.DCB_SUPPORT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Les supports ne peuvent créer que d'autres supports
|
||||
if (currentUserRole === UserRole.DCB_SUPPORT) {
|
||||
return targetRole === UserRole.DCB_SUPPORT;
|
||||
}
|
||||
|
||||
return false;
|
||||
// Pour les autres rôles, utiliser les permissions définies
|
||||
const permissions = this.getPermissionsForRole(currentUserRole);
|
||||
return permissions.assignableRoles.includes(targetRole);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -280,16 +346,15 @@ export class RoleManagementService {
|
||||
* Récupère le libellé d'un rôle
|
||||
*/
|
||||
getRoleLabel(role: UserRole): string {
|
||||
switch (role) {
|
||||
case UserRole.DCB_ADMIN:
|
||||
return 'Administrateur DCB';
|
||||
case UserRole.DCB_SUPPORT:
|
||||
return 'Support DCB';
|
||||
case UserRole.DCB_PARTNER:
|
||||
return 'Partenaire DCB';
|
||||
default:
|
||||
return 'Rôle inconnu';
|
||||
}
|
||||
const roleLabels: { [key in UserRole]: string } = {
|
||||
[UserRole.DCB_ADMIN]: 'Administrateur DCB',
|
||||
[UserRole.DCB_SUPPORT]: 'Support DCB',
|
||||
[UserRole.DCB_PARTNER]: 'Partenaire DCB',
|
||||
[UserRole.DCB_PARTNER_ADMIN]: 'Admin Partenaire',
|
||||
[UserRole.DCB_PARTNER_MANAGER]: 'Manager Partenaire',
|
||||
[UserRole.DCB_PARTNER_SUPPORT]: 'Support Partenaire'
|
||||
};
|
||||
return roleLabels[role] || 'Rôle inconnu';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -303,9 +368,12 @@ export class RoleManagementService {
|
||||
return 'bg-info';
|
||||
case UserRole.DCB_PARTNER:
|
||||
return 'bg-success';
|
||||
case UserRole.DCB_PARTNER_ADMIN: return 'bg-danger';
|
||||
case UserRole.DCB_PARTNER_MANAGER: return 'bg-success';
|
||||
case UserRole.DCB_PARTNER_SUPPORT: return 'bg-info';
|
||||
case UserRole.DCB_PARTNER_ADMIN:
|
||||
return 'bg-danger';
|
||||
case UserRole.DCB_PARTNER_MANAGER:
|
||||
return 'bg-warning text-dark';
|
||||
case UserRole.DCB_PARTNER_SUPPORT:
|
||||
return 'bg-info text-white';
|
||||
default:
|
||||
return 'bg-secondary';
|
||||
}
|
||||
@ -354,11 +422,19 @@ export class RoleManagementService {
|
||||
return role === UserRole.DCB_PARTNER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un rôle est un rôle marchand
|
||||
*/
|
||||
isMerchantRole(role: UserRole): boolean {
|
||||
const merchantRoles = [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT];
|
||||
return merchantRoles.includes(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère tous les rôles disponibles sous forme de tableau
|
||||
*/
|
||||
getAllRoles(): UserRole[] {
|
||||
return [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER];
|
||||
return Object.values(UserRole);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -367,15 +443,22 @@ export class RoleManagementService {
|
||||
getAssignableRoles(currentUserRole: UserRole | null): UserRole[] {
|
||||
if (!currentUserRole) return [];
|
||||
|
||||
if (currentUserRole === UserRole.DCB_ADMIN) {
|
||||
return this.getAllRoles();
|
||||
}
|
||||
const permissions = this.getPermissionsForRole(currentUserRole);
|
||||
return permissions.assignableRoles;
|
||||
}
|
||||
|
||||
if (currentUserRole === UserRole.DCB_SUPPORT) {
|
||||
return [UserRole.DCB_SUPPORT];
|
||||
}
|
||||
/**
|
||||
* Récupère uniquement les rôles Hub (DCB_ADMIN, DCB_SUPPORT, DCB_PARTNER)
|
||||
*/
|
||||
getHubRoles(): UserRole[] {
|
||||
return [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER];
|
||||
}
|
||||
|
||||
return [];
|
||||
/**
|
||||
* Récupère uniquement les rôles Marchands
|
||||
*/
|
||||
getMerchantRoles(): UserRole[] {
|
||||
return [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -24,24 +24,16 @@
|
||||
<!-- Séparateur -->
|
||||
@if (item.isDivider) {
|
||||
<div class="dropdown-divider"></div>
|
||||
}
|
||||
|
||||
<!-- Élément normal -->
|
||||
@if (!item.isHeader && !item.isDivider) {
|
||||
<a
|
||||
[routerLink]="item.url"
|
||||
class="dropdown-item"
|
||||
[class]="item.class"
|
||||
[class.disabled]="item.isDisabled"
|
||||
[attr.target]="item.target">
|
||||
<ng-icon
|
||||
[name]="item.icon"
|
||||
size="17"
|
||||
class="align-middle d-inline-flex align-items-center me-2"
|
||||
/>
|
||||
<span class="align-middle">{{ item.label }}</span>
|
||||
</a>
|
||||
}
|
||||
} @if (!item.isHeader && !item.isDivider && item.url) {
|
||||
<a [routerLink]="item.url" class="dropdown-item" [class]="item.class">
|
||||
<ng-icon
|
||||
[name]="item.icon"
|
||||
size="17"
|
||||
class="align-middle d-inline-flex align-items-center me-2"
|
||||
/>
|
||||
<span class="align-middle" [innerHTML]="item.label"></span>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -32,7 +32,6 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
ngOnInit() {
|
||||
this.loadDropdownItems()
|
||||
|
||||
// Optionnel : réagir aux changements d'authentification
|
||||
this.subscription = this.authService.onAuthState().subscribe(() => {
|
||||
this.loadDropdownItems()
|
||||
})
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
<span class="d-none d-md-inline-block align-middle">Liste des Utilisateurs</span>
|
||||
</a>
|
||||
<ng-template ngbNavContent>
|
||||
<app-users-list
|
||||
<app-hub-users-list
|
||||
[canCreateUsers]="canCreateUsers"
|
||||
[canDeleteUsers]="canDeleteUsers"
|
||||
(userSelected)="showTab('profile', $event)"
|
||||
@ -75,7 +75,7 @@
|
||||
</a>
|
||||
<ng-template ngbNavContent>
|
||||
@if (selectedUserId) {
|
||||
<app-user-profile
|
||||
<app-hub-user-profile
|
||||
[userId]="selectedUserId"
|
||||
(back)="showTab('list')"
|
||||
/>
|
||||
@ -244,25 +244,28 @@
|
||||
</div>
|
||||
|
||||
<!-- Aperçu du rôle sélectionné -->
|
||||
@if (newUser.role) {
|
||||
<!-- Sélection du partenaire marchand (uniquement pour les rôles marchands) -->
|
||||
@if (newUser.role && isMerchantRole(newUser.role)) {
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info">
|
||||
<div class="d-flex align-items-center">
|
||||
<ng-icon
|
||||
[name]="roleService.getRoleIcon(newUser.role)"
|
||||
class="me-2"
|
||||
></ng-icon>
|
||||
<div>
|
||||
<strong>Rôle sélectionné :</strong>
|
||||
<span class="badge ms-2" [ngClass]="getRoleBadgeClass(newUser.role)">
|
||||
{{ roleService.getRoleLabel(newUser.role) }}
|
||||
</span>
|
||||
<br>
|
||||
<small class="text-muted">
|
||||
{{ getRoleDescription(newUser.role) }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
Partenaire Marchand <span class="text-danger">*</span>
|
||||
</label>
|
||||
<select
|
||||
class="form-select"
|
||||
[(ngModel)]="selectedMerchantPartnerId"
|
||||
name="merchantPartnerId"
|
||||
required
|
||||
[disabled]="creatingUser"
|
||||
>
|
||||
<option value="" disabled>Sélectionnez un partenaire marchand</option>
|
||||
@for (partner of merchantPartners; track partner.id) {
|
||||
<option [value]="partner.id">
|
||||
{{ partner.name }}
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
<div class="form-text">
|
||||
Sélectionnez le partenaire marchand auquel cet utilisateur sera associé
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@ -315,7 +318,7 @@
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
[disabled]="!userForm.form.valid || creatingUser || !canAssignRole(newUser.role)"
|
||||
[disabled]="!userForm.form.valid || creatingUser || !canAssignRole(newUser.role) || (isMerchantRole(newUser.role) && !selectedMerchantPartnerId)"
|
||||
>
|
||||
@if (creatingUser) {
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status">
|
||||
@ -1,5 +1,5 @@
|
||||
import { Routes } from '@angular/router';
|
||||
import { Users } from './users';
|
||||
import { HubUsers } from './hub-users';
|
||||
import { authGuard } from '../../core/guards/auth.guard';
|
||||
import { roleGuard } from '../../core/guards/role.guard';
|
||||
|
||||
@ -7,7 +7,7 @@ export const USERS_ROUTES: Routes = [
|
||||
{
|
||||
path: 'users',
|
||||
canActivate: [authGuard, roleGuard],
|
||||
component: Users,
|
||||
component: HubUsers,
|
||||
data: {
|
||||
title: 'Gestion des Utilisateurs',
|
||||
requiredRoles: ['admin'] // pour information
|
||||
2
src/app/modules/hub-users/hub-users.spec.ts
Normal file
2
src/app/modules/hub-users/hub-users.spec.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { HubUsers } from './hub-users';
|
||||
describe('Users', () => {});
|
||||
@ -1,19 +1,27 @@
|
||||
// src/app/modules/users/users.ts
|
||||
import { Component, inject, OnInit, TemplateRef, ViewChild, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import { NgbNavModule, NgbModal, NgbModalModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { catchError, map, of, Subject, takeUntil } from 'rxjs';
|
||||
import { PageTitle } from '@app/components/page-title/page-title';
|
||||
import { UsersList } from './list/list';
|
||||
import { UserProfile } from './profile/profile';
|
||||
import { HubUsersService, CreateHubUserDto, UserRole, HubUserResponse } from './services/users.service';
|
||||
import { HubUsersList } from './list/list';
|
||||
import { HubUserProfile } from './profile/profile';
|
||||
import { HubUsersService } from './services/hub-users.service';
|
||||
import { RoleManagementService } from '@core/services/role-management.service';
|
||||
import { AuthService } from '@core/services/auth.service';
|
||||
|
||||
import {
|
||||
HubUserDto,
|
||||
CreateUserDto,
|
||||
ResetPasswordDto,
|
||||
UserRole,
|
||||
PaginatedUserResponse,
|
||||
MerchantUserDto,
|
||||
} from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-users',
|
||||
selector: 'app-hub-users',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -22,19 +30,18 @@ import { AuthService } from '@core/services/auth.service';
|
||||
NgbNavModule,
|
||||
NgbModalModule,
|
||||
PageTitle,
|
||||
UsersList,
|
||||
UserProfile
|
||||
HubUsersList,
|
||||
HubUserProfile,
|
||||
],
|
||||
templateUrl: './users.html',
|
||||
templateUrl: './hub-users.html',
|
||||
})
|
||||
export class Users implements OnInit, OnDestroy {
|
||||
export class HubUsers implements OnInit, OnDestroy {
|
||||
private modalService = inject(NgbModal);
|
||||
private usersService = inject(HubUsersService);
|
||||
private authService = inject(AuthService);
|
||||
private cdRef = inject(ChangeDetectorRef);
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
// Rendre le service accessible au template via des méthodes proxy
|
||||
protected roleService = inject(RoleManagementService);
|
||||
|
||||
activeTab: 'list' | 'profile' = 'list';
|
||||
@ -48,7 +55,7 @@ export class Users implements OnInit, OnDestroy {
|
||||
canManageRoles = false;
|
||||
|
||||
// Données pour la création d'utilisateur
|
||||
newUser: CreateHubUserDto = {
|
||||
newUser: CreateUserDto = {
|
||||
username: '',
|
||||
email: '',
|
||||
firstName: '',
|
||||
@ -59,6 +66,10 @@ export class Users implements OnInit, OnDestroy {
|
||||
emailVerified: false
|
||||
};
|
||||
|
||||
// Liste des partenaires marchands (à récupérer depuis votre API)
|
||||
merchantPartners: any[] = [];
|
||||
selectedMerchantPartnerId: string = '';
|
||||
|
||||
availableRoles: { value: UserRole; label: string; description: string }[] = [];
|
||||
assignableRoles: UserRole[] = [];
|
||||
|
||||
@ -66,14 +77,14 @@ export class Users implements OnInit, OnDestroy {
|
||||
createUserError = '';
|
||||
|
||||
// Données pour la réinitialisation de mot de passe
|
||||
selectedUserForReset: HubUserResponse | null = null;
|
||||
selectedUserForReset: HubUserDto | null = null;
|
||||
newPassword = '';
|
||||
temporaryPassword = false;
|
||||
resettingPassword = false;
|
||||
resetPasswordError = '';
|
||||
resetPasswordSuccess = '';
|
||||
|
||||
selectedUserForDelete: HubUserResponse | null = null;
|
||||
selectedUserForDelete: HubUserDto | null = null;
|
||||
deletingUser = false;
|
||||
deleteUserError = '';
|
||||
|
||||
@ -81,6 +92,7 @@ export class Users implements OnInit, OnDestroy {
|
||||
this.activeTab = 'list';
|
||||
this.initializeUserPermissions();
|
||||
this.loadAvailableRoles();
|
||||
this.loadMerchantPartners();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -96,8 +108,7 @@ export class Users implements OnInit, OnDestroy {
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (profile) => {
|
||||
// Supposons que le rôle principal est le premier rôle
|
||||
this.currentUserRole = profile?.roles?.[0] as UserRole || null;
|
||||
this.currentUserRole = profile?.role?.[0] as UserRole || null;
|
||||
|
||||
if (this.currentUserRole) {
|
||||
this.roleService.setCurrentUserRole(this.currentUserRole);
|
||||
@ -106,7 +117,6 @@ export class Users implements OnInit, OnDestroy {
|
||||
this.canDeleteUsers = this.roleService.canDeleteUsers(this.currentUserRole);
|
||||
this.canManageRoles = this.roleService.canManageRoles(this.currentUserRole);
|
||||
|
||||
// Rôles que l'utilisateur peut attribuer
|
||||
this.assignableRoles = this.roleService.getAssignableRoles(this.currentUserRole);
|
||||
}
|
||||
},
|
||||
@ -120,24 +130,58 @@ export class Users implements OnInit, OnDestroy {
|
||||
* Charge les rôles disponibles
|
||||
*/
|
||||
private loadAvailableRoles(): void {
|
||||
this.roleService.getAvailableRolesSimple()
|
||||
this.usersService.getAvailableHubRoles()
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (roles) => {
|
||||
this.availableRoles = roles;
|
||||
next: (response) => {
|
||||
this.availableRoles = response.roles.map(role => ({
|
||||
value: role.value,
|
||||
label: role.label,
|
||||
description: role.description
|
||||
}));
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error loading available roles:', error);
|
||||
// Fallback en cas d'erreur
|
||||
console.error('Error loading available hub roles:', error);
|
||||
this.availableRoles = [
|
||||
{ value: UserRole.DCB_ADMIN, label: 'DCB Admin', description: 'Administrateur système' },
|
||||
{ value: UserRole.DCB_SUPPORT, label: 'DCB Support', description: 'Support technique' },
|
||||
{ value: UserRole.DCB_PARTNER, label: 'DCB Partner', description: 'Partenaire commercial' }
|
||||
{ value: UserRole.DCB_PARTNER, label: 'DCB Partner', description: 'Partenaire commercial' },
|
||||
{ value: UserRole.DCB_PARTNER_ADMIN, label: 'Partner Admin', description: 'Administrateur partenaire' },
|
||||
{ value: UserRole.DCB_PARTNER_MANAGER, label: 'Partner Manager', description: 'Manager partenaire' },
|
||||
{ value: UserRole.DCB_PARTNER_SUPPORT, label: 'Partner Support', description: 'Support partenaire' }
|
||||
];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge la liste des partenaires marchands
|
||||
*/
|
||||
private loadMerchantPartners(): void {
|
||||
this.usersService.findAllMerchantUsers().pipe(
|
||||
map((response: PaginatedUserResponse) => {
|
||||
return response.users as MerchantUserDto[];
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('❌ Error loading all merchant users:', error);
|
||||
return of([]);
|
||||
})
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si un rôle est un rôle marchand
|
||||
*/
|
||||
isMerchantRole(role: UserRole): boolean {
|
||||
const merchantRoles = [
|
||||
UserRole.DCB_PARTNER_ADMIN,
|
||||
UserRole.DCB_PARTNER_MANAGER,
|
||||
UserRole.DCB_PARTNER_SUPPORT
|
||||
];
|
||||
return merchantRoles.includes(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur peut attribuer un rôle spécifique
|
||||
*/
|
||||
@ -163,7 +207,6 @@ export class Users implements OnInit, OnDestroy {
|
||||
return roleInfo?.description || 'Description non disponible';
|
||||
}
|
||||
|
||||
|
||||
showTab(tab: 'list' | 'profile', userId?: string) {
|
||||
this.activeTab = tab;
|
||||
|
||||
@ -203,13 +246,14 @@ export class Users implements OnInit, OnDestroy {
|
||||
enabled: true,
|
||||
emailVerified: false
|
||||
};
|
||||
this.selectedMerchantPartnerId = '';
|
||||
this.createUserError = '';
|
||||
this.openModal(this.createUserModal);
|
||||
}
|
||||
|
||||
// Méthode pour ouvrir le modal de réinitialisation de mot de passe
|
||||
openResetPasswordModal(userId: string) {
|
||||
this.usersService.getUserById(userId)
|
||||
this.usersService.getHubUserById(userId)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (user) => {
|
||||
@ -246,19 +290,34 @@ export class Users implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier merchantPartnerId pour les rôles marchands
|
||||
if (this.isMerchantRole(this.newUser.role) && !this.selectedMerchantPartnerId) {
|
||||
this.createUserError = 'Le partenaire marchand est requis pour les utilisateurs marchands';
|
||||
return;
|
||||
}
|
||||
|
||||
this.creatingUser = true;
|
||||
this.createUserError = '';
|
||||
|
||||
this.usersService.createUser(this.newUser)
|
||||
// Préparer les données pour l'API
|
||||
const userData: CreateUserDto = {
|
||||
...this.newUser
|
||||
};
|
||||
|
||||
// Ajouter merchantPartnerId si c'est un rôle marchand
|
||||
if (this.isMerchantRole(this.newUser.role)) {
|
||||
userData.merchantPartnerId = this.selectedMerchantPartnerId;
|
||||
}
|
||||
|
||||
this.usersService.createHubUser(userData)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (createdUser) => {
|
||||
this.creatingUser = false;
|
||||
this.modalService.dismissAll();
|
||||
|
||||
// Rafraîchir la liste
|
||||
if (this.usersListComponent) {
|
||||
this.usersListComponent.loadUsers();
|
||||
this.usersListComponent.refreshData();
|
||||
}
|
||||
|
||||
this.showTab('list');
|
||||
@ -283,10 +342,14 @@ export class Users implements OnInit, OnDestroy {
|
||||
this.resetPasswordError = '';
|
||||
this.resetPasswordSuccess = '';
|
||||
|
||||
this.usersService.resetPassword(
|
||||
const resetPasswordDto: ResetPasswordDto = {
|
||||
newPassword: this.newPassword,
|
||||
temporary: this.temporaryPassword
|
||||
};
|
||||
|
||||
this.usersService.resetHubUserPassword(
|
||||
this.selectedUserForReset.id,
|
||||
this.newPassword,
|
||||
this.temporaryPassword
|
||||
resetPasswordDto
|
||||
)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
@ -310,7 +373,7 @@ export class Users implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
this.usersService.getUserById(userId)
|
||||
this.usersService.getHubUserById(userId)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (user) => {
|
||||
@ -331,16 +394,15 @@ export class Users implements OnInit, OnDestroy {
|
||||
this.deletingUser = true;
|
||||
this.deleteUserError = '';
|
||||
|
||||
this.usersService.deleteUser(this.selectedUserForDelete.id)
|
||||
this.usersService.deleteHubUser(this.selectedUserForDelete.id)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
this.deletingUser = false;
|
||||
this.modalService.dismissAll();
|
||||
|
||||
// Rafraîchir la liste
|
||||
if (this.usersListComponent) {
|
||||
this.usersListComponent.loadUsers();
|
||||
this.usersListComponent.refreshData();
|
||||
}
|
||||
|
||||
this.cdRef.detectChanges();
|
||||
@ -431,7 +493,7 @@ export class Users implements OnInit, OnDestroy {
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
@ViewChild(UsersList) usersListComponent!: UsersList;
|
||||
@ViewChild(HubUsersList) usersListComponent!: HubUsersList;
|
||||
|
||||
// Références aux templates de modals
|
||||
@ViewChild('createUserModal') createUserModal!: TemplateRef<any>;
|
||||
@ -1,16 +1,22 @@
|
||||
// src/app/modules/users/list/list.ts
|
||||
import { Component, inject, OnInit, Output, EventEmitter, ChangeDetectorRef, Input, OnDestroy } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import { NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { HubUsersService, HubUserResponse, UserRole } from '../services/users.service';
|
||||
import { HubUsersService } from '../services/hub-users.service';
|
||||
import { RoleManagementService } from '@core/services/role-management.service';
|
||||
import { UiCard } from '@app/components/ui-card';
|
||||
|
||||
import {
|
||||
HubUserDto,
|
||||
PaginatedUserResponse,
|
||||
UserRole,
|
||||
UserType
|
||||
} from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-users-list',
|
||||
selector: 'app-hub-users-list',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -21,13 +27,14 @@ import { UiCard } from '@app/components/ui-card';
|
||||
],
|
||||
templateUrl: './list.html',
|
||||
})
|
||||
export class UsersList implements OnInit, OnDestroy {
|
||||
export class HubUsersList implements OnInit, OnDestroy {
|
||||
private usersService = inject(HubUsersService);
|
||||
private roleService = inject(RoleManagementService);
|
||||
private cdRef = inject(ChangeDetectorRef);
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
readonly UserRole = UserRole;
|
||||
readonly UserType = UserType;
|
||||
|
||||
@Input() canCreateUsers: boolean = false;
|
||||
@Input() canDeleteUsers: boolean = false;
|
||||
@ -38,9 +45,9 @@ export class UsersList implements OnInit, OnDestroy {
|
||||
@Output() openDeleteUserModal = new EventEmitter<string>();
|
||||
|
||||
// Données
|
||||
allUsers: HubUserResponse[] = [];
|
||||
filteredUsers: HubUserResponse[] = [];
|
||||
displayedUsers: HubUserResponse[] = [];
|
||||
allUsers: HubUserDto[] = [];
|
||||
filteredUsers: HubUserDto[] = [];
|
||||
displayedUsers: HubUserDto[] = [];
|
||||
|
||||
// États
|
||||
loading = false;
|
||||
@ -59,15 +66,15 @@ export class UsersList implements OnInit, OnDestroy {
|
||||
totalPages = 0;
|
||||
|
||||
// Tri
|
||||
sortField: keyof HubUserResponse = 'username';
|
||||
sortField: keyof HubUserDto = 'username';
|
||||
sortDirection: 'asc' | 'desc' = 'asc';
|
||||
|
||||
// Rôles disponibles pour le filtre
|
||||
availableRoles = [
|
||||
{ value: 'all' as const, label: 'Tous les rôles' },
|
||||
{ value: UserRole.DCB_ADMIN, label: 'Administrateurs' },
|
||||
{ value: UserRole.DCB_SUPPORT, label: 'Support' },
|
||||
{ value: UserRole.DCB_PARTNER, label: 'Partenaires' }
|
||||
{ value: UserRole.DCB_ADMIN, label: 'Administrateurs DCB' },
|
||||
{ value: UserRole.DCB_SUPPORT, label: 'Support DCB' },
|
||||
{ value: UserRole.DCB_PARTNER, label: 'Partenaires DCB' }
|
||||
];
|
||||
|
||||
ngOnInit() {
|
||||
@ -83,20 +90,22 @@ export class UsersList implements OnInit, OnDestroy {
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
|
||||
this.usersService.findAllUsers()
|
||||
this.usersService.getHubUsers(this.currentPage, this.itemsPerPage)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
this.allUsers = response.users;
|
||||
next: (response: PaginatedUserResponse) => {
|
||||
this.allUsers = response.users as HubUserDto[];
|
||||
this.totalItems = response.total;
|
||||
this.totalPages = response.totalPages;
|
||||
this.applyFiltersAndPagination();
|
||||
this.loading = false;
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
this.error = 'Erreur lors du chargement des utilisateurs';
|
||||
this.error = 'Erreur lors du chargement des utilisateurs Hub';
|
||||
this.loading = false;
|
||||
this.cdRef.detectChanges();
|
||||
console.error('Error loading users:', error);
|
||||
console.error('Error loading hub users:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -172,7 +181,7 @@ export class UsersList implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
// Tri
|
||||
sort(field: keyof HubUserResponse) {
|
||||
sort(field: keyof HubUserDto) {
|
||||
if (this.sortField === field) {
|
||||
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
@ -182,7 +191,7 @@ export class UsersList implements OnInit, OnDestroy {
|
||||
this.applyFiltersAndPagination();
|
||||
}
|
||||
|
||||
getSortIcon(field: keyof HubUserResponse): string {
|
||||
getSortIcon(field: keyof HubUserDto): string {
|
||||
if (this.sortField !== field) return 'lucideArrowUpDown';
|
||||
return this.sortDirection === 'asc' ? 'lucideArrowUp' : 'lucideArrowDown';
|
||||
}
|
||||
@ -207,45 +216,53 @@ export class UsersList implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
// Méthode pour réinitialiser le mot de passe
|
||||
resetPassword(user: HubUserResponse) {
|
||||
resetPassword(user: HubUserDto) {
|
||||
this.openResetPasswordModal.emit(user.id);
|
||||
}
|
||||
|
||||
// Méthode pour ouvrir le modal de suppression
|
||||
deleteUser(user: HubUserResponse) {
|
||||
deleteUser(user: HubUserDto) {
|
||||
if (this.canDeleteUsers) {
|
||||
this.openDeleteUserModal.emit(user.id);
|
||||
}
|
||||
}
|
||||
|
||||
enableUser(user: HubUserResponse) {
|
||||
this.usersService.enableUser(user.id)
|
||||
enableUser(user: HubUserDto) {
|
||||
this.usersService.enableHubUser(user.id)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
user.enabled = true;
|
||||
next: (updatedUser) => {
|
||||
// Mettre à jour l'utilisateur dans la liste
|
||||
const index = this.allUsers.findIndex(u => u.id === user.id);
|
||||
if (index !== -1) {
|
||||
this.allUsers[index] = updatedUser;
|
||||
}
|
||||
this.applyFiltersAndPagination();
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error enabling user:', error);
|
||||
console.error('Error enabling hub user:', error);
|
||||
this.error = 'Erreur lors de l\'activation de l\'utilisateur';
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
disableUser(user: HubUserResponse) {
|
||||
this.usersService.disableUser(user.id)
|
||||
disableUser(user: HubUserDto) {
|
||||
this.usersService.disableHubUser(user.id)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
user.enabled = false;
|
||||
next: (updatedUser) => {
|
||||
// Mettre à jour l'utilisateur dans la liste
|
||||
const index = this.allUsers.findIndex(u => u.id === user.id);
|
||||
if (index !== -1) {
|
||||
this.allUsers[index] = updatedUser;
|
||||
}
|
||||
this.applyFiltersAndPagination();
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) =>{
|
||||
console.error('Error disabling user:', error);
|
||||
error: (error) => {
|
||||
console.error('Error disabling hub user:', error);
|
||||
this.error = 'Erreur lors de la désactivation de l\'utilisateur';
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
@ -253,13 +270,13 @@ export class UsersList implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
// Utilitaires d'affichage
|
||||
getStatusBadgeClass(user: HubUserResponse): string {
|
||||
getStatusBadgeClass(user: HubUserDto): string {
|
||||
if (!user.enabled) return 'badge bg-danger';
|
||||
if (!user.emailVerified) return 'badge bg-warning';
|
||||
return 'badge bg-success';
|
||||
}
|
||||
|
||||
getStatusText(user: HubUserResponse): string {
|
||||
getStatusText(user: HubUserDto): string {
|
||||
if (!user.enabled) return 'Désactivé';
|
||||
if (!user.emailVerified) return 'Email non vérifié';
|
||||
return 'Actif';
|
||||
@ -288,11 +305,11 @@ export class UsersList implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
getUserInitials(user: HubUserResponse): string {
|
||||
getUserInitials(user: HubUserDto): string {
|
||||
return (user.firstName?.charAt(0) || '') + (user.lastName?.charAt(0) || '') || 'U';
|
||||
}
|
||||
|
||||
getUserDisplayName(user: HubUserResponse): string {
|
||||
getUserDisplayName(user: HubUserDto): string {
|
||||
if (user.firstName && user.lastName) {
|
||||
return `${user.firstName} ${user.lastName}`;
|
||||
}
|
||||
@ -300,9 +317,6 @@ export class UsersList implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
// Statistiques
|
||||
/**
|
||||
* Récupère le nombre d'utilisateurs par rôle (méthode publique pour le template)
|
||||
*/
|
||||
getUsersCountByRole(role: UserRole): number {
|
||||
return this.allUsers.filter(user => user.role === role).length;
|
||||
}
|
||||
@ -315,11 +329,12 @@ export class UsersList implements OnInit, OnDestroy {
|
||||
return this.allUsers.filter(user => !user.enabled).length;
|
||||
}
|
||||
|
||||
// Vérification des permissions pour les actions
|
||||
canManageUser(user: HubUserResponse): boolean {
|
||||
// Implémentez votre logique de permission ici
|
||||
// Par exemple, empêcher un utilisateur de se modifier lui-même
|
||||
return true;
|
||||
getEmailVerifiedCount(): number {
|
||||
return this.allUsers.filter(user => user.emailVerified).length;
|
||||
}
|
||||
|
||||
getEmailNotVerifiedCount(): number {
|
||||
return this.allUsers.filter(user => !user.emailVerified).length;
|
||||
}
|
||||
|
||||
// Recherche rapide par rôle
|
||||
@ -328,4 +343,30 @@ export class UsersList implements OnInit, OnDestroy {
|
||||
this.currentPage = 1;
|
||||
this.applyFiltersAndPagination();
|
||||
}
|
||||
|
||||
// Recharger les données
|
||||
refreshData() {
|
||||
this.loadUsers();
|
||||
}
|
||||
|
||||
// Méthode pour charger plus d'utilisateurs (scroll infini optionnel)
|
||||
loadMoreUsers() {
|
||||
if (this.currentPage < this.totalPages) {
|
||||
this.currentPage++;
|
||||
this.usersService.getHubUsers(this.currentPage, this.itemsPerPage)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (response: PaginatedUserResponse) => {
|
||||
this.allUsers = [...this.allUsers, ...(response.users as HubUserDto[])];
|
||||
this.applyFiltersAndPagination();
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error loading more hub users:', error);
|
||||
this.error = 'Erreur lors du chargement des utilisateurs supplémentaires';
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
115
src/app/modules/hub-users/models/hub-user.model.ts
Normal file
115
src/app/modules/hub-users/models/hub-user.model.ts
Normal file
@ -0,0 +1,115 @@
|
||||
// src/app/core/models/user.model.ts
|
||||
|
||||
export enum UserType {
|
||||
HUB = 'HUB',
|
||||
MERCHANT = 'MERCHANT',
|
||||
MERCHANT_USER = 'MERCHANT_USER'
|
||||
}
|
||||
|
||||
export enum UserRole {
|
||||
// HUB roles
|
||||
DCB_ADMIN = 'DCB_ADMIN',
|
||||
DCB_SUPPORT = 'DCB_SUPPORT',
|
||||
DCB_PARTNER = 'DCB_PARTNER',
|
||||
|
||||
// MERCHANT roles
|
||||
DCB_PARTNER_ADMIN = 'DCB_PARTNER_ADMIN',
|
||||
DCB_PARTNER_MANAGER = 'DCB_PARTNER_MANAGER',
|
||||
DCB_PARTNER_SUPPORT = 'DCB_PARTNER_SUPPORT'
|
||||
}
|
||||
|
||||
// === BASE USER MODEL ===
|
||||
export interface BaseUserDto {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: UserRole;
|
||||
enabled: boolean;
|
||||
emailVerified: boolean;
|
||||
createdBy: string;
|
||||
createdByUsername: string;
|
||||
createdTimestamp: number;
|
||||
lastLogin?: number;
|
||||
userType: UserType;
|
||||
}
|
||||
|
||||
// === EXTENSIONS ===
|
||||
export interface HubUserDto extends BaseUserDto {
|
||||
userType: UserType.HUB;
|
||||
}
|
||||
|
||||
export interface MerchantUserDto extends BaseUserDto {
|
||||
userType: UserType.MERCHANT;
|
||||
merchantPartnerId: string;
|
||||
}
|
||||
|
||||
// === DTOs CRUD ===
|
||||
export interface CreateUserDto {
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
password: string;
|
||||
role: UserRole;
|
||||
enabled?: boolean;
|
||||
emailVerified?: boolean;
|
||||
merchantPartnerId?: string; // obligatoire si MERCHANT
|
||||
}
|
||||
|
||||
export interface UpdateUserDto {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ResetPasswordDto {
|
||||
userId?: string;
|
||||
newPassword: string;
|
||||
temporary?: boolean;
|
||||
}
|
||||
|
||||
// === PAGINATION / STATS ===
|
||||
export interface PaginatedUserResponse {
|
||||
users: BaseUserDto[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface MerchantPartnerStatsResponse {
|
||||
totalAdmins: number;
|
||||
totalManagers: number;
|
||||
totalSupport: number;
|
||||
totalUsers: number;
|
||||
activeUsers: number;
|
||||
inactiveUsers: number;
|
||||
}
|
||||
|
||||
// === ROLES ===
|
||||
export interface AvailableRole {
|
||||
value: UserRole;
|
||||
label: string;
|
||||
description: string;
|
||||
allowedForCreation: boolean;
|
||||
}
|
||||
|
||||
export interface AvailableRolesResponse {
|
||||
roles: AvailableRole[];
|
||||
}
|
||||
|
||||
export interface RoleOperationResponse {
|
||||
message: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// === SEARCH ===
|
||||
export interface SearchUsersParams {
|
||||
query?: string;
|
||||
role?: UserRole;
|
||||
enabled?: boolean;
|
||||
userType?: UserType;
|
||||
}
|
||||
@ -5,12 +5,18 @@ import { FormsModule } from '@angular/forms';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import { NgbAlertModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { HubUsersService, HubUserResponse, UserRole, UpdateHubUserDto } from '../services/users.service';
|
||||
import { HubUsersService } from '../services/hub-users.service';
|
||||
import { RoleManagementService } from '@core/services/role-management.service';
|
||||
import { AuthService } from '@core/services/auth.service';
|
||||
|
||||
import {
|
||||
HubUserDto,
|
||||
UpdateUserDto,
|
||||
UserRole
|
||||
} from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-profile',
|
||||
selector: 'app-hub-user-profile',
|
||||
standalone: true,
|
||||
imports: [CommonModule, FormsModule, NgIcon, NgbAlertModule],
|
||||
templateUrl: './profile.html',
|
||||
@ -24,7 +30,7 @@ import { AuthService } from '@core/services/auth.service';
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class UserProfile implements OnInit, OnDestroy {
|
||||
export class HubUserProfile implements OnInit, OnDestroy {
|
||||
private usersService = inject(HubUsersService);
|
||||
private roleService = inject(RoleManagementService);
|
||||
private authService = inject(AuthService);
|
||||
@ -35,7 +41,7 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
@Output() back = new EventEmitter<void>();
|
||||
@Output() openResetPasswordModal = new EventEmitter<string>();
|
||||
|
||||
user: HubUserResponse | null = null;
|
||||
user: HubUserDto | null = null;
|
||||
loading = false;
|
||||
saving = false;
|
||||
error = '';
|
||||
@ -49,7 +55,7 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
|
||||
// Édition
|
||||
isEditing = false;
|
||||
editedUser: UpdateHubUserDto = {};
|
||||
editedUser: UpdateUserDto = {};
|
||||
|
||||
// Gestion des rôles
|
||||
availableRoles: { value: UserRole; label: string; description: string }[] = [];
|
||||
@ -76,7 +82,7 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (profile) => {
|
||||
this.currentUserRole = profile?.roles?.[0] as UserRole || null;
|
||||
this.currentUserRole = profile?.role?.[0] as UserRole || null;
|
||||
if (this.currentUserRole) {
|
||||
this.canEditUsers = this.roleService.canEditUsers(this.currentUserRole);
|
||||
this.canManageRoles = this.roleService.canManageRoles(this.currentUserRole);
|
||||
@ -93,14 +99,18 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
* Charge les rôles disponibles
|
||||
*/
|
||||
private loadAvailableRoles(): void {
|
||||
this.roleService.getAvailableRolesSimple()
|
||||
this.usersService.getAvailableHubRoles()
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (roles) => {
|
||||
this.availableRoles = roles;
|
||||
next: (response) => {
|
||||
this.availableRoles = response.roles.map(role => ({
|
||||
value: role.value,
|
||||
label: role.label,
|
||||
description: role.description
|
||||
}));
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error loading available roles:', error);
|
||||
console.error('Error loading available hub roles:', error);
|
||||
// Fallback
|
||||
this.availableRoles = [
|
||||
{ value: UserRole.DCB_ADMIN, label: 'DCB Admin', description: 'Administrateur système' },
|
||||
@ -115,7 +125,7 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
|
||||
this.usersService.getUserById(this.userId)
|
||||
this.usersService.getHubUserById(this.userId)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (user) => {
|
||||
@ -124,10 +134,10 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
this.error = 'Erreur lors du chargement du profil utilisateur';
|
||||
this.error = 'Erreur lors du chargement du profil utilisateur Hub';
|
||||
this.loading = false;
|
||||
this.cdRef.detectChanges();
|
||||
console.error('Error loading user profile:', error);
|
||||
console.error('Error loading hub user profile:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -163,7 +173,7 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
this.error = '';
|
||||
this.success = '';
|
||||
|
||||
this.usersService.updateUser(this.user.id, this.editedUser)
|
||||
this.usersService.updateHubUser(this.user.id, this.editedUser)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (updatedUser) => {
|
||||
@ -192,11 +202,18 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier que c'est un rôle Hub valide
|
||||
const hubRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER];
|
||||
if (!hubRoles.includes(newRole)) {
|
||||
this.error = 'Rôle Hub invalide';
|
||||
return;
|
||||
}
|
||||
|
||||
this.updatingRoles = true;
|
||||
this.error = '';
|
||||
this.success = '';
|
||||
|
||||
this.usersService.updateUserRole(this.user.id, newRole)
|
||||
this.usersService.updateHubUserRole(this.user.id, newRole)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (updatedUser) => {
|
||||
@ -217,12 +234,12 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
enableUser() {
|
||||
if (!this.user || !this.canEditUsers) return;
|
||||
|
||||
this.usersService.enableUser(this.user.id)
|
||||
this.usersService.enableHubUser(this.user.id)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (updatedUser) => {
|
||||
this.user = updatedUser;
|
||||
this.success = 'Utilisateur activé avec succès';
|
||||
this.success = 'Utilisateur Hub activé avec succès';
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
@ -235,12 +252,12 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
disableUser() {
|
||||
if (!this.user || !this.canEditUsers) return;
|
||||
|
||||
this.usersService.disableUser(this.user.id)
|
||||
this.usersService.disableHubUser(this.user.id)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (updatedUser) => {
|
||||
this.user = updatedUser;
|
||||
this.success = 'Utilisateur désactivé avec succès';
|
||||
this.success = 'Utilisateur Hub désactivé avec succès';
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
@ -266,7 +283,7 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
return 'Vous n\'avez pas les permissions nécessaires pour cette action';
|
||||
}
|
||||
if (error.status === 404) {
|
||||
return 'Utilisateur non trouvé';
|
||||
return 'Utilisateur Hub non trouvé';
|
||||
}
|
||||
if (error.status === 400) {
|
||||
return 'Données invalides';
|
||||
@ -306,7 +323,7 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getUserDisplayName(): string {
|
||||
if (!this.user) return 'Utilisateur';
|
||||
if (!this.user) return 'Utilisateur Hub';
|
||||
if (this.user.firstName && this.user.lastName) {
|
||||
return `${this.user.firstName} ${this.user.lastName}`;
|
||||
}
|
||||
@ -337,7 +354,25 @@ export class UserProfile implements OnInit, OnDestroy {
|
||||
|
||||
// Vérifie si c'est le profil de l'utilisateur courant
|
||||
isCurrentUserProfile(): boolean {
|
||||
// Implémentez cette logique selon votre système d'authentification
|
||||
return false;
|
||||
if (!this.user || !this.user.id) return false;
|
||||
return this.authService.isCurrentUserProfile(this.user.id);
|
||||
}
|
||||
|
||||
// Vérifier les permissions
|
||||
canEditUser(): boolean {
|
||||
if (this.isCurrentUserProfile()) return true; // Toujours éditer son profil
|
||||
return this.authService.canManageHubUsers();
|
||||
}
|
||||
|
||||
// Méthode pour obtenir les rôles Hub assignables
|
||||
getAssignableHubRoles(): UserRole[] {
|
||||
const hubRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER];
|
||||
return hubRoles.filter(role => this.canAssignRole(role));
|
||||
}
|
||||
|
||||
// Vérifie si un rôle est un rôle Hub
|
||||
isHubRole(role: UserRole): boolean {
|
||||
const hubRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER];
|
||||
return hubRoles.includes(role);
|
||||
}
|
||||
}
|
||||
361
src/app/modules/hub-users/services/hub-users.service.ts
Normal file
361
src/app/modules/hub-users/services/hub-users.service.ts
Normal file
@ -0,0 +1,361 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { environment } from '@environments/environment';
|
||||
import { Observable, map, catchError, throwError, of } from 'rxjs';
|
||||
|
||||
import {
|
||||
HubUserDto,
|
||||
CreateUserDto,
|
||||
UpdateUserDto,
|
||||
ResetPasswordDto,
|
||||
PaginatedUserResponse,
|
||||
AvailableRolesResponse,
|
||||
SearchUsersParams,
|
||||
UserRole,
|
||||
UserType,
|
||||
MerchantUserDto
|
||||
} from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HubUsersService {
|
||||
private http = inject(HttpClient);
|
||||
private apiUrl = `${environment.iamApiUrl}/hub-users`;
|
||||
|
||||
// === CRÉATION ===
|
||||
|
||||
/**
|
||||
* Crée un nouvel utilisateur Hub
|
||||
*/
|
||||
createHubUser(createUserDto: CreateUserDto): Observable<HubUserDto> {
|
||||
// Validation pour les utilisateurs Hub
|
||||
if (!createUserDto.username?.trim()) {
|
||||
return throwError(() => 'Username is required and cannot be empty');
|
||||
}
|
||||
|
||||
if (!createUserDto.email?.trim()) {
|
||||
return throwError(() => 'Email is required and cannot be empty');
|
||||
}
|
||||
|
||||
if (!createUserDto.password || createUserDto.password.length < 8) {
|
||||
return throwError(() => 'Password must be at least 8 characters');
|
||||
}
|
||||
|
||||
if (!createUserDto.role) {
|
||||
return throwError(() => 'Role is required');
|
||||
}
|
||||
|
||||
// Vérification que le rôle est bien un rôle Hub
|
||||
const hubRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER];
|
||||
if (!hubRoles.includes(createUserDto.role)) {
|
||||
return throwError(() => 'Invalid role for Hub user');
|
||||
}
|
||||
|
||||
// Nettoyage des données
|
||||
const payload = {
|
||||
...createUserDto,
|
||||
username: createUserDto.username.trim(),
|
||||
email: createUserDto.email.trim(),
|
||||
firstName: (createUserDto.firstName || '').trim(),
|
||||
lastName: (createUserDto.lastName || '').trim(),
|
||||
enabled: createUserDto.enabled !== undefined ? createUserDto.enabled : true,
|
||||
emailVerified: createUserDto.emailVerified !== undefined ? createUserDto.emailVerified : false,
|
||||
};
|
||||
|
||||
return this.http.post<HubUserDto>(this.apiUrl, payload).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error creating hub user:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === LECTURE ===
|
||||
|
||||
/**
|
||||
* Récupère tous les utilisateurs Hub avec pagination
|
||||
*/
|
||||
getHubUsers(page: number = 1, limit: number = 10, filters?: SearchUsersParams): Observable<PaginatedUserResponse> {
|
||||
let params = new HttpParams()
|
||||
.set('page', page.toString())
|
||||
.set('limit', limit.toString())
|
||||
.set('userType', UserType.HUB);
|
||||
|
||||
if (filters) {
|
||||
Object.keys(filters).forEach(key => {
|
||||
if (filters[key as keyof SearchUsersParams] !== undefined && filters[key as keyof SearchUsersParams] !== null) {
|
||||
params = params.set(key, filters[key as keyof SearchUsersParams]!.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return this.http.get<HubUserDto[]>(this.apiUrl, { params, observe: 'response' }).pipe(
|
||||
map(response => {
|
||||
const users = response.body || [];
|
||||
const total = parseInt(response.headers.get('X-Total-Count') || '0');
|
||||
|
||||
return {
|
||||
users,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit)
|
||||
};
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error loading hub users:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all merchant partners
|
||||
*/
|
||||
/**
|
||||
* Récupère tous les utilisateurs Hub avec pagination
|
||||
*/
|
||||
findAllMerchantUsers(page: number = 1, limit: number = 10, filters?: any): Observable<PaginatedUserResponse> {
|
||||
let params = new HttpParams()
|
||||
.set('page', page.toString())
|
||||
.set('limit', limit.toString());
|
||||
|
||||
if (filters) {
|
||||
Object.keys(filters).forEach(key => {
|
||||
if (filters[key] !== undefined && filters[key] !== null) {
|
||||
params = params.set(key, filters[key].toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return this.http.get<MerchantUserDto[]>(`${this.apiUrl}/merchants/all`, { params, observe: 'response' }).pipe(
|
||||
map(response => {
|
||||
const users = response.body || [];
|
||||
const total = parseInt(response.headers.get('X-Total-Count') || '0');
|
||||
|
||||
return {
|
||||
users,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit)
|
||||
};
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error loading users:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère un utilisateur Hub par ID
|
||||
*/
|
||||
getHubUserById(id: string): Observable<HubUserDto> {
|
||||
return this.http.get<HubUserDto>(`${this.apiUrl}/${id}`).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error loading hub user ${id}:`, error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === MISE À JOUR ===
|
||||
|
||||
/**
|
||||
* Met à jour un utilisateur Hub
|
||||
*/
|
||||
updateHubUser(id: string, updateUserDto: UpdateUserDto): Observable<HubUserDto> {
|
||||
return this.http.put<HubUserDto>(`${this.apiUrl}/${id}`, updateUserDto).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error updating hub user ${id}:`, error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour le rôle d'un utilisateur Hub
|
||||
*/
|
||||
updateHubUserRole(id: string, role: UserRole): Observable<HubUserDto> {
|
||||
const hubRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER];
|
||||
if (!hubRoles.includes(role)) {
|
||||
return throwError(() => 'Invalid role for Hub user');
|
||||
}
|
||||
|
||||
return this.http.put<HubUserDto>(`${this.apiUrl}/${id}/role`, { role }).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error updating role for hub user ${id}:`, error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === SUPPRESSION ===
|
||||
|
||||
/**
|
||||
* Supprime un utilisateur Hub
|
||||
*/
|
||||
deleteHubUser(id: string): Observable<{ message: string }> {
|
||||
return this.http.delete<{ message: string }>(`${this.apiUrl}/${id}`).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error deleting hub user ${id}:`, error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === GESTION DES MOTS DE PASSE ===
|
||||
|
||||
/**
|
||||
* Réinitialise le mot de passe d'un utilisateur Hub
|
||||
*/
|
||||
resetHubUserPassword(id: string, resetPasswordDto: ResetPasswordDto): Observable<{ message: string }> {
|
||||
return this.http.post<{ message: string }>(
|
||||
`${this.apiUrl}/${id}/reset-password`,
|
||||
resetPasswordDto
|
||||
).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error resetting password for hub user ${id}:`, error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie un email de réinitialisation de mot de passe
|
||||
*/
|
||||
sendHubUserPasswordResetEmail(id: string): Observable<{ message: string }> {
|
||||
return this.http.post<{ message: string }>(
|
||||
`${this.apiUrl}/${id}/send-reset-email`,
|
||||
{}
|
||||
).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error sending reset email for hub user ${id}:`, error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === GESTION DU STATUT ===
|
||||
|
||||
/**
|
||||
* Active un utilisateur Hub
|
||||
*/
|
||||
enableHubUser(id: string): Observable<HubUserDto> {
|
||||
return this.updateHubUser(id, { enabled: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Désactive un utilisateur Hub
|
||||
*/
|
||||
disableHubUser(id: string): Observable<HubUserDto> {
|
||||
return this.updateHubUser(id, { enabled: false });
|
||||
}
|
||||
|
||||
// === GESTION DES RÔLES ===
|
||||
|
||||
/**
|
||||
* Récupère les rôles Hub disponibles
|
||||
*/
|
||||
getAvailableHubRoles(): Observable<AvailableRolesResponse> {
|
||||
return this.http.get<AvailableRolesResponse>(`${this.apiUrl}/roles/available`).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error loading available hub roles:', error);
|
||||
// Fallback en cas d'erreur
|
||||
return of({
|
||||
roles: [
|
||||
{
|
||||
value: UserRole.DCB_ADMIN,
|
||||
label: 'DCB Admin',
|
||||
description: 'Full administrative access to the entire system',
|
||||
allowedForCreation: true
|
||||
},
|
||||
{
|
||||
value: UserRole.DCB_SUPPORT,
|
||||
label: 'DCB Support',
|
||||
description: 'Support access with limited administrative capabilities',
|
||||
allowedForCreation: true
|
||||
},
|
||||
{
|
||||
value: UserRole.DCB_PARTNER,
|
||||
label: 'DCB Partner',
|
||||
description: 'Partner access to merchant management',
|
||||
allowedForCreation: true
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les utilisateurs par rôle spécifique
|
||||
*/
|
||||
getHubUsersByRole(role: UserRole): Observable<HubUserDto[]> {
|
||||
return this.http.get<HubUserDto[]>(`${this.apiUrl}/role/${role}`).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error loading hub users with role ${role}:`, error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === RECHERCHE ===
|
||||
|
||||
/**
|
||||
* Recherche des utilisateurs Hub
|
||||
*/
|
||||
searchHubUsers(params: SearchUsersParams): Observable<HubUserDto[]> {
|
||||
let httpParams = new HttpParams().set('userType', UserType.HUB);
|
||||
|
||||
if (params.query) {
|
||||
httpParams = httpParams.set('query', params.query);
|
||||
}
|
||||
|
||||
if (params.role) {
|
||||
httpParams = httpParams.set('role', params.role);
|
||||
}
|
||||
|
||||
if (params.enabled !== undefined) {
|
||||
httpParams = httpParams.set('enabled', params.enabled.toString());
|
||||
}
|
||||
|
||||
return this.http.get<HubUserDto[]>(`${this.apiUrl}/search`, { params: httpParams }).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error searching hub users:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === STATISTIQUES ===
|
||||
|
||||
/**
|
||||
* Récupère les statistiques des utilisateurs Hub
|
||||
*/
|
||||
getHubUsersStats(): Observable<any> {
|
||||
return this.http.get<any>(`${this.apiUrl}/stats/overview`).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error loading hub users stats:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === UTILITAIRES ===
|
||||
|
||||
/**
|
||||
* Vérifie si un nom d'utilisateur existe parmi les utilisateurs Hub
|
||||
*/
|
||||
hubUserExists(username: string): Observable<{ exists: boolean }> {
|
||||
return this.searchHubUsers({ query: username }).pipe(
|
||||
map(users => ({
|
||||
exists: users.some(user => user.username === username)
|
||||
})),
|
||||
catchError(error => {
|
||||
console.error('Error checking if hub user exists:', error);
|
||||
return of({ exists: false });
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
import { MerchantPartners } from './merchant-partners';
|
||||
describe('Merchant Partners', () => {});
|
||||
@ -5,14 +5,17 @@ import { FormsModule } from '@angular/forms';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import { NgbPaginationModule, NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { catchError, map, of, Subject, takeUntil } from 'rxjs';
|
||||
|
||||
import {
|
||||
MerchantUsersService,
|
||||
MerchantUserResponse,
|
||||
MerchantUserDto,
|
||||
PaginatedUserResponse,
|
||||
SearchUsersParams,
|
||||
UserRole,
|
||||
} from '../services/merchant-partners.service';
|
||||
|
||||
import { HubUsersService, UserRole as HubUserRole } from '../../users/services/users.service';
|
||||
UserType
|
||||
} from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
import { MerchantUsersService } from '../services/merchant-users.service';
|
||||
import { HubUsersService } from '../../hub-users/services/hub-users.service';
|
||||
import { AuthService } from '@core/services/auth.service';
|
||||
import { UiCard } from '@app/components/ui-card';
|
||||
|
||||
@ -37,7 +40,7 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
readonly UserRole = UserRole;
|
||||
readonly HubUserRole = HubUserRole;
|
||||
readonly UserType = UserType;
|
||||
|
||||
@Output() userSelected = new EventEmitter<string>();
|
||||
@Output() openCreateModal = new EventEmitter<void>();
|
||||
@ -45,9 +48,9 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
@Output() openDeleteUserModal = new EventEmitter<string>();
|
||||
|
||||
// Données
|
||||
allUsers: MerchantUserResponse[] = [];
|
||||
filteredUsers: MerchantUserResponse[] = [];
|
||||
displayedUsers: MerchantUserResponse[] = [];
|
||||
allUsers: MerchantUserDto[] = [];
|
||||
filteredUsers: MerchantUserDto[] = [];
|
||||
displayedUsers: MerchantUserDto[] = [];
|
||||
|
||||
// États
|
||||
loading = false;
|
||||
@ -66,7 +69,7 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
totalPages = 0;
|
||||
|
||||
// Tri
|
||||
sortField: keyof MerchantUserResponse = 'username';
|
||||
sortField: keyof MerchantUserDto = 'username';
|
||||
sortDirection: 'asc' | 'desc' = 'asc';
|
||||
|
||||
// Rôles disponibles pour le filtre
|
||||
@ -79,7 +82,7 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
|
||||
// ID du merchant partner courant et permissions
|
||||
currentMerchantPartnerId: string = '';
|
||||
currentUserRole: HubUserRole | null = null;
|
||||
currentUserRole: UserRole | null = null;
|
||||
isHubAdminOrSupport = false;
|
||||
canViewAllMerchants = false;
|
||||
isDcbPartner = false;
|
||||
@ -99,130 +102,234 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
// Méthode robuste pour récupérer le rôle
|
||||
this.currentUserRole = this.extractUserRole(user);
|
||||
|
||||
// Déterminer le type d'utilisateur
|
||||
this.isHubAdminOrSupport = this.currentUserRole === HubUserRole.DCB_ADMIN ||
|
||||
this.currentUserRole === HubUserRole.DCB_SUPPORT;
|
||||
this.isDcbPartner = this.currentUserRole === HubUserRole.DCB_PARTNER;
|
||||
this.canViewAllMerchants = this.isHubAdminOrSupport;
|
||||
// Déterminer le type d'utilisateur avec des méthodes plus robustes
|
||||
this.isHubAdminOrSupport = this.isHubAdminOrSupportRole(this.currentUserRole);
|
||||
this.isDcbPartner = this.isDcbPartnerRole(this.currentUserRole);
|
||||
this.canViewAllMerchants = this.canViewAllMerchantsCheck(this.currentUserRole);
|
||||
|
||||
|
||||
// Déterminer le merchantPartnerId
|
||||
this.currentMerchantPartnerId = this.extractMerchantPartnerId(user);
|
||||
|
||||
console.log('🎯 Final Permissions:', {
|
||||
currentUserRole: this.currentUserRole,
|
||||
isHubAdminOrSupport: this.isHubAdminOrSupport,
|
||||
isDcbPartner: this.isDcbPartner,
|
||||
canViewAllMerchants: this.canViewAllMerchants,
|
||||
currentMerchantPartnerId: this.currentMerchantPartnerId
|
||||
});
|
||||
if(this.isDcbPartner){
|
||||
this.currentMerchantPartnerId = user.id;
|
||||
}
|
||||
else if(!this.isDcbPartner || !this.isHubAdminOrSupport) {
|
||||
this.currentMerchantPartnerId = this.extractMerchantPartnerId(user);
|
||||
}
|
||||
|
||||
this.loadUsers();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('❌ Error loading current user permissions:', error);
|
||||
// Fallback: utiliser les méthodes d'AuthService
|
||||
this.fallbackPermissions();
|
||||
this.loadUsers(); // Charger quand même les utilisateurs
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait le rôle de l'utilisateur de manière robuste
|
||||
* Méthode robuste pour extraire le rôle de l'utilisateur
|
||||
*/
|
||||
private extractUserRole(user: any): HubUserRole | null {
|
||||
// Essayer différentes sources possibles pour le rôle
|
||||
if (user.roles && user.roles.length > 0) {
|
||||
return user.roles[0] as HubUserRole;
|
||||
private extractUserRole(user: any): UserRole | null {
|
||||
console.log('🔍 Extracting user role from:', user);
|
||||
|
||||
// 1. Essayer depuis les rôles du token (méthode principale)
|
||||
const tokenRoles = this.authService.getCurrentUserRoles();
|
||||
if (tokenRoles && tokenRoles.length > 0) {
|
||||
console.log('✅ Role from token:', tokenRoles[0]);
|
||||
return tokenRoles[0];
|
||||
}
|
||||
|
||||
if (user.role) {
|
||||
return user.role as HubUserRole;
|
||||
// 2. Essayer depuis le profil user.role
|
||||
if (user?.role && Object.values(UserRole).includes(user.role)) {
|
||||
console.log('✅ Role from user.role:', user.role);
|
||||
return user.role as UserRole;
|
||||
}
|
||||
|
||||
console.warn('No role found in user profile');
|
||||
// 3. Essayer depuis user.userType pour déduire le rôle
|
||||
if (user?.userType) {
|
||||
const roleFromType = this.getRoleFromUserType(user.userType);
|
||||
if (roleFromType) {
|
||||
console.log('✅ Role deduced from userType:', roleFromType);
|
||||
return roleFromType;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Essayer depuis les attributs étendus
|
||||
if (user?.attributes?.role?.[0]) {
|
||||
const roleFromAttributes = user.attributes.role[0];
|
||||
if (Object.values(UserRole).includes(roleFromAttributes as UserRole)) {
|
||||
console.log('✅ Role from attributes:', roleFromAttributes);
|
||||
return roleFromAttributes as UserRole;
|
||||
}
|
||||
}
|
||||
|
||||
console.warn('❌ No valid role found in user profile');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrait le merchantPartnerId de manière robuste
|
||||
* Déduire le rôle à partir du userType
|
||||
*/
|
||||
private getRoleFromUserType(userType: string): UserRole | null {
|
||||
const typeMapping: { [key: string]: UserRole } = {
|
||||
[UserType.HUB]: UserRole.DCB_ADMIN, // Fallback pour HUB
|
||||
[UserType.MERCHANT]: UserRole.DCB_PARTNER_ADMIN, // Fallback pour MERCHANT
|
||||
};
|
||||
|
||||
return typeMapping[userType] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si l'utilisateur est Hub Admin ou Support
|
||||
*/
|
||||
private isHubAdminOrSupportRole(role: UserRole | null): boolean {
|
||||
if (!role) return false;
|
||||
|
||||
const hubAdminSupportRoles = [
|
||||
UserRole.DCB_ADMIN,
|
||||
UserRole.DCB_SUPPORT,
|
||||
UserRole.DCB_PARTNER // DCB_PARTNER peut aussi être considéré comme Hub
|
||||
];
|
||||
|
||||
return hubAdminSupportRoles.includes(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si l'utilisateur est DCB Partner
|
||||
*/
|
||||
private isDcbPartnerRole(role: UserRole | null): boolean {
|
||||
if (!role) return false;
|
||||
|
||||
const partnerRoles = [
|
||||
UserRole.DCB_PARTNER,
|
||||
UserRole.DCB_PARTNER_ADMIN,
|
||||
UserRole.DCB_PARTNER_MANAGER,
|
||||
UserRole.DCB_PARTNER_SUPPORT
|
||||
];
|
||||
|
||||
return partnerRoles.includes(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si l'utilisateur peut voir tous les merchants
|
||||
*/
|
||||
private canViewAllMerchantsCheck(role: UserRole | null): boolean {
|
||||
if (!role) return false;
|
||||
|
||||
const canViewAllRoles = [
|
||||
UserRole.DCB_ADMIN,
|
||||
UserRole.DCB_SUPPORT
|
||||
];
|
||||
|
||||
return canViewAllRoles.includes(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extraire le merchantPartnerId de manière robuste
|
||||
*/
|
||||
private extractMerchantPartnerId(user: any): string {
|
||||
if (this.isDcbPartner) {
|
||||
// Pour DCB_PARTNER, utiliser son ID comme merchantPartnerId
|
||||
return user.id || '';
|
||||
}
|
||||
console.log('🔍 Extracting merchantPartnerId from:', user);
|
||||
|
||||
// Pour les autres, chercher le merchantPartnerId dans différentes sources
|
||||
if (user.merchantPartnerId) {
|
||||
// 1. Essayer depuis merchantPartnerId direct
|
||||
if (user?.merchantPartnerId) {
|
||||
console.log('✅ merchantPartnerId from direct property:', user.merchantPartnerId);
|
||||
return user.merchantPartnerId;
|
||||
}
|
||||
|
||||
if (user.attributes?.merchantPartnerId?.[0]) {
|
||||
// 2. Essayer depuis les attributs
|
||||
if (user?.attributes?.merchantPartnerId?.[0]) {
|
||||
console.log('✅ merchantPartnerId from attributes:', user.attributes.merchantPartnerId[0]);
|
||||
return user.attributes.merchantPartnerId[0];
|
||||
}
|
||||
|
||||
if (user.attributes?.partnerId?.[0]) {
|
||||
return user.attributes.partnerId[0];
|
||||
// 3. Essayer depuis AuthService
|
||||
const authServiceMerchantId = this.authService.getCurrentMerchantPartnerId();
|
||||
if (authServiceMerchantId) {
|
||||
console.log('✅ merchantPartnerId from AuthService:', authServiceMerchantId);
|
||||
return authServiceMerchantId;
|
||||
}
|
||||
|
||||
console.warn('No merchantPartnerId found in user profile');
|
||||
// 4. Pour les rôles Hub, pas de merchantPartnerId
|
||||
if (this.isHubAdminOrSupport) {
|
||||
console.log('ℹ️ Hub user - no merchantPartnerId');
|
||||
return '';
|
||||
}
|
||||
|
||||
console.warn('❌ No merchantPartnerId found');
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback en cas d'erreur de chargement du profil
|
||||
*/
|
||||
private fallbackPermissions(): void {
|
||||
console.warn('🔄 Using fallback permissions');
|
||||
|
||||
// Utiliser les méthodes d'AuthService comme fallback
|
||||
this.currentUserRole = this.authService.getCurrentUserRole();
|
||||
this.isHubAdminOrSupport = this.authService.isHubUser() ||
|
||||
this.authService.hasRole(UserRole.DCB_ADMIN) ||
|
||||
this.authService.hasRole(UserRole.DCB_SUPPORT);
|
||||
this.isDcbPartner = this.authService.isMerchantUser() ||
|
||||
this.authService.hasRole(UserRole.DCB_PARTNER);
|
||||
this.canViewAllMerchants = this.authService.canViewAllMerchants();
|
||||
this.currentMerchantPartnerId = this.authService.getCurrentMerchantPartnerId() || '';
|
||||
|
||||
console.log('🔄 Fallback permissions:', {
|
||||
currentUserRole: this.currentUserRole,
|
||||
isHubAdminOrSupport: this.isHubAdminOrSupport,
|
||||
isDcbPartner: this.isDcbPartner,
|
||||
canViewAllMerchants: this.canViewAllMerchants,
|
||||
currentMerchantPartnerId: this.currentMerchantPartnerId
|
||||
});
|
||||
}
|
||||
|
||||
loadUsers() {
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
|
||||
console.log('🚀 Loading users with permissions:', {
|
||||
canViewAllMerchants: this.canViewAllMerchants,
|
||||
currentMerchantPartnerId: this.currentMerchantPartnerId,
|
||||
currentUserRole: this.currentUserRole
|
||||
});
|
||||
|
||||
let usersObservable;
|
||||
|
||||
if (this.canViewAllMerchants) {
|
||||
// Admin/Support DCB : charger tous les utilisateurs via HubUsersService
|
||||
console.log('📊 Loading ALL merchant users (DCB Admin view)');
|
||||
|
||||
usersObservable = this.hubUsersService.findAllMerchantUsers(1, 1000).pipe(
|
||||
map((response: any) => {
|
||||
console.log('📦 Hub Users API Response:', response);
|
||||
|
||||
// Adapter selon la structure de votre API
|
||||
if (response && response.users) {
|
||||
return response.users;
|
||||
} else if (Array.isArray(response)) {
|
||||
return response;
|
||||
}
|
||||
return [];
|
||||
if (this.canViewAllMerchants && !this.currentMerchantPartnerId) {
|
||||
console.log('🔍 Loading ALL merchant users (Hub Admin/Support)');
|
||||
usersObservable = this.hubUsersService.findAllMerchantUsers(this.currentPage, this.itemsPerPage).pipe(
|
||||
map((response: PaginatedUserResponse) => {
|
||||
console.log('✅ All merchant users loaded:', response.users.length);
|
||||
return response.users as MerchantUserDto[];
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('❌ Error loading hub users:', error);
|
||||
console.error('❌ Error loading all merchant users:', error);
|
||||
this.error = 'Erreur lors du chargement de tous les utilisateurs marchands';
|
||||
return of([]);
|
||||
})
|
||||
);
|
||||
} else if (this.currentMerchantPartnerId) {
|
||||
// Utilisateur marchand (DCB_PARTNER) : charger seulement ses utilisateurs
|
||||
console.log('🏢 Loading merchant users for partner:', this.currentMerchantPartnerId);
|
||||
|
||||
usersObservable = this.merchantUsersService.getMyMerchantUsers().pipe(
|
||||
console.log(`🔍 Loading merchant users for partner: ${this.currentMerchantPartnerId}`);
|
||||
usersObservable = this.merchantUsersService.getMerchantUsersByPartner(this.currentMerchantPartnerId).pipe(
|
||||
catchError(error => {
|
||||
console.error('❌ Error loading merchant users:', error);
|
||||
console.error('❌ Error loading merchant users by partner:', error);
|
||||
this.error = 'Erreur lors du chargement des utilisateurs du partenaire';
|
||||
return of([]);
|
||||
})
|
||||
);
|
||||
} else {
|
||||
this.error = 'Impossible de déterminer les permissions de chargement';
|
||||
this.loading = false;
|
||||
console.error('❌ No valid permission scenario');
|
||||
return;
|
||||
console.log('🔍 Loading my merchant users');
|
||||
usersObservable = this.merchantUsersService.getMyMerchantUsers(this.currentMerchantPartnerId).pipe(
|
||||
catchError(error => {
|
||||
console.error('❌ Error loading my merchant users:', error);
|
||||
this.error = 'Erreur lors du chargement de mes utilisateurs marchands';
|
||||
return of([]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
usersObservable
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (users: any[]) => {
|
||||
next: (users: MerchantUserDto[]) => {
|
||||
this.allUsers = users || [];
|
||||
console.log(`✅ Loaded ${this.allUsers.length} merchant users`);
|
||||
|
||||
this.applyFiltersAndPagination();
|
||||
this.loading = false;
|
||||
@ -262,6 +369,8 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
this.allUsers = [];
|
||||
}
|
||||
|
||||
console.log(`🔍 Applying filters to ${this.allUsers.length} users`);
|
||||
|
||||
// Appliquer les filtres
|
||||
this.filteredUsers = this.allUsers.filter(user => {
|
||||
// Filtre de recherche
|
||||
@ -287,6 +396,8 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
return matchesSearch && matchesStatus && matchesEmailVerified && matchesRole;
|
||||
});
|
||||
|
||||
console.log(`✅ Filtered to ${this.filteredUsers.length} users`);
|
||||
|
||||
// Appliquer le tri
|
||||
this.filteredUsers.sort((a, b) => {
|
||||
const aValue = a[this.sortField];
|
||||
@ -314,26 +425,30 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
const startIndex = (this.currentPage - 1) * this.itemsPerPage;
|
||||
const endIndex = startIndex + this.itemsPerPage;
|
||||
this.displayedUsers = this.filteredUsers.slice(startIndex, endIndex);
|
||||
|
||||
console.log(`📄 Pagination: page ${this.currentPage} of ${this.totalPages}, showing ${this.displayedUsers.length} users`);
|
||||
}
|
||||
|
||||
// Tri
|
||||
sort(field: keyof MerchantUserResponse) {
|
||||
sort(field: keyof MerchantUserDto) {
|
||||
if (this.sortField === field) {
|
||||
this.sortDirection = this.sortDirection === 'asc' ? 'desc' : 'asc';
|
||||
} else {
|
||||
this.sortField = field;
|
||||
this.sortDirection = 'asc';
|
||||
}
|
||||
console.log(`🔀 Sorting by ${field} (${this.sortDirection})`);
|
||||
this.applyFiltersAndPagination();
|
||||
}
|
||||
|
||||
getSortIcon(field: keyof MerchantUserResponse): string {
|
||||
getSortIcon(field: keyof MerchantUserDto): string {
|
||||
if (this.sortField !== field) return 'lucideArrowUpDown';
|
||||
return this.sortDirection === 'asc' ? 'lucideArrowUp' : 'lucideArrowDown';
|
||||
}
|
||||
|
||||
// Pagination
|
||||
onPageChange(page: number) {
|
||||
console.log(`📄 Changing to page ${page}`);
|
||||
this.currentPage = page;
|
||||
this.applyFiltersAndPagination();
|
||||
}
|
||||
@ -348,31 +463,40 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
|
||||
// Actions
|
||||
viewUserProfile(userId: string) {
|
||||
console.log(`👤 Viewing user profile: ${userId}`);
|
||||
this.userSelected.emit(userId);
|
||||
}
|
||||
|
||||
// Méthode pour réinitialiser le mot de passe
|
||||
resetPassword(user: MerchantUserResponse) {
|
||||
resetPassword(user: MerchantUserDto) {
|
||||
console.log(`🔑 Resetting password for user: ${user.username}`);
|
||||
this.openResetPasswordModal.emit(user.id);
|
||||
}
|
||||
|
||||
// Méthode pour ouvrir le modal de suppression
|
||||
deleteUser(user: MerchantUserResponse) {
|
||||
deleteUser(user: MerchantUserDto) {
|
||||
console.log(`🗑️ Deleting user: ${user.username}`);
|
||||
this.openDeleteUserModal.emit(user.id);
|
||||
}
|
||||
|
||||
// Activer un utilisateur
|
||||
enableUser(user: MerchantUserResponse) {
|
||||
enableUser(user: MerchantUserDto) {
|
||||
console.log(`✅ Enabling user: ${user.username}`);
|
||||
this.merchantUsersService.enableMerchantUser(user.id)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (updatedUser) => {
|
||||
user.enabled = updatedUser.enabled;
|
||||
console.log(`✅ User ${user.username} enabled successfully`);
|
||||
// Mettre à jour l'utilisateur dans la liste
|
||||
const index = this.allUsers.findIndex(u => u.id === user.id);
|
||||
if (index !== -1) {
|
||||
this.allUsers[index] = updatedUser;
|
||||
}
|
||||
this.applyFiltersAndPagination();
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error enabling merchant user:', error);
|
||||
console.error('❌ Error enabling merchant user:', error);
|
||||
this.error = 'Erreur lors de l\'activation de l\'utilisateur';
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
@ -380,17 +504,23 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
// Désactiver un utilisateur
|
||||
disableUser(user: MerchantUserResponse) {
|
||||
disableUser(user: MerchantUserDto) {
|
||||
console.log(`❌ Disabling user: ${user.username}`);
|
||||
this.merchantUsersService.disableMerchantUser(user.id)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (updatedUser) => {
|
||||
user.enabled = updatedUser.enabled;
|
||||
console.log(`✅ User ${user.username} disabled successfully`);
|
||||
// Mettre à jour l'utilisateur dans la liste
|
||||
const index = this.allUsers.findIndex(u => u.id === user.id);
|
||||
if (index !== -1) {
|
||||
this.allUsers[index] = updatedUser;
|
||||
}
|
||||
this.applyFiltersAndPagination();
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error disabling merchant user:', error);
|
||||
console.error('❌ Error disabling merchant user:', error);
|
||||
this.error = 'Erreur lors de la désactivation de l\'utilisateur';
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
@ -399,13 +529,13 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
|
||||
// ==================== UTILITAIRES D'AFFICHAGE ====================
|
||||
|
||||
getStatusBadgeClass(user: MerchantUserResponse): string {
|
||||
getStatusBadgeClass(user: MerchantUserDto): string {
|
||||
if (!user.enabled) return 'badge bg-danger';
|
||||
if (!user.emailVerified) return 'badge bg-warning';
|
||||
return 'badge bg-success';
|
||||
}
|
||||
|
||||
getStatusText(user: MerchantUserResponse): string {
|
||||
getStatusText(user: MerchantUserDto): string {
|
||||
if (!user.enabled) return 'Désactivé';
|
||||
if (!user.emailVerified) return 'Email non vérifié';
|
||||
return 'Actif';
|
||||
@ -426,9 +556,12 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
|
||||
getRoleDisplayName(role: UserRole): string {
|
||||
const roleNames = {
|
||||
[UserRole.DCB_PARTNER_ADMIN]: 'Administrateur',
|
||||
[UserRole.DCB_PARTNER_MANAGER]: 'Manager',
|
||||
[UserRole.DCB_PARTNER_SUPPORT]: 'Support'
|
||||
[UserRole.DCB_ADMIN]: 'Administrateur',
|
||||
[UserRole.DCB_PARTNER]: 'Manager',
|
||||
[UserRole.DCB_SUPPORT]: 'Support',
|
||||
[UserRole.DCB_PARTNER_ADMIN]: 'Admin Marchand',
|
||||
[UserRole.DCB_PARTNER_MANAGER]: 'Manager Marchand',
|
||||
[UserRole.DCB_PARTNER_SUPPORT]: 'Support Marchand'
|
||||
};
|
||||
return roleNames[role] || role;
|
||||
}
|
||||
@ -457,11 +590,11 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
getUserInitials(user: MerchantUserResponse): string {
|
||||
getUserInitials(user: MerchantUserDto): string {
|
||||
return (user.firstName?.charAt(0) || '') + (user.lastName?.charAt(0) || '') || 'U';
|
||||
}
|
||||
|
||||
getUserDisplayName(user: MerchantUserResponse): string {
|
||||
getUserDisplayName(user: MerchantUserDto): string {
|
||||
if (user.firstName && user.lastName) {
|
||||
return `${user.firstName} ${user.lastName}`;
|
||||
}
|
||||
@ -490,35 +623,21 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
return this.allUsers.length;
|
||||
}
|
||||
|
||||
// ==================== VÉRIFICATIONS DE PERMISSIONS ====================
|
||||
|
||||
canManageUser(user: MerchantUserResponse): boolean {
|
||||
// Logique pour déterminer si l'utilisateur connecté peut gérer cet utilisateur
|
||||
// À adapter selon votre logique métier
|
||||
return true;
|
||||
}
|
||||
|
||||
canDeleteUser(user: MerchantUserResponse): boolean {
|
||||
// Empêcher la suppression de soi-même
|
||||
// À adapter avec l'ID de l'utilisateur connecté
|
||||
return user.id !== 'current-user-id';
|
||||
}
|
||||
|
||||
// ==================== MÉTHODES UTILITAIRES ====================
|
||||
|
||||
hasRole(user: MerchantUserResponse, role: UserRole): boolean {
|
||||
hasRole(user: MerchantUserDto, role: UserRole): boolean {
|
||||
return user.role === role;
|
||||
}
|
||||
|
||||
isAdmin(user: MerchantUserResponse): boolean {
|
||||
isAdmin(user: MerchantUserDto): boolean {
|
||||
return this.hasRole(user, UserRole.DCB_PARTNER_ADMIN);
|
||||
}
|
||||
|
||||
isManager(user: MerchantUserResponse): boolean {
|
||||
isManager(user: MerchantUserDto): boolean {
|
||||
return this.hasRole(user, UserRole.DCB_PARTNER_MANAGER);
|
||||
}
|
||||
|
||||
isSupport(user: MerchantUserResponse): boolean {
|
||||
isSupport(user: MerchantUserDto): boolean {
|
||||
return this.hasRole(user, UserRole.DCB_PARTNER_SUPPORT);
|
||||
}
|
||||
|
||||
@ -533,21 +652,32 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
searchUsers() {
|
||||
if (this.searchTerm.trim()) {
|
||||
this.loading = true;
|
||||
this.merchantUsersService.searchMerchantUsers({
|
||||
const searchParams: SearchUsersParams = {
|
||||
query: this.searchTerm,
|
||||
role: this.roleFilter !== 'all' ? this.roleFilter as UserRole : undefined,
|
||||
enabled: this.statusFilter !== 'all' ? this.statusFilter === 'enabled' : undefined
|
||||
})
|
||||
userType: UserType.MERCHANT
|
||||
};
|
||||
|
||||
if (this.roleFilter !== 'all') {
|
||||
searchParams.role = this.roleFilter as UserRole;
|
||||
}
|
||||
if (this.statusFilter !== 'all') {
|
||||
searchParams.enabled = this.statusFilter === 'enabled';
|
||||
}
|
||||
|
||||
console.log('🔍 Performing advanced search:', searchParams);
|
||||
|
||||
this.merchantUsersService.searchMerchantUsers(searchParams)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (users) => {
|
||||
this.allUsers = users;
|
||||
console.log(`✅ Advanced search found ${users.length} users`);
|
||||
this.applyFiltersAndPagination();
|
||||
this.loading = false;
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error: any) => {
|
||||
console.error('Error searching users:', error);
|
||||
console.error('❌ Error searching users:', error);
|
||||
this.loading = false;
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
@ -556,4 +686,10 @@ export class MerchantUsersList implements OnInit, OnDestroy {
|
||||
this.loadUsers(); // Recharger tous les utilisateurs si la recherche est vide
|
||||
}
|
||||
}
|
||||
|
||||
// Recharger les données
|
||||
refreshData() {
|
||||
console.log('🔄 Refreshing data...');
|
||||
this.loadUsers();
|
||||
}
|
||||
}
|
||||
@ -43,6 +43,11 @@
|
||||
(back)="backToList()"
|
||||
(openResetPasswordModal)="onResetPasswordRequested($event)"
|
||||
/>
|
||||
} @else {
|
||||
<div class="alert alert-warning text-center">
|
||||
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
||||
Aucun utilisateur sélectionné
|
||||
</div>
|
||||
}
|
||||
</ng-template>
|
||||
</li>
|
||||
@ -65,6 +70,7 @@
|
||||
class="btn-close"
|
||||
(click)="modal.dismiss()"
|
||||
[disabled]="creatingUser"
|
||||
aria-label="Fermer"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
@ -78,7 +84,29 @@
|
||||
}
|
||||
|
||||
<form (ngSubmit)="createMerchantUser()" #userForm="ngForm">
|
||||
|
||||
<div class="row g-3">
|
||||
<!-- Merchant Partner ID (lecture seule) -->
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Merchant Partner ID</label>
|
||||
<div class="form-control-plaintext font-monospace small">
|
||||
{{ currentMerchantPartnerId || 'Chargement...' }}
|
||||
<input
|
||||
type="hidden"
|
||||
[(ngModel)]="newMerchantUser.merchantPartnerId"
|
||||
name="merchantPartnerId"
|
||||
[value]="currentMerchantPartnerId"
|
||||
required
|
||||
>
|
||||
</div>
|
||||
@if (!currentMerchantPartnerId) {
|
||||
<div class="form-text text-warning">
|
||||
<ng-icon name="lucideAlertTriangle" class="me-1"></ng-icon>
|
||||
Merchant Partner ID non disponible
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Informations de base -->
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">
|
||||
@ -92,7 +120,13 @@
|
||||
name="firstName"
|
||||
required
|
||||
[disabled]="creatingUser"
|
||||
#firstName="ngModel"
|
||||
>
|
||||
@if (firstName.invalid && firstName.touched) {
|
||||
<div class="text-danger small">
|
||||
Le prénom est requis
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
@ -107,7 +141,13 @@
|
||||
name="lastName"
|
||||
required
|
||||
[disabled]="creatingUser"
|
||||
#lastName="ngModel"
|
||||
>
|
||||
@if (lastName.invalid && lastName.touched) {
|
||||
<div class="text-danger small">
|
||||
Le nom est requis
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
@ -122,8 +162,14 @@
|
||||
name="username"
|
||||
required
|
||||
[disabled]="creatingUser"
|
||||
#username="ngModel"
|
||||
>
|
||||
<div class="form-text">Doit être unique dans le système</div>
|
||||
@if (username.invalid && username.touched) {
|
||||
<div class="text-danger small">
|
||||
Le nom d'utilisateur est requis
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
@ -137,8 +183,20 @@
|
||||
[(ngModel)]="newMerchantUser.email"
|
||||
name="email"
|
||||
required
|
||||
email
|
||||
[disabled]="creatingUser"
|
||||
#email="ngModel"
|
||||
>
|
||||
@if (email.invalid && email.touched) {
|
||||
<div class="text-danger small">
|
||||
@if (email.errors?.['required']) {
|
||||
L'email est requis
|
||||
}
|
||||
@if (email.errors?.['email']) {
|
||||
Format d'email invalide
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
@ -154,10 +212,21 @@
|
||||
required
|
||||
minlength="8"
|
||||
[disabled]="creatingUser"
|
||||
#password="ngModel"
|
||||
>
|
||||
<div class="form-text">
|
||||
Le mot de passe doit contenir au moins 8 caractères.
|
||||
</div>
|
||||
@if (password.invalid && password.touched) {
|
||||
<div class="text-danger small">
|
||||
@if (password.errors?.['required']) {
|
||||
Le mot de passe est requis
|
||||
}
|
||||
@if (password.errors?.['minlength']) {
|
||||
Le mot de passe doit contenir au moins 8 caractères
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Sélection du rôle -->
|
||||
@ -170,17 +239,18 @@
|
||||
[(ngModel)]="newMerchantUser.role"
|
||||
name="role"
|
||||
required
|
||||
[disabled]="creatingUser"
|
||||
[disabled]="creatingUser || !canManageAllRoles"
|
||||
#roleSelect="ngModel"
|
||||
>
|
||||
<option value="" disabled>Sélectionnez un rôle</option>
|
||||
@if (availableRoles) {
|
||||
@for (role of availableRoles.roles; track role.value) {
|
||||
<option value="" disabled selected>Sélectionnez un rôle</option>
|
||||
@for (role of getAvailableRoles(); track role.value) {
|
||||
@if (isMerchantRole(role.value) && isRoleAllowedForCreation(role.value)) {
|
||||
<option
|
||||
[value]="role.value"
|
||||
[disabled]="!role.allowedForCreation"
|
||||
[disabled]="!canAssignRole(role.value)"
|
||||
>
|
||||
{{ role.label }} - {{ role.description }}
|
||||
@if (!role.allowedForCreation) {
|
||||
{{ getRoleDisplayName(role.value) }} - {{ role.description }}
|
||||
@if (!canAssignRole(role.value)) {
|
||||
(Non autorisé)
|
||||
}
|
||||
</option>
|
||||
@ -188,10 +258,43 @@
|
||||
}
|
||||
</select>
|
||||
<div class="form-text">
|
||||
Sélectionnez le rôle à assigner à cet utilisateur marchand
|
||||
@if (canManageAllRoles) {
|
||||
<span class="text-success">
|
||||
<ng-icon name="lucideShield" class="me-1"></ng-icon>
|
||||
Vous pouvez attribuer tous les rôles
|
||||
</span>
|
||||
} @else {
|
||||
<span class="text-warning">
|
||||
<ng-icon name="lucideShield" class="me-1"></ng-icon>
|
||||
Permissions de rôle limitées
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
@if (roleSelect.invalid && roleSelect.touched) {
|
||||
<div class="text-danger small">
|
||||
Le rôle est requis
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Avertissement pour les non-DCB_PARTNER -->
|
||||
@if (!canManageAllRoles) {
|
||||
<div class="col-12">
|
||||
<div class="alert alert-warning">
|
||||
<div class="d-flex align-items-center">
|
||||
<ng-icon name="lucideInfo" class="me-2"></ng-icon>
|
||||
<div>
|
||||
<small>
|
||||
<strong>Permissions limitées :</strong>
|
||||
Vous ne pouvez créer que des utilisateurs avec des rôles spécifiques.
|
||||
Seul un <strong>DCB Partner</strong> peut attribuer tous les rôles.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Aperçu du rôle sélectionné -->
|
||||
@if (newMerchantUser.role) {
|
||||
<div class="col-12">
|
||||
@ -259,7 +362,8 @@
|
||||
<strong>Informations système :</strong><br>
|
||||
• Merchant Partner ID : {{ currentMerchantPartnerId || 'Chargement...' }}<br>
|
||||
• Type d'utilisateur : MERCHANT<br>
|
||||
• Créé par : Utilisateur courant
|
||||
• Créé par : Utilisateur courant<br>
|
||||
• Votre rôle : {{ currentUserRole || 'Non défini' }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
@ -272,12 +376,13 @@
|
||||
(click)="modal.dismiss()"
|
||||
[disabled]="creatingUser"
|
||||
>
|
||||
<ng-icon name="lucideX" class="me-1"></ng-icon>
|
||||
Annuler
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-primary"
|
||||
[disabled]="!userForm.form.valid || creatingUser || !isRoleAllowedForCreation(newMerchantUser.role)"
|
||||
[disabled]="!userForm.form.valid || creatingUser || !isRoleAllowedForCreation(newMerchantUser.role) || !currentMerchantPartnerId"
|
||||
>
|
||||
@if (creatingUser) {
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status">
|
||||
@ -306,6 +411,7 @@
|
||||
class="btn-close"
|
||||
(click)="modal.dismiss()"
|
||||
[disabled]="resettingPassword"
|
||||
aria-label="Fermer"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
@ -363,10 +469,21 @@
|
||||
required
|
||||
minlength="8"
|
||||
[disabled]="resettingPassword"
|
||||
#newPasswordInput="ngModel"
|
||||
>
|
||||
<div class="form-text">
|
||||
Le mot de passe doit contenir au moins 8 caractères.
|
||||
</div>
|
||||
@if (newPasswordInput.invalid && newPasswordInput.touched) {
|
||||
<div class="text-danger small">
|
||||
@if (newPasswordInput.errors?.['required']) {
|
||||
Le mot de passe est requis
|
||||
}
|
||||
@if (newPasswordInput.errors?.['minlength']) {
|
||||
Le mot de passe doit contenir au moins 8 caractères
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@ -389,6 +506,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
} @else if (!selectedUserForReset) {
|
||||
<div class="alert alert-warning text-center">
|
||||
<ng-icon name="lucideAlertTriangle" class="me-2"></ng-icon>
|
||||
Aucun utilisateur sélectionné pour la réinitialisation
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -415,7 +537,7 @@
|
||||
type="button"
|
||||
class="btn btn-primary"
|
||||
(click)="confirmResetPassword()"
|
||||
[disabled]="!newPassword || newPassword.length < 8 || resettingPassword"
|
||||
[disabled]="!newPassword || newPassword.length < 8 || resettingPassword || !selectedUserForReset"
|
||||
>
|
||||
@if (resettingPassword) {
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status">
|
||||
@ -442,6 +564,7 @@
|
||||
type="button"
|
||||
class="btn-close"
|
||||
(click)="modal.dismiss()"
|
||||
aria-label="Fermer"
|
||||
></button>
|
||||
</div>
|
||||
|
||||
@ -478,6 +601,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="alert alert-warning">
|
||||
<ng-icon name="lucideAlertCircle" class="me-2"></ng-icon>
|
||||
Aucun utilisateur sélectionné pour la suppression
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Message d'erreur -->
|
||||
@ -503,7 +631,7 @@
|
||||
type="button"
|
||||
class="btn btn-danger"
|
||||
(click)="confirmDeleteUser()"
|
||||
[disabled]="deletingUser"
|
||||
[disabled]="deletingUser || !selectedUserForDelete"
|
||||
>
|
||||
@if (deletingUser) {
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status">
|
||||
2
src/app/modules/merchant-users/merchant-users.spec.ts
Normal file
2
src/app/modules/merchant-users/merchant-users.spec.ts
Normal file
@ -0,0 +1,2 @@
|
||||
import { MerchantUsers } from './merchant-users';
|
||||
describe('Merchant Users', () => {});
|
||||
@ -7,17 +7,21 @@ import { Subject, takeUntil } from 'rxjs';
|
||||
import { PageTitle } from '@app/components/page-title/page-title';
|
||||
import { MerchantUsersList } from './list/list';
|
||||
import { MerchantUserProfile } from './profile/profile';
|
||||
import {
|
||||
MerchantUsersService,
|
||||
CreateMerchantUserDto,
|
||||
MerchantUserResponse,
|
||||
UserRole,
|
||||
AvailableRolesResponse,
|
||||
} from './services/merchant-partners.service';
|
||||
import { MerchantUsersService } from './services/merchant-users.service';
|
||||
import { AuthService } from '@core/services/auth.service';
|
||||
|
||||
import {
|
||||
MerchantUserDto,
|
||||
CreateUserDto,
|
||||
ResetPasswordDto,
|
||||
UserRole,
|
||||
UserType,
|
||||
AvailableRolesResponse,
|
||||
} from '@core/models/dcb-bo-hub-user.model';
|
||||
import { RoleManagementService } from '@core/services/role-management.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-merchant-partners',
|
||||
selector: 'app-merchant-users',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -29,21 +33,28 @@ import { AuthService } from '@core/services/auth.service';
|
||||
MerchantUsersList,
|
||||
MerchantUserProfile
|
||||
],
|
||||
templateUrl: './merchant-partners.html',
|
||||
templateUrl: './merchant-users.html',
|
||||
})
|
||||
export class MerchantPartners implements OnInit, OnDestroy {
|
||||
export class MerchantUsers implements OnInit, OnDestroy {
|
||||
private modalService = inject(NgbModal);
|
||||
private authService = inject(AuthService);
|
||||
private merchantUsersService = inject(MerchantUsersService);
|
||||
protected roleService = inject(RoleManagementService);
|
||||
private cdRef = inject(ChangeDetectorRef);
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
activeTab: 'list' | 'stats' | 'profile' = 'list';
|
||||
readonly UserRole = UserRole;
|
||||
readonly UserType = UserType;
|
||||
|
||||
// Ajouter cette propriété manquante
|
||||
user: MerchantUserDto | null = null;
|
||||
|
||||
activeTab: 'list' | 'profile' = 'list';
|
||||
selectedUserId: string | null = null;
|
||||
currentMerchantPartnerId: string = '';
|
||||
|
||||
// Données pour la création d'utilisateur marchand
|
||||
newMerchantUser: CreateMerchantUserDto = {
|
||||
newMerchantUser: CreateUserDto = {
|
||||
username: '',
|
||||
email: '',
|
||||
firstName: '',
|
||||
@ -58,22 +69,28 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
availableRoles: AvailableRolesResponse | null = null;
|
||||
creatingUser = false;
|
||||
createUserError = '';
|
||||
currentUserRole: UserRole | null = null;
|
||||
|
||||
// Données pour la réinitialisation de mot de passe
|
||||
selectedUserForReset: MerchantUserResponse | null = null;
|
||||
selectedUserForReset: MerchantUserDto | null = null;
|
||||
newPassword = '';
|
||||
temporaryPassword = true;
|
||||
resettingPassword = false;
|
||||
resetPasswordError = '';
|
||||
resetPasswordSuccess = '';
|
||||
|
||||
selectedUserForDelete: MerchantUserResponse | null = null;
|
||||
selectedUserForDelete: MerchantUserDto | null = null;
|
||||
deletingUser = false;
|
||||
deleteUserError = '';
|
||||
|
||||
// Permissions utilisateur
|
||||
isHubAdminOrSupport = false;
|
||||
isDcbPartner = false;
|
||||
canViewAllMerchants = false;
|
||||
|
||||
ngOnInit() {
|
||||
this.activeTab = 'list';
|
||||
this.loadCurrentMerchantPartnerId();
|
||||
this.loadCurrentUserPermissions();
|
||||
this.loadAvailableRoles();
|
||||
}
|
||||
|
||||
@ -82,37 +99,188 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
private loadCurrentMerchantPartnerId() {
|
||||
private loadCurrentUserPermissions() {
|
||||
this.authService.getProfile().subscribe({
|
||||
next: (user) => {
|
||||
this.currentMerchantPartnerId = user.merchantPartnerId || '';
|
||||
this.newMerchantUser.merchantPartnerId = this.currentMerchantPartnerId;
|
||||
next: (user: any) => {
|
||||
this.currentUserRole = this.extractUserRole(user);
|
||||
|
||||
this.isHubAdminOrSupport = this.isHubAdminOrSupportRole(this.currentUserRole);
|
||||
this.isDcbPartner = this.isDcbPartnerRole(this.currentUserRole);
|
||||
this.canViewAllMerchants = this.canViewAllMerchantsCheck(this.currentUserRole);
|
||||
|
||||
|
||||
// Déterminer le merchantPartnerId
|
||||
if(this.isDcbPartner){
|
||||
this.currentMerchantPartnerId = user.id;
|
||||
}
|
||||
else if(!this.isDcbPartner || !this.isHubAdminOrSupport) {
|
||||
this.currentMerchantPartnerId = this.extractMerchantPartnerId(user);
|
||||
}
|
||||
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error loading current merchant partner ID:', error);
|
||||
this.fallbackPermissions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthode robuste pour extraire le rôle de l'utilisateur
|
||||
*/
|
||||
private extractUserRole(user: any): UserRole | null {
|
||||
// 1. Essayer depuis les rôles du token (méthode principale)
|
||||
const tokenRoles = this.authService.getCurrentUserRoles();
|
||||
if (tokenRoles && tokenRoles.length > 0) {
|
||||
return tokenRoles[0];
|
||||
}
|
||||
|
||||
// 2. Essayer depuis le profil user.role
|
||||
if (user?.role && Object.values(UserRole).includes(user.role)) {
|
||||
return user.role as UserRole;
|
||||
}
|
||||
|
||||
// 3. Essayer depuis user.userType pour déduire le rôle
|
||||
if (user?.userType) {
|
||||
const roleFromType = this.getRoleFromUserType(user.userType);
|
||||
if (roleFromType) {
|
||||
return roleFromType;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Essayer depuis les attributs étendus
|
||||
if (user?.attributes?.role?.[0]) {
|
||||
const roleFromAttributes = user.attributes.role[0];
|
||||
if (Object.values(UserRole).includes(roleFromAttributes as UserRole)) {
|
||||
return roleFromAttributes as UserRole;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Déduire le rôle à partir du userType
|
||||
*/
|
||||
private getRoleFromUserType(userType: string): UserRole | null {
|
||||
const typeMapping: { [key: string]: UserRole } = {
|
||||
[UserType.HUB]: UserRole.DCB_ADMIN,
|
||||
[UserType.MERCHANT]: UserRole.DCB_PARTNER_ADMIN,
|
||||
};
|
||||
|
||||
return typeMapping[userType] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si l'utilisateur est Hub Admin ou Support
|
||||
*/
|
||||
private isHubAdminOrSupportRole(role: UserRole | null): boolean {
|
||||
if (!role) return false;
|
||||
|
||||
const hubAdminSupportRoles = [
|
||||
UserRole.DCB_ADMIN,
|
||||
UserRole.DCB_SUPPORT
|
||||
];
|
||||
|
||||
return hubAdminSupportRoles.includes(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si l'utilisateur est DCB Partner
|
||||
*/
|
||||
private isDcbPartnerRole(role: UserRole | null): boolean {
|
||||
if (!role) return false;
|
||||
|
||||
const partnerRoles = [
|
||||
UserRole.DCB_PARTNER,
|
||||
UserRole.DCB_PARTNER_ADMIN,
|
||||
UserRole.DCB_PARTNER_MANAGER,
|
||||
UserRole.DCB_PARTNER_SUPPORT
|
||||
];
|
||||
|
||||
return partnerRoles.includes(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifier si l'utilisateur peut voir tous les merchants
|
||||
*/
|
||||
private canViewAllMerchantsCheck(role: UserRole | null): boolean {
|
||||
if (!role) return false;
|
||||
|
||||
const canViewAllRoles = [
|
||||
UserRole.DCB_ADMIN,
|
||||
UserRole.DCB_SUPPORT,
|
||||
];
|
||||
|
||||
return canViewAllRoles.includes(role);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extraire le merchantPartnerId de manière robuste
|
||||
*/
|
||||
private extractMerchantPartnerId(user: any): string {
|
||||
// 1. Essayer depuis merchantPartnerId direct
|
||||
if (user?.merchantPartnerId) {
|
||||
return user.merchantPartnerId;
|
||||
}
|
||||
|
||||
// 2. Essayer depuis les attributs
|
||||
if (user?.attributes?.merchantPartnerId?.[0]) {
|
||||
return user.attributes.merchantPartnerId[0];
|
||||
}
|
||||
|
||||
// 3. Essayer depuis AuthService
|
||||
const authServiceMerchantId = this.authService.getCurrentMerchantPartnerId();
|
||||
if (authServiceMerchantId) {
|
||||
return authServiceMerchantId;
|
||||
}
|
||||
|
||||
// 4. Pour les rôles Hub, pas de merchantPartnerId
|
||||
if (this.isHubAdminOrSupport) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback en cas d'erreur de chargement du profil
|
||||
*/
|
||||
private fallbackPermissions(): void {
|
||||
|
||||
// Utiliser les méthodes d'AuthService comme fallback
|
||||
this.currentUserRole = this.authService.getCurrentUserRole();
|
||||
this.isHubAdminOrSupport = this.authService.isHubUser() ||
|
||||
this.authService.hasRole(UserRole.DCB_ADMIN) ||
|
||||
this.authService.hasRole(UserRole.DCB_SUPPORT);
|
||||
this.isDcbPartner = this.authService.isMerchantUser() ||
|
||||
this.authService.hasRole(UserRole.DCB_PARTNER);
|
||||
this.canViewAllMerchants = this.authService.canViewAllMerchants();
|
||||
this.currentMerchantPartnerId = this.authService.getCurrentMerchantPartnerId() || '';
|
||||
this.newMerchantUser.merchantPartnerId = this.currentMerchantPartnerId;
|
||||
|
||||
}
|
||||
|
||||
private loadAvailableRoles() {
|
||||
this.merchantUsersService.getAvailableMerchantRoles()
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (roles) => {
|
||||
this.availableRoles = roles;
|
||||
|
||||
// Sélectionner le premier rôle disponible par défaut
|
||||
const firstAllowedRole = roles.roles.find(role => role.allowedForCreation);
|
||||
if (firstAllowedRole) {
|
||||
this.newMerchantUser.role = firstAllowedRole.value as any;
|
||||
this.newMerchantUser.role = firstAllowedRole.value;
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error loading available roles:', error);
|
||||
console.error('❌ Error loading available roles:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showTab(tab: 'list' | 'stats' | 'profile', userId?: string) {
|
||||
showTab(tab: 'list' | 'profile', userId?: string) {
|
||||
console.log(`Switching to tab: ${tab}`, userId ? `for user ${userId}` : '');
|
||||
this.activeTab = tab;
|
||||
|
||||
if (userId) {
|
||||
@ -121,6 +289,7 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
backToList() {
|
||||
console.log('🔙 Returning to list view');
|
||||
this.activeTab = 'list';
|
||||
this.selectedUserId = null;
|
||||
}
|
||||
@ -167,6 +336,7 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
enabled: true,
|
||||
emailVerified: false
|
||||
};
|
||||
console.log('🔄 User form reset');
|
||||
}
|
||||
|
||||
// Méthode pour ouvrir le modal de réinitialisation de mot de passe
|
||||
@ -181,16 +351,19 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
this.resetPasswordError = '';
|
||||
this.resetPasswordSuccess = '';
|
||||
this.openModal(this.resetPasswordModal);
|
||||
console.log('✅ User loaded for password reset:', user.username);
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error loading user for password reset:', error);
|
||||
console.error('❌ Error loading user for password reset:', error);
|
||||
this.resetPasswordError = 'Erreur lors du chargement de l\'utilisateur';
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Méthode pour ouvrir le modal de suppression
|
||||
openDeleteUserModal(userId: string) {
|
||||
console.log(`🗑️ Opening delete modal for user: ${userId}`);
|
||||
this.merchantUsersService.getMerchantUserById(userId)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
@ -198,19 +371,23 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
this.selectedUserForDelete = user;
|
||||
this.deleteUserError = '';
|
||||
this.openModal(this.deleteUserModal);
|
||||
console.log('✅ User loaded for deletion:', user.username);
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error loading user for deletion:', error);
|
||||
console.error('❌ Error loading user for deletion:', error);
|
||||
this.deleteUserError = 'Erreur lors du chargement de l\'utilisateur';
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Créer un utilisateur marchand
|
||||
createMerchantUser() {
|
||||
console.log('🚀 Creating new merchant user...');
|
||||
const validation = this.validateUserForm();
|
||||
if (!validation.isValid) {
|
||||
this.createUserError = validation.error!;
|
||||
console.error('❌ Form validation failed:', validation.error);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -227,11 +404,13 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
this.creatingUser = false;
|
||||
this.modalService.dismissAll();
|
||||
this.refreshUsersList();
|
||||
this.showSuccessMessage(`Utilisateur "${createdUser.username}" créé avec succès`);
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('❌ Error creating user:', error);
|
||||
this.creatingUser = false;
|
||||
this.createUserError = this.getErrorMessage(error);
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -240,22 +419,28 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
confirmResetPassword() {
|
||||
if (!this.selectedUserForReset || !this.newPassword || this.newPassword.length < 8) {
|
||||
this.resetPasswordError = 'Veuillez saisir un mot de passe valide (au moins 8 caractères).';
|
||||
console.error('❌ Password reset validation failed');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🔑 Confirming password reset for user:', this.selectedUserForReset.username);
|
||||
|
||||
this.resettingPassword = true;
|
||||
this.resetPasswordError = '';
|
||||
this.resetPasswordSuccess = '';
|
||||
|
||||
const resetPasswordDto: ResetPasswordDto = {
|
||||
newPassword: this.newPassword,
|
||||
temporary: this.temporaryPassword
|
||||
};
|
||||
|
||||
this.merchantUsersService.resetMerchantUserPassword(
|
||||
this.selectedUserForReset.id,
|
||||
{
|
||||
newPassword: this.newPassword,
|
||||
temporary: this.temporaryPassword
|
||||
}
|
||||
resetPasswordDto
|
||||
).pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (response) => {
|
||||
console.log('✅ Password reset successfully');
|
||||
this.resettingPassword = false;
|
||||
this.resetPasswordSuccess = 'Mot de passe réinitialisé avec succès !';
|
||||
this.cdRef.detectChanges();
|
||||
@ -266,6 +451,7 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
}, 2000);
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('❌ Error resetting password:', error);
|
||||
this.resettingPassword = false;
|
||||
this.resetPasswordError = this.getResetPasswordErrorMessage(error);
|
||||
this.cdRef.detectChanges();
|
||||
@ -274,7 +460,12 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
confirmDeleteUser() {
|
||||
if (!this.selectedUserForDelete) return;
|
||||
if (!this.selectedUserForDelete) {
|
||||
console.error('❌ No user selected for deletion');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🗑️ Confirming user deletion:', this.selectedUserForDelete.username);
|
||||
|
||||
this.deletingUser = true;
|
||||
this.deleteUserError = '';
|
||||
@ -283,13 +474,14 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: () => {
|
||||
console.log('✅ User deleted successfully');
|
||||
this.deletingUser = false;
|
||||
this.modalService.dismissAll();
|
||||
this.refreshUsersList();
|
||||
this.showSuccessMessage(`Utilisateur "${this.selectedUserForDelete?.username}" supprimé avec succès`);
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('❌ Error deleting user:', error);
|
||||
this.deletingUser = false;
|
||||
this.deleteUserError = this.getDeleteErrorMessage(error);
|
||||
this.cdRef.detectChanges();
|
||||
@ -300,10 +492,11 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
@ViewChild(MerchantUsersList) usersListComponent!: MerchantUsersList;
|
||||
|
||||
private refreshUsersList(): void {
|
||||
if (this.usersListComponent && typeof this.usersListComponent.loadUsers === 'function') {
|
||||
this.usersListComponent.loadUsers();
|
||||
if (this.usersListComponent && typeof this.usersListComponent.refreshData === 'function') {
|
||||
console.log('🔄 Refreshing users list...');
|
||||
this.usersListComponent.refreshData();
|
||||
} else {
|
||||
console.warn('MerchantUsersList component not available for refresh');
|
||||
console.warn('❌ MerchantUsersList component not available for refresh');
|
||||
this.showTab('list');
|
||||
}
|
||||
}
|
||||
@ -400,24 +593,89 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
return { isValid: true };
|
||||
}
|
||||
|
||||
// ==================== MESSAGES DE SUCCÈS ====================
|
||||
|
||||
private showSuccessMessage(message: string) {
|
||||
// Vous pouvez implémenter un service de notification ici
|
||||
console.log('Success:', message);
|
||||
// Exemple avec un toast:
|
||||
// this.notificationService.success(message);
|
||||
}
|
||||
|
||||
// ==================== MÉTHODES UTILITAIRES ====================
|
||||
|
||||
getRoleDisplayName(role: UserRole): string {
|
||||
const roleNames = {
|
||||
[UserRole.DCB_PARTNER_ADMIN]: 'Administrateur Partenaire',
|
||||
[UserRole.DCB_PARTNER_MANAGER]: 'Manager Partenaire',
|
||||
[UserRole.DCB_PARTNER_SUPPORT]: 'Support Partenaire'
|
||||
};
|
||||
return roleNames[role] || role;
|
||||
// Seulement gérer les rôles marchands, ignorer les rôles Hub
|
||||
switch (role) {
|
||||
case UserRole.DCB_PARTNER_ADMIN:
|
||||
return 'Administrateur Partenaire';
|
||||
case UserRole.DCB_PARTNER_MANAGER:
|
||||
return 'Manager Partenaire';
|
||||
case UserRole.DCB_PARTNER_SUPPORT:
|
||||
return 'Support Partenaire';
|
||||
default:
|
||||
// Pour les rôles Hub, retourner le nom technique
|
||||
return role;
|
||||
}
|
||||
}
|
||||
|
||||
// Méthode utilitaire pour vérifier si un rôle est un rôle marchand
|
||||
isMerchantRole(role: UserRole): boolean {
|
||||
const merchantRoles = [
|
||||
UserRole.DCB_PARTNER_ADMIN,
|
||||
UserRole.DCB_PARTNER_MANAGER,
|
||||
UserRole.DCB_PARTNER_SUPPORT
|
||||
];
|
||||
return merchantRoles.includes(role);
|
||||
}
|
||||
|
||||
// Méthode pour filtrer les rôles disponibles (uniquement les rôles marchands)
|
||||
getFilteredAvailableRoles(): { value: UserRole; label: string; description: string }[] {
|
||||
if (!this.availableRoles) return [];
|
||||
|
||||
return this.availableRoles.roles
|
||||
.filter(role => this.isMerchantRole(role.value))
|
||||
.map(role => ({
|
||||
value: role.value,
|
||||
label: this.getRoleDisplayName(role.value),
|
||||
description: role.description
|
||||
}));
|
||||
}
|
||||
|
||||
getAvailableRoles(): any[] {
|
||||
if (!this.availableRoles) return [];
|
||||
return this.availableRoles.roles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur peut attribuer un rôle spécifique
|
||||
*/
|
||||
canAssignRole(targetRole: UserRole): boolean {
|
||||
// Seul DCB_PARTNER peut attribuer tous les rôles
|
||||
if (this.currentUserRole === UserRole.DCB_PARTNER || this.currentUserRole === UserRole.DCB_ADMIN || this.currentUserRole === UserRole.DCB_SUPPORT) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Les autres rôles ont des permissions limitées
|
||||
return this.roleService.canAssignRole(this.currentUserRole, targetRole);
|
||||
}
|
||||
|
||||
canManageRoles(): boolean {
|
||||
return this.currentUserRole === UserRole.DCB_PARTNER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur peut gérer les rôles
|
||||
*/
|
||||
get canManageAllRoles(): boolean {
|
||||
return this.currentUserRole === UserRole.DCB_PARTNER;
|
||||
}
|
||||
|
||||
// Méthode pour obtenir uniquement les rôles marchands assignables
|
||||
getAssignableMerchantRoles(): UserRole[] {
|
||||
const merchantRoles = [
|
||||
UserRole.DCB_PARTNER_ADMIN,
|
||||
UserRole.DCB_PARTNER_MANAGER,
|
||||
UserRole.DCB_PARTNER_SUPPORT
|
||||
];
|
||||
|
||||
if (!this.availableRoles) return merchantRoles;
|
||||
|
||||
return merchantRoles.filter(role => {
|
||||
const roleInfo = this.availableRoles!.roles.find(r => r.value === role);
|
||||
return roleInfo?.allowedForCreation !== false;
|
||||
});
|
||||
}
|
||||
|
||||
getRoleDescription(role: UserRole): string {
|
||||
@ -435,11 +693,11 @@ export class MerchantPartners implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
// Méthodes utilitaires pour le template
|
||||
getUserInitials(user: MerchantUserResponse): string {
|
||||
getUserInitials(user: MerchantUserDto): string {
|
||||
return (user.firstName?.charAt(0) || '') + (user.lastName?.charAt(0) || '') || 'U';
|
||||
}
|
||||
|
||||
getUserType(user: MerchantUserResponse): string {
|
||||
getUserType(user: MerchantUserDto): string {
|
||||
switch (user.role) {
|
||||
case UserRole.DCB_PARTNER_ADMIN:
|
||||
return 'Administrateur';
|
||||
115
src/app/modules/merchant-users/models/merchant-user.model.ts
Normal file
115
src/app/modules/merchant-users/models/merchant-user.model.ts
Normal file
@ -0,0 +1,115 @@
|
||||
// src/app/core/models/user.model.ts
|
||||
|
||||
export enum UserType {
|
||||
HUB = 'HUB',
|
||||
MERCHANT = 'MERCHANT',
|
||||
MERCHANT_USER = 'MERCHANT_USER'
|
||||
}
|
||||
|
||||
export enum UserRole {
|
||||
// HUB roles
|
||||
DCB_ADMIN = 'DCB_ADMIN',
|
||||
DCB_SUPPORT = 'DCB_SUPPORT',
|
||||
DCB_PARTNER = 'DCB_PARTNER',
|
||||
|
||||
// MERCHANT roles
|
||||
DCB_PARTNER_ADMIN = 'DCB_PARTNER_ADMIN',
|
||||
DCB_PARTNER_MANAGER = 'DCB_PARTNER_MANAGER',
|
||||
DCB_PARTNER_SUPPORT = 'DCB_PARTNER_SUPPORT'
|
||||
}
|
||||
|
||||
// === BASE USER MODEL ===
|
||||
export interface BaseUserDto {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: UserRole;
|
||||
enabled: boolean;
|
||||
emailVerified: boolean;
|
||||
createdBy: string;
|
||||
createdByUsername: string;
|
||||
createdTimestamp: number;
|
||||
lastLogin?: number;
|
||||
userType: UserType;
|
||||
}
|
||||
|
||||
// === EXTENSIONS ===
|
||||
export interface HubUserDto extends BaseUserDto {
|
||||
userType: UserType.HUB;
|
||||
}
|
||||
|
||||
export interface MerchantUserDto extends BaseUserDto {
|
||||
userType: UserType.MERCHANT;
|
||||
merchantPartnerId: string;
|
||||
}
|
||||
|
||||
// === DTOs CRUD ===
|
||||
export interface CreateUserDto {
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
password: string;
|
||||
role: UserRole;
|
||||
enabled?: boolean;
|
||||
emailVerified?: boolean;
|
||||
merchantPartnerId?: string; // obligatoire si MERCHANT
|
||||
}
|
||||
|
||||
export interface UpdateUserDto {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ResetPasswordDto {
|
||||
userId?: string;
|
||||
newPassword: string;
|
||||
temporary?: boolean;
|
||||
}
|
||||
|
||||
// === PAGINATION / STATS ===
|
||||
export interface PaginatedUserResponse {
|
||||
users: BaseUserDto[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export interface MerchantPartnerStatsResponse {
|
||||
totalAdmins: number;
|
||||
totalManagers: number;
|
||||
totalSupport: number;
|
||||
totalUsers: number;
|
||||
activeUsers: number;
|
||||
inactiveUsers: number;
|
||||
}
|
||||
|
||||
// === ROLES ===
|
||||
export interface AvailableRole {
|
||||
value: UserRole;
|
||||
label: string;
|
||||
description: string;
|
||||
allowedForCreation: boolean;
|
||||
}
|
||||
|
||||
export interface AvailableRolesResponse {
|
||||
roles: AvailableRole[];
|
||||
}
|
||||
|
||||
export interface RoleOperationResponse {
|
||||
message: string;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
// === SEARCH ===
|
||||
export interface SearchUsersParams {
|
||||
query?: string;
|
||||
role?: UserRole;
|
||||
enabled?: boolean;
|
||||
userType?: UserType;
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
<!-- src/app/modules/merchant-users/profile/profile.html -->
|
||||
<div class="container-fluid">
|
||||
<!-- En-tête avec navigation -->
|
||||
<div class="row mb-4">
|
||||
@ -32,7 +31,7 @@
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<!-- Bouton de réinitialisation de mot de passe -->
|
||||
@if (user && !isEditing) {
|
||||
@if (user && !isEditing && canResetPassword()) {
|
||||
<button
|
||||
class="btn btn-warning"
|
||||
(click)="resetPassword()"
|
||||
@ -40,8 +39,10 @@
|
||||
<ng-icon name="lucideKey" class="me-1"></ng-icon>
|
||||
Réinitialiser MDP
|
||||
</button>
|
||||
}
|
||||
|
||||
<!-- Bouton activation/désactivation -->
|
||||
<!-- Bouton activation/désactivation -->
|
||||
@if (user && !isEditing && canEnableDisableUser()) {
|
||||
@if (user.enabled) {
|
||||
<button
|
||||
class="btn btn-outline-warning"
|
||||
@ -59,8 +60,10 @@
|
||||
Activer
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
<!-- Bouton modification -->
|
||||
<!-- Bouton modification -->
|
||||
@if (user && !isEditing && canEditUser()) {
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
(click)="startEditing()"
|
||||
@ -93,6 +96,31 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Indicateur de permissions -->
|
||||
@if (user && !loading) {
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div class="alert alert-info py-2">
|
||||
<div class="d-flex align-items-center">
|
||||
<ng-icon name="lucideShield" class="me-2"></ng-icon>
|
||||
<div class="flex-grow-1">
|
||||
<small>
|
||||
<strong>Statut :</strong> {{ getPermissionStatus() }}
|
||||
@if (currentUserRole === UserRole.DCB_PARTNER) {
|
||||
<span class="badge bg-success ms-2">Accès complet</span>
|
||||
} @else if (currentUserRole === UserRole.DCB_PARTNER_ADMIN) {
|
||||
<span class="badge bg-warning ms-2">Accès limité au partenaire</span>
|
||||
} @else {
|
||||
<span class="badge bg-secondary ms-2">Permissions restreintes</span>
|
||||
}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<!-- Loading State -->
|
||||
@if (loading) {
|
||||
@ -183,13 +211,22 @@
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<!-- Information sur le rôle -->
|
||||
<div class="alert alert-light mt-3">
|
||||
<small>
|
||||
<strong>Information :</strong> Le rôle de l'utilisateur marchand ne peut pas être modifié directement.
|
||||
Pour changer le rôle, vous devez recréer l'utilisateur avec le nouveau rôle souhaité.
|
||||
</small>
|
||||
</div>
|
||||
<!-- Information sur la modification des rôles -->
|
||||
@if (canManageRoles()) {
|
||||
<div class="alert alert-success mt-3">
|
||||
<small>
|
||||
<ng-icon name="lucideShield" class="me-1"></ng-icon>
|
||||
<strong>DCB Partner :</strong> Vous pouvez modifier le rôle de cet utilisateur.
|
||||
</small>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="alert alert-warning mt-3">
|
||||
<small>
|
||||
<ng-icon name="lucideInfo" class="me-1"></ng-icon>
|
||||
<strong>Information :</strong> Seul un <strong>DCB Partner</strong> peut modifier les rôles des utilisateurs marchands.
|
||||
</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -346,19 +383,75 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Rôle (lecture seule) -->
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Rôle</label>
|
||||
<!-- Rôle (affichage seulement) -->
|
||||
<div class="col-12">
|
||||
<label class="form-label">Rôle Utilisateur</label>
|
||||
<div class="form-control-plaintext">
|
||||
<span class="badge" [ngClass]="getRoleBadgeClass(user.role)">
|
||||
<ng-icon [name]="getRoleIcon(user.role)" class="me-1"></ng-icon>
|
||||
{{ getRoleDisplayName(user.role) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
Rôle assigné à la création
|
||||
@if (canManageRoles()) {
|
||||
<span class="text-success">
|
||||
<ng-icon name="lucideShield" class="me-1"></ng-icon>
|
||||
Vous pouvez modifier ce rôle (DCB Partner)
|
||||
</span>
|
||||
} @else {
|
||||
<span class="text-warning">
|
||||
<ng-icon name="lucideShield" class="me-1"></ng-icon>
|
||||
Seul un DCB Partner peut modifier les rôles
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section modification du rôle (uniquement pour DCB_PARTNER) -->
|
||||
@if (isEditing && canManageRoles()) {
|
||||
<div class="col-12">
|
||||
<div class="card border-warning">
|
||||
<div class="card-header bg-warning bg-opacity-10">
|
||||
<h6 class="card-title mb-0 text-warning">
|
||||
<ng-icon name="lucideShield" class="me-2"></ng-icon>
|
||||
Modification du Rôle (DCB Partner)
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<label class="form-label">Nouveau rôle</label>
|
||||
<select
|
||||
class="form-select"
|
||||
[(ngModel)]="user.role"
|
||||
name="role"
|
||||
[disabled]="updatingRole"
|
||||
>
|
||||
<option value="" disabled>Sélectionnez un nouveau rôle</option>
|
||||
@for (role of availableRoles; track role) {
|
||||
<option [value]="role">
|
||||
{{ getRoleDisplayName(role) }}
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
<div class="form-text">
|
||||
En tant que DCB Partner, vous pouvez modifier le rôle de cet utilisateur.
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-warning mt-2"
|
||||
(click)="updateUserRole(user.role)"
|
||||
[disabled]="updatingRole"
|
||||
>
|
||||
@if (updatingRole) {
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status"></div>
|
||||
}
|
||||
<ng-icon name="lucideShield" class="me-1"></ng-icon>
|
||||
Mettre à jour le rôle
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Merchant Partner ID (lecture seule) -->
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Merchant Partner ID</label>
|
||||
@ -371,7 +464,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Statut activé -->
|
||||
@if (isEditing) {
|
||||
@if (isEditing && canEnableDisableUser()) {
|
||||
<div class="col-md-6">
|
||||
<div class="form-check form-switch">
|
||||
<input
|
||||
@ -389,7 +482,7 @@
|
||||
L'utilisateur peut se connecter si activé
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
} @else if (!isEditing) {
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Statut du compte</label>
|
||||
<div class="form-control-plaintext">
|
||||
@ -456,50 +549,69 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
<button
|
||||
class="btn btn-outline-warning w-100"
|
||||
(click)="resetPassword()"
|
||||
>
|
||||
<ng-icon name="lucideKey" class="me-1"></ng-icon>
|
||||
Réinitialiser MDP
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
@if (user.enabled) {
|
||||
<!-- Réinitialisation MDP -->
|
||||
@if (canResetPassword()) {
|
||||
<div class="col-md-4">
|
||||
<button
|
||||
class="btn btn-outline-secondary w-100"
|
||||
(click)="disableUser()"
|
||||
class="btn btn-outline-warning w-100"
|
||||
(click)="resetPassword()"
|
||||
>
|
||||
<ng-icon name="lucideUserX" class="me-1"></ng-icon>
|
||||
Désactiver
|
||||
<ng-icon name="lucideKey" class="me-1"></ng-icon>
|
||||
Réinitialiser MDP
|
||||
</button>
|
||||
} @else {
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Activation/Désactivation -->
|
||||
@if (canEnableDisableUser()) {
|
||||
<div class="col-md-4">
|
||||
@if (user.enabled) {
|
||||
<button
|
||||
class="btn btn-outline-secondary w-100"
|
||||
(click)="disableUser()"
|
||||
>
|
||||
<ng-icon name="lucideUserX" class="me-1"></ng-icon>
|
||||
Désactiver
|
||||
</button>
|
||||
} @else {
|
||||
<button
|
||||
class="btn btn-outline-success w-100"
|
||||
(click)="enableUser()"
|
||||
>
|
||||
<ng-icon name="lucideUserCheck" class="me-1"></ng-icon>
|
||||
Activer
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Modification -->
|
||||
@if (canEditUser()) {
|
||||
<div class="col-md-4">
|
||||
<button
|
||||
class="btn btn-outline-success w-100"
|
||||
(click)="enableUser()"
|
||||
class="btn btn-outline-primary w-100"
|
||||
(click)="startEditing()"
|
||||
>
|
||||
<ng-icon name="lucideUserCheck" class="me-1"></ng-icon>
|
||||
Activer
|
||||
<ng-icon name="lucideEdit" class="me-1"></ng-icon>
|
||||
Modifier
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button
|
||||
class="btn btn-outline-primary w-100"
|
||||
(click)="startEditing()"
|
||||
>
|
||||
<ng-icon name="lucideEdit" class="me-1"></ng-icon>
|
||||
Modifier
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Avertissement pour la suppression -->
|
||||
<!-- Avertissement pour les permissions -->
|
||||
<div class="alert alert-light mt-3 mb-0">
|
||||
<small>
|
||||
<strong>Note :</strong> Pour supprimer cet utilisateur, utilisez l'action de suppression
|
||||
disponible dans la liste des utilisateurs marchands.
|
||||
@if (currentUserRole === UserRole.DCB_PARTNER) {
|
||||
<ng-icon name="lucideShield" class="me-1 text-success"></ng-icon>
|
||||
<strong>DCB Partner :</strong> Vous avez un accès complet à toutes les fonctionnalités de gestion.
|
||||
} @else if (currentUserRole === UserRole.DCB_PARTNER_ADMIN) {
|
||||
<ng-icon name="lucideInfo" class="me-1 text-warning"></ng-icon>
|
||||
<strong>Admin Partenaire :</strong> Vous pouvez gérer les utilisateurs de votre partenaire marchand, mais pas modifier les rôles.
|
||||
} @else {
|
||||
<ng-icon name="lucideInfo" class="me-1 text-muted"></ng-icon>
|
||||
<strong>Permissions limitées :</strong> Contactez un DCB Partner pour les actions de gestion avancées.
|
||||
}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
@ -5,13 +5,15 @@ import { FormsModule } from '@angular/forms';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import { NgbAlertModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import {
|
||||
MerchantUsersService,
|
||||
MerchantUserResponse,
|
||||
UserRole,
|
||||
UpdateMerchantUserDto
|
||||
} from '../services/merchant-partners.service';
|
||||
import { MerchantUsersService } from '../services/merchant-users.service';
|
||||
import { AuthService } from '@core/services/auth.service';
|
||||
import { RoleManagementService } from '@core/services/role-management.service';
|
||||
|
||||
import {
|
||||
MerchantUserDto,
|
||||
UpdateUserDto,
|
||||
UserRole
|
||||
} from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-merchant-user-profile',
|
||||
@ -31,22 +33,27 @@ import { AuthService } from '@core/services/auth.service';
|
||||
export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
private merchantUsersService = inject(MerchantUsersService);
|
||||
private authService = inject(AuthService);
|
||||
private roleService = inject(RoleManagementService);
|
||||
private cdRef = inject(ChangeDetectorRef);
|
||||
private destroy$ = new Subject<void>();
|
||||
readonly UserRole = UserRole;
|
||||
|
||||
@Input() userId!: string;
|
||||
@Output() back = new EventEmitter<void>();
|
||||
@Output() openResetPasswordModal = new EventEmitter<string>();
|
||||
|
||||
user: MerchantUserResponse | null = null;
|
||||
user: MerchantUserDto | null = null;
|
||||
loading = false;
|
||||
saving = false;
|
||||
error = '';
|
||||
success = '';
|
||||
|
||||
// Gestion des permissions
|
||||
currentUserRole: UserRole | null = null;
|
||||
|
||||
// Édition
|
||||
isEditing = false;
|
||||
editedUser: UpdateMerchantUserDto = {};
|
||||
editedUser: UpdateUserDto = {};
|
||||
|
||||
// Gestion des rôles
|
||||
availableRoles: UserRole[] = [
|
||||
@ -58,6 +65,7 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit() {
|
||||
if (this.userId) {
|
||||
this.loadCurrentUserPermissions();
|
||||
this.loadUserProfile();
|
||||
}
|
||||
}
|
||||
@ -67,6 +75,22 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Charge les permissions de l'utilisateur courant
|
||||
*/
|
||||
private loadCurrentUserPermissions(): void {
|
||||
this.authService.getUserProfile()
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (profile) => {
|
||||
this.currentUserRole = this.authService.getCurrentUserRole();
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error loading user permissions:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
loadUserProfile() {
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
@ -80,7 +104,7 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
this.error = 'Erreur lors du chargement du profil utilisateur';
|
||||
this.error = 'Erreur lors du chargement du profil utilisateur marchand';
|
||||
this.loading = false;
|
||||
this.cdRef.detectChanges();
|
||||
console.error('Error loading merchant user profile:', error);
|
||||
@ -89,6 +113,11 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
startEditing() {
|
||||
if (!this.canEditUser()) {
|
||||
this.error = 'Vous n\'avez pas la permission de modifier cet utilisateur';
|
||||
return;
|
||||
}
|
||||
|
||||
this.isEditing = true;
|
||||
this.editedUser = {
|
||||
firstName: this.user?.firstName,
|
||||
@ -108,7 +137,7 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
saveProfile() {
|
||||
if (!this.user) return;
|
||||
if (!this.user || !this.canEditUser()) return;
|
||||
|
||||
this.saving = true;
|
||||
this.error = '';
|
||||
@ -136,18 +165,26 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
|
||||
// Gestion des rôles
|
||||
updateUserRole(newRole: UserRole) {
|
||||
if (!this.user) return;
|
||||
if (!this.user || !this.canManageRoles()) {
|
||||
this.error = 'Vous n\'avez pas la permission de modifier les rôles';
|
||||
return;
|
||||
}
|
||||
|
||||
// Vérifier que le nouveau rôle est différent
|
||||
if (newRole === this.user.role) {
|
||||
this.error = 'L\'utilisateur a déjà ce rôle';
|
||||
return;
|
||||
}
|
||||
|
||||
this.updatingRole = true;
|
||||
this.error = '';
|
||||
this.success = '';
|
||||
|
||||
// Pour changer le rôle, on doit recréer l'utilisateur ou utiliser une méthode spécifique
|
||||
// Note: La modification de rôle nécessite une méthode spécifique
|
||||
// Pour l'instant, on utilise la mise à jour standard
|
||||
const updateData: UpdateMerchantUserDto = {
|
||||
// Vous devrez peut-être implémenter une méthode updateUserRole dans le service
|
||||
const updateData: UpdateUserDto = {
|
||||
...this.editedUser
|
||||
// Note: Le rôle n'est pas modifiable via update dans l'API actuelle
|
||||
// Vous devrez peut-être implémenter une méthode spécifique dans le service
|
||||
};
|
||||
|
||||
this.merchantUsersService.updateMerchantUser(this.user.id, updateData)
|
||||
@ -156,7 +193,7 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
next: (updatedUser) => {
|
||||
this.user = updatedUser;
|
||||
this.updatingRole = false;
|
||||
this.success = 'Profil mis à jour avec succès';
|
||||
this.success = 'Rôle mis à jour avec succès';
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
@ -169,7 +206,10 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
|
||||
// Gestion du statut
|
||||
enableUser() {
|
||||
if (!this.user) return;
|
||||
if (!this.user || !this.canEnableDisableUser()) {
|
||||
this.error = 'Vous n\'avez pas la permission d\'activer cet utilisateur';
|
||||
return;
|
||||
}
|
||||
|
||||
this.error = '';
|
||||
this.success = '';
|
||||
@ -179,7 +219,7 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
.subscribe({
|
||||
next: (updatedUser) => {
|
||||
this.user = updatedUser;
|
||||
this.success = 'Utilisateur activé avec succès';
|
||||
this.success = 'Utilisateur marchand activé avec succès';
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
@ -191,7 +231,10 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
disableUser() {
|
||||
if (!this.user) return;
|
||||
if (!this.user || !this.canEnableDisableUser()) {
|
||||
this.error = 'Vous n\'avez pas la permission de désactiver cet utilisateur';
|
||||
return;
|
||||
}
|
||||
|
||||
this.error = '';
|
||||
this.success = '';
|
||||
@ -201,7 +244,7 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
.subscribe({
|
||||
next: (updatedUser) => {
|
||||
this.user = updatedUser;
|
||||
this.success = 'Utilisateur désactivé avec succès';
|
||||
this.success = 'Utilisateur marchand désactivé avec succès';
|
||||
this.cdRef.detectChanges();
|
||||
},
|
||||
error: (error) => {
|
||||
@ -214,11 +257,98 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
|
||||
// Réinitialisation du mot de passe
|
||||
resetPassword() {
|
||||
if (this.user) {
|
||||
if (this.user && this.canResetPassword()) {
|
||||
this.openResetPasswordModal.emit(this.user.id);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== VÉRIFICATIONS DE PERMISSIONS ====================
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur peut éditer cet utilisateur
|
||||
*/
|
||||
canEditUser(): boolean {
|
||||
// Seul DCB_PARTNER peut éditer tous les utilisateurs marchands
|
||||
if (this.currentUserRole === UserRole.DCB_PARTNER) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Les administrateurs marchands peuvent éditer les utilisateurs de leur partenaire
|
||||
if (this.currentUserRole === UserRole.DCB_PARTNER_ADMIN) {
|
||||
const currentMerchantPartnerId = this.authService.getCurrentMerchantPartnerId();
|
||||
return this.user?.merchantPartnerId === currentMerchantPartnerId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur peut gérer les rôles
|
||||
*/
|
||||
canManageRoles(): boolean {
|
||||
// SEUL DCB_PARTNER peut modifier les rôles des utilisateurs marchands
|
||||
return this.currentUserRole === UserRole.DCB_PARTNER;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur peut activer/désactiver cet utilisateur
|
||||
*/
|
||||
canEnableDisableUser(): boolean {
|
||||
// Empêcher la désactivation de soi-même
|
||||
if (this.isCurrentUserProfile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Seul DCB_PARTNER peut activer/désactiver les utilisateurs marchands
|
||||
if (this.currentUserRole === UserRole.DCB_PARTNER) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Les administrateurs marchands peuvent gérer les utilisateurs de leur partenaire
|
||||
if (this.currentUserRole === UserRole.DCB_PARTNER_ADMIN) {
|
||||
const currentMerchantPartnerId = this.authService.getCurrentMerchantPartnerId();
|
||||
return this.user?.merchantPartnerId === currentMerchantPartnerId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur peut réinitialiser le mot de passe
|
||||
*/
|
||||
canResetPassword(): boolean {
|
||||
// DCB_PARTNER peut réinitialiser tous les mots de passe
|
||||
if (this.currentUserRole === UserRole.DCB_PARTNER) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Les administrateurs marchands peuvent réinitialiser les mots de passe de leur partenaire
|
||||
if (this.currentUserRole === UserRole.DCB_PARTNER_ADMIN) {
|
||||
const currentMerchantPartnerId = this.authService.getCurrentMerchantPartnerId();
|
||||
return this.user?.merchantPartnerId === currentMerchantPartnerId;
|
||||
}
|
||||
|
||||
// Les utilisateurs peuvent réinitialiser leur propre mot de passe
|
||||
if (this.isCurrentUserProfile()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vérifie si l'utilisateur peut supprimer cet utilisateur
|
||||
*/
|
||||
canDeleteUser(): boolean {
|
||||
// Empêcher la suppression de soi-même
|
||||
if (this.isCurrentUserProfile()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Seul DCB_PARTNER peut supprimer les utilisateurs marchands
|
||||
return this.currentUserRole === UserRole.DCB_PARTNER;
|
||||
}
|
||||
|
||||
// ==================== UTILITAIRES D'AFFICHAGE ====================
|
||||
|
||||
getStatusBadgeClass(): string {
|
||||
@ -252,7 +382,7 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getUserDisplayName(): string {
|
||||
if (!this.user) return 'Utilisateur';
|
||||
if (!this.user) return 'Utilisateur Marchand';
|
||||
if (this.user.firstName && this.user.lastName) {
|
||||
return `${this.user.firstName} ${this.user.lastName}`;
|
||||
}
|
||||
@ -260,42 +390,19 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getRoleBadgeClass(role: UserRole): string {
|
||||
switch (role) {
|
||||
case UserRole.DCB_PARTNER_ADMIN:
|
||||
return 'bg-danger';
|
||||
case UserRole.DCB_PARTNER_MANAGER:
|
||||
return 'bg-warning text-dark';
|
||||
case UserRole.DCB_PARTNER_SUPPORT:
|
||||
return 'bg-info text-white';
|
||||
default:
|
||||
return 'bg-secondary';
|
||||
}
|
||||
return this.roleService.getRoleBadgeClass(role);
|
||||
}
|
||||
|
||||
getRoleDisplayName(role: UserRole): string {
|
||||
const roleNames = {
|
||||
[UserRole.DCB_PARTNER_ADMIN]: 'Administrateur',
|
||||
[UserRole.DCB_PARTNER_MANAGER]: 'Manager',
|
||||
[UserRole.DCB_PARTNER_SUPPORT]: 'Support'
|
||||
};
|
||||
return roleNames[role] || role;
|
||||
return this.roleService.getRoleLabel(role);
|
||||
}
|
||||
|
||||
getRoleIcon(role: UserRole): string {
|
||||
switch (role) {
|
||||
case UserRole.DCB_PARTNER_ADMIN:
|
||||
return 'lucideShield';
|
||||
case UserRole.DCB_PARTNER_MANAGER:
|
||||
return 'lucideUserCog';
|
||||
case UserRole.DCB_PARTNER_SUPPORT:
|
||||
return 'lucideHeadphones';
|
||||
default:
|
||||
return 'lucideUser';
|
||||
}
|
||||
return this.roleService.getRoleIcon(role);
|
||||
}
|
||||
|
||||
getRoleDescription(role: UserRole): string {
|
||||
const descriptions = {
|
||||
const descriptions: { [key in UserRole]?: string } = {
|
||||
[UserRole.DCB_PARTNER_ADMIN]: 'Accès administratif complet au sein du partenaire marchand',
|
||||
[UserRole.DCB_PARTNER_MANAGER]: 'Accès de gestion avec capacités administratives limitées',
|
||||
[UserRole.DCB_PARTNER_SUPPORT]: 'Rôle support avec accès en lecture seule et opérations de base'
|
||||
@ -304,28 +411,13 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
getUserType(): string {
|
||||
if (!this.user) return 'Utilisateur';
|
||||
|
||||
switch (this.user.role) {
|
||||
case UserRole.DCB_PARTNER_ADMIN:
|
||||
return 'Administrateur';
|
||||
case UserRole.DCB_PARTNER_MANAGER:
|
||||
return 'Manager';
|
||||
case UserRole.DCB_PARTNER_SUPPORT:
|
||||
return 'Support';
|
||||
default:
|
||||
return 'Utilisateur';
|
||||
}
|
||||
if (!this.user) return 'Utilisateur Marchand';
|
||||
return this.roleService.getRoleLabel(this.user.role);
|
||||
}
|
||||
|
||||
getUserTypeBadgeClass(): string {
|
||||
const userType = this.getUserType();
|
||||
switch (userType) {
|
||||
case 'Administrateur': return 'bg-danger';
|
||||
case 'Manager': return 'bg-success';
|
||||
case 'Support': return 'bg-info';
|
||||
default: return 'bg-secondary';
|
||||
}
|
||||
if (!this.user) return 'bg-secondary';
|
||||
return this.roleService.getRoleBadgeClass(this.user.role);
|
||||
}
|
||||
|
||||
// ==================== GESTION DES ERREURS ====================
|
||||
@ -341,7 +433,7 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
return 'Vous n\'avez pas les permissions pour effectuer cette action.';
|
||||
}
|
||||
if (error.status === 404) {
|
||||
return 'Utilisateur non trouvé.';
|
||||
return 'Utilisateur marchand non trouvé.';
|
||||
}
|
||||
if (error.status === 409) {
|
||||
return 'Conflit de données. Cet utilisateur existe peut-être déjà.';
|
||||
@ -349,23 +441,6 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
return 'Erreur lors de l\'opération. Veuillez réessayer.';
|
||||
}
|
||||
|
||||
// ==================== VÉRIFICATIONS DE PERMISSIONS ====================
|
||||
|
||||
canEditUser(): boolean {
|
||||
// Logique pour déterminer si l'utilisateur connecté peut éditer cet utilisateur
|
||||
return true; // Temporaire - à implémenter
|
||||
}
|
||||
|
||||
canManageRoles(): boolean {
|
||||
// Logique pour déterminer si l'utilisateur connecté peut gérer les rôles
|
||||
return true; // Temporaire - à implémenter
|
||||
}
|
||||
|
||||
canEnableDisableUser(): boolean {
|
||||
// Empêcher la désactivation de soi-même
|
||||
return this.user?.id !== 'current-user-id';
|
||||
}
|
||||
|
||||
// ==================== MÉTHODES DE NAVIGATION ====================
|
||||
|
||||
goBack() {
|
||||
@ -415,9 +490,8 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
|
||||
// Vérifie si c'est le profil de l'utilisateur courant
|
||||
isCurrentUserProfile(): boolean {
|
||||
// Implémentez cette logique selon votre système d'authentification
|
||||
// Exemple: return this.authService.getCurrentUserId() === this.user?.id;
|
||||
return false;
|
||||
if (!this.user?.id) return false;
|
||||
return this.authService.isCurrentUserProfile(this.user.id);
|
||||
}
|
||||
|
||||
// Méthode pour obtenir la date de création formatée
|
||||
@ -437,4 +511,39 @@ export class MerchantUserProfile implements OnInit, OnDestroy {
|
||||
if (!this.user?.createdByUsername) return 'Non disponible';
|
||||
return this.user.createdByUsername;
|
||||
}
|
||||
|
||||
// Méthode pour obtenir l'ID du partenaire marchand
|
||||
getMerchantPartnerId(): string {
|
||||
return this.user?.merchantPartnerId || 'Non disponible';
|
||||
}
|
||||
|
||||
// Vérifie si l'utilisateur a accès à ce profil marchand
|
||||
canAccessMerchantProfile(): boolean {
|
||||
if (!this.user) return false;
|
||||
|
||||
// DCB_PARTNER peut accéder à tous les profils marchands
|
||||
if (this.currentUserRole === UserRole.DCB_PARTNER) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const currentMerchantPartnerId = this.authService.getCurrentMerchantPartnerId();
|
||||
|
||||
// Les utilisateurs marchands ne peuvent voir que les utilisateurs de leur partenaire
|
||||
if (this.authService.isMerchantUser()) {
|
||||
return this.user.merchantPartnerId === currentMerchantPartnerId;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Affiche le statut des permissions
|
||||
getPermissionStatus(): string {
|
||||
if (this.currentUserRole === UserRole.DCB_PARTNER) {
|
||||
return 'DCB Partner - Accès complet';
|
||||
} else if (this.currentUserRole === UserRole.DCB_PARTNER_ADMIN) {
|
||||
return 'Admin Partenaire - Accès limité à votre partenaire';
|
||||
} else {
|
||||
return 'Permissions limitées';
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3,130 +3,35 @@ import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { environment } from '@environments/environment';
|
||||
import { Observable, map, catchError, throwError, of } from 'rxjs';
|
||||
|
||||
// Interfaces alignées avec le contrôleur MerchantUsersController
|
||||
export interface MerchantUserResponse {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: UserRole.DCB_PARTNER_ADMIN | UserRole.DCB_PARTNER_MANAGER | UserRole.DCB_PARTNER_SUPPORT;
|
||||
enabled: boolean;
|
||||
emailVerified: boolean;
|
||||
merchantPartnerId: string;
|
||||
createdBy: string;
|
||||
createdByUsername: string;
|
||||
createdTimestamp: number;
|
||||
lastLogin?: number;
|
||||
userType: 'MERCHANT';
|
||||
}
|
||||
|
||||
export interface CreateMerchantUserDto {
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
password: string;
|
||||
role: UserRole.DCB_PARTNER_ADMIN | UserRole.DCB_PARTNER_MANAGER | UserRole.DCB_PARTNER_SUPPORT;
|
||||
enabled?: boolean;
|
||||
emailVerified?: boolean;
|
||||
merchantPartnerId: string;
|
||||
}
|
||||
|
||||
export interface UpdateMerchantUserDto {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ResetPasswordDto {
|
||||
newPassword: string;
|
||||
temporary?: boolean;
|
||||
}
|
||||
|
||||
export interface MerchantPartnerStatsResponse {
|
||||
totalAdmins: number;
|
||||
totalManagers: number;
|
||||
totalSupport: number;
|
||||
totalUsers: number;
|
||||
activeUsers: number;
|
||||
inactiveUsers: number;
|
||||
}
|
||||
|
||||
export interface AvailableRole {
|
||||
value: UserRole;
|
||||
label: string;
|
||||
description: string;
|
||||
allowedForCreation: boolean;
|
||||
}
|
||||
|
||||
export interface AvailableRolesResponse {
|
||||
roles: AvailableRole[];
|
||||
}
|
||||
|
||||
export interface SearchMerchantUsersParams {
|
||||
query?: string;
|
||||
role?: UserRole.DCB_PARTNER_ADMIN | UserRole.DCB_PARTNER_MANAGER | UserRole.DCB_PARTNER_SUPPORT;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export enum UserRole {
|
||||
DCB_PARTNER_ADMIN = 'DCB_PARTNER_ADMIN',
|
||||
DCB_PARTNER_MANAGER = 'DCB_PARTNER_MANAGER',
|
||||
DCB_PARTNER_SUPPORT = 'DCB_PARTNER_SUPPORT'
|
||||
}
|
||||
import {
|
||||
MerchantUserDto,
|
||||
CreateUserDto,
|
||||
UpdateUserDto,
|
||||
ResetPasswordDto,
|
||||
PaginatedUserResponse,
|
||||
MerchantPartnerStatsResponse,
|
||||
AvailableRolesResponse,
|
||||
SearchUsersParams,
|
||||
UserRole,
|
||||
UserType
|
||||
} from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MerchantUsersService {
|
||||
private http = inject(HttpClient);
|
||||
private apiUrl = `${environment.iamApiUrl}/merchant-users`;
|
||||
|
||||
// === RÉCUPÉRATION D'UTILISATEURS ===
|
||||
|
||||
/**
|
||||
* Récupère les utilisateurs marchands de l'utilisateur courant
|
||||
*/
|
||||
getMyMerchantUsers(): Observable<MerchantUserResponse[]> {
|
||||
return this.http.get<MerchantUserResponse[]>(this.apiUrl).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error loading my merchant users:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les utilisateurs marchands par ID de partenaire
|
||||
*/
|
||||
getMerchantUsersByPartner(partnerId: string): Observable<MerchantUserResponse[]> {
|
||||
return this.http.get<MerchantUserResponse[]>(`${this.apiUrl}/partner/${partnerId}`).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error loading merchant users for partner ${partnerId}:`, error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère un utilisateur marchand par ID
|
||||
*/
|
||||
getMerchantUserById(id: string): Observable<MerchantUserResponse> {
|
||||
return this.http.get<MerchantUserResponse>(`${this.apiUrl}/${id}`).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error loading merchant user ${id}:`, error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === CRÉATION D'UTILISATEURS ===
|
||||
// === CRÉATION ===
|
||||
|
||||
/**
|
||||
* Crée un nouvel utilisateur marchand
|
||||
*/
|
||||
createMerchantUser(createUserDto: CreateMerchantUserDto): Observable<MerchantUserResponse> {
|
||||
// Validation
|
||||
createMerchantUser(createUserDto: CreateUserDto): Observable<MerchantUserDto> {
|
||||
// Validation spécifique aux marchands
|
||||
if (!createUserDto.merchantPartnerId?.trim()) {
|
||||
return throwError(() => 'Merchant Partner ID is required for merchant users');
|
||||
}
|
||||
|
||||
if (!createUserDto.username?.trim()) {
|
||||
return throwError(() => 'Username is required and cannot be empty');
|
||||
}
|
||||
@ -143,24 +48,25 @@ export class MerchantUsersService {
|
||||
return throwError(() => 'Role is required');
|
||||
}
|
||||
|
||||
if (!createUserDto.merchantPartnerId?.trim()) {
|
||||
return throwError(() => 'Merchant Partner ID is required');
|
||||
// Vérification que le rôle est bien un rôle marchand
|
||||
const merchantRoles = [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT];
|
||||
if (!merchantRoles.includes(createUserDto.role)) {
|
||||
return throwError(() => 'Invalid role for merchant user');
|
||||
}
|
||||
|
||||
// Nettoyage des données
|
||||
const payload = {
|
||||
...createUserDto,
|
||||
username: createUserDto.username.trim(),
|
||||
email: createUserDto.email.trim(),
|
||||
firstName: (createUserDto.firstName || '').trim(),
|
||||
lastName: (createUserDto.lastName || '').trim(),
|
||||
password: createUserDto.password,
|
||||
role: createUserDto.role,
|
||||
merchantPartnerId: createUserDto.merchantPartnerId.trim(),
|
||||
enabled: createUserDto.enabled !== undefined ? createUserDto.enabled : true,
|
||||
emailVerified: createUserDto.emailVerified !== undefined ? createUserDto.emailVerified : false,
|
||||
};
|
||||
|
||||
return this.http.post<MerchantUserResponse>(this.apiUrl, payload).pipe(
|
||||
return this.http.post<MerchantUserDto>(this.apiUrl, payload).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error creating merchant user:', error);
|
||||
return throwError(() => error);
|
||||
@ -168,13 +74,88 @@ export class MerchantUsersService {
|
||||
);
|
||||
}
|
||||
|
||||
// === MISE À JOUR D'UTILISATEURS ===
|
||||
// === LECTURE ===
|
||||
|
||||
/**
|
||||
* Récupère les utilisateurs marchands de l'utilisateur courant
|
||||
*/
|
||||
getMyMerchantUsers(partnerId: string): Observable<MerchantUserDto[]> {
|
||||
return this.http.get<MerchantUserDto[]>(this.apiUrl).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error loading my merchant users:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les utilisateurs marchands par ID de partenaire
|
||||
*/
|
||||
getMerchantUsersByPartner(partnerId: string): Observable<MerchantUserDto[]> {
|
||||
return this.http.get<MerchantUserDto[]>(`${this.apiUrl}/partner/${partnerId}`).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error loading merchant users for partner ${partnerId}:`, error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère un utilisateur marchand par ID
|
||||
*/
|
||||
getMerchantUserById(id: string): Observable<MerchantUserDto> {
|
||||
return this.http.get<MerchantUserDto>(`${this.apiUrl}/${id}`).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error loading merchant user ${id}:`, error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère tous les utilisateurs marchands avec pagination
|
||||
*/
|
||||
getMerchantUsers(page: number = 1, limit: number = 10, filters?: SearchUsersParams): Observable<PaginatedUserResponse> {
|
||||
let params = new HttpParams()
|
||||
.set('page', page.toString())
|
||||
.set('limit', limit.toString())
|
||||
.set('userType', UserType.MERCHANT);
|
||||
|
||||
if (filters) {
|
||||
Object.keys(filters).forEach(key => {
|
||||
if (filters[key as keyof SearchUsersParams] !== undefined && filters[key as keyof SearchUsersParams] !== null) {
|
||||
params = params.set(key, filters[key as keyof SearchUsersParams]!.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return this.http.get<MerchantUserDto[]>(this.apiUrl, { params, observe: 'response' }).pipe(
|
||||
map(response => {
|
||||
const users = response.body || [];
|
||||
const total = parseInt(response.headers.get('X-Total-Count') || '0');
|
||||
|
||||
return {
|
||||
users,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit)
|
||||
};
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error loading merchant users:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// === MISE À JOUR ===
|
||||
|
||||
/**
|
||||
* Met à jour un utilisateur marchand
|
||||
*/
|
||||
updateMerchantUser(id: string, updateUserDto: UpdateMerchantUserDto): Observable<MerchantUserResponse> {
|
||||
return this.http.put<MerchantUserResponse>(`${this.apiUrl}/${id}`, updateUserDto).pipe(
|
||||
updateMerchantUser(id: string, updateUserDto: UpdateUserDto): Observable<MerchantUserDto> {
|
||||
return this.http.put<MerchantUserDto>(`${this.apiUrl}/${id}`, updateUserDto).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error updating merchant user ${id}:`, error);
|
||||
return throwError(() => error);
|
||||
@ -182,7 +163,7 @@ export class MerchantUsersService {
|
||||
);
|
||||
}
|
||||
|
||||
// === SUPPRESSION D'UTILISATEURS ===
|
||||
// === SUPPRESSION ===
|
||||
|
||||
/**
|
||||
* Supprime un utilisateur marchand
|
||||
@ -213,12 +194,28 @@ export class MerchantUsersService {
|
||||
);
|
||||
}
|
||||
|
||||
// === STATISTIQUES ET RAPPORTS ===
|
||||
// === GESTION DU STATUT ===
|
||||
|
||||
/**
|
||||
* Active un utilisateur marchand
|
||||
*/
|
||||
enableMerchantUser(id: string): Observable<MerchantUserDto> {
|
||||
return this.updateMerchantUser(id, { enabled: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Désactive un utilisateur marchand
|
||||
*/
|
||||
disableMerchantUser(id: string): Observable<MerchantUserDto> {
|
||||
return this.updateMerchantUser(id, { enabled: false });
|
||||
}
|
||||
|
||||
// === STATISTIQUES ===
|
||||
|
||||
/**
|
||||
* Récupère les statistiques des utilisateurs marchands
|
||||
*/
|
||||
getMerchantUsersStats(): Observable<MerchantPartnerStatsResponse> {
|
||||
getMerchantPartnerStats(): Observable<MerchantPartnerStatsResponse> {
|
||||
return this.http.get<MerchantPartnerStatsResponse>(`${this.apiUrl}/stats/overview`).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error loading merchant users stats:', error);
|
||||
@ -227,13 +224,13 @@ export class MerchantUsersService {
|
||||
);
|
||||
}
|
||||
|
||||
// === RECHERCHE ET FILTRES ===
|
||||
// === RECHERCHE ===
|
||||
|
||||
/**
|
||||
* Recherche des utilisateurs marchands avec filtres
|
||||
*/
|
||||
searchMerchantUsers(params: SearchMerchantUsersParams): Observable<MerchantUserResponse[]> {
|
||||
let httpParams = new HttpParams();
|
||||
searchMerchantUsers(params: SearchUsersParams): Observable<MerchantUserDto[]> {
|
||||
let httpParams = new HttpParams().set('userType', UserType.MERCHANT);
|
||||
|
||||
if (params.query) {
|
||||
httpParams = httpParams.set('query', params.query);
|
||||
@ -247,7 +244,7 @@ export class MerchantUsersService {
|
||||
httpParams = httpParams.set('enabled', params.enabled.toString());
|
||||
}
|
||||
|
||||
return this.http.get<MerchantUserResponse[]>(`${this.apiUrl}/search`, { params: httpParams }).pipe(
|
||||
return this.http.get<MerchantUserDto[]>(`${this.apiUrl}/search`, { params: httpParams }).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error searching merchant users:', error);
|
||||
return throwError(() => error);
|
||||
@ -264,7 +261,6 @@ export class MerchantUsersService {
|
||||
return this.http.get<AvailableRolesResponse>(`${this.apiUrl}/roles/available`).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error loading available merchant roles:', error);
|
||||
// Fallback en cas d'erreur
|
||||
return of({
|
||||
roles: [
|
||||
{
|
||||
@ -291,29 +287,13 @@ export class MerchantUsersService {
|
||||
);
|
||||
}
|
||||
|
||||
// === GESTION DU STATUT ===
|
||||
|
||||
/**
|
||||
* Active un utilisateur marchand
|
||||
*/
|
||||
enableMerchantUser(id: string): Observable<MerchantUserResponse> {
|
||||
return this.updateMerchantUser(id, { enabled: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Désactive un utilisateur marchand
|
||||
*/
|
||||
disableMerchantUser(id: string): Observable<MerchantUserResponse> {
|
||||
return this.updateMerchantUser(id, { enabled: false });
|
||||
}
|
||||
|
||||
// === UTILITAIRES ===
|
||||
|
||||
/**
|
||||
* Vérifie si un nom d'utilisateur existe parmi les utilisateurs marchands
|
||||
*/
|
||||
merchantUserExists(username: string): Observable<{ exists: boolean }> {
|
||||
return this.getMyMerchantUsers().pipe(
|
||||
return this.searchMerchantUsers({ query: username }).pipe(
|
||||
map(users => ({
|
||||
exists: users.some(user => user.username === username)
|
||||
})),
|
||||
@ -327,21 +307,25 @@ export class MerchantUsersService {
|
||||
/**
|
||||
* Récupère les utilisateurs par rôle spécifique
|
||||
*/
|
||||
getMerchantUsersByRole(role: UserRole.DCB_PARTNER_ADMIN | UserRole.DCB_PARTNER_MANAGER | UserRole.DCB_PARTNER_SUPPORT): Observable<MerchantUserResponse[]> {
|
||||
getMerchantUsersByRole(role: UserRole): Observable<MerchantUserDto[]> {
|
||||
const merchantRoles = [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT];
|
||||
if (!merchantRoles.includes(role)) {
|
||||
return throwError(() => 'Invalid role for merchant user');
|
||||
}
|
||||
return this.searchMerchantUsers({ role });
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère uniquement les utilisateurs actifs
|
||||
*/
|
||||
getActiveMerchantUsers(): Observable<MerchantUserResponse[]> {
|
||||
getActiveMerchantUsers(): Observable<MerchantUserDto[]> {
|
||||
return this.searchMerchantUsers({ enabled: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère uniquement les utilisateurs inactifs
|
||||
*/
|
||||
getInactiveMerchantUsers(): Observable<MerchantUserResponse[]> {
|
||||
getInactiveMerchantUsers(): Observable<MerchantUserDto[]> {
|
||||
return this.searchMerchantUsers({ enabled: false });
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@ import { Component, Input } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import { UiCard } from '@app/components/ui-card';
|
||||
import { MerchantPartnerStatsResponse } from '../services/merchant-partners.service';
|
||||
import { MerchantPartnerStatsResponse } from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-merchant-users-stats',
|
||||
@ -2,13 +2,13 @@ import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { authGuard } from '../core/guards/auth.guard';
|
||||
import { roleGuard } from '../core/guards/role.guard';
|
||||
import { Users } from '@modules/users/users';
|
||||
import { HubUsers } from '@modules/hub-users/hub-users';
|
||||
|
||||
// Composants principaux
|
||||
import { DcbDashboard } from './dcb-dashboard/dcb-dashboard';
|
||||
import { Team } from './team/team';
|
||||
import { Transactions } from './transactions/transactions';
|
||||
import { MerchantPartners } from './merchant-partners/merchant-partners';
|
||||
import { MerchantUsers } from './merchant-users/merchant-users';
|
||||
import { OperatorsConfig } from './operators/config/config';
|
||||
import { OperatorsStats } from './operators/stats/stats';
|
||||
import { WebhooksHistory } from './webhooks/history/history';
|
||||
@ -77,7 +77,7 @@ const routes: Routes = [
|
||||
{
|
||||
path: 'users',
|
||||
canActivate: [authGuard, roleGuard],
|
||||
component: Users,
|
||||
component: HubUsers,
|
||||
data: {
|
||||
title: 'Gestion des Utilisateurs',
|
||||
module: 'users'
|
||||
@ -89,7 +89,7 @@ const routes: Routes = [
|
||||
// ---------------------------
|
||||
{
|
||||
path: 'merchant-partners',
|
||||
component: MerchantPartners,
|
||||
component: MerchantUsers,
|
||||
canActivate: [authGuard, roleGuard],
|
||||
data: {
|
||||
title: 'Gestion Partners/Marchants',
|
||||
|
||||
@ -5,10 +5,15 @@ import { FormsModule } from '@angular/forms';
|
||||
import { NgIcon } from '@ng-icons/core';
|
||||
import { NgbAlertModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { HubUsersService, UserRole, UpdateHubUserDto } from '../users/services/users.service';
|
||||
import { HubUsersService } from '../hub-users/services/hub-users.service';
|
||||
import { RoleManagementService } from '@core/services/role-management.service';
|
||||
import { AuthService } from '@core/services/auth.service';
|
||||
|
||||
import {
|
||||
UpdateUserDto,
|
||||
UserRole
|
||||
} from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-profile',
|
||||
standalone: true,
|
||||
@ -48,7 +53,7 @@ export class MyProfile implements OnInit, OnDestroy {
|
||||
|
||||
// Édition
|
||||
isEditing = false;
|
||||
editedUser: UpdateHubUserDto = {};
|
||||
editedUser: UpdateUserDto = {};
|
||||
|
||||
// Gestion des rôles (simplifiée pour profil personnel)
|
||||
availableRoles: { value: UserRole; label: string; description: string }[] = [];
|
||||
@ -73,7 +78,7 @@ export class MyProfile implements OnInit, OnDestroy {
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (profile) => {
|
||||
this.currentUserRole = profile?.roles?.[0] as UserRole || null;
|
||||
this.currentUserRole = profile?.role?.[0] as UserRole || null;
|
||||
// Pour le profil personnel, on peut toujours éditer son propre profil
|
||||
this.canEditUsers = true;
|
||||
this.canManageRoles = false; // On ne peut pas gérer les rôles de son propre profil
|
||||
@ -89,15 +94,19 @@ export class MyProfile implements OnInit, OnDestroy {
|
||||
* Charge les rôles disponibles (lecture seule pour profil personnel)
|
||||
*/
|
||||
private loadAvailableRoles(): void {
|
||||
this.roleService.getAvailableRolesSimple()
|
||||
this.usersService.getAvailableHubRoles()
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (roles) => {
|
||||
this.availableRoles = roles;
|
||||
next: (response) => {
|
||||
this.availableRoles = response.roles.map(role => ({
|
||||
value: role.value,
|
||||
label: role.label,
|
||||
description: role.description
|
||||
}));
|
||||
},
|
||||
error: (error) => {
|
||||
console.error('Error loading available roles:', error);
|
||||
// Fallback
|
||||
// Fallback avec tous les rôles
|
||||
this.availableRoles = [
|
||||
{ value: UserRole.DCB_ADMIN, label: 'DCB Admin', description: 'Administrateur système' },
|
||||
{ value: UserRole.DCB_SUPPORT, label: 'DCB Support', description: 'Support technique' },
|
||||
@ -158,7 +167,7 @@ export class MyProfile implements OnInit, OnDestroy {
|
||||
this.error = '';
|
||||
this.success = '';
|
||||
|
||||
this.usersService.updateUser(this.user.id, this.editedUser)
|
||||
this.usersService.updateHubUser(this.user.id, this.editedUser)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe({
|
||||
next: (updatedUser) => {
|
||||
@ -204,7 +213,7 @@ export class MyProfile implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
// Gestion des erreurs - même méthode que le premier composant
|
||||
// Gestion des erreurs
|
||||
private getErrorMessage(error: any): string {
|
||||
if (error.error?.message) {
|
||||
return error.error.message;
|
||||
@ -221,7 +230,7 @@ export class MyProfile implements OnInit, OnDestroy {
|
||||
return 'Une erreur est survenue. Veuillez réessayer.';
|
||||
}
|
||||
|
||||
// Utilitaires d'affichage - mêmes méthodes que le premier composant
|
||||
// Utilitaires d'affichage
|
||||
getStatusBadgeClass(): string {
|
||||
if (!this.user) return 'badge bg-secondary';
|
||||
if (!this.user.enabled) return 'badge bg-danger';
|
||||
@ -279,11 +288,39 @@ export class MyProfile implements OnInit, OnDestroy {
|
||||
|
||||
// Vérification des permissions pour les actions - toujours false pour les actions sensibles
|
||||
canAssignRole(targetRole: UserRole): boolean {
|
||||
return false; // Jamais autorisé pour le profil personnel
|
||||
return false;
|
||||
}
|
||||
|
||||
// Vérifie si c'est le profil de l'utilisateur courant - toujours true
|
||||
isCurrentUserProfile(): boolean {
|
||||
return true; // Toujours vrai pour le profil personnel
|
||||
return true;
|
||||
}
|
||||
|
||||
// Méthode utilitaire pour déterminer le type d'utilisateur
|
||||
getUserType(): string {
|
||||
if (!this.currentUserRole) return 'Utilisateur';
|
||||
|
||||
const roleNames: { [key in UserRole]?: string } = {
|
||||
[UserRole.DCB_ADMIN]: 'Administrateur DCB',
|
||||
[UserRole.DCB_SUPPORT]: 'Support DCB',
|
||||
[UserRole.DCB_PARTNER]: 'Partenaire DCB',
|
||||
[UserRole.DCB_PARTNER_ADMIN]: 'Administrateur Partenaire',
|
||||
[UserRole.DCB_PARTNER_MANAGER]: 'Manager Partenaire',
|
||||
[UserRole.DCB_PARTNER_SUPPORT]: 'Support Partenaire'
|
||||
};
|
||||
|
||||
return roleNames[this.currentUserRole] || this.currentUserRole;
|
||||
}
|
||||
|
||||
// Vérifie si l'utilisateur est un utilisateur Hub
|
||||
isHubUser(): boolean {
|
||||
const hubRoles = [UserRole.DCB_ADMIN, UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER];
|
||||
return this.currentUserRole ? hubRoles.includes(this.currentUserRole) : false;
|
||||
}
|
||||
|
||||
// Vérifie si l'utilisateur est un utilisateur marchand
|
||||
isMerchantUser(): boolean {
|
||||
const merchantRoles = [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT];
|
||||
return this.currentUserRole ? merchantRoles.includes(this.currentUserRole) : false;
|
||||
}
|
||||
}
|
||||
@ -1,325 +0,0 @@
|
||||
// src/app/modules/users/services/users.service.ts
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { environment } from '@environments/environment';
|
||||
import { Observable, map, catchError, throwError, of } from 'rxjs';
|
||||
import { AvailableRolesResponse } from '@core/services/role-management.service';
|
||||
// Interfaces alignées avec le contrôleur NestJS
|
||||
export interface HubUserResponse {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: UserRole;
|
||||
enabled: boolean;
|
||||
emailVerified: boolean;
|
||||
createdBy: string;
|
||||
createdByUsername: string;
|
||||
createdTimestamp: number;
|
||||
lastLogin?: number;
|
||||
userType: 'HUB';
|
||||
}
|
||||
|
||||
export interface CreateHubUserDto {
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
password: string;
|
||||
role: UserRole;
|
||||
enabled?: boolean;
|
||||
emailVerified?: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateHubUserDto {
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
email?: string;
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
export interface ResetPasswordDto {
|
||||
newPassword: string;
|
||||
temporary?: boolean;
|
||||
}
|
||||
|
||||
export interface PaginatedUserResponse {
|
||||
users: HubUserResponse[];
|
||||
total: number;
|
||||
page: number;
|
||||
limit: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export enum UserRole {
|
||||
DCB_ADMIN = 'dcb-admin',
|
||||
DCB_SUPPORT = 'dcb-support',
|
||||
DCB_PARTNER = 'dcb-partner',
|
||||
DCB_PARTNER_ADMIN = 'dcb-partner-admin',
|
||||
DCB_PARTNER_MANAGER = 'dcb-partner-manager',
|
||||
DCB_PARTNER_SUPPORT = 'dcb-partner-support'
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HubUsersService {
|
||||
private http = inject(HttpClient);
|
||||
private apiUrl = `${environment.iamApiUrl}/hub-users`;
|
||||
|
||||
// === CRUD COMPLET ===
|
||||
|
||||
/**
|
||||
* Crée un nouvel utilisateur Hub
|
||||
*/
|
||||
createUser(createUserDto: CreateHubUserDto): Observable<HubUserResponse> {
|
||||
// Validation
|
||||
if (!createUserDto.username?.trim()) {
|
||||
return throwError(() => 'Username is required and cannot be empty');
|
||||
}
|
||||
|
||||
if (!createUserDto.email?.trim()) {
|
||||
return throwError(() => 'Email is required and cannot be empty');
|
||||
}
|
||||
|
||||
if (!createUserDto.password || createUserDto.password.length < 8) {
|
||||
return throwError(() => 'Password must be at least 8 characters');
|
||||
}
|
||||
|
||||
if (!createUserDto.role) {
|
||||
return throwError(() => 'Role is required');
|
||||
}
|
||||
|
||||
// Nettoyage des données
|
||||
const payload = {
|
||||
username: createUserDto.username.trim(),
|
||||
email: createUserDto.email.trim(),
|
||||
firstName: (createUserDto.firstName || '').trim(),
|
||||
lastName: (createUserDto.lastName || '').trim(),
|
||||
password: createUserDto.password,
|
||||
role: createUserDto.role,
|
||||
enabled: createUserDto.enabled !== undefined ? createUserDto.enabled : true,
|
||||
emailVerified: createUserDto.emailVerified !== undefined ? createUserDto.emailVerified : false,
|
||||
};
|
||||
|
||||
return this.http.post<HubUserResponse>(this.apiUrl, payload).pipe(
|
||||
catchError(error => throwError(() => error))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère tous les utilisateurs Hub avec pagination
|
||||
*/
|
||||
findAllUsers(page: number = 1, limit: number = 10, filters?: any): Observable<PaginatedUserResponse> {
|
||||
let params = new HttpParams()
|
||||
.set('page', page.toString())
|
||||
.set('limit', limit.toString());
|
||||
|
||||
if (filters) {
|
||||
Object.keys(filters).forEach(key => {
|
||||
if (filters[key] !== undefined && filters[key] !== null) {
|
||||
params = params.set(key, filters[key].toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return this.http.get<HubUserResponse[]>(this.apiUrl, { params, observe: 'response' }).pipe(
|
||||
map(response => {
|
||||
const users = response.body || [];
|
||||
const total = parseInt(response.headers.get('X-Total-Count') || '0');
|
||||
|
||||
return {
|
||||
users,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit)
|
||||
};
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error loading users:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère tous les utilisateurs Hub avec pagination
|
||||
*/
|
||||
findAllMerchantUsers(page: number = 1, limit: number = 10, filters?: any): Observable<PaginatedUserResponse> {
|
||||
let params = new HttpParams()
|
||||
.set('page', page.toString())
|
||||
.set('limit', limit.toString());
|
||||
|
||||
if (filters) {
|
||||
Object.keys(filters).forEach(key => {
|
||||
if (filters[key] !== undefined && filters[key] !== null) {
|
||||
params = params.set(key, filters[key].toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return this.http.get<HubUserResponse[]>(`${this.apiUrl}//merchants/all`, { params, observe: 'response' }).pipe(
|
||||
map(response => {
|
||||
const users = response.body || [];
|
||||
const total = parseInt(response.headers.get('X-Total-Count') || '0');
|
||||
|
||||
return {
|
||||
users,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit)
|
||||
};
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('Error loading users:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère un utilisateur Hub par ID
|
||||
*/
|
||||
getUserById(id: string): Observable<HubUserResponse> {
|
||||
return this.http.get<HubUserResponse>(`${this.apiUrl}/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Met à jour un utilisateur Hub
|
||||
*/
|
||||
updateUser(id: string, updateUserDto: UpdateHubUserDto): Observable<HubUserResponse> {
|
||||
return this.http.put<HubUserResponse>(`${this.apiUrl}/${id}`, updateUserDto);
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime un utilisateur Hub
|
||||
*/
|
||||
deleteUser(id: string): Observable<{ message: string }> {
|
||||
return this.http.delete<{ message: string }>(`${this.apiUrl}/${id}`);
|
||||
}
|
||||
|
||||
// === GESTION DES MOTS DE PASSE ===
|
||||
|
||||
/**
|
||||
* Réinitialise le mot de passe d'un utilisateur
|
||||
*/
|
||||
resetPassword(userId: string, newPassword: string, temporary: boolean = true): Observable<{ message: string }> {
|
||||
return this.http.post<{ message: string }>(
|
||||
`${this.apiUrl}/${userId}/reset-password`,
|
||||
{ newPassword, temporary }
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie un email de réinitialisation de mot de passe
|
||||
*/
|
||||
sendPasswordResetEmail(userId: string): Observable<{ message: string }> {
|
||||
return this.http.post<{ message: string }>(
|
||||
`${this.apiUrl}/${userId}/send-reset-email`,
|
||||
{}
|
||||
);
|
||||
}
|
||||
|
||||
// === GESTION DU STATUT ===
|
||||
|
||||
/**
|
||||
* Active un utilisateur
|
||||
*/
|
||||
enableUser(id: string): Observable<HubUserResponse> {
|
||||
return this.http.put<HubUserResponse>(`${this.apiUrl}/${id}`, { enabled: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Désactive un utilisateur
|
||||
*/
|
||||
disableUser(id: string): Observable<HubUserResponse> {
|
||||
return this.http.put<HubUserResponse>(`${this.apiUrl}/${id}`, { enabled: false });
|
||||
}
|
||||
|
||||
// === GESTION DES RÔLES ===
|
||||
|
||||
/**
|
||||
* Met à jour le rôle d'un utilisateur
|
||||
*/
|
||||
updateUserRole(id: string, role: UserRole): Observable<HubUserResponse> {
|
||||
return this.http.put<HubUserResponse>(`${this.apiUrl}/${id}/role`, { role });
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les rôles Hub disponibles
|
||||
*/
|
||||
getAvailableHubRoles(): Observable<AvailableRolesResponse> {
|
||||
return this.http.get<AvailableRolesResponse>(
|
||||
`${this.apiUrl}/roles/available`
|
||||
).pipe(
|
||||
catchError(error => {
|
||||
console.error('Error loading available roles:', error);
|
||||
// Fallback en cas d'erreur
|
||||
return of({
|
||||
roles: [
|
||||
{
|
||||
value: UserRole.DCB_ADMIN,
|
||||
label: 'DCB Admin',
|
||||
description: 'Full administrative access to the entire system'
|
||||
},
|
||||
{
|
||||
value: UserRole.DCB_SUPPORT,
|
||||
label: 'DCB Support',
|
||||
description: 'Support access with limited administrative capabilities'
|
||||
},
|
||||
{
|
||||
value: UserRole.DCB_PARTNER,
|
||||
label: 'DCB Partner',
|
||||
description: 'Merchant partner with access to their own merchant ecosystem'
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les utilisateurs par rôle
|
||||
*/
|
||||
getUsersByRole(role: UserRole): Observable<HubUserResponse[]> {
|
||||
return this.http.get<HubUserResponse[]>(`${this.apiUrl}/role/${role}`);
|
||||
}
|
||||
|
||||
// === STATISTIQUES ===
|
||||
|
||||
/**
|
||||
* Récupère les statistiques des utilisateurs
|
||||
*/
|
||||
getUsersStats(): Observable<any> {
|
||||
return this.http.get<any>(`${this.apiUrl}/stats/overview`);
|
||||
}
|
||||
|
||||
// === UTILITAIRES ===
|
||||
|
||||
/**
|
||||
* Vérifie si un nom d'utilisateur existe
|
||||
*/
|
||||
userExists(username: string): Observable<{ exists: boolean }> {
|
||||
// Implémentation temporaire - à adapter selon votre API
|
||||
return this.findAllUsers().pipe(
|
||||
map(response => ({
|
||||
exists: response.users.some(user => user.username === username)
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche des utilisateurs
|
||||
*/
|
||||
searchUsers(query: string): Observable<HubUserResponse[]> {
|
||||
return this.findAllUsers().pipe(
|
||||
map(response => response.users.filter(user =>
|
||||
user.username.toLowerCase().includes(query.toLowerCase()) ||
|
||||
user.email.toLowerCase().includes(query.toLowerCase()) ||
|
||||
user.firstName?.toLowerCase().includes(query.toLowerCase()) ||
|
||||
user.lastName?.toLowerCase().includes(query.toLowerCase())
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,2 +0,0 @@
|
||||
import { Users } from './users';
|
||||
describe('Users', () => {});
|
||||
Loading…
Reference in New Issue
Block a user