diff --git a/src/app/core/models/merchant-config.model.ts b/src/app/core/models/merchant-config.model.ts index 23c9ef6..867396a 100644 --- a/src/app/core/models/merchant-config.model.ts +++ b/src/app/core/models/merchant-config.model.ts @@ -58,7 +58,7 @@ export interface TechnicalContact { } export interface MerchantUser { - userId: number; + userId: string; role: UserRole; username?: string; email?: string; @@ -134,8 +134,8 @@ export interface CreateMerchantDto { description?: string; adresse: string; phone: string; - configs: Omit[]; - technicalContacts: Omit[]; + configs?: Omit[]; + technicalContacts?: Omit[]; } export interface UpdateMerchantDto extends Partial {} @@ -226,15 +226,7 @@ export class MerchantUtils { if (!merchant.phone?.trim()) { errors.push('Le téléphone est requis'); } - - if (!merchant.technicalContacts || merchant.technicalContacts.length === 0) { - errors.push('Au moins un contact technique est requis'); - } - - if (!merchant.configs || merchant.configs.length === 0) { - errors.push('Au moins une configuration est requise'); - } - + return errors; } diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts index df2924a..b4eb4cf 100644 --- a/src/app/core/services/auth.service.ts +++ b/src/app/core/services/auth.service.ts @@ -233,6 +233,7 @@ export class AuthService { emailVerified: apiUser.emailVerified ?? apiUser.email_verified ?? false, userType: userType, merchantPartnerId: apiUser.merchantPartnerId || apiUser.partnerId || apiUser.merchantId || null, + merchantConfigId: apiUser.merchantConfigId || apiUser.configId || apiUser.merchantId || null, role: apiUser.clientRoles || apiUser.clientRoles?.[0] || '', // Gérer rôle unique ou tableau createdBy: apiUser.createdBy || apiUser.creatorId || null, createdByUsername: apiUser.createdByUsername || apiUser.creatorUsername || null, diff --git a/src/app/core/services/hub-users-roles-management.service.ts b/src/app/core/services/hub-users-roles-management.service.ts index 38e9a02..6e7c268 100644 --- a/src/app/core/services/hub-users-roles-management.service.ts +++ b/src/app/core/services/hub-users-roles-management.service.ts @@ -4,6 +4,7 @@ import { MerchantUsersService } from '@modules/hub-users-management/merchant-use import { BehaviorSubject, Observable, map, tap, of, catchError } from 'rxjs'; import { UserRole, UserType, AvailableRole } from '@core/models/dcb-bo-hub-user.model'; +// Interfaces export interface RolePermission { canCreateUsers: boolean; canEditUsers: boolean; @@ -21,6 +22,196 @@ export interface AvailableRolesWithPermissions { roles: (AvailableRole & { permissions: RolePermission })[]; } +interface RoleConfig { + label: string; + description: string; + badgeClass: string; + icon: string; + permissions: RolePermission; +} + +// Permissions par défaut +const DEFAULT_PERMISSIONS: RolePermission = { + canCreateUsers: false, + canEditUsers: false, + canDeleteUsers: false, + canManageRoles: false, + canViewStats: false, + canManageMerchants: false, + canAccessAdmin: false, + canAccessSupport: false, + canAccessPartner: false, + assignableRoles: [] +}; + +// Configuration des rôles +const ROLE_CONFIG: Record = { + [UserRole.DCB_ADMIN]: { + label: 'Administrateur DCB', + description: 'Administrateur système avec tous les accès', + badgeClass: 'bg-danger', + icon: 'lucideShield', + permissions: { + canCreateUsers: true, + canEditUsers: true, + canDeleteUsers: true, + canManageRoles: true, + canViewStats: true, + canManageMerchants: true, + canAccessAdmin: true, + canAccessSupport: true, + canAccessPartner: true, + assignableRoles: Object.values(UserRole) + } + }, + [UserRole.DCB_SUPPORT]: { + label: 'Support DCB', + description: 'Support technique avec accès étendus', + badgeClass: 'bg-info', + icon: 'lucideHeadphones', + permissions: { + canCreateUsers: true, + canEditUsers: true, + canDeleteUsers: false, + canManageRoles: true, + canViewStats: true, + canManageMerchants: true, + canAccessAdmin: false, + canAccessSupport: true, + canAccessPartner: true, + assignableRoles: [ + UserRole.DCB_SUPPORT, + UserRole.DCB_PARTNER, + UserRole.DCB_PARTNER_ADMIN, + UserRole.DCB_PARTNER_MANAGER, + UserRole.DCB_PARTNER_SUPPORT + ] + } + }, + [UserRole.DCB_PARTNER]: { + label: 'Partenaire DCB', + description: 'Partenaire commercial principal', + badgeClass: 'bg-primary', + icon: 'lucideBuilding', + permissions: { + canCreateUsers: true, + canEditUsers: true, + canDeleteUsers: true, + canManageRoles: true, + canViewStats: true, + canManageMerchants: false, + canAccessAdmin: false, + canAccessSupport: false, + canAccessPartner: false, + assignableRoles: [ + UserRole.DCB_PARTNER_ADMIN, + UserRole.DCB_PARTNER_MANAGER, + UserRole.DCB_PARTNER_SUPPORT + ] + } + }, + [UserRole.DCB_PARTNER_ADMIN]: { + label: 'Admin Partenaire', + description: 'Administrateur de partenaire marchand', + badgeClass: 'bg-warning', + icon: 'lucideShieldCheck', + permissions: { + canCreateUsers: true, + canEditUsers: true, + canDeleteUsers: true, + canManageRoles: true, + canViewStats: true, + canManageMerchants: false, + canAccessAdmin: false, + canAccessSupport: false, + canAccessPartner: false, + assignableRoles: [UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT] + } + }, + [UserRole.DCB_PARTNER_MANAGER]: { + label: 'Manager Partenaire', + description: 'Manager opérationnel partenaire', + badgeClass: 'bg-success', + icon: 'lucideUserCog', + permissions: { + canCreateUsers: false, + canEditUsers: false, + canDeleteUsers: false, + canManageRoles: false, + canViewStats: true, + canManageMerchants: true, + canAccessAdmin: false, + canAccessSupport: false, + canAccessPartner: true, + assignableRoles: [] + } + }, + [UserRole.DCB_PARTNER_SUPPORT]: { + label: 'Support Partenaire', + description: 'Support technique partenaire', + badgeClass: 'bg-secondary', + icon: 'lucideHeadphones', + permissions: { + canCreateUsers: false, + canEditUsers: false, + canDeleteUsers: false, + canManageRoles: false, + canViewStats: true, + canManageMerchants: false, + canAccessAdmin: false, + canAccessSupport: false, + canAccessPartner: true, + assignableRoles: [] + } + }, + [UserRole.MERCHANT_CONFIG_ADMIN]: { + label: 'Admin Marchand', + description: 'Administrateur de configuration marchand', + badgeClass: 'bg-warning', + icon: 'lucideSettings', + permissions: DEFAULT_PERMISSIONS + }, + [UserRole.MERCHANT_CONFIG_MANAGER]: { + label: 'Manager Marchand', + description: 'Manager de configuration marchand', + badgeClass: 'bg-success', + icon: 'lucideUserCog', + permissions: DEFAULT_PERMISSIONS + }, + [UserRole.MERCHANT_CONFIG_TECHNICAL]: { + label: 'Technique Marchand', + description: 'Support technique configuration marchand', + badgeClass: 'bg-secondary', + icon: 'lucideWrench', + permissions: DEFAULT_PERMISSIONS + }, + [UserRole.MERCHANT_CONFIG_VIEWER]: { + label: 'Visualiseur Marchand', + description: 'Visualiseur de configuration marchand', + badgeClass: 'bg-light', + icon: 'lucideEye', + permissions: DEFAULT_PERMISSIONS + } +} as const; + +// Rôles Hub (pour les filtres) +const HUB_ROLES = [ + UserRole.DCB_ADMIN, + UserRole.DCB_SUPPORT, + UserRole.DCB_PARTNER +] as const; + +// Rôles Marchands (pour les filtres) +const MERCHANT_ROLES = [ + UserRole.DCB_PARTNER_ADMIN, + UserRole.DCB_PARTNER_MANAGER, + UserRole.DCB_PARTNER_SUPPORT, + UserRole.MERCHANT_CONFIG_ADMIN, + UserRole.MERCHANT_CONFIG_MANAGER, + UserRole.MERCHANT_CONFIG_TECHNICAL, + UserRole.MERCHANT_CONFIG_VIEWER +] as const; + @Injectable({ providedIn: 'root' }) @@ -35,28 +226,30 @@ export class RoleManagementService { * Charge les rôles Hub disponibles */ loadAvailableHubRoles(): Observable { - return this.hubUsersService.getAvailableHubRoles().pipe( - map(apiResponse => ({ - roles: apiResponse.roles.map(role => ({ - ...role, - permissions: this.getPermissionsForRole(role.value) - })) - })), - tap(roles => this.availableRoles$.next(roles)), - catchError(error => { - console.error('Error loading hub roles:', error); - // On renvoie un observable valide pour éviter le crash - return of({ roles: [] } as AvailableRolesWithPermissions); - }) + return this.loadRoles( + () => this.hubUsersService.getAvailableHubRoles(), + 'hub' ); } - /** * Charge les rôles Marchands disponibles */ loadAvailableMerchantRoles(): Observable { - return this.merchantUsersService.getAvailableMerchantRoles().pipe( + return this.loadRoles( + () => this.merchantUsersService.getAvailableMerchantRoles(), + 'merchant' + ); + } + + /** + * Méthode générique pour charger les rôles + */ + private loadRoles( + fetchFn: () => Observable<{ roles: AvailableRole[] }>, + type: 'hub' | 'merchant' + ): Observable { + return fetchFn().pipe( map(apiResponse => ({ roles: apiResponse.roles.map(role => ({ ...role, @@ -65,8 +258,7 @@ export class RoleManagementService { })), tap(roles => this.availableRoles$.next(roles)), catchError(error => { - console.error('Error loading merchant roles:', error); - // On renvoie un observable valide pour éviter le crash + console.error(`Error loading ${type} roles:`, error); return of({ roles: [] } as AvailableRolesWithPermissions); }) ); @@ -98,119 +290,9 @@ export class RoleManagementService { */ getPermissionsForRole(role: UserRole | null): RolePermission { if (!role) { - return this.getDefaultPermissions(); + return DEFAULT_PERMISSIONS; } - - const allRoles = this.getAllRoles(); - const hubRoles = [UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER, UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT]; - const merchantRoles = [UserRole.DCB_PARTNER_ADMIN, UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT]; - - switch (role) { - case UserRole.DCB_ADMIN: - return { - canCreateUsers: true, - canEditUsers: true, - canDeleteUsers: true, - canManageRoles: true, - canViewStats: true, - canManageMerchants: true, - canAccessAdmin: true, - canAccessSupport: true, - canAccessPartner: true, - assignableRoles: allRoles - }; - - case UserRole.DCB_SUPPORT: - return { - canCreateUsers: true, - canEditUsers: true, - canDeleteUsers: false, - canManageRoles: true, - canViewStats: true, - canManageMerchants: true, - canAccessAdmin: false, - canAccessSupport: true, - canAccessPartner: true, - assignableRoles: hubRoles, - }; - - case UserRole.DCB_PARTNER: - return { - canCreateUsers: true, - canEditUsers: true, - canDeleteUsers: true, - canManageRoles: true, - canViewStats: true, - canManageMerchants: false, - canAccessAdmin: false, - canAccessSupport: false, - canAccessPartner: false, - assignableRoles: merchantRoles - }; - - case UserRole.DCB_PARTNER_ADMIN: - return { - canCreateUsers: true, - canEditUsers: true, - canDeleteUsers: true, - canManageRoles: true, - canViewStats: true, - canManageMerchants: false, - canAccessAdmin: false, - canAccessSupport: false, - canAccessPartner: false, - assignableRoles: [UserRole.DCB_PARTNER_MANAGER, UserRole.DCB_PARTNER_SUPPORT] - }; - - case UserRole.DCB_PARTNER_MANAGER: - return { - canCreateUsers: false, - canEditUsers: false, - canDeleteUsers: false, - canManageRoles: false, - canViewStats: true, - canManageMerchants: true, - canAccessAdmin: false, - canAccessSupport: false, - canAccessPartner: true, - assignableRoles: [] - }; - - case UserRole.DCB_PARTNER_SUPPORT: - return { - canCreateUsers: false, - canEditUsers: false, - canDeleteUsers: false, - canManageRoles: false, - canViewStats: true, - canManageMerchants: false, - canAccessAdmin: false, - canAccessSupport: false, - canAccessPartner: true, - assignableRoles: [] - }; - - default: - return this.getDefaultPermissions(); - } - } - - /** - * Permissions par défaut (rôle inconnu ou non défini) - */ - private getDefaultPermissions(): RolePermission { - return { - canCreateUsers: false, - canEditUsers: false, - canDeleteUsers: false, - canManageRoles: false, - canViewStats: false, - canManageMerchants: false, - canAccessAdmin: false, - canAccessSupport: false, - canAccessPartner: false, - assignableRoles: [] - }; + return ROLE_CONFIG[role]?.permissions || DEFAULT_PERMISSIONS; } /** @@ -219,10 +301,9 @@ export class RoleManagementService { canAssignRole(currentUserRole: UserRole | null, targetRole: UserRole): boolean { if (!currentUserRole) return false; - // Rôles qui peuvent attribuer tous les rôles const fullPermissionRoles = [ - UserRole.DCB_ADMIN, - UserRole.DCB_SUPPORT, + UserRole.DCB_ADMIN, + UserRole.DCB_SUPPORT, UserRole.DCB_PARTNER ]; @@ -230,287 +311,163 @@ export class RoleManagementService { return true; } - // Pour les autres rôles, utiliser les permissions définies const permissions = this.getPermissionsForRole(currentUserRole); return permissions.assignableRoles.includes(targetRole); } - /** - * Vérifie si l'utilisateur courant peut créer des utilisateurs - */ + // Méthodes d'utilité pour les permissions canCreateUsers(currentUserRole: UserRole | null): boolean { - return currentUserRole ? this.getPermissionsForRole(currentUserRole).canCreateUsers : false; + return this.getPermission(currentUserRole, 'canCreateUsers'); } - /** - * Vérifie si l'utilisateur courant peut éditer des utilisateurs - */ canEditUsers(currentUserRole: UserRole | null): boolean { - return currentUserRole ? this.getPermissionsForRole(currentUserRole).canEditUsers : false; + return this.getPermission(currentUserRole, 'canEditUsers'); } - /** - * Vérifie si l'utilisateur courant peut supprimer des utilisateurs - */ canDeleteUsers(currentUserRole: UserRole | null): boolean { - return currentUserRole ? this.getPermissionsForRole(currentUserRole).canDeleteUsers : false; + return this.getPermission(currentUserRole, 'canDeleteUsers'); } - /** - * Vérifie si l'utilisateur courant peut gérer les rôles - */ canManageRoles(currentUserRole: UserRole | null): boolean { - return currentUserRole ? this.getPermissionsForRole(currentUserRole).canManageRoles : false; + return this.getPermission(currentUserRole, 'canManageRoles'); } - /** - * Vérifie si l'utilisateur courant peut accéder aux statistiques - */ canViewStats(currentUserRole: UserRole | null): boolean { - return currentUserRole ? this.getPermissionsForRole(currentUserRole).canViewStats : false; + return this.getPermission(currentUserRole, 'canViewStats'); } - /** - * Vérifie si l'utilisateur courant peut gérer les merchants - */ canManageMerchants(currentUserRole: UserRole | null): boolean { - return currentUserRole ? this.getPermissionsForRole(currentUserRole).canManageMerchants : false; + return this.getPermission(currentUserRole, 'canManageMerchants'); } - /** - * Vérifie si l'utilisateur courant peut accéder à l'admin - */ canAccessAdmin(currentUserRole: UserRole | null): boolean { - return currentUserRole ? this.getPermissionsForRole(currentUserRole).canAccessAdmin : false; + return this.getPermission(currentUserRole, 'canAccessAdmin'); } - /** - * Vérifie si l'utilisateur courant peut accéder au support - */ canAccessSupport(currentUserRole: UserRole | null): boolean { - return currentUserRole ? this.getPermissionsForRole(currentUserRole).canAccessSupport : false; + return this.getPermission(currentUserRole, 'canAccessSupport'); } - /** - * Vérifie si l'utilisateur courant peut accéder à l'espace partenaire - */ canAccessPartner(currentUserRole: UserRole | null): boolean { - return currentUserRole ? this.getPermissionsForRole(currentUserRole).canAccessPartner : false; + return this.getPermission(currentUserRole, 'canAccessPartner'); } /** - * Récupère le libellé d'un rôle + * Méthode helper générique pour les permissions + */ + private getPermission( + role: UserRole | null, + permissionKey: keyof RolePermission + ): boolean { + if (!role) return false; + const permissions = this.getPermissionsForRole(role); + return Boolean(permissions[permissionKey]); + } + + /** + * Méthodes d'utilité pour les rôles */ getRoleLabel(role: string): string { const userRole = role as UserRole; - switch (userRole) { - case UserRole.DCB_ADMIN: - return 'Administrateur DCB'; - case UserRole.DCB_SUPPORT: - return 'Support DCB'; - case UserRole.DCB_PARTNER: - return 'Partenaire DCB'; - case UserRole.DCB_PARTNER_ADMIN: - return 'Admin Partenaire'; - case UserRole.DCB_PARTNER_MANAGER: - return 'Manager Partenaire'; - case UserRole.DCB_PARTNER_SUPPORT: - return 'Support Partenaire'; - default: - return role; - } + return ROLE_CONFIG[userRole]?.label || role; } - /** - * Récupère la description d'un rôle - */ getRoleDescription(role: string | UserRole): string { const userRole = role as UserRole; - const roleDescriptions: { [key in UserRole]: string } = { - [UserRole.DCB_ADMIN]: 'Administrateur système avec tous les accès', - [UserRole.DCB_SUPPORT]: 'Support technique avec accès étendus', - [UserRole.DCB_PARTNER]: 'Partenaire commercial principal', - [UserRole.DCB_PARTNER_ADMIN]: 'Administrateur de partenaire marchand', - [UserRole.DCB_PARTNER_MANAGER]: 'Manager opérationnel partenaire', - [UserRole.DCB_PARTNER_SUPPORT]: 'Support technique partenaire', - [UserRole.MERCHANT_CONFIG_ADMIN]: 'Administrateur de partenaire marchand', - [UserRole.MERCHANT_CONFIG_MANAGER]: 'Manager opérationnel partenaire', - [UserRole.MERCHANT_CONFIG_TECHNICAL]: 'Support technique partenaire', - [UserRole.MERCHANT_CONFIG_VIEWER]: 'Support technique partenaire' - }; - return roleDescriptions[userRole] || 'Description non disponible'; + return ROLE_CONFIG[userRole]?.description || 'Description non disponible'; } - /** - * Récupère la classe CSS pour un badge de rôle - */ getRoleBadgeClass(role: string): string { const userRole = role as UserRole; - switch (userRole) { - case UserRole.DCB_ADMIN: - return 'bg-danger'; - case UserRole.DCB_SUPPORT: - return 'bg-info'; - case UserRole.DCB_PARTNER: - return 'bg-primary'; - case UserRole.DCB_PARTNER_ADMIN: - return 'bg-warning'; - case UserRole.DCB_PARTNER_MANAGER: - return 'bg-success'; - case UserRole.DCB_PARTNER_SUPPORT: - return 'bg-secondary'; - default: - return 'bg-secondary'; - } + return ROLE_CONFIG[userRole]?.badgeClass || 'bg-secondary'; } - /** - * Récupère l'icône pour un rôle - */ getRoleIcon(role: string): string { const userRole = role as UserRole; - switch (userRole) { - case UserRole.DCB_ADMIN: - return 'lucideShield'; - case UserRole.DCB_SUPPORT: - return 'lucideHeadphones'; - case UserRole.DCB_PARTNER: - return 'lucideBuilding'; - case UserRole.DCB_PARTNER_ADMIN: - return 'lucideShieldCheck'; - case UserRole.DCB_PARTNER_MANAGER: - return 'lucideUserCog'; - case UserRole.DCB_PARTNER_SUPPORT: - return 'lucideHeadphones'; - default: - return 'lucideUser'; - } + return ROLE_CONFIG[userRole]?.icon || 'lucideUser'; } - /** - * Vérifie si un rôle est un rôle administrateur + * Vérifications de type de rôle */ isAdminRole(role: UserRole): boolean { return role === UserRole.DCB_ADMIN; } - /** - * Vérifie si un rôle est un rôle support - */ isSupportRole(role: UserRole): boolean { return role === UserRole.DCB_SUPPORT; } - /** - * Vérifie si un rôle est un rôle partenaire - */ isPartnerRole(role: UserRole): boolean { 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); + isMerchantUserRole(role: UserRole): boolean { + return role === UserRole.DCB_PARTNER_ADMIN + || role === UserRole.DCB_PARTNER_MANAGER + || role === UserRole.DCB_PARTNER_SUPPORT + || role === UserRole.MERCHANT_CONFIG_ADMIN + || role === UserRole.MERCHANT_CONFIG_MANAGER + || role === UserRole.MERCHANT_CONFIG_TECHNICAL + || role === UserRole.MERCHANT_CONFIG_VIEWER; } /** - * Récupère tous les rôles disponibles sous forme de tableau + * Gestion des listes de rôles */ getAllRoles(): UserRole[] { return Object.values(UserRole); } - /** - * Récupère les rôles que l'utilisateur courant peut attribuer - */ + getHubRoles(): UserRole[] { + return [...HUB_ROLES]; + } + + getMerchantRoles(): UserRole[] { + return [...MERCHANT_ROLES]; + } + getAssignableRoles(currentUserRole: UserRole | null): UserRole[] { if (!currentUserRole) return []; return this.getPermissionsForRole(currentUserRole).assignableRoles; } - /** - * Récupère les rôles Hub assignables (pour le composant unifié) - */ getAssignableHubRoles(currentUserRole: UserRole | null): UserRole[] { - if (!currentUserRole) return []; - - const allHubRoles = this.getHubRoles(); - const permissions = this.getPermissionsForRole(currentUserRole); - - return allHubRoles.filter(role => - permissions.assignableRoles.includes(role) - ); + return this.filterAssignableRoles(currentUserRole, HUB_ROLES); } - /** - * Récupère les rôles Marchands assignables (pour le composant unifié) - */ getAssignableMerchantRoles(currentUserRole: UserRole | null): UserRole[] { + return this.filterAssignableRoles(currentUserRole, MERCHANT_ROLES); + } + + private filterAssignableRoles( + currentUserRole: UserRole | null, + roleList: readonly UserRole[] + ): UserRole[] { if (!currentUserRole) return []; - - const allMerchantRoles = this.getMerchantRoles(); const permissions = this.getPermissionsForRole(currentUserRole); - - return allMerchantRoles.filter(role => - permissions.assignableRoles.includes(role) - ); + return roleList.filter(role => permissions.assignableRoles.includes(role)); } /** - * Récupère uniquement les rôles Hub - */ - getHubRoles(): UserRole[] { - return [ - UserRole.DCB_ADMIN, - UserRole.DCB_SUPPORT, - UserRole.DCB_PARTNER - ]; - } - - /** - * Récupère uniquement les rôles Marchands - */ - getMerchantRoles(): UserRole[] { - return [ - UserRole.DCB_PARTNER_ADMIN, - UserRole.DCB_PARTNER_MANAGER, - UserRole.DCB_PARTNER_SUPPORT - ]; - } - - /** - * Vérifie si l'utilisateur a un rôle spécifique + * Vérifications de rôles */ hasRole(userRole: UserRole | null, targetRole: UserRole): boolean { return userRole === targetRole; } - /** - * Vérifie si l'utilisateur a au moins un des rôles spécifiés - */ hasAnyRole(userRole: UserRole | null, targetRoles: UserRole[]): boolean { return userRole ? targetRoles.includes(userRole) : false; } /** - * Réinitialise le cache des rôles + * Gestion du cache */ clearCache(): void { this.availableRoles$.next(null); this.currentUserRole$.next(null); } - /** - * Récupère les rôles disponibles (observable) - */ getAvailableRoles(): Observable { return this.availableRoles$.asObservable(); } diff --git a/src/app/modules/hub-users-management/hub-users.html b/src/app/modules/hub-users-management/hub-users.html index 93785f4..6978217 100644 --- a/src/app/modules/hub-users-management/hub-users.html +++ b/src/app/modules/hub-users-management/hub-users.html @@ -316,6 +316,103 @@ + + @if (newUser.role === UserRole.DCB_PARTNER) { +
+
+ + Informations de base du marchand +
+
+ +
+ + + @if (merchantName.invalid && merchantName.touched) { +
+ Le nom du marchand est requis +
+ } +
+ +
+ + +
+ +
+ + +
+ +
+ + + @if (merchantAdresse.invalid && merchantAdresse.touched) { +
+ L'adresse est requise +
+ } +
+ +
+ + + @if (merchantPhone.invalid && merchantPhone.touched) { +
+ Le téléphone est requis +
+ } +
+ } + @if (!canManageRoles) {
@@ -401,6 +498,9 @@ • Type d'utilisateur : HUB
• Créé par : Utilisateur courant
• Votre rôle : {{ currentUserRole || 'Non défini' }} + @if (newUser.role === UserRole.DCB_PARTNER) { +
• Un marchand sera créé avec cet utilisateur + }
diff --git a/src/app/modules/hub-users-management/hub-users.ts b/src/app/modules/hub-users-management/hub-users.ts index e49ccd0..039d2fa 100644 --- a/src/app/modules/hub-users-management/hub-users.ts +++ b/src/app/modules/hub-users-management/hub-users.ts @@ -8,6 +8,7 @@ import { Subject, takeUntil } from 'rxjs'; import { HubUsersService } from './hub-users.service'; import { RoleManagementService } from '@core/services/hub-users-roles-management.service'; import { AuthService } from '@core/services/auth.service'; +import { MerchantSyncService } from './merchant-sync-orchestrator.service'; import { PageTitle } from '@app/components/page-title/page-title'; import { HubUsersList } from './hub-users-list/hub-users-list'; import { HubUserProfile } from './hub-users-profile/hub-users-profile'; @@ -17,6 +18,7 @@ import { UserRole, UserType } from '@core/models/dcb-bo-hub-user.model'; +import { CreateMerchantDto } from '@core/models/merchant-config.model'; @Component({ selector: 'app-hub-users', @@ -37,6 +39,7 @@ export class HubUsersManagement implements OnInit, OnDestroy { private modalService = inject(NgbModal); private authService = inject(AuthService); private hubUsersService = inject(HubUsersService); + private merchantSyncService = inject(MerchantSyncService); protected roleService = inject(RoleManagementService); private cdRef = inject(ChangeDetectorRef); private destroy$ = new Subject(); @@ -60,7 +63,7 @@ export class HubUsersManagement implements OnInit, OnDestroy { canDeleteUsers = false; canManageRoles = false; - // Formulaire de création + // Formulaire de création utilisateur newUser: { username: string; email: string; @@ -70,12 +73,17 @@ export class HubUsersManagement implements OnInit, OnDestroy { role: UserRole; enabled: boolean; emailVerified: boolean; - userType: UserType + userType: UserType; } = this.getDefaultUserForm(); + // Formulaire de création marchand (pour DCB_PARTNER) + newMerchant: CreateMerchantDto = this.getDefaultMerchantForm(); + // États des opérations creatingUser = false; createUserError = ''; + creatingMerchant = false; + resettingPassword = false; resetPasswordError = ''; resetPasswordSuccess = ''; @@ -218,6 +226,18 @@ export class HubUsersManagement implements OnInit, OnDestroy { }; } + private getDefaultMerchantForm(): CreateMerchantDto { + return { + name: '', + logo: '', + description: '', + adresse: '', + phone: '', + configs:[], + technicalContacts: [] + }; + } + // ==================== MÉTHODES D'INTERFACE ==================== userProfiles: { [userId: string]: any } = {}; // Stocker les profils par userId @@ -313,17 +333,8 @@ export class HubUsersManagement implements OnInit, OnDestroy { } private resetUserForm() { - this.newUser = { - username: '', - email: '', - firstName: '', - lastName: '', - password: '', - role: UserRole.DCB_SUPPORT, - enabled: true, - emailVerified: false, - userType: UserType.HUB - }; + this.newUser = this.getDefaultUserForm(); + this.newMerchant = this.getDefaultMerchantForm(); console.log('🔄 Hub user form reset'); } @@ -386,10 +397,11 @@ export class HubUsersManagement implements OnInit, OnDestroy { return; } - const validation = this.validateUserForm(); - if (!validation.isValid) { - this.createUserError = validation.error!; - console.error('❌ Form validation failed:', validation.error); + // Validation du formulaire utilisateur + const userValidation = this.validateUserForm(); + if (!userValidation.isValid) { + this.createUserError = userValidation.error!; + console.error('❌ User form validation failed:', userValidation.error); return; } @@ -402,8 +414,96 @@ export class HubUsersManagement implements OnInit, OnDestroy { this.creatingUser = true; this.createUserError = ''; - console.log('📤 Creating hub user with data:', this.newUser); + console.log('📤 Creating hub user:', { + username: this.newUser.username, + role: this.newUser.role, + type: this.newUser.role === UserRole.DCB_PARTNER ? 'DCB Partner (avec marchand)' : 'Utilisateur Hub standard' + }); + // Pour DCB_PARTNER: créer d'abord le marchand + if (this.newUser.role === UserRole.DCB_PARTNER) { + // Validation du formulaire marchand pour DCB_PARTNER + const merchantValidation = this.validateMerchantForm(); + if (!merchantValidation.isValid) { + this.createUserError = merchantValidation.error!; + console.error('❌ Merchant form validation failed:', merchantValidation.error); + this.creatingUser = false; + return; + } + + this.createMerchantForDcbPartner(); + } else { + // Pour les autres rôles: créer uniquement l'utilisateur Hub + this.createHubUserOnly(); + } + } + + /** + * Crée un marchand avec un DCB Partner (ordre: merchantConfig -> Keycloak) + */ + private createMerchantForDcbPartner() { + this.creatingMerchant = true; + console.log('📤 Creating merchant for DCB_PARTNER:', this.newMerchant); + + // Données du DCB Partner + const dcbPartnerData = { + username: this.newUser.username, + email: this.newUser.email, + password: this.newUser.password, + firstName: this.newUser.firstName, + lastName: this.newUser.lastName, + enabled: this.newUser.enabled, + emailVerified: this.newUser.emailVerified + }; + + // Données du marchand + const merchantData: CreateMerchantDto = { + ...this.newMerchant + + }; + + console.log('🔧 Données envoyées:'); + console.log(' Merchant:', merchantData); + console.log(' DCB Partner:', { ...dcbPartnerData, password: '***' }); + + // Utiliser le MerchantSyncService pour créer d'abord dans merchantConfig puis dans Keycloak + this.merchantSyncService.createMerchant(merchantData, dcbPartnerData) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: (createResult) => { + console.log('✅ Merchant and DCB Partner created successfully:', createResult); + this.creatingUser = false; + this.creatingMerchant = false; + + console.log('📊 Résultat création:'); + console.log(' Merchant Config ID:', createResult.merchantConfig?.id); + console.log(' Keycloak Merchant ID:', createResult.keycloakMerchant?.id); + console.log(' Merchant name:', createResult.merchantConfig?.name); + console.log(' DCB Partner email:', createResult.keycloakMerchant?.email); + + this.modalService.dismissAll(); + this.refreshUsersList(); + this.cdRef.detectChanges(); + }, + error: (error) => { + console.error('❌ Error creating merchant and DCB Partner:', error); + this.creatingUser = false; + this.creatingMerchant = false; + + this.createUserError = this.getMerchantErrorMessage(error); + console.error('❌ Détails de l\'erreur:', error); + + this.cdRef.detectChanges(); + } + }); + } + + /** + * Crée uniquement un utilisateur Hub (pas de marchand) + */ + private createHubUserOnly() { + console.log('📤 Creating hub user only (no merchant)'); + this.hubUsersService.createHubUser(this.newUser) .pipe(takeUntil(this.destroy$)) .subscribe({ @@ -556,6 +656,25 @@ export class HubUsersManagement implements OnInit, OnDestroy { return 'Erreur lors de la création de l\'utilisateur. Veuillez réessayer.'; } + private getMerchantErrorMessage(error: any): string { + if (error.error?.message) { + return error.error.message; + } + if (error.status === 400) { + return 'Données du marchand ou du DCB Partner invalides.'; + } + if (error.status === 409) { + return 'Un marchand avec ce nom ou un utilisateur avec ces identifiants existe déjà.'; + } + if (error.status === 403) { + return 'Vous n\'avez pas les permissions pour créer un marchand ou un DCB Partner.'; + } + if (error.status === 404) { + return 'Service de création de marchand non disponible.'; + } + return 'Erreur lors de la création du marchand et du DCB Partner.'; + } + private getResetPasswordErrorMessage(error: any): string { if (error.error?.message) { return error.error.message; @@ -588,7 +707,7 @@ export class HubUsersManagement implements OnInit, OnDestroy { return 'Erreur lors de la suppression de l\'utilisateur. Veuillez réessayer.'; } - // ==================== VALIDATION DU FORMULAIRE ==================== + // ==================== VALIDATION DES FORMULAIRES ==================== private validateUserForm(): { isValid: boolean; error?: string } { const requiredFields = [ @@ -625,4 +744,20 @@ export class HubUsersManagement implements OnInit, OnDestroy { return { isValid: true }; } + + private validateMerchantForm(): { isValid: boolean; error?: string } { + const requiredFields = [ + { field: this.newMerchant.name?.trim(), name: 'Nom du marchand' }, + { field: this.newMerchant.adresse?.trim(), name: 'Adresse' }, + { field: this.newMerchant.phone?.trim(), name: 'Téléphone' }, + ]; + + for (const { field, name } of requiredFields) { + if (!field) { + return { isValid: false, error: `${name} est requis pour un DCB Partner` }; + } + } + + return { isValid: true }; + } } \ No newline at end of file diff --git a/src/app/modules/hub-users-management/merchant-sync-orchestrator.service.ts b/src/app/modules/hub-users-management/merchant-sync-orchestrator.service.ts index 962c481..9090842 100644 --- a/src/app/modules/hub-users-management/merchant-sync-orchestrator.service.ts +++ b/src/app/modules/hub-users-management/merchant-sync-orchestrator.service.ts @@ -42,7 +42,7 @@ export interface MerchantSyncStatus { } @Injectable({ providedIn: 'root' }) -export class MerchantSyncCrudService { +export class MerchantSyncService { private hubUsersService = inject(HubUsersService); private merchantUsersService = inject(MerchantUsersService); private merchantConfigService = inject(MerchantConfigService); @@ -302,7 +302,7 @@ export class MerchantSyncCrudService { const users = keycloakUsers.map(keycloakUser => { const merchantConfigUser = merchantConfigUsers.find( - mcUser => mcUser.email === keycloakUser.email + mcUser => mcUser.userId === keycloakUser.id ); return { @@ -382,7 +382,7 @@ export class MerchantSyncCrudService { */ updateMerchantUser( keycloakUserId: string, - merchantConfigUserId: number, + merchantConfigUserId: string, merchantConfigId: string, updates: { keycloak?: UpdateUserDto; @@ -519,7 +519,7 @@ export class MerchantSyncCrudService { */ deleteMerchantUser( keycloakUserId: string, - merchantConfigUserId: number, + merchantConfigUserId: string, merchantConfigId: string, ): Observable<{ success: boolean; message: string }> { console.log('🗑️ DELETE Merchant User...'); diff --git a/src/app/modules/hub-users-management/merchant-users.ts b/src/app/modules/hub-users-management/merchant-users.ts index c6ba8e2..d065f71 100644 --- a/src/app/modules/hub-users-management/merchant-users.ts +++ b/src/app/modules/hub-users-management/merchant-users.ts @@ -20,7 +20,6 @@ import { UserType } from '@core/models/dcb-bo-hub-user.model'; import { HubUsersService } from './hub-users.service'; -import { MerchantSyncCrudService } from './merchant-sync-orchestrator.service'; @Component({ selector: 'app-merchant-users', @@ -226,7 +225,7 @@ export class MerchantUsersManagement implements OnInit, OnDestroy { } get showMerchantPartnerField(): boolean { - if (this.isMerchantRole(this.newUser.role)) { + if (this.isMerchantRole(this.newUser.role) && !this.isPartnerUser) { return true; } return false; @@ -264,7 +263,13 @@ export class MerchantUsersManagement implements OnInit, OnDestroy { .subscribe({ next: (user) => { this.currentUserRole = this.extractUserRole(user); - this.currentMerchantPartnerId = this.extractMerchantPartnerId(user); + + if(user?.role.includes(UserRole.DCB_PARTNER)){ + this.currentMerchantPartnerId = user.id; + } else { + this.currentMerchantPartnerId = this.extractMerchantPartnerId(user); + } + this.currentUserType = this.extractUserType(user); console.log(`MERCHANT User ROLE: ${this.currentUserRole}`); diff --git a/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.ts b/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.ts index 48f97f0..214ebc3 100644 --- a/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.ts +++ b/src/app/modules/merchant-config/merchant-config-list/merchant-config-list.ts @@ -11,6 +11,7 @@ import { ConfigType, Operator, MerchantUtils, + UserRole, } from '@core/models/merchant-config.model'; import { MerchantConfigService } from '../merchant-config.service'; @@ -81,6 +82,7 @@ export class MerchantConfigsList implements OnInit, OnDestroy { // Permissions currentUserRole: any = null; canViewAllMerchants = false; + currentMerchantConfigId: string | undefined; // ==================== CONVERSION IDS ==================== @@ -131,7 +133,7 @@ export class MerchantConfigsList implements OnInit, OnDestroy { } getColumnCount(): number { - return 8; // Nombre de colonnes dans le tableau + return 8; } ngOnInit() { @@ -153,7 +155,19 @@ export class MerchantConfigsList implements OnInit, OnDestroy { .subscribe({ next: (user) => { this.currentUserRole = this.extractUserRole(user); + + if(user?.role.includes(UserRole.DCB_PARTNER)){ + this.currentMerchantConfigId = user.merchantConfigId; + } else { + this.currentMerchantConfigId = this.extractMerchantConfigId(user); + } + this.canViewAllMerchants = this.canViewAllMerchantsCheck(this.currentUserRole); + + console.log(`MERCHANT User ROLE: ${this.currentUserRole}`); + console.log(`Merchant Config ID: ${this.currentMerchantConfigId}`); + console.log(`canViewAllMerchants: ${this.canViewAllMerchants}`); + this.loadMerchants(); }, error: (error) => { @@ -163,6 +177,17 @@ export class MerchantConfigsList implements OnInit, OnDestroy { }); } + /** + * Extraire le merchantPartnerId + */ + private extractMerchantConfigId(user: any): string { + if (user?.merchantConfigId) { + return user.merchantConfigId; + } + return ''; + } + + private extractUserRole(user: any): any { const userRoles = this.authService.getCurrentUserRoles(); return userRoles && userRoles.length > 0 ? userRoles[0] : null; @@ -172,9 +197,8 @@ export class MerchantConfigsList implements OnInit, OnDestroy { if (!role) return false; const canViewAllRoles = [ - 'DCB_ADMIN', - 'DCB_SUPPORT', - 'DCB_PARTNER_ADMIN' + UserRole.DCB_ADMIN, + UserRole.DCB_SUPPORT ]; return canViewAllRoles.includes(role); @@ -239,7 +263,17 @@ export class MerchantConfigsList implements OnInit, OnDestroy { } private getMyMerchants(): Observable { - return this.getAllMerchants(); + const merchantConfigId = Number(this.currentMerchantConfigId); + + return this.merchantConfigService.getMerchantUsers(merchantConfigId).pipe( + map(response => { + return this.convertMerchantsToFrontend(response); + }), + catchError(error => { + console.error('Error getting all merchants:', error); + return of([]); + }) + ); } diff --git a/src/app/modules/merchant-config/merchant-config-view/merchant-config-view.html b/src/app/modules/merchant-config/merchant-config-view/merchant-config-view.html index 230fe44..70e01b5 100644 --- a/src/app/modules/merchant-config/merchant-config-view/merchant-config-view.html +++ b/src/app/modules/merchant-config/merchant-config-view/merchant-config-view.html @@ -399,91 +399,6 @@ - - @if (addingConfig) { -
-
-
- - Nouvelle configuration -
-
-
-
-
- - -
- -
- - -
- -
- - - @if (isNewConfigSensitive()) { -
- - - Cette valeur contient des informations sensibles. Soyez prudent. - -
- } -
- -
-
- - -
-
-
-
-
- } - @if (isEditingConfig(config.id!)) {
@@ -572,6 +487,7 @@
} + @@ -596,6 +512,91 @@ } + + + @if (addingConfig) { +
+
+
+ + Nouvelle configuration +
+
+
+
+
+ + +
+ +
+ + +
+ +
+ + + @if (isNewConfigSensitive()) { +
+ + + Cette valeur contient des informations sensibles. Soyez prudent. + +
+ } +
+ +
+
+ + +
+
+
+
+
+ } diff --git a/src/app/modules/merchant-config/merchant-config-view/merchant-config-view.ts b/src/app/modules/merchant-config/merchant-config-view/merchant-config-view.ts index de20130..c99c255 100644 --- a/src/app/modules/merchant-config/merchant-config-view/merchant-config-view.ts +++ b/src/app/modules/merchant-config/merchant-config-view/merchant-config-view.ts @@ -198,7 +198,7 @@ export class MerchantConfigView implements OnInit, OnDestroy { success = ''; // Navigation par onglets - activeTab: 'overview' | 'configs' | 'contacts' | 'users' = 'overview'; + activeTab: 'overview' | 'configs' | 'contacts' = 'overview'; // Gestion des permissions currentUserRole: UserRole | null = null; @@ -314,7 +314,7 @@ export class MerchantConfigView implements OnInit, OnDestroy { // ==================== GESTION DES ONGLETS ==================== - setActiveTab(tab: 'overview' | 'configs' | 'contacts' | 'users') { + setActiveTab(tab: 'overview' | 'configs' | 'contacts') { this.activeTab = tab; } @@ -414,7 +414,7 @@ export class MerchantConfigView implements OnInit, OnDestroy { }; // États pour la modification de rôle - editingUserId: number | null = null; + editingUserId: string | null = null; newUserRole: UserRole | null = null; // ==================== GESTION DES CONFIGURATIONS ==================== @@ -663,7 +663,7 @@ export class MerchantConfigView implements OnInit, OnDestroy { }); } - isEditingUserRole(userId: number): boolean { + isEditingUserRole(userId: string): boolean { return this.editingUserId === userId; } @@ -950,7 +950,6 @@ export class MerchantConfigView implements OnInit, OnDestroy { switch (this.activeTab) { case 'configs': return this.merchant?.configs || []; case 'contacts': return this.merchant?.technicalContacts || []; - case 'users': return this.merchant?.users || []; default: return []; } } @@ -1042,7 +1041,8 @@ export class MerchantConfigView implements OnInit, OnDestroy { return contact.id || index; } - trackByUserId(index: number, user: MerchantUser): number { - return user.userId || index; + trackByUserId(index: number, user: MerchantUser): string { + return user?.userId ?? `user-${index}`; } + } \ No newline at end of file diff --git a/src/app/modules/merchant-config/merchant-config.html b/src/app/modules/merchant-config/merchant-config.html index ba509aa..aa03024 100644 --- a/src/app/modules/merchant-config/merchant-config.html +++ b/src/app/modules/merchant-config/merchant-config.html @@ -24,9 +24,9 @@ Permissions limitées } - @if (selectedMerchantPartnerId) { + @if (selectedMerchantConfigId) { - Marchand : {{ selectedMerchantPartnerId }} + Marchand : {{ selectedMerchantConfigId }} } @@ -65,7 +65,13 @@
  • - Marchands + + @if (isMerchantUser) { + Utilisateurs + } @else { + Marchands + } +
  • -
  • +
  • - Détails Marchand + + @if (isMerchantUser) { + Mon Marchand + } @else { + Détails Marchand + } + - @if (selectedMerchantId) { - + @if (showProfileTab) { + + @if (isMerchantUser && userMerchantId) { + + } @else if (!isMerchantUser && selectedMerchantId) { + + } } @else {
    @@ -107,7 +130,6 @@
    - @@ -239,72 +261,75 @@ } - - @for (contact of newMerchant.technicalContacts; track $index; let i = $index) { -
    -
    - Contact {{ i + 1 }} - @if (newMerchant.technicalContacts.length > 1) { - - } -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - + @for (contact of newMerchant.technicalContacts; track $index; let i = $index) { +
    +
    + Contact {{ i + 1 }} + @if (newMerchant.technicalContacts.length > 1) { + + } +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    -
    + } } - } -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - + + Supprimer + + } +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    -
    + } }