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