feat: add DCB User Service API - Authentication system with KEYCLOAK - Modular architecture with services for each feature
This commit is contained in:
parent
2093e50b27
commit
572b54bcfa
@ -13,7 +13,15 @@ export enum UserRole {
|
||||
// Rôles Merchant Partner (avec merchantPartnerId obligatoire = ID du DCB_PARTNER)
|
||||
DCB_PARTNER_ADMIN = 'dcb-partner-admin',
|
||||
DCB_PARTNER_MANAGER = 'dcb-partner-manager',
|
||||
DCB_PARTNER_SUPPORT = 'dcb-partner-support'
|
||||
DCB_PARTNER_SUPPORT = 'dcb-partner-support',
|
||||
|
||||
MERCHANT_CONFIG_ADMIN = 'ADMIN',
|
||||
MERCHANT_CONFIG_MANAGER = 'MANAGER',
|
||||
MERCHANT_CONFIG_TECHNICAL = 'TECHNICAL',
|
||||
MERCHANT_CONFIG_VIEWER = 'VIEWER',
|
||||
|
||||
//ADMIN, MANAGER, TECHNICAL, VIEWER
|
||||
|
||||
}
|
||||
|
||||
// Enum pour le contexte Angular (identique à l'ancien)
|
||||
@ -39,7 +47,7 @@ export interface UsersStatistics {
|
||||
totalUsers: number;
|
||||
}
|
||||
|
||||
// === MODÈLE USER PRINCIPAL ===
|
||||
// dcb-bo-hub-user.model.ts - MIS À JOUR
|
||||
export interface User {
|
||||
id: string; // UUID Keycloak
|
||||
username: string;
|
||||
@ -49,10 +57,13 @@ export interface User {
|
||||
enabled: boolean;
|
||||
emailVerified: boolean;
|
||||
userType: UserType; // HUB ou MERCHANT_PARTNER
|
||||
merchantPartnerId?: string; // Pour les users merchant: ID du DCB_PARTNER propriétaire
|
||||
|
||||
// C'est l'ID du "merchant" (DCB_PARTNER) qui est propriétaire
|
||||
merchantPartnerId?: string; // Référence à l'ID Keycloak du DCB_PARTNER
|
||||
|
||||
role: UserRole;
|
||||
// Merchant Config
|
||||
merchantConfigId?: number; // ID INT dans Merchant Config
|
||||
// Merchant Config - Stocker l'ID du merchant dans l'autre système
|
||||
merchantConfigId?: string; // ID INT dans Merchant Config pour CE merchant
|
||||
createdBy?: string;
|
||||
createdByUsername?: string;
|
||||
createdTimestamp: number;
|
||||
@ -79,6 +90,7 @@ export interface CreateUserDto {
|
||||
enabled?: boolean;
|
||||
emailVerified?: boolean;
|
||||
merchantPartnerId?: string;
|
||||
merchantConfigId?: string;
|
||||
}
|
||||
|
||||
export interface UpdateUserDto {
|
||||
@ -138,6 +150,7 @@ export interface SearchUsersParams {
|
||||
enabled?: boolean;
|
||||
userType?: UserType;
|
||||
merchantPartnerId?: string;
|
||||
merchantConfigId?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}
|
||||
@ -164,7 +177,13 @@ export class UserUtils {
|
||||
[UserRole.DCB_PARTNER]: 'DCB Partner',
|
||||
[UserRole.DCB_PARTNER_ADMIN]: 'Partner Admin',
|
||||
[UserRole.DCB_PARTNER_MANAGER]: 'Partner Manager',
|
||||
[UserRole.DCB_PARTNER_SUPPORT]: 'Partner Support'
|
||||
[UserRole.DCB_PARTNER_SUPPORT]: 'Partner Support',
|
||||
|
||||
[UserRole.MERCHANT_CONFIG_ADMIN]: 'ADMIN',
|
||||
[UserRole.MERCHANT_CONFIG_MANAGER]: 'MANAGER',
|
||||
[UserRole.MERCHANT_CONFIG_TECHNICAL]: 'TECHNICAL',
|
||||
[UserRole.MERCHANT_CONFIG_VIEWER]: 'VIEWER',
|
||||
|
||||
};
|
||||
return roleNames[role] || role;
|
||||
}
|
||||
|
||||
@ -13,7 +13,12 @@ export enum UserRole {
|
||||
// Rôles Merchant Partner (avec merchantPartnerId obligatoire)
|
||||
DCB_PARTNER_ADMIN = 'dcb-partner-admin',
|
||||
DCB_PARTNER_MANAGER = 'dcb-partner-manager',
|
||||
DCB_PARTNER_SUPPORT = 'dcb-partner-support'
|
||||
DCB_PARTNER_SUPPORT = 'dcb-partner-support',
|
||||
|
||||
MERCHANT_CONFIG_ADMIN = 'ADMIN',
|
||||
MERCHANT_CONFIG_MANAGER = 'MANAGER',
|
||||
MERCHANT_CONFIG_TECHNICAL = 'TECHNICAL',
|
||||
MERCHANT_CONFIG_VIEWER = 'VIEWER',
|
||||
}
|
||||
|
||||
export enum ConfigType {
|
||||
@ -32,38 +37,38 @@ export enum Operator {
|
||||
|
||||
// === MODÈLES PRINCIPAUX ===
|
||||
export interface MerchantConfig {
|
||||
id?: string;
|
||||
id?: number
|
||||
name: ConfigType | string;
|
||||
value: string;
|
||||
operatorId: Operator | null;
|
||||
merchantPartnerId?: string;
|
||||
operatorId: Operator | 1;
|
||||
merchantPartnerId?: number
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface TechnicalContact {
|
||||
id?: string;
|
||||
id?: number
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
merchantPartnerId?: string;
|
||||
merchantPartnerId?: number
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
export interface MerchantUser {
|
||||
userId: string;
|
||||
role: UserRole; // Utilisation de vos rôles existants
|
||||
userId: number;
|
||||
role: UserRole;
|
||||
username?: string;
|
||||
email?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
merchantPartnerId?: string;
|
||||
merchantPartnerId?: number
|
||||
}
|
||||
|
||||
export interface Merchant {
|
||||
id?: string;
|
||||
id?: number
|
||||
name: string;
|
||||
logo?: string;
|
||||
description?: string;
|
||||
@ -81,7 +86,7 @@ export interface ApiMerchantConfig {
|
||||
id?: number;
|
||||
name: ConfigType | string;
|
||||
value: string;
|
||||
operatorId: Operator | null;
|
||||
operatorId: Operator | 1;
|
||||
merchantPartnerId?: number;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
@ -99,7 +104,7 @@ export interface ApiTechnicalContact {
|
||||
}
|
||||
|
||||
export interface ApiMerchantUser {
|
||||
userId: string;
|
||||
userId: number;
|
||||
role: UserRole;
|
||||
username?: string;
|
||||
email?: string;
|
||||
@ -139,13 +144,13 @@ export interface UpdateMerchantDto extends Partial<CreateMerchantDto> {}
|
||||
export interface UpdateMerchantConfigDto {
|
||||
name?: string;
|
||||
value?: string;
|
||||
operatorId?: Operator | null;
|
||||
operatorId?: Operator | 1;
|
||||
}
|
||||
|
||||
export interface AddUserToMerchantDto {
|
||||
userId: string;
|
||||
role: UserRole;
|
||||
merchantPartnerId: string;
|
||||
merchantPartnerId: number;
|
||||
}
|
||||
|
||||
export interface UpdateUserRoleDto {
|
||||
|
||||
@ -1,116 +0,0 @@
|
||||
// === MODÈLES POUR LA SYNCHRONISATION ===
|
||||
|
||||
import { User, UserRole } from "./dcb-bo-hub-user.model";
|
||||
|
||||
export interface MerchantUserSyncDto {
|
||||
// Données de base pour Keycloak
|
||||
username: string;
|
||||
email: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
password: string;
|
||||
role: UserRole;
|
||||
enabled?: boolean;
|
||||
emailVerified?: boolean;
|
||||
|
||||
// Référence au DCB_PARTNER propriétaire
|
||||
merchantPartnerId: string; // ID Keycloak du DCB_PARTNER
|
||||
|
||||
// Données pour Merchant Config
|
||||
merchantConfig?: {
|
||||
phone?: string;
|
||||
technicalContacts?: Array<{
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
email: string;
|
||||
phone: string;
|
||||
}>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IdMapping {
|
||||
id?: number;
|
||||
keycloakId: string; // UUID Keycloak
|
||||
merchantConfigId: number; // INT Merchant Config
|
||||
merchantPartnerId: string; // ID du DCB_PARTNER propriétaire
|
||||
entityType: 'merchant' | 'user';
|
||||
username?: string;
|
||||
email?: string;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export interface SyncResult {
|
||||
success: boolean;
|
||||
keycloakUser?: User;
|
||||
merchantConfigUser?: any;
|
||||
mapping?: IdMapping;
|
||||
errors?: string[];
|
||||
}
|
||||
|
||||
export interface DCBPartnerInfo {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
enabled: boolean;
|
||||
merchantPartnerId?: string; // Pour DCB_PARTNER, ce doit être undefined
|
||||
}
|
||||
|
||||
// === UTILITAIRES DE SYNCHRONISATION ===
|
||||
export class SyncUtils {
|
||||
static validateDCBPartner(dcbPartner: User): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!dcbPartner) {
|
||||
errors.push('DCB_PARTNER non trouvé');
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (dcbPartner.role !== UserRole.DCB_PARTNER) {
|
||||
errors.push(`L'utilisateur ${dcbPartner.username} n'est pas un DCB_PARTNER`);
|
||||
}
|
||||
|
||||
if (!dcbPartner.enabled) {
|
||||
errors.push(`Le DCB_PARTNER ${dcbPartner.username} est désactivé`);
|
||||
}
|
||||
|
||||
if (dcbPartner.merchantPartnerId) {
|
||||
errors.push(`Un DCB_PARTNER ne doit pas avoir de merchantPartnerId`);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
static validateMerchantUserCreation(dto: MerchantUserSyncDto): string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
if (!dto.merchantPartnerId) {
|
||||
errors.push('merchantPartnerId est obligatoire');
|
||||
}
|
||||
|
||||
if (!dto.username?.trim()) {
|
||||
errors.push('Username est obligatoire');
|
||||
}
|
||||
|
||||
if (!dto.email?.trim()) {
|
||||
errors.push('Email est obligatoire');
|
||||
}
|
||||
|
||||
if (!dto.password || dto.password.length < 8) {
|
||||
errors.push('Le mot de passe doit contenir au moins 8 caractères');
|
||||
}
|
||||
|
||||
// Validation du rôle
|
||||
const merchantRoles = [
|
||||
UserRole.DCB_PARTNER_ADMIN,
|
||||
UserRole.DCB_PARTNER_MANAGER,
|
||||
UserRole.DCB_PARTNER_SUPPORT
|
||||
];
|
||||
|
||||
if (!merchantRoles.includes(dto.role)) {
|
||||
errors.push(`Rôle invalide pour un utilisateur merchant: ${dto.role}`);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
@ -332,7 +332,11 @@ export class RoleManagementService {
|
||||
[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.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';
|
||||
}
|
||||
|
||||
@ -83,12 +83,16 @@ export class UserProfileComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Map role to display name
|
||||
const roleDisplayNames: { [key in UserRole]: string } = {
|
||||
[UserRole.DCB_ADMIN]: 'Administrateur',
|
||||
[UserRole.DCB_SUPPORT]: 'Support Technique',
|
||||
[UserRole.DCB_PARTNER]: 'Partenaire',
|
||||
[UserRole.DCB_PARTNER_ADMIN]: 'Admin Partenaire',
|
||||
[UserRole.DCB_PARTNER_MANAGER]: 'Manager Partenaire',
|
||||
[UserRole.DCB_PARTNER_SUPPORT]: 'Support Partenaire',
|
||||
[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'
|
||||
};
|
||||
|
||||
const primaryRole = role;
|
||||
|
||||
@ -52,15 +52,20 @@ export interface MerchantPartnerIdResponse {
|
||||
merchantPartnerId: string | null;
|
||||
}
|
||||
|
||||
export interface MerchantConfigIdResponse {
|
||||
merchantPartnerId: number;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class HubUsersService {
|
||||
private http = inject(HttpClient);
|
||||
private baseApiUrl = `${environment.iamApiUrl}/hub-users`;
|
||||
private baseIamApiUrl = `${environment.iamApiUrl}/hub-users`;
|
||||
private baseConfigApiUrl = `${environment.configApiUrl}/merchants`;
|
||||
|
||||
// === MÉTHODES SPÉCIFIQUES HUB ===
|
||||
|
||||
getAllUsers(): Observable<GlobalUsersOverview> {
|
||||
return this.http.get<GlobalUsersOverview>(`${this.baseApiUrl}/all-users`).pipe(
|
||||
return this.http.get<GlobalUsersOverview>(`${this.baseIamApiUrl}/all-users`).pipe(
|
||||
map(response => {
|
||||
// Validation de la réponse
|
||||
if (!response || !Array.isArray(response.hubUsers) || !Array.isArray(response.merchantUsers)) {
|
||||
@ -89,7 +94,7 @@ export class HubUsersService {
|
||||
|
||||
// Méthode pour les statistiques seulement
|
||||
getUsersStatistics(): Observable<UsersStatistics> {
|
||||
return this.http.get<GlobalUsersOverview>(`${this.baseApiUrl}`).pipe(
|
||||
return this.http.get<GlobalUsersOverview>(`${this.baseIamApiUrl}`).pipe(
|
||||
map(response => response.statistics),
|
||||
catchError(error => {
|
||||
console.error('Error loading users statistics:', error);
|
||||
@ -118,6 +123,10 @@ export class HubUsersService {
|
||||
return throwError(() => 'Password must be at least 8 characters');
|
||||
}
|
||||
|
||||
if (!createUserDto.merchantConfigId) {
|
||||
return throwError(() => 'Merchant Config is required and cannot be empty');
|
||||
}
|
||||
|
||||
// Avant de créer le payload, valider les données
|
||||
if (createUserDto.userType === UserType.MERCHANT_PARTNER && !createUserDto.merchantPartnerId) {
|
||||
return throwError(() => 'merchantPartnerId is required for merchant users');
|
||||
@ -133,6 +142,7 @@ export class HubUsersService {
|
||||
enabled: createUserDto.enabled ?? true,
|
||||
emailVerified: createUserDto.emailVerified ?? true,
|
||||
merchantPartnerId: createUserDto.merchantPartnerId,
|
||||
merchantConfigId: createUserDto.merchantConfigId,
|
||||
userType: createUserDto.userType.trim()
|
||||
};
|
||||
|
||||
@ -143,7 +153,7 @@ export class HubUsersService {
|
||||
|
||||
console.log(payload)
|
||||
|
||||
return this.http.post<User>(`${this.baseApiUrl}`, payload).pipe(
|
||||
return this.http.post<User>(`${this.baseIamApiUrl}`, payload).pipe(
|
||||
map(user => this.mapToUserModel(user, UserType.HUB)),
|
||||
catchError(error => {
|
||||
console.error('Error creating hub user:', error);
|
||||
@ -153,7 +163,7 @@ export class HubUsersService {
|
||||
}
|
||||
|
||||
getHubUsers(page: number = 1, limit: number = 10, filters?: SearchUsersParams): Observable<PaginatedUserResponse> {
|
||||
return this.http.get<User[]>(`${this.baseApiUrl}`).pipe(
|
||||
return this.http.get<User[]>(`${this.baseIamApiUrl}`).pipe(
|
||||
map(users => {
|
||||
const mappedUsers = users.map(user => this.mapToUserModel(user, UserType.HUB));
|
||||
return this.filterAndPaginateUsers(mappedUsers, page, limit, filters);
|
||||
@ -166,7 +176,7 @@ export class HubUsersService {
|
||||
}
|
||||
|
||||
getAllDcbPartners(page: number = 1, limit: number = 10, filters?: SearchUsersParams): Observable<PaginatedUserResponse> {
|
||||
return this.http.get<User[]>(`${this.baseApiUrl}/partners/dcb-partners`).pipe(
|
||||
return this.http.get<User[]>(`${this.baseIamApiUrl}/partners/dcb-partners`).pipe(
|
||||
map(users => {
|
||||
const mappedUsers = users.map(user => this.mapToUserModel(user, UserType.HUB));
|
||||
return this.filterAndPaginateUsers(mappedUsers, page, limit, filters);
|
||||
@ -179,7 +189,7 @@ export class HubUsersService {
|
||||
}
|
||||
|
||||
getHubUserById(id: string): Observable<User> {
|
||||
return this.http.get<User>(`${this.baseApiUrl}/${id}`).pipe(
|
||||
return this.http.get<User>(`${this.baseIamApiUrl}/${id}`).pipe(
|
||||
map(user => this.mapToUserModel(user, UserType.HUB)),
|
||||
catchError(error => {
|
||||
console.error(`Error loading hub user ${id}:`, error);
|
||||
@ -196,7 +206,7 @@ export class HubUsersService {
|
||||
enabled: updateUserDto.enabled
|
||||
};
|
||||
|
||||
return this.http.put<User>(`${this.baseApiUrl}/${id}`, payload).pipe(
|
||||
return this.http.put<User>(`${this.baseIamApiUrl}/${id}`, payload).pipe(
|
||||
map(user => this.mapToUserModel(user, UserType.HUB)),
|
||||
catchError(error => {
|
||||
console.error(`Error updating hub user ${id}:`, error);
|
||||
@ -211,7 +221,7 @@ export class HubUsersService {
|
||||
return throwError(() => 'Invalid role for Hub user');
|
||||
}
|
||||
|
||||
return this.http.put<User>(`${this.baseApiUrl}/${id}/role`, { role }).pipe(
|
||||
return this.http.put<User>(`${this.baseIamApiUrl}/${id}/role`, { role }).pipe(
|
||||
map(user => this.mapToUserModel(user, UserType.HUB)),
|
||||
catchError(error => {
|
||||
console.error(`Error updating role for hub user ${id}:`, error);
|
||||
@ -221,7 +231,7 @@ export class HubUsersService {
|
||||
}
|
||||
|
||||
deleteHubUser(id: string): Observable<MessageResponse> {
|
||||
return this.http.delete<MessageResponse>(`${this.baseApiUrl}/${id}`).pipe(
|
||||
return this.http.delete<MessageResponse>(`${this.baseIamApiUrl}/${id}`).pipe(
|
||||
catchError(error => {
|
||||
console.error(`Error deleting hub user ${id}:`, error);
|
||||
return throwError(() => error);
|
||||
@ -236,7 +246,7 @@ export class HubUsersService {
|
||||
};
|
||||
|
||||
return this.http.post<MessageResponse>(
|
||||
`${this.baseApiUrl}/${id}/reset-password`,
|
||||
`${this.baseIamApiUrl}/${id}/reset-password`,
|
||||
payload
|
||||
).pipe(
|
||||
catchError(error => {
|
||||
@ -283,7 +293,7 @@ export class HubUsersService {
|
||||
}
|
||||
|
||||
getHubUsersByRole(role: UserRole): Observable<User[]> {
|
||||
return this.http.get<User[]>(`${this.baseApiUrl}/role/${role}`).pipe(
|
||||
return this.http.get<User[]>(`${this.baseIamApiUrl}/role/${role}`).pipe(
|
||||
map(users => users.map(user => this.mapToUserModel(user, UserType.HUB))),
|
||||
catchError(error => {
|
||||
console.error(`Error loading hub users with role ${role}:`, error);
|
||||
@ -330,6 +340,7 @@ export class HubUsersService {
|
||||
emailVerified: apiUser.emailVerified,
|
||||
userType: userType,
|
||||
merchantPartnerId: apiUser.merchantPartnerId,
|
||||
merchantConfigId: apiUser.merchantConfigId,
|
||||
role: apiUser.role,
|
||||
createdBy: apiUser.createdBy,
|
||||
createdByUsername: apiUser.createdByUsername,
|
||||
|
||||
494
src/app/modules/hub-users-management/merchant-manager.ts
Normal file
494
src/app/modules/hub-users-management/merchant-manager.ts
Normal file
@ -0,0 +1,494 @@
|
||||
import { Observable, of, throwError, firstValueFrom, lastValueFrom } from 'rxjs';
|
||||
|
||||
// Mock des services
|
||||
const mockMerchantConfigService = {
|
||||
createMerchant: (data: any): Observable<any> => of({
|
||||
id: 123,
|
||||
name: data.name,
|
||||
users: []
|
||||
}),
|
||||
getMerchantById: (id: number): Observable<any> => of({
|
||||
id,
|
||||
name: 'Test Merchant',
|
||||
users: []
|
||||
}),
|
||||
updateMerchant: (id: number, data: any): Observable<any> => of({
|
||||
id,
|
||||
...data
|
||||
}),
|
||||
deleteMerchant: (id: number): Observable<void> => of(void 0),
|
||||
getMerchantUsers: (id: number): Observable<any[]> => of([
|
||||
{ userId: 456, email: 'user1@test.com', role: 'MERCHANT_CONFIG_ADMIN' }
|
||||
]),
|
||||
addUserToMerchant: (data: any): Observable<any> => of({
|
||||
userId: data.userId,
|
||||
email: data.email,
|
||||
role: data.role
|
||||
}),
|
||||
updateUserRole: (merchantId: number, userId: number, data: any): Observable<any> => of({
|
||||
userId,
|
||||
role: data.role
|
||||
}),
|
||||
removeUserFromMerchant: (merchantId: number, userId: number): Observable<void> => of(void 0)
|
||||
};
|
||||
|
||||
const mockHubUsersService = {
|
||||
createHubUser: (data: any): Observable<any> => of({
|
||||
id: 'keycloak-123',
|
||||
username: data.username,
|
||||
merchantConfigId: 123
|
||||
}),
|
||||
getHubUserById: (id: string): Observable<any> => of({
|
||||
id,
|
||||
username: 'owner',
|
||||
merchantConfigId: 123
|
||||
}),
|
||||
updateHubUser: (id: string, data: any): Observable<any> => of({
|
||||
id,
|
||||
...data
|
||||
}),
|
||||
deleteHubUser: (id: string): Observable<void> => of(void 0),
|
||||
getAllDcbPartners: (): Observable<{users: any[]}> => of({
|
||||
users: [{ id: 'keycloak-123', merchantConfigId: 123 }]
|
||||
})
|
||||
};
|
||||
|
||||
const mockMerchantUsersService = {
|
||||
createMerchantUser: (data: any): Observable<any> => of({
|
||||
id: 'keycloak-user-456',
|
||||
email: data.email,
|
||||
merchantPartnerId: data.merchantPartnerId
|
||||
}),
|
||||
updateMerchantUser: (id: string, data: any): Observable<any> => of({
|
||||
id,
|
||||
...data
|
||||
}),
|
||||
deleteMerchantUser: (id: string): Observable<void> => of(void 0),
|
||||
getMerchantUsersByPartner: (partnerId: string): Observable<any[]> => of([
|
||||
{ id: 'keycloak-user-456', email: 'user1@test.com' }
|
||||
]),
|
||||
searchMerchantUsers: (params: any): Observable<any[]> => of([
|
||||
{ id: 'keycloak-user-456', email: 'user1@test.com' }
|
||||
]),
|
||||
resetMerchantUserPassword: (id: string, data: any): Observable<{ message: string }> =>
|
||||
of({ message: 'Password reset' })
|
||||
};
|
||||
|
||||
// Classe de test CRUD avec RxJS 7+
|
||||
export class MerchantCrudTest {
|
||||
private testData = {
|
||||
currentMerchantId: 0,
|
||||
currentDcbPartnerId: '',
|
||||
currentUserId: '',
|
||||
currentMerchantConfigUserId: 0
|
||||
};
|
||||
|
||||
constructor() {}
|
||||
|
||||
// ==================== NOUVELLES MÉTHODES RxJS 7+ ====================
|
||||
|
||||
/**
|
||||
* Remplacer toPromise() par firstValueFrom() ou lastValueFrom()
|
||||
*/
|
||||
private async toPromise<T>(observable: Observable<T>): Promise<T> {
|
||||
// firstValueFrom: prend la première valeur et complète
|
||||
// lastValueFrom: prend la dernière valeur avant completion
|
||||
return firstValueFrom(observable);
|
||||
}
|
||||
|
||||
// ==================== TESTS CRUD ====================
|
||||
|
||||
async testCreateMerchant(): Promise<any> {
|
||||
console.log('🧪 TEST: CREATE Merchant');
|
||||
|
||||
const merchantData = {
|
||||
name: 'Test Merchant',
|
||||
adresse: '123 Test Street',
|
||||
phone: '+33612345678',
|
||||
configs: [{ name: 'API_KEY', value: 'test-key' }],
|
||||
technicalContacts: [{
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
phone: '+33612345678',
|
||||
email: 'john@test.com'
|
||||
}]
|
||||
};
|
||||
|
||||
const ownerData = {
|
||||
username: 'owner.test',
|
||||
email: 'owner@test.com',
|
||||
password: 'Password123!',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe'
|
||||
};
|
||||
|
||||
try {
|
||||
// 1. Créer merchant dans Merchant Config (RxJS 7+)
|
||||
const merchantConfig = await firstValueFrom(
|
||||
mockMerchantConfigService.createMerchant(merchantData)
|
||||
);
|
||||
console.log('✅ Merchant Config créé:', merchantConfig);
|
||||
|
||||
// 2. Créer DCB_PARTNER dans Keycloak
|
||||
const dcbPartnerDto = {
|
||||
...ownerData,
|
||||
userType: 'HUB',
|
||||
role: 'DCB_PARTNER',
|
||||
merchantConfigId: merchantConfig.id
|
||||
};
|
||||
|
||||
const keycloakMerchant = await firstValueFrom(
|
||||
mockHubUsersService.createHubUser(dcbPartnerDto)
|
||||
);
|
||||
console.log('✅ DCB_PARTNER créé:', keycloakMerchant);
|
||||
|
||||
// Sauvegarder les IDs
|
||||
this.testData.currentMerchantId = merchantConfig.id;
|
||||
this.testData.currentDcbPartnerId = keycloakMerchant.id;
|
||||
|
||||
const result = {
|
||||
merchantConfig,
|
||||
keycloakMerchant
|
||||
};
|
||||
|
||||
console.log('🎯 RESULTAT CREATE:', result);
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR CREATE:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async testCreateMerchantUser(): Promise<void> {
|
||||
console.log('🧪 TEST: CREATE Merchant User');
|
||||
|
||||
if (!this.testData.currentMerchantId || !this.testData.currentDcbPartnerId) {
|
||||
console.log('⚠️ Créez d\'abord un merchant');
|
||||
return;
|
||||
}
|
||||
|
||||
const userData = {
|
||||
username: 'user.test',
|
||||
email: 'user@test.com',
|
||||
password: 'UserPass123!',
|
||||
firstName: 'Jane',
|
||||
lastName: 'Smith',
|
||||
role: 'DCB_PARTNER_ADMIN'
|
||||
};
|
||||
|
||||
try {
|
||||
// 1. Créer dans Keycloak
|
||||
const keycloakUserDto = {
|
||||
...userData,
|
||||
userType: 'MERCHANT_PARTNER',
|
||||
merchantPartnerId: this.testData.currentDcbPartnerId,
|
||||
merchantConfigId: this.testData.currentMerchantId
|
||||
};
|
||||
|
||||
const keycloakUser = await firstValueFrom(
|
||||
mockMerchantUsersService.createMerchantUser(keycloakUserDto)
|
||||
);
|
||||
console.log('✅ Utilisateur Keycloak créé:', keycloakUser);
|
||||
|
||||
// 2. Ajouter à Merchant Config
|
||||
const merchantConfigUserId = this.generateMerchantConfigId(keycloakUser.id);
|
||||
|
||||
const merchantConfigUserDto = {
|
||||
userId: merchantConfigUserId,
|
||||
role: 'MERCHANT_CONFIG_ADMIN',
|
||||
merchantPartnerId: this.testData.currentMerchantId,
|
||||
username: keycloakUser.username,
|
||||
email: keycloakUser.email,
|
||||
firstName: keycloakUser.firstName,
|
||||
lastName: keycloakUser.lastName
|
||||
};
|
||||
|
||||
const merchantConfigUser = await firstValueFrom(
|
||||
mockMerchantConfigService.addUserToMerchant(merchantConfigUserDto)
|
||||
);
|
||||
console.log('✅ Utilisateur Merchant Config ajouté:', merchantConfigUser);
|
||||
|
||||
// Sauvegarder
|
||||
this.testData.currentUserId = keycloakUser.id;
|
||||
this.testData.currentMerchantConfigUserId = merchantConfigUserId;
|
||||
|
||||
console.log('🎯 RESULTAT CREATE USER:', {
|
||||
keycloakUser,
|
||||
merchantConfigUser
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR CREATE USER:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async testReadMerchant(): Promise<void> {
|
||||
console.log('🧪 TEST: READ Merchant');
|
||||
|
||||
if (!this.testData.currentMerchantId || !this.testData.currentDcbPartnerId) {
|
||||
console.log('⚠️ Créez d\'abord un merchant');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const [merchantConfig, keycloakMerchant] = await Promise.all([
|
||||
firstValueFrom(
|
||||
mockMerchantConfigService.getMerchantById(this.testData.currentMerchantId)
|
||||
),
|
||||
firstValueFrom(
|
||||
mockHubUsersService.getHubUserById(this.testData.currentDcbPartnerId)
|
||||
)
|
||||
]);
|
||||
|
||||
console.log('🎯 RESULTAT READ:', {
|
||||
merchantConfig,
|
||||
keycloakMerchant,
|
||||
coherence: keycloakMerchant.merchantConfigId === merchantConfig.id ? '✅ OK' : '❌ INCOHÉRENT'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR READ:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async testUpdateMerchant(): Promise<void> {
|
||||
console.log('🧪 TEST: UPDATE Merchant');
|
||||
|
||||
if (!this.testData.currentMerchantId || !this.testData.currentDcbPartnerId) {
|
||||
console.log('⚠️ Créez d\'abord un merchant');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const newName = 'Merchant Updated';
|
||||
const newEmail = 'updated@test.com';
|
||||
|
||||
const [updatedMerchant, updatedDcbPartner] = await Promise.all([
|
||||
firstValueFrom(
|
||||
mockMerchantConfigService.updateMerchant(
|
||||
this.testData.currentMerchantId,
|
||||
{ name: newName }
|
||||
)
|
||||
),
|
||||
firstValueFrom(
|
||||
mockHubUsersService.updateHubUser(
|
||||
this.testData.currentDcbPartnerId,
|
||||
{ email: newEmail }
|
||||
)
|
||||
)
|
||||
]);
|
||||
|
||||
console.log('🎯 RESULTAT UPDATE:', {
|
||||
updatedMerchant,
|
||||
updatedDcbPartner,
|
||||
message: 'Merchant mis à jour avec succès'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR UPDATE:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async testUpdateUser(): Promise<void> {
|
||||
console.log('🧪 TEST: UPDATE User');
|
||||
|
||||
if (!this.testData.currentUserId || !this.testData.currentMerchantConfigUserId) {
|
||||
console.log('⚠️ Créez d\'abord un utilisateur');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const newFirstName = 'Jane Updated';
|
||||
const newRole = 'MERCHANT_CONFIG_MANAGER';
|
||||
|
||||
const [updatedKeycloakUser, updatedMerchantConfigUser] = await Promise.all([
|
||||
firstValueFrom(
|
||||
mockMerchantUsersService.updateMerchantUser(
|
||||
this.testData.currentUserId,
|
||||
{ firstName: newFirstName }
|
||||
)
|
||||
),
|
||||
firstValueFrom(
|
||||
mockMerchantConfigService.updateUserRole(
|
||||
this.testData.currentMerchantId,
|
||||
this.testData.currentMerchantConfigUserId,
|
||||
{ role: newRole }
|
||||
)
|
||||
)
|
||||
]);
|
||||
|
||||
console.log('🎯 RESULTAT UPDATE USER:', {
|
||||
updatedKeycloakUser,
|
||||
updatedMerchantConfigUser
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR UPDATE USER:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async testDeleteUser(): Promise<void> {
|
||||
console.log('🧪 TEST: DELETE User');
|
||||
|
||||
if (!this.testData.currentUserId || !this.testData.currentMerchantConfigUserId) {
|
||||
console.log('⚠️ Créez d\'abord un utilisateur');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all([
|
||||
firstValueFrom(
|
||||
mockMerchantUsersService.deleteMerchantUser(this.testData.currentUserId)
|
||||
),
|
||||
firstValueFrom(
|
||||
mockMerchantConfigService.removeUserFromMerchant(
|
||||
this.testData.currentMerchantId,
|
||||
this.testData.currentMerchantConfigUserId
|
||||
)
|
||||
)
|
||||
]);
|
||||
|
||||
console.log('🎯 RESULTAT DELETE USER: Utilisateur supprimé des deux systèmes');
|
||||
|
||||
// Réinitialiser
|
||||
this.testData.currentUserId = '';
|
||||
this.testData.currentMerchantConfigUserId = 0;
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR DELETE USER:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async testDeleteMerchant(): Promise<void> {
|
||||
console.log('🧪 TEST: DELETE Merchant');
|
||||
|
||||
if (!this.testData.currentMerchantId || !this.testData.currentDcbPartnerId) {
|
||||
console.log('⚠️ Créez d\'abord un merchant');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Récupérer les utilisateurs
|
||||
const users = await firstValueFrom(
|
||||
mockMerchantConfigService.getMerchantUsers(this.testData.currentMerchantId)
|
||||
);
|
||||
|
||||
console.log(`Suppression de ${users.length} utilisateurs...`);
|
||||
|
||||
// Supprimer le merchant et le DCB_PARTNER
|
||||
await Promise.all([
|
||||
firstValueFrom(
|
||||
mockMerchantConfigService.deleteMerchant(this.testData.currentMerchantId)
|
||||
),
|
||||
firstValueFrom(
|
||||
mockHubUsersService.deleteHubUser(this.testData.currentDcbPartnerId)
|
||||
)
|
||||
]);
|
||||
|
||||
console.log('🎯 RESULTAT DELETE MERCHANT: Merchant supprimé des deux systèmes');
|
||||
|
||||
// Réinitialiser
|
||||
this.testData.currentMerchantId = 0;
|
||||
this.testData.currentDcbPartnerId = '';
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR DELETE MERCHANT:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async testAllOperations(): Promise<void> {
|
||||
console.log('🚀 DÉMARRAGE DES TESTS COMPLETS');
|
||||
console.log('='.repeat(50));
|
||||
|
||||
try {
|
||||
await this.testCreateMerchant();
|
||||
console.log('-'.repeat(30));
|
||||
|
||||
await this.testCreateMerchantUser();
|
||||
console.log('-'.repeat(30));
|
||||
|
||||
await this.testReadMerchant();
|
||||
console.log('-'.repeat(30));
|
||||
|
||||
await this.testUpdateMerchant();
|
||||
console.log('-'.repeat(30));
|
||||
|
||||
await this.testUpdateUser();
|
||||
console.log('-'.repeat(30));
|
||||
|
||||
await this.testDeleteUser();
|
||||
console.log('-'.repeat(30));
|
||||
|
||||
await this.testReadMerchant();
|
||||
console.log('-'.repeat(30));
|
||||
|
||||
await this.testDeleteMerchant();
|
||||
console.log('-'.repeat(30));
|
||||
|
||||
console.log('✅ TOUS LES TESTS TERMINÉS AVEC SUCCÈS');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR DANS LES TESTS:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private generateMerchantConfigId(keycloakId: string): number {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < keycloakId.length; i++) {
|
||||
hash = ((hash << 5) - hash) + keycloakId.charCodeAt(i);
|
||||
hash = hash & hash;
|
||||
}
|
||||
return Math.abs(hash) % 1000000;
|
||||
}
|
||||
|
||||
getTestData() {
|
||||
return { ...this.testData };
|
||||
}
|
||||
|
||||
resetTestData() {
|
||||
this.testData = {
|
||||
currentMerchantId: 0,
|
||||
currentDcbPartnerId: '',
|
||||
currentUserId: '',
|
||||
currentMerchantConfigUserId: 0
|
||||
};
|
||||
console.log('🧹 Données de test réinitialisées');
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== EXÉCUTION ====================
|
||||
|
||||
/**
|
||||
* Fonction pour exécuter les tests
|
||||
*/
|
||||
export async function runTests() {
|
||||
console.log('🚀 LANCEMENT DES TESTS');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// Option 1: Avec RxJS 7+
|
||||
console.log('\n📦 TEST AVEC RxJS 7+:');
|
||||
console.log('-'.repeat(40));
|
||||
const rxjsTest = new MerchantCrudTest();
|
||||
await rxjsTest.testAllOperations();
|
||||
|
||||
console.log('\n🎉 TOUS LES TESTS SONT TERMINÉS');
|
||||
}
|
||||
|
||||
// Exemple d'utilisation dans un environnement de test
|
||||
/*
|
||||
import { runIndividualTests } from './merchant-crud.test';
|
||||
|
||||
const tests = runIndividualTests();
|
||||
|
||||
// Exécuter un test spécifique
|
||||
tests.testCreate().then(() => {
|
||||
console.log('Test CREATE terminé');
|
||||
console.log('Données:', tests.getData());
|
||||
});
|
||||
|
||||
// Ou exécuter tous les tests
|
||||
tests.testAll().then(() => {
|
||||
console.log('Tous les tests terminés');
|
||||
});
|
||||
*/
|
||||
@ -0,0 +1,684 @@
|
||||
import { Injectable, inject } from '@angular/core';
|
||||
import { Observable, forkJoin, map, switchMap, catchError, of, throwError, tap } from 'rxjs';
|
||||
import {
|
||||
CreateUserDto,
|
||||
UpdateUserDto,
|
||||
User,
|
||||
UserType,
|
||||
UserRole,
|
||||
UserUtils
|
||||
} from '@core/models/dcb-bo-hub-user.model';
|
||||
|
||||
import { HubUsersService} from '@modules/hub-users-management/hub-users.service';
|
||||
import { MerchantUsersService} from '@modules/hub-users-management/merchant-users.service';
|
||||
|
||||
import { MerchantConfigService } from '@modules/merchant-config/merchant-config.service';
|
||||
|
||||
import {
|
||||
CreateMerchantDto,
|
||||
UpdateMerchantDto,
|
||||
Merchant,
|
||||
MerchantUser,
|
||||
AddUserToMerchantDto,
|
||||
UpdateUserRoleDto
|
||||
} from '@core/models/merchant-config.model';
|
||||
|
||||
export interface MerchantSyncResult {
|
||||
merchantConfig: Merchant;
|
||||
keycloakMerchant: User;
|
||||
}
|
||||
|
||||
export interface MerchantUserSyncResult {
|
||||
keycloakUser: User;
|
||||
merchantConfigUser: MerchantUser;
|
||||
}
|
||||
|
||||
export interface MerchantSyncStatus {
|
||||
existsInKeycloak: boolean;
|
||||
existsInMerchantConfig: boolean;
|
||||
usersSynced: boolean;
|
||||
syncedUserCount: number;
|
||||
totalUserCount: number;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class MerchantSyncCrudService {
|
||||
private hubUsersService = inject(HubUsersService);
|
||||
private merchantUsersService = inject(MerchantUsersService);
|
||||
private merchantConfigService = inject(MerchantConfigService);
|
||||
|
||||
// ==================== CONSTANTES ====================
|
||||
|
||||
private readonly KEYCLOAK_MERCHANT_ROLES = [
|
||||
UserRole.DCB_PARTNER_ADMIN,
|
||||
UserRole.DCB_PARTNER_MANAGER,
|
||||
UserRole.DCB_PARTNER_SUPPORT
|
||||
];
|
||||
|
||||
private readonly MERCHANT_CONFIG_ROLES = [
|
||||
UserRole.MERCHANT_CONFIG_ADMIN,
|
||||
UserRole.MERCHANT_CONFIG_MANAGER,
|
||||
UserRole.MERCHANT_CONFIG_TECHNICAL,
|
||||
UserRole.MERCHANT_CONFIG_VIEWER
|
||||
];
|
||||
|
||||
private readonly ROLE_MAPPING: Map<UserRole, UserRole> = new Map([
|
||||
// Keycloak -> Merchant Config
|
||||
[UserRole.DCB_PARTNER_ADMIN, UserRole.MERCHANT_CONFIG_ADMIN],
|
||||
[UserRole.DCB_PARTNER_MANAGER, UserRole.MERCHANT_CONFIG_MANAGER],
|
||||
[UserRole.DCB_PARTNER_SUPPORT, UserRole.MERCHANT_CONFIG_VIEWER],
|
||||
|
||||
// Merchant Config -> Keycloak
|
||||
[UserRole.MERCHANT_CONFIG_ADMIN, UserRole.DCB_PARTNER_ADMIN],
|
||||
[UserRole.MERCHANT_CONFIG_MANAGER, UserRole.DCB_PARTNER_MANAGER],
|
||||
[UserRole.MERCHANT_CONFIG_TECHNICAL, UserRole.DCB_PARTNER_SUPPORT],
|
||||
[UserRole.MERCHANT_CONFIG_VIEWER, UserRole.DCB_PARTNER_SUPPORT]
|
||||
]);
|
||||
|
||||
// ==================== CREATE ====================
|
||||
|
||||
/**
|
||||
* CREATE - Créer un merchant complet dans les deux systèmes
|
||||
*/
|
||||
createMerchant(
|
||||
merchantData: CreateMerchantDto,
|
||||
dcbPartnerData: {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
}
|
||||
): Observable<MerchantSyncResult> {
|
||||
console.log('📝 CREATE Merchant...');
|
||||
|
||||
return this.merchantConfigService.createMerchant(merchantData).pipe(
|
||||
switchMap(merchantConfig => {
|
||||
console.log('✅ Merchant créé dans Merchant Config:', merchantConfig);
|
||||
|
||||
const merchantConfigId = String(merchantConfig.id);
|
||||
|
||||
const dcbPartnerDto: CreateUserDto = {
|
||||
username: dcbPartnerData.username,
|
||||
email: dcbPartnerData.email,
|
||||
firstName: dcbPartnerData.firstName,
|
||||
lastName: dcbPartnerData.lastName,
|
||||
password: dcbPartnerData.password,
|
||||
userType: UserType.HUB,
|
||||
role: UserRole.DCB_PARTNER,
|
||||
enabled: true,
|
||||
emailVerified: true,
|
||||
merchantConfigId: merchantConfigId
|
||||
};
|
||||
|
||||
return this.hubUsersService.createHubUser(dcbPartnerDto).pipe(
|
||||
map(keycloakMerchant => ({
|
||||
merchantConfig,
|
||||
keycloakMerchant: {
|
||||
...keycloakMerchant,
|
||||
merchantConfigId: merchantConfigId // <<<<< ✔ string
|
||||
}
|
||||
})),
|
||||
catchError(keycloakError => {
|
||||
console.error('❌ Échec Keycloak, rollback...');
|
||||
return this.rollbackMerchantCreation(String(merchantConfig.id!), keycloakError);
|
||||
})
|
||||
);
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('❌ Échec création merchant:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* CREATE - Créer un utilisateur merchant
|
||||
*/
|
||||
createMerchantUser(
|
||||
merchantConfigId: string,
|
||||
keycloakMerchantId: string,
|
||||
userData: {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
role: UserRole; // Rôle Keycloak
|
||||
}
|
||||
): Observable<MerchantUserSyncResult> {
|
||||
console.log('📝 CREATE Merchant User...');
|
||||
|
||||
// Validation rôle Keycloak
|
||||
if (!this.KEYCLOAK_MERCHANT_ROLES.includes(userData.role)) {
|
||||
return throwError(() => new Error('Rôle Keycloak invalide'));
|
||||
}
|
||||
|
||||
// 1. Créer dans Keycloak
|
||||
const keycloakUserDto: CreateUserDto = {
|
||||
...userData,
|
||||
userType: UserType.MERCHANT_PARTNER,
|
||||
merchantPartnerId: keycloakMerchantId,
|
||||
merchantConfigId: merchantConfigId,
|
||||
enabled: true,
|
||||
emailVerified: true
|
||||
};
|
||||
|
||||
return this.merchantUsersService.createMerchantUser(keycloakUserDto).pipe(
|
||||
switchMap(keycloakUser => {
|
||||
console.log('✅ Utilisateur créé dans Keycloak:', keycloakUser);
|
||||
|
||||
// 2. Convertir rôle et ajouter à Merchant Config
|
||||
const merchantConfigRole = this.mapToMerchantConfigRole(userData.role);
|
||||
const merchantConfigUserId = keycloakUser.id;
|
||||
|
||||
const merchantConfigUserDto: AddUserToMerchantDto = {
|
||||
userId: keycloakUser.id,
|
||||
role: merchantConfigRole,
|
||||
merchantPartnerId: Number(merchantConfigId),
|
||||
};
|
||||
|
||||
return this.merchantConfigService.addUserToMerchant(merchantConfigUserDto).pipe(
|
||||
map(merchantConfigUser => {
|
||||
console.log('✅ Utilisateur ajouté à Merchant Config:', merchantConfigUser);
|
||||
|
||||
return {
|
||||
keycloakUser: {
|
||||
...keycloakUser,
|
||||
merchantConfigUserId,
|
||||
merchantConfigId
|
||||
},
|
||||
merchantConfigUser
|
||||
};
|
||||
}),
|
||||
catchError(merchantConfigError => {
|
||||
console.error('❌ Échec Merchant Config, rollback Keycloak...');
|
||||
return this.rollbackUserCreation(keycloakUser.id, merchantConfigError);
|
||||
})
|
||||
);
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('❌ Échec création utilisateur:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== READ ====================
|
||||
|
||||
/**
|
||||
* READ - Récupérer un merchant par ID
|
||||
*/
|
||||
getMerchant(
|
||||
merchantConfigId: string,
|
||||
keycloakMerchantId: string
|
||||
): Observable<MerchantSyncResult> {
|
||||
console.log('🔍 READ Merchant...');
|
||||
|
||||
return forkJoin({
|
||||
merchantConfig: this.merchantConfigService.getMerchantById(Number(merchantConfigId)),
|
||||
keycloakMerchant: this.hubUsersService.getHubUserById(keycloakMerchantId)
|
||||
}).pipe(
|
||||
map(({ merchantConfig, keycloakMerchant }) => {
|
||||
|
||||
// Vérifier la cohérence des IDs
|
||||
if (String(merchantConfig.id) !== keycloakMerchant.merchantConfigId) {
|
||||
console.warn('⚠️ Incohérence: merchantConfigId ne correspond pas');
|
||||
}
|
||||
|
||||
return {
|
||||
merchantConfig,
|
||||
keycloakMerchant: {
|
||||
...keycloakMerchant,
|
||||
merchantConfigId: String(merchantConfig.id)
|
||||
}
|
||||
};
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('❌ Erreur récupération merchant:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* READ - Récupérer tous les merchants
|
||||
*/
|
||||
getAllMerchants(): Observable<MerchantSyncResult[]> {
|
||||
console.log('🔍 READ All Merchants...');
|
||||
|
||||
return forkJoin({
|
||||
merchants: this.merchantConfigService.getAllMerchants(),
|
||||
dcbPartners: this.hubUsersService.getAllDcbPartners()
|
||||
}).pipe(
|
||||
map(({ merchants, dcbPartners }) => {
|
||||
return merchants
|
||||
.map(merchant => {
|
||||
const dcbPartner = dcbPartners.users.find(
|
||||
partner => partner.merchantConfigId === merchant.id
|
||||
);
|
||||
|
||||
if (!dcbPartner) {
|
||||
console.warn(`⚠️ Merchant ${merchant.id} n'a pas de DCB_PARTNER correspondant`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
merchantConfig: merchant,
|
||||
keycloakMerchant: dcbPartner
|
||||
};
|
||||
})
|
||||
.filter((merchant): merchant is MerchantSyncResult => merchant !== null);
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('❌ Erreur récupération merchants:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* READ - Récupérer tous les utilisateurs d'un merchant
|
||||
*/
|
||||
getMerchantUsers(
|
||||
merchantConfigId: string,
|
||||
keycloakMerchantId: string
|
||||
): Observable<{
|
||||
merchantConfig: Merchant;
|
||||
keycloakMerchant: User;
|
||||
users: Array<{
|
||||
keycloakUser: User;
|
||||
merchantConfigUser?: MerchantUser;
|
||||
synced: boolean;
|
||||
}>;
|
||||
}> {
|
||||
return forkJoin({
|
||||
merchantConfig: this.merchantConfigService.getMerchantById(Number(merchantConfigId)),
|
||||
keycloakMerchant: this.hubUsersService.getHubUserById(keycloakMerchantId),
|
||||
merchantConfigUsers: this.merchantConfigService.getMerchantUsers(Number(merchantConfigId)),
|
||||
keycloakUsers: this.merchantUsersService.getMerchantUsersByPartner(keycloakMerchantId)
|
||||
}).pipe(
|
||||
map(({ merchantConfig, keycloakMerchant, merchantConfigUsers, keycloakUsers }) => {
|
||||
|
||||
const users = keycloakUsers.map(keycloakUser => {
|
||||
const merchantConfigUser = merchantConfigUsers.find(
|
||||
mcUser => mcUser.email === keycloakUser.email
|
||||
);
|
||||
|
||||
return {
|
||||
keycloakUser,
|
||||
merchantConfigUser,
|
||||
synced: !!merchantConfigUser
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
merchantConfig,
|
||||
keycloakMerchant: {
|
||||
...keycloakMerchant,
|
||||
merchantConfigId: String(merchantConfig.id)
|
||||
},
|
||||
users
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== UPDATE ====================
|
||||
|
||||
/**
|
||||
* UPDATE - Mettre à jour un merchant
|
||||
*/
|
||||
updateMerchant(
|
||||
merchantConfigId: string,
|
||||
keycloakMerchantId: string,
|
||||
updates: {
|
||||
merchantConfig?: UpdateMerchantDto;
|
||||
dcbPartner?: UpdateUserDto;
|
||||
}
|
||||
): Observable<MerchantSyncResult> {
|
||||
console.log('✏️ UPDATE Merchant...');
|
||||
|
||||
const operations: Observable<any>[] = [];
|
||||
|
||||
// Mise à jour MerchantConfig
|
||||
if (updates.merchantConfig) {
|
||||
operations.push(
|
||||
this.merchantConfigService.updateMerchant(
|
||||
Number(merchantConfigId),
|
||||
updates.merchantConfig
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Mise à jour du user DCB_PARTNER
|
||||
if (updates.dcbPartner && keycloakMerchantId) {
|
||||
operations.push(
|
||||
this.hubUsersService.updateHubUser(keycloakMerchantId, updates.dcbPartner)
|
||||
);
|
||||
}
|
||||
|
||||
if (operations.length === 0) {
|
||||
return throwError(() => new Error('Aucune donnée à mettre à jour'));
|
||||
}
|
||||
|
||||
return forkJoin(operations).pipe(
|
||||
switchMap(() => {
|
||||
// Recharger les données mises à jour
|
||||
return this.getMerchant(
|
||||
merchantConfigId,
|
||||
keycloakMerchantId
|
||||
);
|
||||
}),
|
||||
catchError(error => {
|
||||
console.error('❌ Erreur mise à jour merchant:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* UPDATE - Mettre à jour un utilisateur merchant
|
||||
*/
|
||||
updateMerchantUser(
|
||||
keycloakUserId: string,
|
||||
merchantConfigUserId: number,
|
||||
merchantConfigId: string,
|
||||
updates: {
|
||||
keycloak?: UpdateUserDto;
|
||||
merchantConfig?: {
|
||||
role?: UserRole;
|
||||
};
|
||||
}
|
||||
): Observable<{
|
||||
keycloakUser?: User;
|
||||
merchantConfigUser?: MerchantUser;
|
||||
}> {
|
||||
console.log('✏️ UPDATE Merchant User...');
|
||||
|
||||
const operations: Observable<any>[] = [];
|
||||
|
||||
// Mise à jour Keycloak
|
||||
if (updates.keycloak) {
|
||||
operations.push(
|
||||
this.merchantUsersService.updateMerchantUser(keycloakUserId, updates.keycloak)
|
||||
);
|
||||
}
|
||||
|
||||
// Mise à jour Merchant Config
|
||||
if (updates.merchantConfig?.role) {
|
||||
if (!this.MERCHANT_CONFIG_ROLES.includes(updates.merchantConfig.role)) {
|
||||
return throwError(() => new Error('Rôle Merchant Config invalide'));
|
||||
}
|
||||
|
||||
const updateRoleDto: UpdateUserRoleDto = {
|
||||
role: updates.merchantConfig.role
|
||||
};
|
||||
|
||||
operations.push(
|
||||
this.merchantConfigService.updateUserRole(
|
||||
Number(merchantConfigId),
|
||||
merchantConfigUserId,
|
||||
updateRoleDto
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (operations.length === 0) {
|
||||
return throwError(() => new Error('Aucune donnée à mettre à jour'));
|
||||
}
|
||||
|
||||
return forkJoin(operations).pipe(
|
||||
map(([keycloakUpdate, merchantConfigUpdate]) => ({
|
||||
keycloakUser: keycloakUpdate,
|
||||
merchantConfigUser: merchantConfigUpdate
|
||||
})),
|
||||
catchError(error => {
|
||||
console.error('❌ Erreur mise à jour utilisateur:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* UPDATE - Activer/désactiver un utilisateur
|
||||
*/
|
||||
toggleUserStatus(
|
||||
keycloakUserId: string,
|
||||
enabled: boolean
|
||||
): Observable<User> {
|
||||
console.log(`🔄 ${enabled ? 'Activer' : 'Désactiver'} utilisateur...`);
|
||||
|
||||
return this.merchantUsersService.updateMerchantUser(keycloakUserId, { enabled });
|
||||
}
|
||||
|
||||
// ==================== DELETE ====================
|
||||
|
||||
/**
|
||||
* DELETE - Supprimer un merchant
|
||||
*/
|
||||
deleteMerchant(
|
||||
merchantConfigId: string,
|
||||
keycloakMerchantId: string
|
||||
): Observable<{ success: boolean; message: string }> {
|
||||
console.log('🗑️ DELETE Merchant...');
|
||||
|
||||
// 1. D'abord supprimer tous les utilisateurs
|
||||
return this.merchantConfigService.getMerchantUsers(Number(merchantConfigId)).pipe(
|
||||
switchMap(merchantConfigUsers => {
|
||||
const userDeletions: Observable<any>[] = [];
|
||||
|
||||
// Supprimer chaque utilisateur
|
||||
merchantConfigUsers.forEach(mcUser => {
|
||||
if (mcUser.email) {
|
||||
userDeletions.push(
|
||||
this.merchantUsersService.searchMerchantUsers({ query: mcUser.email }).pipe(
|
||||
switchMap(users => {
|
||||
if (users.length > 0) {
|
||||
return this.deleteMerchantUser(
|
||||
users[0].id,
|
||||
mcUser.userId,
|
||||
merchantConfigId
|
||||
).pipe(catchError(() => of(null)));
|
||||
}
|
||||
return of(null);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Supprimer le merchant
|
||||
userDeletions.push(
|
||||
this.merchantConfigService.deleteMerchant(Number(merchantConfigId))
|
||||
);
|
||||
|
||||
// Supprimer le DCB_PARTNER
|
||||
userDeletions.push(
|
||||
this.hubUsersService.deleteHubUser(keycloakMerchantId).pipe(
|
||||
catchError(() => of({ ignored: true }))
|
||||
)
|
||||
);
|
||||
|
||||
return forkJoin(userDeletions).pipe(
|
||||
map(() => ({
|
||||
success: true,
|
||||
message: 'Merchant et utilisateurs supprimés avec succès'
|
||||
}))
|
||||
);
|
||||
}),
|
||||
catchError(async (error) => ({
|
||||
success: false,
|
||||
message: `Erreur suppression: ${error.message}`
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE - Supprimer un utilisateur merchant
|
||||
*/
|
||||
deleteMerchantUser(
|
||||
keycloakUserId: string,
|
||||
merchantConfigUserId: number,
|
||||
merchantConfigId: string,
|
||||
): Observable<{ success: boolean; message: string }> {
|
||||
console.log('🗑️ DELETE Merchant User...');
|
||||
|
||||
return forkJoin({
|
||||
keycloakDeletion: this.merchantUsersService.deleteMerchantUser(keycloakUserId),
|
||||
merchantConfigDeletion: this.merchantConfigService.removeUserFromMerchant(
|
||||
Number(merchantConfigId),
|
||||
merchantConfigUserId
|
||||
).pipe(catchError(() => of({ ignored: true })))
|
||||
}).pipe(
|
||||
map(() => ({
|
||||
success: true,
|
||||
message: 'Utilisateur supprimé des deux systèmes'
|
||||
})),
|
||||
catchError(async (error) => ({
|
||||
success: false,
|
||||
message: `Erreur suppression: ${error.message}`
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== UTILITAIRES ====================
|
||||
|
||||
/**
|
||||
* Vérifier le statut de synchronisation
|
||||
*/
|
||||
checkSyncStatus(
|
||||
merchantConfigId: string,
|
||||
keycloakMerchantId: string
|
||||
): Observable<MerchantSyncStatus> {
|
||||
return forkJoin({
|
||||
merchantConfig: this.merchantConfigService.getMerchantById(Number(merchantConfigId)).pipe(
|
||||
catchError(() => of(null))
|
||||
),
|
||||
keycloakMerchant: this.hubUsersService.getHubUserById(keycloakMerchantId).pipe(
|
||||
catchError(() => of(null))
|
||||
),
|
||||
merchantConfigUsers: this.merchantConfigService.getMerchantUsers(Number(merchantConfigId)).pipe(
|
||||
catchError(() => of([]))
|
||||
),
|
||||
keycloakUsers: this.merchantUsersService.getMerchantUsersByPartner(keycloakMerchantId).pipe(
|
||||
catchError(() => of([]))
|
||||
)
|
||||
}).pipe(
|
||||
map(({ merchantConfig, keycloakMerchant, merchantConfigUsers, keycloakUsers }) => {
|
||||
|
||||
const syncedUsers = keycloakUsers.filter(kcUser =>
|
||||
merchantConfigUsers.some(mcUser => mcUser.email === kcUser.email)
|
||||
);
|
||||
|
||||
return {
|
||||
existsInKeycloak: !!keycloakMerchant,
|
||||
existsInMerchantConfig: !!merchantConfig,
|
||||
usersSynced: syncedUsers.length === keycloakUsers.length &&
|
||||
syncedUsers.length === merchantConfigUsers.length,
|
||||
syncedUserCount: syncedUsers.length,
|
||||
totalUserCount: Math.max(keycloakUsers.length, merchantConfigUsers.length)
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Rechercher des merchants
|
||||
*/
|
||||
searchMerchants(query: string): Observable<MerchantSyncResult[]> {
|
||||
return this.merchantConfigService.getAllMerchants({ query }).pipe(
|
||||
switchMap(merchants => {
|
||||
if (merchants.length === 0) return of([]);
|
||||
|
||||
return this.hubUsersService.getAllDcbPartners().pipe(
|
||||
map(dcbPartners => {
|
||||
return merchants
|
||||
.map(merchant => {
|
||||
const dcbPartner = dcbPartners.users.find(
|
||||
partner => partner.merchantConfigId === merchant.id
|
||||
);
|
||||
|
||||
return dcbPartner ? {
|
||||
merchantConfig: merchant,
|
||||
keycloakMerchant: dcbPartner
|
||||
} : null;
|
||||
})
|
||||
.filter((merchant): merchant is MerchantSyncResult => merchant !== null);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinitialiser le mot de passe d'un utilisateur
|
||||
*/
|
||||
resetUserPassword(
|
||||
keycloakUserId: string,
|
||||
newPassword: string,
|
||||
temporary: boolean = true
|
||||
): Observable<{ success: boolean; message: string }> {
|
||||
return this.merchantUsersService.resetMerchantUserPassword(
|
||||
keycloakUserId,
|
||||
{ newPassword, temporary }
|
||||
).pipe(
|
||||
map(() => ({
|
||||
success: true,
|
||||
message: 'Mot de passe réinitialisé avec succès'
|
||||
})),
|
||||
catchError(async (error) => ({
|
||||
success: false,
|
||||
message: `Erreur réinitialisation: ${error.message}`
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// ==================== MÉTHODES PRIVÉES ====================
|
||||
|
||||
private mapToMerchantConfigRole(keycloakRole: UserRole): UserRole {
|
||||
const mappedRole = this.ROLE_MAPPING.get(keycloakRole);
|
||||
return mappedRole || UserRole.MERCHANT_CONFIG_VIEWER;
|
||||
}
|
||||
|
||||
private mapToKeycloakRole(merchantConfigRole: UserRole): UserRole {
|
||||
// Recherche inverse
|
||||
for (const [key, value] of this.ROLE_MAPPING.entries()) {
|
||||
if (value === merchantConfigRole) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
return UserRole.DCB_PARTNER_SUPPORT;
|
||||
}
|
||||
|
||||
private generateMerchantConfigId(keycloakId: string): number {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < keycloakId.length; i++) {
|
||||
hash = ((hash << 5) - hash) + keycloakId.charCodeAt(i);
|
||||
hash = hash & hash;
|
||||
}
|
||||
return Math.abs(hash) % 1000000;
|
||||
}
|
||||
|
||||
private rollbackMerchantCreation(
|
||||
merchantConfigId: string,
|
||||
originalError: any
|
||||
): Observable<never> {
|
||||
return this.merchantConfigService.deleteMerchant(Number(merchantConfigId)).pipe(
|
||||
switchMap(() => throwError(() => new Error(
|
||||
`Échec création DCB_PARTNER: ${originalError.message}. Rollback effectué.`
|
||||
)))
|
||||
);
|
||||
}
|
||||
|
||||
private rollbackUserCreation(
|
||||
keycloakUserId: string,
|
||||
originalError: any
|
||||
): Observable<never> {
|
||||
return this.merchantUsersService.deleteMerchantUser(keycloakUserId).pipe(
|
||||
switchMap(() => throwError(() => new Error(
|
||||
`Échec création Merchant Config: ${originalError.message}. Rollback effectué.`
|
||||
)))
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -88,7 +88,6 @@ export class MerchantUsersService {
|
||||
return throwError(() => 'Password must be at least 8 characters');
|
||||
}
|
||||
|
||||
// Adapter le payload pour le nouveau contrôleur
|
||||
const payload = {
|
||||
username: createUserDto.username.trim(),
|
||||
email: createUserDto.email.trim(),
|
||||
@ -99,6 +98,7 @@ export class MerchantUsersService {
|
||||
enabled: createUserDto.enabled !== undefined ? createUserDto.enabled : true,
|
||||
emailVerified: createUserDto.emailVerified !== undefined ? createUserDto.emailVerified : true,
|
||||
merchantPartnerId: createUserDto.merchantPartnerId?.trim() || null,
|
||||
//merchantConfigId: createUserDto.merchantConfigId?.trim() || null,
|
||||
userType: createUserDto.userType.trim()
|
||||
};
|
||||
|
||||
@ -291,7 +291,8 @@ export class MerchantUsersService {
|
||||
emailVerified: apiUser.emailVerified,
|
||||
userType: userType,
|
||||
merchantPartnerId: apiUser.merchantPartnerId,
|
||||
role: apiUser.role, // Convertir le rôle unique en tableau
|
||||
merchantConfigId: apiUser.merchantConfigId,
|
||||
role: apiUser.role,
|
||||
createdBy: apiUser.createdBy,
|
||||
createdByUsername: apiUser.createdByUsername,
|
||||
createdTimestamp: apiUser.createdTimestamp,
|
||||
|
||||
@ -20,6 +20,7 @@ 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',
|
||||
@ -132,8 +133,12 @@ export class MerchantUsersManagement implements OnInit, OnDestroy {
|
||||
|
||||
// Initialiser le formulaire selon le contexte
|
||||
this.initializeMerchantPartner();
|
||||
|
||||
}
|
||||
|
||||
|
||||
constructor() {}
|
||||
|
||||
// Initialiser le merchant partner selon le contexte
|
||||
initializeMerchantPartner() {
|
||||
if (this.currentUserRole === UserRole.DCB_PARTNER && this.currentMerchantPartnerId) {
|
||||
|
||||
@ -47,7 +47,7 @@ export class MerchantConfigsList implements OnInit, OnDestroy {
|
||||
@Input() canDeleteMerchants: boolean = false;
|
||||
|
||||
// Outputs
|
||||
@Output() merchantSelected = new EventEmitter<string>();
|
||||
@Output() merchantSelected = new EventEmitter<number>();
|
||||
@Output() openCreateMerchantModal = new EventEmitter<void>();
|
||||
@Output() editMerchantRequested = new EventEmitter<Merchant>();
|
||||
@Output() deleteMerchantRequested = new EventEmitter<Merchant>();
|
||||
|
||||
@ -184,9 +184,9 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
readonly Operator = Operator;
|
||||
readonly MerchantUtils = MerchantUtils;
|
||||
|
||||
@Input() merchantId!: string;
|
||||
@Input() merchantId!: number;
|
||||
@Output() back = new EventEmitter<void>();
|
||||
@Output() editConfigRequested = new EventEmitter<string>();
|
||||
@Output() editConfigRequested = new EventEmitter<number>();
|
||||
@Output() editMerchantRequested = new EventEmitter<Merchant>();
|
||||
|
||||
merchant: Merchant | null = null;
|
||||
@ -205,16 +205,18 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
currentMerchantPartnerId: string = '';
|
||||
|
||||
// Édition des configurations
|
||||
editingConfigId: string | null = null;
|
||||
editingConfigId: number | null = null;
|
||||
editedConfig: UpdateMerchantConfigDto = {};
|
||||
configToDelete: MerchantConfig | null = null;
|
||||
private deleteModalRef: any = null;
|
||||
|
||||
// Affichage des valeurs sensibles
|
||||
showSensitiveValues: { [configId: string]: boolean } = {};
|
||||
showSensitiveValues: { [configId: number]: boolean } = {};
|
||||
|
||||
// Expansion des sections
|
||||
expandedSections: { [sectionId: string]: boolean } = {};
|
||||
expandedSections: { [sectionId: number]: boolean } = {};
|
||||
|
||||
expandedSection: string | null = null;
|
||||
|
||||
// Pagination
|
||||
page = 1;
|
||||
@ -318,12 +320,12 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
|
||||
// ==================== GESTION DE L'EXPANSION ====================
|
||||
|
||||
toggleSection(sectionId: string) {
|
||||
this.expandedSections[sectionId] = !this.expandedSections[sectionId];
|
||||
toggleSection(section: string) {
|
||||
this.expandedSection = this.expandedSection === section ? null : section;
|
||||
}
|
||||
|
||||
isSectionExpanded(sectionId: string): boolean {
|
||||
return !!this.expandedSections[sectionId];
|
||||
isSectionExpanded(section: string): boolean {
|
||||
return this.expandedSection === section;
|
||||
}
|
||||
|
||||
// ==================== ÉDITION DES CONFIGURATIONS ====================
|
||||
@ -408,11 +410,11 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
newConfig: Omit<MerchantConfig, 'id' | 'merchantPartnerId'> = {
|
||||
name: ConfigType.CUSTOM,
|
||||
value: '',
|
||||
operatorId: null
|
||||
operatorId: 1
|
||||
};
|
||||
|
||||
// États pour la modification de rôle
|
||||
editingUserId: string | null = null;
|
||||
editingUserId: number | null = null;
|
||||
newUserRole: UserRole | null = null;
|
||||
|
||||
// ==================== GESTION DES CONFIGURATIONS ====================
|
||||
@ -422,7 +424,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
this.newConfig = {
|
||||
name: ConfigType.CUSTOM,
|
||||
value: '',
|
||||
operatorId: null
|
||||
operatorId: 1
|
||||
};
|
||||
}
|
||||
|
||||
@ -431,7 +433,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
this.newConfig = {
|
||||
name: ConfigType.CUSTOM,
|
||||
value: '',
|
||||
operatorId: null
|
||||
operatorId: 1
|
||||
};
|
||||
this.error = '';
|
||||
}
|
||||
@ -459,7 +461,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
this.newConfig = {
|
||||
name: ConfigType.CUSTOM,
|
||||
value: '',
|
||||
operatorId: null
|
||||
operatorId: 1
|
||||
};
|
||||
this.clearCache();
|
||||
this.cdRef.detectChanges();
|
||||
@ -504,7 +506,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
return this.isSensitiveConfig({
|
||||
name: this.newConfig.name,
|
||||
value: '',
|
||||
operatorId: null
|
||||
operatorId: 1
|
||||
} as MerchantConfig);
|
||||
}
|
||||
|
||||
@ -661,7 +663,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
isEditingUserRole(userId: string): boolean {
|
||||
isEditingUserRole(userId: number): boolean {
|
||||
return this.editingUserId === userId;
|
||||
}
|
||||
|
||||
@ -723,7 +725,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
return this.canManageAllMerchants();
|
||||
}
|
||||
|
||||
isEditingConfig(configId: string): boolean {
|
||||
isEditingConfig(configId: number): boolean {
|
||||
return this.editingConfigId === configId;
|
||||
}
|
||||
|
||||
@ -734,7 +736,9 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
|
||||
if (this.canManageAllMerchants()) return true;
|
||||
|
||||
return config.merchantPartnerId === this.currentMerchantPartnerId;
|
||||
return true;
|
||||
|
||||
//return config.merchantPartnerId === this.currentMerchantPartnerId;
|
||||
}
|
||||
|
||||
canManageAllMerchants(): boolean {
|
||||
@ -747,12 +751,14 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
|
||||
if (this.canManageAllMerchants()) return true;
|
||||
|
||||
return true;
|
||||
|
||||
// PARTNER ne peut modifier que ses propres marchands
|
||||
return this.merchant.configs?.some(config =>
|
||||
config.merchantPartnerId === this.currentMerchantPartnerId
|
||||
) || this.merchant.users?.some(user =>
|
||||
user.merchantPartnerId === this.currentMerchantPartnerId
|
||||
);
|
||||
// return this.merchant.configs?.some(config =>
|
||||
// config.merchantPartnerId === this.currentMerchantPartnerId
|
||||
// ) || this.merchant.users?.some(user =>
|
||||
// user.merchantPartnerId === this.currentMerchantPartnerId
|
||||
// );
|
||||
}
|
||||
|
||||
// ==================== UTILITAIRES D'AFFICHAGE ====================
|
||||
@ -781,7 +787,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
return this.isSensitiveConfig(config) || config.value.length > 100;
|
||||
}
|
||||
|
||||
toggleSensitiveValue(configId: string) {
|
||||
toggleSensitiveValue(configId: number) {
|
||||
this.showSensitiveValues[configId] = !this.showSensitiveValues[configId];
|
||||
}
|
||||
|
||||
@ -965,7 +971,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
this.back.emit();
|
||||
}
|
||||
|
||||
requestEditConfig(configId: string) {
|
||||
requestEditConfig(configId: number) {
|
||||
this.editConfigRequested.emit(configId);
|
||||
}
|
||||
|
||||
@ -1028,15 +1034,15 @@ export class MerchantConfigView implements OnInit, OnDestroy {
|
||||
|
||||
// ==================== TRACKBY FUNCTIONS ====================
|
||||
|
||||
trackByConfigId(index: number, config: MerchantConfig): string {
|
||||
return config.id || `config-${index}`;
|
||||
trackByConfigId(index: number, config: MerchantConfig): number {
|
||||
return config.id || index;
|
||||
}
|
||||
|
||||
trackByContactId(index: number, contact: TechnicalContact): string {
|
||||
return contact.id || `contact-${index}`;
|
||||
trackByContactId(index: number, contact: TechnicalContact): number {
|
||||
return contact.id || index;
|
||||
}
|
||||
|
||||
trackByUserId(index: number, user: MerchantUser): string {
|
||||
return user.userId || `user-${index}`;
|
||||
trackByUserId(index: number, user: MerchantUser): number {
|
||||
return user.userId || index;
|
||||
}
|
||||
}
|
||||
@ -114,7 +114,7 @@ export class MerchantConfigService {
|
||||
);
|
||||
}
|
||||
|
||||
getMerchantById(id: string): Observable<Merchant> {
|
||||
getMerchantById(id: number): Observable<Merchant> {
|
||||
const numericId = this.convertIdToNumber(id);
|
||||
|
||||
console.log(`📥 Loading merchant ${id}`);
|
||||
@ -130,14 +130,14 @@ export class MerchantConfigService {
|
||||
);
|
||||
}
|
||||
|
||||
updateMerchant(id: string, updateMerchantDto: UpdateMerchantDto): Observable<Merchant> {
|
||||
const numericId = this.convertIdToNumber(id);
|
||||
updateMerchant(id: number, updateMerchantDto: UpdateMerchantDto): Observable<Merchant> {
|
||||
//const numericId = this.convertIdToNumber(id);
|
||||
|
||||
const apiDto = this.dataAdapter.convertUpdateMerchantToApi(updateMerchantDto);
|
||||
|
||||
console.log(`📤 Updating merchant ${id}:`, apiDto);
|
||||
|
||||
return this.http.patch<ApiMerchant>(`${this.baseApiUrl}/${numericId}`, apiDto).pipe(
|
||||
return this.http.patch<ApiMerchant>(`${this.baseApiUrl}/${id}`, apiDto).pipe(
|
||||
timeout(this.REQUEST_TIMEOUT),
|
||||
map(apiMerchant => {
|
||||
console.log(`✅ Merchant ${id} updated successfully`);
|
||||
@ -147,7 +147,7 @@ export class MerchantConfigService {
|
||||
);
|
||||
}
|
||||
|
||||
deleteMerchant(id: string): Observable<void> {
|
||||
deleteMerchant(id: number): Observable<void> {
|
||||
const numericId = this.convertIdToNumber(id);
|
||||
|
||||
console.log(`🗑️ Deleting merchant ${id}`);
|
||||
@ -164,30 +164,24 @@ export class MerchantConfigService {
|
||||
// ==================== USER MANAGEMENT ====================
|
||||
|
||||
addUserToMerchant(addUserDto: AddUserToMerchantDto): Observable<MerchantUser> {
|
||||
// CONVERSION AVEC L'ADAPTER
|
||||
const apiDto = {
|
||||
...addUserDto,
|
||||
merchantPartnerId: addUserDto.merchantPartnerId ?
|
||||
this.convertIdToNumber(addUserDto.merchantPartnerId) : undefined
|
||||
};
|
||||
|
||||
console.log('📤 Adding user to merchant:', addUserDto);
|
||||
|
||||
console.log('📤 Adding user to merchant:', apiDto);
|
||||
|
||||
return this.http.post<ApiMerchantUser>(`${this.baseApiUrl}/users`, apiDto).pipe(
|
||||
return this.http.post<ApiMerchantUser>(`${this.baseApiUrl}/users`, addUserDto).pipe(
|
||||
timeout(this.REQUEST_TIMEOUT),
|
||||
map(apiUser => {
|
||||
console.log('✅ User added to merchant successfully');
|
||||
// ✅ UTILISATION DE L'ADAPTER
|
||||
|
||||
return this.dataAdapter.convertApiUserToFrontend(apiUser);
|
||||
}),
|
||||
catchError(error => this.handleError('addUserToMerchant', error))
|
||||
);
|
||||
}
|
||||
|
||||
getMerchantUsers(merchantId: string): Observable<MerchantUser[]> {
|
||||
const numericMerchantId = this.convertIdToNumber(merchantId);
|
||||
getMerchantUsers(merchantId: number): Observable<MerchantUser[]> {
|
||||
//const merchantId = this.convertIdToNumber(merchantId);
|
||||
|
||||
return this.http.get<ApiMerchant>(`${this.baseApiUrl}/${numericMerchantId}`).pipe(
|
||||
return this.http.get<ApiMerchant>(`${this.baseApiUrl}/${merchantId}`).pipe(
|
||||
timeout(this.REQUEST_TIMEOUT),
|
||||
map(apiMerchant => {
|
||||
return (apiMerchant.users || []).map(user =>
|
||||
@ -199,11 +193,11 @@ export class MerchantConfigService {
|
||||
);
|
||||
}
|
||||
|
||||
updateUserRole(merchantId: string, userId: string, updateRoleDto: UpdateUserRoleDto): Observable<MerchantUser> {
|
||||
const numericMerchantId = this.convertIdToNumber(merchantId);
|
||||
updateUserRole(merchantId: number, userId: number, updateRoleDto: UpdateUserRoleDto): Observable<MerchantUser> {
|
||||
//const merchantId = this.convertIdToNumber(merchantId);
|
||||
|
||||
return this.http.patch<ApiMerchantUser>(
|
||||
`${this.baseApiUrl}/${numericMerchantId}/users/${userId}/role`,
|
||||
`${this.baseApiUrl}/${merchantId}/users/${userId}/role`,
|
||||
updateRoleDto
|
||||
).pipe(
|
||||
timeout(this.REQUEST_TIMEOUT),
|
||||
@ -216,10 +210,10 @@ export class MerchantConfigService {
|
||||
);
|
||||
}
|
||||
|
||||
removeUserFromMerchant(merchantId: string, userId: string): Observable<void> {
|
||||
const numericMerchantId = this.convertIdToNumber(merchantId);
|
||||
removeUserFromMerchant(merchantId: number, userId: number): Observable<void> {
|
||||
//const merchantId = this.convertIdToNumber(merchantId);
|
||||
|
||||
return this.http.delete<void>(`${this.baseApiUrl}/${numericMerchantId}/users/${userId}`).pipe(
|
||||
return this.http.delete<void>(`${this.baseApiUrl}/${merchantId}/users/${userId}`).pipe(
|
||||
timeout(this.REQUEST_TIMEOUT),
|
||||
map(() => {
|
||||
console.log(`✅ User ${userId} removed from merchant ${merchantId} successfully`);
|
||||
@ -228,7 +222,7 @@ export class MerchantConfigService {
|
||||
);
|
||||
}
|
||||
|
||||
getUserMerchants(userId: string): Observable<Merchant[]> {
|
||||
getUserMerchants(userId: number): Observable<Merchant[]> {
|
||||
return this.http.get<ApiMerchant[]>(`${this.baseApiUrl}/user/${userId}`).pipe(
|
||||
timeout(this.REQUEST_TIMEOUT),
|
||||
map(apiMerchants => {
|
||||
@ -242,19 +236,19 @@ export class MerchantConfigService {
|
||||
|
||||
// ==================== CONFIG MANAGEMENT ====================
|
||||
|
||||
addConfigToMerchant(merchantId: string, config: Omit<MerchantConfig, 'id' | 'merchantPartnerId'>): Observable<MerchantConfig> {
|
||||
const numericMerchantId = this.convertIdToNumber(merchantId);
|
||||
addConfigToMerchant(merchantId: number, config: Omit<MerchantConfig, 'id' | 'merchantPartnerId'>): Observable<MerchantConfig> {
|
||||
//const merchantId = this.convertIdToNumber(merchantId);
|
||||
|
||||
// CONVERSION AVEC L'ADAPTER
|
||||
const apiConfig: Omit<ApiMerchantConfig, 'id'> = {
|
||||
...config,
|
||||
operatorId: config.operatorId,
|
||||
merchantPartnerId: numericMerchantId
|
||||
merchantPartnerId: merchantId
|
||||
};
|
||||
|
||||
console.log(`📤 Adding config to merchant ${merchantId}:`, apiConfig);
|
||||
|
||||
return this.http.post<ApiMerchantConfig>(`${this.baseApiUrl}/${numericMerchantId}/configs`, apiConfig).pipe(
|
||||
return this.http.post<ApiMerchantConfig>(`${this.baseApiUrl}/${merchantId}/configs`, apiConfig).pipe(
|
||||
timeout(this.REQUEST_TIMEOUT),
|
||||
map(apiConfig => {
|
||||
console.log('✅ Config added to merchant successfully');
|
||||
@ -265,14 +259,14 @@ export class MerchantConfigService {
|
||||
);
|
||||
}
|
||||
|
||||
updateConfig(configId: string, config: Partial<MerchantConfig>): Observable<MerchantConfig> {
|
||||
const numericConfigId = this.convertIdToNumber(configId);
|
||||
updateConfig(configId: number, config: Partial<MerchantConfig>): Observable<MerchantConfig> {
|
||||
//const configId = this.convertIdToNumber(configId);
|
||||
|
||||
const apiConfig = this.dataAdapter.convertConfigUpdateToApi(config);
|
||||
|
||||
console.log(`📤 Updating config ${configId}:`, apiConfig);
|
||||
|
||||
return this.http.patch<ApiMerchantConfig>(`${this.baseApiUrl}/configs/${numericConfigId}`, apiConfig).pipe(
|
||||
return this.http.patch<ApiMerchantConfig>(`${this.baseApiUrl}/configs/${configId}`, apiConfig).pipe(
|
||||
timeout(this.REQUEST_TIMEOUT),
|
||||
map(apiConfig => {
|
||||
console.log(`✅ Config ${configId} updated successfully`);
|
||||
@ -283,12 +277,12 @@ export class MerchantConfigService {
|
||||
);
|
||||
}
|
||||
|
||||
deleteConfig(configId: string): Observable<void> {
|
||||
const numericConfigId = this.convertIdToNumber(configId);
|
||||
deleteConfig(configId: number): Observable<void> {
|
||||
//const configId = this.convertIdToNumber(configId);
|
||||
|
||||
console.log(`🗑️ Deleting config ${configId}`);
|
||||
|
||||
return this.http.delete<void>(`${this.baseApiUrl}/configs/${numericConfigId}`).pipe(
|
||||
return this.http.delete<void>(`${this.baseApiUrl}/configs/${configId}`).pipe(
|
||||
timeout(this.REQUEST_TIMEOUT),
|
||||
map(() => {
|
||||
console.log(`✅ Config ${configId} deleted successfully`);
|
||||
@ -297,7 +291,7 @@ export class MerchantConfigService {
|
||||
);
|
||||
}
|
||||
|
||||
getMerchantConfigs(merchantId: string): Observable<MerchantConfig[]> {
|
||||
getMerchantConfigs(merchantId: number): Observable<MerchantConfig[]> {
|
||||
return this.getMerchantById(merchantId).pipe(
|
||||
map(merchant => merchant?.configs ?? [])
|
||||
);
|
||||
@ -358,7 +352,7 @@ export class MerchantConfigService {
|
||||
return throwError(() => new Error(userMessage));
|
||||
}
|
||||
|
||||
private convertIdToNumber(id: string): number {
|
||||
private convertIdToNumber(id: number): number {
|
||||
if (!id) {
|
||||
throw new Error('ID cannot be null or undefined');
|
||||
}
|
||||
|
||||
@ -64,8 +64,8 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
||||
|
||||
// État de l'interface
|
||||
activeTab: 'list' | 'profile' = 'list';
|
||||
selectedMerchantId: string | null = null;
|
||||
selectedConfigId: string | null = null;
|
||||
selectedMerchantId: number | null = null;
|
||||
selectedConfigId: number | null = null;
|
||||
|
||||
// Gestion des permissions
|
||||
currentUserRole: UserRole | null = null;
|
||||
@ -91,8 +91,8 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
||||
selectedMerchantForDelete: Merchant | null = null;
|
||||
|
||||
// États pour la gestion des modifications
|
||||
editingConfigs: { [configId: string]: boolean } = {};
|
||||
editingContacts: { [contactId: string]: boolean } = {};
|
||||
editingConfigs: { [configId: number]: boolean } = {};
|
||||
editingContacts: { [contactId: number]: boolean } = {};
|
||||
|
||||
// Références aux templates de modals
|
||||
@ViewChild('createMerchantModal') createMerchantModal!: TemplateRef<any>;
|
||||
@ -126,8 +126,8 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
||||
selectedMerchantPartnerId: string = '';
|
||||
|
||||
// Cache des marchands
|
||||
merchantProfiles: { [merchantId: string]: Merchant } = {};
|
||||
loadingMerchants: { [merchantId: string]: boolean } = {};
|
||||
merchantProfiles: { [merchantId: number]: Merchant } = {};
|
||||
loadingMerchants: { [merchantId: number]: boolean } = {};
|
||||
|
||||
ngOnInit() {
|
||||
this.activeTab = 'list';
|
||||
@ -303,11 +303,13 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
||||
*/
|
||||
canModifyMerchant(merchant: Merchant): boolean {
|
||||
if (this.canManageMerchants) return true;
|
||||
|
||||
return true;
|
||||
|
||||
// PARTNER ne peut modifier que ses propres marchands
|
||||
// Vérifier via les configs ou users
|
||||
return merchant.configs?.some(config => config.merchantPartnerId === this.currentMerchantPartnerId) ||
|
||||
merchant.users?.some(user => user.merchantPartnerId === this.currentMerchantPartnerId);
|
||||
//return merchant.configs?.some(config => config.merchantPartnerId === this.currentMerchantPartnerId) ||
|
||||
// merchant.users?.some(user => user.merchantPartnerId === this.currentMerchantPartnerId);
|
||||
}
|
||||
|
||||
// ==================== GESTION DES MARCHANDS ====================
|
||||
@ -320,8 +322,8 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
onMerchantSelectionChange(selectedMerchantId: string): void {
|
||||
this.selectedMerchantPartnerId = selectedMerchantId;
|
||||
onMerchantSelectionChange(selectedMerchantId: number): void {
|
||||
//this.selectedMerchantPartnerId = selectedMerchantId;
|
||||
|
||||
// Recharger les marchands pour le partenaire sélectionné
|
||||
if (this.merchantConfigsList) {
|
||||
@ -331,7 +333,7 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
||||
|
||||
// ==================== GESTION DES ONGLETS ====================
|
||||
|
||||
showTab(tab: 'list' | 'profile', merchantId?: string): void {
|
||||
showTab(tab: 'list' | 'profile', merchantId?: number): void {
|
||||
console.log(`Switching to tab: ${tab}`, merchantId ? `for merchant ${merchantId}` : '');
|
||||
this.activeTab = tab;
|
||||
|
||||
@ -347,7 +349,7 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private loadMerchantProfile(merchantId: string): void {
|
||||
private loadMerchantProfile(merchantId: number): void {
|
||||
if (this.loadingMerchants[merchantId]) return;
|
||||
|
||||
this.loadingMerchants[merchantId] = true;
|
||||
@ -374,7 +376,7 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
||||
|
||||
// ==================== ÉVÉNEMENTS DES COMPOSANTS ENFANTS ====================
|
||||
|
||||
onMerchantSelected(merchantId: string): void {
|
||||
onMerchantSelected(merchantId: number): void {
|
||||
this.showTab('profile', merchantId);
|
||||
}
|
||||
|
||||
@ -388,7 +390,7 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
||||
this.openDeleteMerchantModal(merchant.id!);
|
||||
}
|
||||
|
||||
onEditConfigRequested(configId: string): void {
|
||||
onEditConfigRequested(configId: number): void {
|
||||
this.openEditMerchantModal(configId);
|
||||
}
|
||||
|
||||
@ -413,7 +415,7 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
||||
this.openModal(this.createMerchantModal);
|
||||
}
|
||||
|
||||
private openEditMerchantModal(merchantId: string): void {
|
||||
private openEditMerchantModal(merchantId: number): void {
|
||||
|
||||
this.merchantConfigService.getMerchantById(merchantId)
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
@ -440,7 +442,7 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private openDeleteMerchantModal(merchantId: string): void {
|
||||
private openDeleteMerchantModal(merchantId: number): void {
|
||||
if (!this.canDeleteMerchants) {
|
||||
console.warn('User does not have permission to delete merchants');
|
||||
return;
|
||||
@ -765,12 +767,12 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
// Méthodes utilitaires pour le template d'édition
|
||||
trackByConfigId(index: number, config: MerchantConfig): string {
|
||||
return config.id || `new-${index}`;
|
||||
trackByConfigId(index: number, config: MerchantConfig): number {
|
||||
return config.id || index;
|
||||
}
|
||||
|
||||
trackByContactId(index: number, contact: TechnicalContact): string {
|
||||
return contact.id || `new-${index}`;
|
||||
trackByContactId(index: number, contact: TechnicalContact): number {
|
||||
return contact.id || index;
|
||||
}
|
||||
|
||||
// Méthode pour détecter les configs sensibles
|
||||
|
||||
@ -29,7 +29,7 @@ export class MerchantDataAdapter {
|
||||
|
||||
return {
|
||||
...apiMerchant,
|
||||
id: this.convertIdToString(apiMerchant.id),
|
||||
id: apiMerchant.id, //this.convertIdToString(apiMerchant.id),
|
||||
configs: (apiMerchant.configs || []).map(config =>
|
||||
this.convertApiConfigToFrontend(config)
|
||||
),
|
||||
@ -48,8 +48,8 @@ export class MerchantDataAdapter {
|
||||
convertApiConfigToFrontend(apiConfig: ApiMerchantConfig): MerchantConfig {
|
||||
return {
|
||||
...apiConfig,
|
||||
id: this.convertIdToString(apiConfig.id),
|
||||
merchantPartnerId: this.convertIdToString(apiConfig.merchantPartnerId)
|
||||
id: apiConfig.id, //this.convertIdToString(apiConfig.id),
|
||||
merchantPartnerId: apiConfig.merchantPartnerId // this.convertIdToString(apiConfig.merchantPartnerId)
|
||||
};
|
||||
}
|
||||
|
||||
@ -59,8 +59,8 @@ export class MerchantDataAdapter {
|
||||
convertApiContactToFrontend(apiContact: ApiTechnicalContact): TechnicalContact {
|
||||
return {
|
||||
...apiContact,
|
||||
id: this.convertIdToString(apiContact.id),
|
||||
merchantPartnerId: this.convertIdToString(apiContact.merchantPartnerId)
|
||||
id: apiContact.id, //this.convertIdToString(apiContact.id),
|
||||
merchantPartnerId: apiContact.merchantPartnerId //this.convertIdToString(apiContact.merchantPartnerId)
|
||||
};
|
||||
}
|
||||
|
||||
@ -160,7 +160,7 @@ export class MerchantDataAdapter {
|
||||
}
|
||||
|
||||
if (config.merchantPartnerId !== undefined) {
|
||||
updateData.merchantPartnerId = this.convertIdToNumber(config.merchantPartnerId);
|
||||
updateData.merchantPartnerId = config.merchantPartnerId //this.convertIdToNumber(config.merchantPartnerId);
|
||||
}
|
||||
|
||||
return updateData;
|
||||
|
||||
@ -291,12 +291,12 @@ const routes: Routes = [
|
||||
// Support & Profile (Tous les utilisateurs authentifiés)
|
||||
// ---------------------------
|
||||
{
|
||||
path: 'dcb-support',
|
||||
path: 'support',
|
||||
component: Support,
|
||||
canActivate: [authGuard, roleGuard],
|
||||
data: {
|
||||
title: 'Support',
|
||||
module: 'dcb-support'
|
||||
module: 'support'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@ -1,7 +1,503 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { MerchantSyncCrudService } from '../hub-users-management/merchant-sync-orchestrator.service';
|
||||
import { UserRole } from '@core/models/dcb-bo-hub-user.model';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-support',
|
||||
templateUrl: './support.html',
|
||||
templateUrl: './support.html'
|
||||
})
|
||||
export class Support {}
|
||||
export class Support implements OnInit {
|
||||
|
||||
private testData = {
|
||||
merchantConfigId: '',
|
||||
keycloakMerchantId: '',
|
||||
testUserId: '',
|
||||
testMerchantConfigUserId:''
|
||||
};
|
||||
|
||||
constructor(private merchantCrudService: MerchantSyncCrudService) {}
|
||||
|
||||
ngOnInit() {
|
||||
console.log('🚀 Support Component - Tests CRUD Merchants');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// Démarrer les tests automatiquement
|
||||
this.runAllTests();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exécuter tous les tests
|
||||
*/
|
||||
async runAllTests(): Promise<void> {
|
||||
try {
|
||||
console.log('\n🧪 DÉMARRAGE DES TESTS COMPLETS');
|
||||
console.log('='.repeat(50));
|
||||
|
||||
// Test 1: Création
|
||||
await this.testCreateOperations();
|
||||
|
||||
// Test 2: Lecture
|
||||
await this.testReadOperations();
|
||||
|
||||
// Test 3: Mise à jour
|
||||
await this.testUpdateOperations();
|
||||
|
||||
// Test 4: Gestion utilisateurs
|
||||
await this.testUserOperations();
|
||||
|
||||
// Test 5: Suppression
|
||||
await this.testDeleteOperations();
|
||||
|
||||
console.log('\n✅ TOUS LES TESTS TERMINÉS AVEC SUCCÈS!');
|
||||
console.log('='.repeat(50));
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR CRITIQUE DANS LES TESTS:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 1: Opérations de création
|
||||
*/
|
||||
private async testCreateOperations(): Promise<void> {
|
||||
console.log('\n📝 TEST 1: Opérations CREATE');
|
||||
console.log('-'.repeat(40));
|
||||
|
||||
try {
|
||||
// 1.1 Créer un merchant de test
|
||||
console.log('1.1 Création d\'un merchant test...');
|
||||
|
||||
const merchantData = {
|
||||
name: 'Test Merchant ' + Date.now(),
|
||||
adresse: '123 Test Street',
|
||||
phone: '+336' + Math.floor(10000000 + Math.random() * 90000000),
|
||||
configs: [
|
||||
{
|
||||
name: 'API_KEY',
|
||||
value: 'test-api-key-' + Date.now(),
|
||||
operatorId: 1
|
||||
}
|
||||
],
|
||||
technicalContacts: [
|
||||
{
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
phone: '+33612345678',
|
||||
email: `test.${Date.now()}@example.com`
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const ownerData = {
|
||||
username: `testowner.${Date.now()}`,
|
||||
email: `owner.${Date.now()}@test-merchant.com`,
|
||||
password: 'TestPassword123!',
|
||||
firstName: 'Test',
|
||||
lastName: 'Owner'
|
||||
};
|
||||
|
||||
console.log('📤 Données merchant:', merchantData);
|
||||
console.log('👤 Données owner:', { ...ownerData, password: '***' });
|
||||
|
||||
const createResult = await firstValueFrom(
|
||||
this.merchantCrudService.createMerchant(merchantData, ownerData)
|
||||
);
|
||||
|
||||
this.testData.merchantConfigId = String(createResult.merchantConfig.id!);
|
||||
this.testData.keycloakMerchantId = createResult.keycloakMerchant.id;
|
||||
|
||||
console.log('✅ Merchant créé avec succès!');
|
||||
console.log(' Merchant Config ID:', this.testData.merchantConfigId);
|
||||
console.log(' Keycloak Merchant ID:', this.testData.keycloakMerchantId);
|
||||
console.log(' Merchant name:', createResult.merchantConfig.name);
|
||||
console.log(' DCB Partner email:', createResult.keycloakMerchant.email);
|
||||
|
||||
// 1.2 Créer un utilisateur pour le merchant
|
||||
console.log('\n1.2 Création d\'un utilisateur test...');
|
||||
|
||||
const userData = {
|
||||
username: `testuser.${Date.now()}`,
|
||||
email: `user.${Date.now()}@test-merchant.com`,
|
||||
password: 'UserPassword123!',
|
||||
firstName: 'Test',
|
||||
lastName: 'User',
|
||||
role: UserRole.DCB_PARTNER_ADMIN
|
||||
};
|
||||
|
||||
const userResult = await firstValueFrom(
|
||||
this.merchantCrudService.createMerchantUser(
|
||||
this.testData.merchantConfigId,
|
||||
this.testData.keycloakMerchantId,
|
||||
userData
|
||||
)
|
||||
);
|
||||
|
||||
this.testData.testUserId = userResult.keycloakUser.id;
|
||||
this.testData.testMerchantConfigUserId = String(userResult.merchantConfigUser.userId);
|
||||
|
||||
console.log('✅ Utilisateur créé avec succès!');
|
||||
console.log(' Keycloak User ID:', this.testData.testUserId);
|
||||
console.log(' Merchant Config User ID:', this.testData.testMerchantConfigUserId);
|
||||
console.log(' Email:', userResult.keycloakUser.email);
|
||||
console.log(' Rôle Keycloak:', userResult.keycloakUser.role);
|
||||
console.log(' Rôle Merchant Config:', userResult.merchantConfigUser.role);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR lors de la création:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 2: Opérations de lecture
|
||||
*/
|
||||
private async testReadOperations(): Promise<void> {
|
||||
console.log('\n🔍 TEST 2: Opérations READ');
|
||||
console.log('-'.repeat(40));
|
||||
|
||||
if (!this.testData.merchantConfigId) {
|
||||
console.log('⚠️ Aucun merchant créé, passage au test suivant');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 2.1 Lire le merchant par ID
|
||||
console.log('2.1 Lecture du merchant par ID...');
|
||||
|
||||
const merchant = await firstValueFrom(
|
||||
this.merchantCrudService.getMerchant(
|
||||
this.testData.merchantConfigId,
|
||||
this.testData.keycloakMerchantId
|
||||
)
|
||||
);
|
||||
|
||||
console.log('✅ Merchant récupéré:');
|
||||
console.log(' ID:', merchant.merchantConfig.id);
|
||||
console.log(' Nom:', merchant.merchantConfig.name);
|
||||
console.log(' Adresse:', merchant.merchantConfig.adresse);
|
||||
console.log(' Configurations:', merchant.merchantConfig.configs?.length || 0);
|
||||
console.log(' Contacts techniques:', merchant.merchantConfig.technicalContacts?.length || 0);
|
||||
|
||||
// 2.2 Lire tous les merchants
|
||||
console.log('\n2.2 Lecture de tous les merchants...');
|
||||
|
||||
const allMerchants = await firstValueFrom(
|
||||
this.merchantCrudService.getAllMerchants()
|
||||
);
|
||||
|
||||
console.log(`✅ ${allMerchants.length} merchants trouvés au total`);
|
||||
|
||||
if (allMerchants.length > 0) {
|
||||
console.log(' Dernier merchant:', allMerchants[allMerchants.length - 1].merchantConfig.name);
|
||||
}
|
||||
|
||||
// 2.3 Lire les utilisateurs du merchant
|
||||
console.log('\n2.3 Lecture des utilisateurs du merchant...');
|
||||
|
||||
const merchantUsers = await firstValueFrom(
|
||||
this.merchantCrudService.getMerchantUsers(
|
||||
this.testData.merchantConfigId,
|
||||
this.testData.keycloakMerchantId
|
||||
)
|
||||
);
|
||||
|
||||
console.log(`✅ ${merchantUsers.users.length} utilisateurs dans ce merchant`);
|
||||
|
||||
merchantUsers.users.forEach((user: any, index: number) => {
|
||||
console.log(` ${index + 1}. ${user.keycloakUser?.email || 'Inconnu'}`);
|
||||
console.log(` Synchronisé: ${user.synced ? '✅' : '❌'}`);
|
||||
});
|
||||
|
||||
// 2.4 Vérifier le statut de synchronisation
|
||||
console.log('\n2.4 Vérification statut synchronisation...');
|
||||
|
||||
const syncStatus = await firstValueFrom(
|
||||
this.merchantCrudService.checkSyncStatus(
|
||||
String(this.testData.merchantConfigId),
|
||||
this.testData.keycloakMerchantId
|
||||
)
|
||||
);
|
||||
|
||||
console.log('✅ Statut de synchronisation:');
|
||||
console.log(' Existe dans Keycloak:', syncStatus.existsInKeycloak ? '✅' : '❌');
|
||||
console.log(' Existe dans Merchant Config:', syncStatus.existsInMerchantConfig ? '✅' : '❌');
|
||||
console.log(' Utilisateurs synchronisés:', syncStatus.usersSynced ? '✅' : '❌');
|
||||
console.log(` ${syncStatus.syncedUserCount}/${syncStatus.totalUserCount} utilisateurs synchronisés`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR lors de la lecture:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 3: Opérations de mise à jour
|
||||
*/
|
||||
private async testUpdateOperations(): Promise<void> {
|
||||
console.log('\n✏️ TEST 3: Opérations UPDATE');
|
||||
console.log('-'.repeat(40));
|
||||
|
||||
if (!this.testData.merchantConfigId) {
|
||||
console.log('⚠️ Aucun merchant créé, passage au test suivant');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 3.1 Mettre à jour le merchant
|
||||
console.log('3.1 Mise à jour du merchant...');
|
||||
|
||||
const newName = `Updated Merchant ${Date.now()}`;
|
||||
const newEmail = `updated.${Date.now()}@test-merchant.com`;
|
||||
|
||||
const updateResult = await firstValueFrom(
|
||||
this.merchantCrudService.updateMerchant(
|
||||
this.testData.merchantConfigId,
|
||||
this.testData.keycloakMerchantId,
|
||||
{
|
||||
merchantConfig: {
|
||||
name: newName,
|
||||
description: 'Mis à jour par les tests'
|
||||
},
|
||||
dcbPartner: {
|
||||
email: newEmail
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
console.log('✅ Merchant mis à jour:');
|
||||
console.log(' Nouveau nom:', newName);
|
||||
console.log(' Nouvel email DCB Partner:', newEmail);
|
||||
|
||||
// 3.2 Mettre à jour l'utilisateur
|
||||
console.log('\n3.2 Mise à jour de l\'utilisateur...');
|
||||
|
||||
if (this.testData.testUserId) {
|
||||
const userUpdateResult = await firstValueFrom(
|
||||
this.merchantCrudService.updateMerchantUser(
|
||||
this.testData.testUserId,
|
||||
Number(this.testData.testMerchantConfigUserId),
|
||||
this.testData.merchantConfigId,
|
||||
{
|
||||
keycloak: {
|
||||
firstName: 'Updated',
|
||||
lastName: 'User'
|
||||
},
|
||||
merchantConfig: {
|
||||
role: UserRole.MERCHANT_CONFIG_MANAGER
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
console.log('✅ Utilisateur mis à jour:');
|
||||
console.log(' Nouveau nom:', 'Updated User');
|
||||
console.log(' Nouveau rôle:', UserRole.MERCHANT_CONFIG_MANAGER);
|
||||
|
||||
} else {
|
||||
console.log('⚠️ Aucun utilisateur à mettre à jour');
|
||||
}
|
||||
|
||||
// 3.3 Activer/désactiver un utilisateur
|
||||
console.log('\n3.3 Changement statut utilisateur...');
|
||||
|
||||
if (this.testData.testUserId) {
|
||||
const toggleResult = await firstValueFrom(
|
||||
this.merchantCrudService.toggleUserStatus(
|
||||
this.testData.testUserId,
|
||||
false // Désactiver
|
||||
)
|
||||
);
|
||||
|
||||
console.log('✅ Statut utilisateur changé:');
|
||||
console.log(' Activé:', toggleResult.enabled ? '✅' : '❌');
|
||||
|
||||
// Réactiver pour les tests suivants
|
||||
await firstValueFrom(
|
||||
this.merchantCrudService.toggleUserStatus(
|
||||
this.testData.testUserId,
|
||||
true
|
||||
)
|
||||
);
|
||||
console.log(' Réactivé pour les tests suivants');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR lors de la mise à jour:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 4: Opérations utilisateurs avancées
|
||||
*/
|
||||
private async testUserOperations(): Promise<void> {
|
||||
console.log('\n👥 TEST 4: Opérations utilisateurs avancées');
|
||||
console.log('-'.repeat(40));
|
||||
|
||||
if (!this.testData.testUserId) {
|
||||
console.log('⚠️ Aucun utilisateur créé, passage au test suivant');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 4.1 Réinitialiser le mot de passe
|
||||
console.log('4.1 Réinitialisation mot de passe...');
|
||||
|
||||
const resetResult = await firstValueFrom(
|
||||
this.merchantCrudService.resetUserPassword(
|
||||
this.testData.testUserId,
|
||||
'NewPassword123!',
|
||||
true
|
||||
)
|
||||
);
|
||||
|
||||
console.log('✅ Mot de passe réinitialisé:');
|
||||
console.log(' Succès:', resetResult.success);
|
||||
console.log(' Message:', resetResult.message);
|
||||
|
||||
// 4.2 Rechercher des merchants
|
||||
console.log('\n4.2 Recherche de merchants...');
|
||||
|
||||
const searchResults = await firstValueFrom(
|
||||
this.merchantCrudService.searchMerchants('Test')
|
||||
);
|
||||
|
||||
console.log(`✅ ${searchResults.length} merchants trouvés avec "Test"`);
|
||||
|
||||
if (searchResults.length > 0) {
|
||||
console.log(' Premier résultat:', searchResults[0].merchantConfig.name);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR opérations utilisateurs:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* TEST 5: Opérations de suppression
|
||||
*/
|
||||
private async testDeleteOperations(): Promise<void> {
|
||||
console.log('\n🗑️ TEST 5: Opérations DELETE');
|
||||
console.log('-'.repeat(40));
|
||||
|
||||
if (!this.testData.merchantConfigId) {
|
||||
console.log('⚠️ Aucun merchant créé, passage au test suivant');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 5.1 Supprimer l'utilisateur test
|
||||
console.log('5.1 Suppression de l\'utilisateur test...');
|
||||
|
||||
if (this.testData.testUserId) {
|
||||
const deleteUserResult = await firstValueFrom(
|
||||
this.merchantCrudService.deleteMerchantUser(
|
||||
this.testData.testUserId,
|
||||
Number(this.testData.testMerchantConfigUserId),
|
||||
this.testData.merchantConfigId
|
||||
)
|
||||
);
|
||||
|
||||
console.log('✅ Utilisateur supprimé:');
|
||||
console.log(' Succès:', deleteUserResult.success);
|
||||
console.log(' Message:', deleteUserResult.message);
|
||||
|
||||
// Réinitialiser les IDs utilisateur
|
||||
this.testData.testUserId = '';
|
||||
this.testData.testMerchantConfigUserId = '';
|
||||
} else {
|
||||
console.log('⚠️ Aucun utilisateur à supprimer');
|
||||
}
|
||||
|
||||
// 5.2 Supprimer le merchant test
|
||||
console.log('\n5.2 Suppression du merchant test...');
|
||||
|
||||
const deleteMerchantResult = await firstValueFrom(
|
||||
this.merchantCrudService.deleteMerchant(
|
||||
this.testData.merchantConfigId,
|
||||
this.testData.keycloakMerchantId
|
||||
)
|
||||
);
|
||||
|
||||
console.log('✅ Merchant supprimé:');
|
||||
console.log(' Succès:', deleteMerchantResult.success);
|
||||
console.log(' Message:', deleteMerchantResult.message);
|
||||
|
||||
// 5.3 Vérifier la suppression
|
||||
console.log('\n5.3 Vérification de la suppression...');
|
||||
|
||||
try {
|
||||
await firstValueFrom(
|
||||
this.merchantCrudService.getMerchant(
|
||||
this.testData.merchantConfigId,
|
||||
this.testData.keycloakMerchantId
|
||||
)
|
||||
);
|
||||
console.log('❌ Le merchant existe toujours - PROBLÈME!');
|
||||
} catch (error) {
|
||||
console.log('✅ Le merchant a bien été supprimé');
|
||||
}
|
||||
|
||||
// Réinitialiser toutes les données
|
||||
this.testData = {
|
||||
merchantConfigId: '',
|
||||
keycloakMerchantId: '',
|
||||
testUserId: '',
|
||||
testMerchantConfigUserId: ''
|
||||
};
|
||||
|
||||
console.log('🧹 Données de test réinitialisées');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ ERREUR lors de la suppression:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Méthodes pour exécuter des tests individuels
|
||||
*/
|
||||
async testCreateOnly(): Promise<void> {
|
||||
console.log('🧪 Test CREATE uniquement');
|
||||
await this.testCreateOperations();
|
||||
}
|
||||
|
||||
async testReadOnly(): Promise<void> {
|
||||
console.log('🧪 Test READ uniquement');
|
||||
await this.testReadOperations();
|
||||
}
|
||||
|
||||
async testUpdateOnly(): Promise<void> {
|
||||
console.log('🧪 Test UPDATE uniquement');
|
||||
await this.testUpdateOperations();
|
||||
}
|
||||
|
||||
async testDeleteOnly(): Promise<void> {
|
||||
console.log('🧪 Test DELETE uniquement');
|
||||
await this.testDeleteOperations();
|
||||
}
|
||||
|
||||
/**
|
||||
* Afficher l'état actuel des tests
|
||||
*/
|
||||
showTestStatus(): void {
|
||||
console.log('\n📊 ÉTAT ACTUEL DES TESTS');
|
||||
console.log('-'.repeat(30));
|
||||
console.log('Merchant Config ID:', this.testData.merchantConfigId || 'Non créé');
|
||||
console.log('Keycloak Merchant ID:', this.testData.keycloakMerchantId || 'Non créé');
|
||||
console.log('Test User ID:', this.testData.testUserId || 'Non créé');
|
||||
console.log('Test Merchant Config User ID:', this.testData.testMerchantConfigUserId || 'Non créé');
|
||||
}
|
||||
|
||||
/**
|
||||
* Réinitialiser les données de test
|
||||
*/
|
||||
resetTestData(): void {
|
||||
this.testData = {
|
||||
merchantConfigId: '',
|
||||
keycloakMerchantId: '',
|
||||
testUserId: '',
|
||||
testMerchantConfigUserId: ''
|
||||
};
|
||||
console.log('🧹 Données de test réinitialisées');
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user