feat: add DCB User Service API - Authentication system with KEYCLOAK - Modular architecture with services for each feature

This commit is contained in:
diallolatoile 2025-12-03 00:12:23 +00:00
parent 2093e50b27
commit 572b54bcfa
17 changed files with 1866 additions and 257 deletions

View File

@ -13,7 +13,15 @@ export enum UserRole {
// Rôles Merchant Partner (avec merchantPartnerId obligatoire = ID du DCB_PARTNER) // Rôles Merchant Partner (avec merchantPartnerId obligatoire = ID du DCB_PARTNER)
DCB_PARTNER_ADMIN = 'dcb-partner-admin', DCB_PARTNER_ADMIN = 'dcb-partner-admin',
DCB_PARTNER_MANAGER = 'dcb-partner-manager', 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) // Enum pour le contexte Angular (identique à l'ancien)
@ -39,7 +47,7 @@ export interface UsersStatistics {
totalUsers: number; totalUsers: number;
} }
// === MODÈLE USER PRINCIPAL === // dcb-bo-hub-user.model.ts - MIS À JOUR
export interface User { export interface User {
id: string; // UUID Keycloak id: string; // UUID Keycloak
username: string; username: string;
@ -49,10 +57,13 @@ export interface User {
enabled: boolean; enabled: boolean;
emailVerified: boolean; emailVerified: boolean;
userType: UserType; // HUB ou MERCHANT_PARTNER 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; role: UserRole;
// Merchant Config // Merchant Config - Stocker l'ID du merchant dans l'autre système
merchantConfigId?: number; // ID INT dans Merchant Config merchantConfigId?: string; // ID INT dans Merchant Config pour CE merchant
createdBy?: string; createdBy?: string;
createdByUsername?: string; createdByUsername?: string;
createdTimestamp: number; createdTimestamp: number;
@ -79,6 +90,7 @@ export interface CreateUserDto {
enabled?: boolean; enabled?: boolean;
emailVerified?: boolean; emailVerified?: boolean;
merchantPartnerId?: string; merchantPartnerId?: string;
merchantConfigId?: string;
} }
export interface UpdateUserDto { export interface UpdateUserDto {
@ -138,6 +150,7 @@ export interface SearchUsersParams {
enabled?: boolean; enabled?: boolean;
userType?: UserType; userType?: UserType;
merchantPartnerId?: string; merchantPartnerId?: string;
merchantConfigId?: string;
page?: number; page?: number;
limit?: number; limit?: number;
} }
@ -164,7 +177,13 @@ export class UserUtils {
[UserRole.DCB_PARTNER]: 'DCB Partner', [UserRole.DCB_PARTNER]: 'DCB Partner',
[UserRole.DCB_PARTNER_ADMIN]: 'Partner Admin', [UserRole.DCB_PARTNER_ADMIN]: 'Partner Admin',
[UserRole.DCB_PARTNER_MANAGER]: 'Partner Manager', [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; return roleNames[role] || role;
} }

View File

@ -13,7 +13,12 @@ export enum UserRole {
// Rôles Merchant Partner (avec merchantPartnerId obligatoire) // Rôles Merchant Partner (avec merchantPartnerId obligatoire)
DCB_PARTNER_ADMIN = 'dcb-partner-admin', DCB_PARTNER_ADMIN = 'dcb-partner-admin',
DCB_PARTNER_MANAGER = 'dcb-partner-manager', 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 { export enum ConfigType {
@ -32,38 +37,38 @@ export enum Operator {
// === MODÈLES PRINCIPAUX === // === MODÈLES PRINCIPAUX ===
export interface MerchantConfig { export interface MerchantConfig {
id?: string; id?: number
name: ConfigType | string; name: ConfigType | string;
value: string; value: string;
operatorId: Operator | null; operatorId: Operator | 1;
merchantPartnerId?: string; merchantPartnerId?: number
createdAt?: string; createdAt?: string;
updatedAt?: string; updatedAt?: string;
} }
export interface TechnicalContact { export interface TechnicalContact {
id?: string; id?: number
firstName: string; firstName: string;
lastName: string; lastName: string;
phone: string; phone: string;
email: string; email: string;
merchantPartnerId?: string; merchantPartnerId?: number
createdAt?: string; createdAt?: string;
updatedAt?: string; updatedAt?: string;
} }
export interface MerchantUser { export interface MerchantUser {
userId: string; userId: number;
role: UserRole; // Utilisation de vos rôles existants role: UserRole;
username?: string; username?: string;
email?: string; email?: string;
firstName?: string; firstName?: string;
lastName?: string; lastName?: string;
merchantPartnerId?: string; merchantPartnerId?: number
} }
export interface Merchant { export interface Merchant {
id?: string; id?: number
name: string; name: string;
logo?: string; logo?: string;
description?: string; description?: string;
@ -81,7 +86,7 @@ export interface ApiMerchantConfig {
id?: number; id?: number;
name: ConfigType | string; name: ConfigType | string;
value: string; value: string;
operatorId: Operator | null; operatorId: Operator | 1;
merchantPartnerId?: number; merchantPartnerId?: number;
createdAt?: string; createdAt?: string;
updatedAt?: string; updatedAt?: string;
@ -99,7 +104,7 @@ export interface ApiTechnicalContact {
} }
export interface ApiMerchantUser { export interface ApiMerchantUser {
userId: string; userId: number;
role: UserRole; role: UserRole;
username?: string; username?: string;
email?: string; email?: string;
@ -139,13 +144,13 @@ export interface UpdateMerchantDto extends Partial<CreateMerchantDto> {}
export interface UpdateMerchantConfigDto { export interface UpdateMerchantConfigDto {
name?: string; name?: string;
value?: string; value?: string;
operatorId?: Operator | null; operatorId?: Operator | 1;
} }
export interface AddUserToMerchantDto { export interface AddUserToMerchantDto {
userId: string; userId: string;
role: UserRole; role: UserRole;
merchantPartnerId: string; merchantPartnerId: number;
} }
export interface UpdateUserRoleDto { export interface UpdateUserRoleDto {

View File

@ -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;
}
}

View File

@ -332,7 +332,11 @@ export class RoleManagementService {
[UserRole.DCB_PARTNER]: 'Partenaire commercial principal', [UserRole.DCB_PARTNER]: 'Partenaire commercial principal',
[UserRole.DCB_PARTNER_ADMIN]: 'Administrateur de partenaire marchand', [UserRole.DCB_PARTNER_ADMIN]: 'Administrateur de partenaire marchand',
[UserRole.DCB_PARTNER_MANAGER]: 'Manager opérationnel partenaire', [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'; return roleDescriptions[userRole] || 'Description non disponible';
} }

View File

@ -83,12 +83,16 @@ export class UserProfileComponent implements OnInit, OnDestroy {
// Map role to display name // Map role to display name
const roleDisplayNames: { [key in UserRole]: string } = { const roleDisplayNames: { [key in UserRole]: string } = {
[UserRole.DCB_ADMIN]: 'Administrateur', [UserRole.DCB_ADMIN]: 'Administrateur système avec tous les accès',
[UserRole.DCB_SUPPORT]: 'Support Technique', [UserRole.DCB_SUPPORT]: 'Support technique avec accès étendus',
[UserRole.DCB_PARTNER]: 'Partenaire', [UserRole.DCB_PARTNER]: 'Partenaire commercial principal',
[UserRole.DCB_PARTNER_ADMIN]: 'Admin Partenaire', [UserRole.DCB_PARTNER_ADMIN]: 'Administrateur de partenaire marchand',
[UserRole.DCB_PARTNER_MANAGER]: 'Manager Partenaire', [UserRole.DCB_PARTNER_MANAGER]: 'Manager opérationnel partenaire',
[UserRole.DCB_PARTNER_SUPPORT]: 'Support 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; const primaryRole = role;

View File

@ -52,15 +52,20 @@ export interface MerchantPartnerIdResponse {
merchantPartnerId: string | null; merchantPartnerId: string | null;
} }
export interface MerchantConfigIdResponse {
merchantPartnerId: number;
}
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class HubUsersService { export class HubUsersService {
private http = inject(HttpClient); 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 === // === MÉTHODES SPÉCIFIQUES HUB ===
getAllUsers(): Observable<GlobalUsersOverview> { 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 => { map(response => {
// Validation de la réponse // Validation de la réponse
if (!response || !Array.isArray(response.hubUsers) || !Array.isArray(response.merchantUsers)) { if (!response || !Array.isArray(response.hubUsers) || !Array.isArray(response.merchantUsers)) {
@ -89,7 +94,7 @@ export class HubUsersService {
// Méthode pour les statistiques seulement // Méthode pour les statistiques seulement
getUsersStatistics(): Observable<UsersStatistics> { getUsersStatistics(): Observable<UsersStatistics> {
return this.http.get<GlobalUsersOverview>(`${this.baseApiUrl}`).pipe( return this.http.get<GlobalUsersOverview>(`${this.baseIamApiUrl}`).pipe(
map(response => response.statistics), map(response => response.statistics),
catchError(error => { catchError(error => {
console.error('Error loading users statistics:', error); console.error('Error loading users statistics:', error);
@ -118,6 +123,10 @@ export class HubUsersService {
return throwError(() => 'Password must be at least 8 characters'); 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 // Avant de créer le payload, valider les données
if (createUserDto.userType === UserType.MERCHANT_PARTNER && !createUserDto.merchantPartnerId) { if (createUserDto.userType === UserType.MERCHANT_PARTNER && !createUserDto.merchantPartnerId) {
return throwError(() => 'merchantPartnerId is required for merchant users'); return throwError(() => 'merchantPartnerId is required for merchant users');
@ -133,6 +142,7 @@ export class HubUsersService {
enabled: createUserDto.enabled ?? true, enabled: createUserDto.enabled ?? true,
emailVerified: createUserDto.emailVerified ?? true, emailVerified: createUserDto.emailVerified ?? true,
merchantPartnerId: createUserDto.merchantPartnerId, merchantPartnerId: createUserDto.merchantPartnerId,
merchantConfigId: createUserDto.merchantConfigId,
userType: createUserDto.userType.trim() userType: createUserDto.userType.trim()
}; };
@ -143,7 +153,7 @@ export class HubUsersService {
console.log(payload) 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)), map(user => this.mapToUserModel(user, UserType.HUB)),
catchError(error => { catchError(error => {
console.error('Error creating hub user:', 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> { 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 => { map(users => {
const mappedUsers = users.map(user => this.mapToUserModel(user, UserType.HUB)); const mappedUsers = users.map(user => this.mapToUserModel(user, UserType.HUB));
return this.filterAndPaginateUsers(mappedUsers, page, limit, filters); 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> { 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 => { map(users => {
const mappedUsers = users.map(user => this.mapToUserModel(user, UserType.HUB)); const mappedUsers = users.map(user => this.mapToUserModel(user, UserType.HUB));
return this.filterAndPaginateUsers(mappedUsers, page, limit, filters); return this.filterAndPaginateUsers(mappedUsers, page, limit, filters);
@ -179,7 +189,7 @@ export class HubUsersService {
} }
getHubUserById(id: string): Observable<User> { 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)), map(user => this.mapToUserModel(user, UserType.HUB)),
catchError(error => { catchError(error => {
console.error(`Error loading hub user ${id}:`, error); console.error(`Error loading hub user ${id}:`, error);
@ -196,7 +206,7 @@ export class HubUsersService {
enabled: updateUserDto.enabled 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)), map(user => this.mapToUserModel(user, UserType.HUB)),
catchError(error => { catchError(error => {
console.error(`Error updating hub user ${id}:`, error); console.error(`Error updating hub user ${id}:`, error);
@ -211,7 +221,7 @@ export class HubUsersService {
return throwError(() => 'Invalid role for Hub user'); 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)), map(user => this.mapToUserModel(user, UserType.HUB)),
catchError(error => { catchError(error => {
console.error(`Error updating role for hub user ${id}:`, error); console.error(`Error updating role for hub user ${id}:`, error);
@ -221,7 +231,7 @@ export class HubUsersService {
} }
deleteHubUser(id: string): Observable<MessageResponse> { 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 => { catchError(error => {
console.error(`Error deleting hub user ${id}:`, error); console.error(`Error deleting hub user ${id}:`, error);
return throwError(() => error); return throwError(() => error);
@ -236,7 +246,7 @@ export class HubUsersService {
}; };
return this.http.post<MessageResponse>( return this.http.post<MessageResponse>(
`${this.baseApiUrl}/${id}/reset-password`, `${this.baseIamApiUrl}/${id}/reset-password`,
payload payload
).pipe( ).pipe(
catchError(error => { catchError(error => {
@ -283,7 +293,7 @@ export class HubUsersService {
} }
getHubUsersByRole(role: UserRole): Observable<User[]> { 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))), map(users => users.map(user => this.mapToUserModel(user, UserType.HUB))),
catchError(error => { catchError(error => {
console.error(`Error loading hub users with role ${role}:`, error); console.error(`Error loading hub users with role ${role}:`, error);
@ -330,6 +340,7 @@ export class HubUsersService {
emailVerified: apiUser.emailVerified, emailVerified: apiUser.emailVerified,
userType: userType, userType: userType,
merchantPartnerId: apiUser.merchantPartnerId, merchantPartnerId: apiUser.merchantPartnerId,
merchantConfigId: apiUser.merchantConfigId,
role: apiUser.role, role: apiUser.role,
createdBy: apiUser.createdBy, createdBy: apiUser.createdBy,
createdByUsername: apiUser.createdByUsername, createdByUsername: apiUser.createdByUsername,

View 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');
});
*/

View File

@ -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é.`
)))
);
}
}

View File

@ -88,7 +88,6 @@ export class MerchantUsersService {
return throwError(() => 'Password must be at least 8 characters'); return throwError(() => 'Password must be at least 8 characters');
} }
// Adapter le payload pour le nouveau contrôleur
const payload = { const payload = {
username: createUserDto.username.trim(), username: createUserDto.username.trim(),
email: createUserDto.email.trim(), email: createUserDto.email.trim(),
@ -99,6 +98,7 @@ export class MerchantUsersService {
enabled: createUserDto.enabled !== undefined ? createUserDto.enabled : true, enabled: createUserDto.enabled !== undefined ? createUserDto.enabled : true,
emailVerified: createUserDto.emailVerified !== undefined ? createUserDto.emailVerified : true, emailVerified: createUserDto.emailVerified !== undefined ? createUserDto.emailVerified : true,
merchantPartnerId: createUserDto.merchantPartnerId?.trim() || null, merchantPartnerId: createUserDto.merchantPartnerId?.trim() || null,
//merchantConfigId: createUserDto.merchantConfigId?.trim() || null,
userType: createUserDto.userType.trim() userType: createUserDto.userType.trim()
}; };
@ -291,7 +291,8 @@ export class MerchantUsersService {
emailVerified: apiUser.emailVerified, emailVerified: apiUser.emailVerified,
userType: userType, userType: userType,
merchantPartnerId: apiUser.merchantPartnerId, merchantPartnerId: apiUser.merchantPartnerId,
role: apiUser.role, // Convertir le rôle unique en tableau merchantConfigId: apiUser.merchantConfigId,
role: apiUser.role,
createdBy: apiUser.createdBy, createdBy: apiUser.createdBy,
createdByUsername: apiUser.createdByUsername, createdByUsername: apiUser.createdByUsername,
createdTimestamp: apiUser.createdTimestamp, createdTimestamp: apiUser.createdTimestamp,

View File

@ -20,6 +20,7 @@ import {
UserType UserType
} from '@core/models/dcb-bo-hub-user.model'; } from '@core/models/dcb-bo-hub-user.model';
import { HubUsersService } from './hub-users.service'; import { HubUsersService } from './hub-users.service';
import { MerchantSyncCrudService } from './merchant-sync-orchestrator.service';
@Component({ @Component({
selector: 'app-merchant-users', selector: 'app-merchant-users',
@ -132,8 +133,12 @@ export class MerchantUsersManagement implements OnInit, OnDestroy {
// Initialiser le formulaire selon le contexte // Initialiser le formulaire selon le contexte
this.initializeMerchantPartner(); this.initializeMerchantPartner();
} }
constructor() {}
// Initialiser le merchant partner selon le contexte // Initialiser le merchant partner selon le contexte
initializeMerchantPartner() { initializeMerchantPartner() {
if (this.currentUserRole === UserRole.DCB_PARTNER && this.currentMerchantPartnerId) { if (this.currentUserRole === UserRole.DCB_PARTNER && this.currentMerchantPartnerId) {

View File

@ -47,7 +47,7 @@ export class MerchantConfigsList implements OnInit, OnDestroy {
@Input() canDeleteMerchants: boolean = false; @Input() canDeleteMerchants: boolean = false;
// Outputs // Outputs
@Output() merchantSelected = new EventEmitter<string>(); @Output() merchantSelected = new EventEmitter<number>();
@Output() openCreateMerchantModal = new EventEmitter<void>(); @Output() openCreateMerchantModal = new EventEmitter<void>();
@Output() editMerchantRequested = new EventEmitter<Merchant>(); @Output() editMerchantRequested = new EventEmitter<Merchant>();
@Output() deleteMerchantRequested = new EventEmitter<Merchant>(); @Output() deleteMerchantRequested = new EventEmitter<Merchant>();

View File

@ -184,9 +184,9 @@ export class MerchantConfigView implements OnInit, OnDestroy {
readonly Operator = Operator; readonly Operator = Operator;
readonly MerchantUtils = MerchantUtils; readonly MerchantUtils = MerchantUtils;
@Input() merchantId!: string; @Input() merchantId!: number;
@Output() back = new EventEmitter<void>(); @Output() back = new EventEmitter<void>();
@Output() editConfigRequested = new EventEmitter<string>(); @Output() editConfigRequested = new EventEmitter<number>();
@Output() editMerchantRequested = new EventEmitter<Merchant>(); @Output() editMerchantRequested = new EventEmitter<Merchant>();
merchant: Merchant | null = null; merchant: Merchant | null = null;
@ -205,16 +205,18 @@ export class MerchantConfigView implements OnInit, OnDestroy {
currentMerchantPartnerId: string = ''; currentMerchantPartnerId: string = '';
// Édition des configurations // Édition des configurations
editingConfigId: string | null = null; editingConfigId: number | null = null;
editedConfig: UpdateMerchantConfigDto = {}; editedConfig: UpdateMerchantConfigDto = {};
configToDelete: MerchantConfig | null = null; configToDelete: MerchantConfig | null = null;
private deleteModalRef: any = null; private deleteModalRef: any = null;
// Affichage des valeurs sensibles // Affichage des valeurs sensibles
showSensitiveValues: { [configId: string]: boolean } = {}; showSensitiveValues: { [configId: number]: boolean } = {};
// Expansion des sections // Expansion des sections
expandedSections: { [sectionId: string]: boolean } = {}; expandedSections: { [sectionId: number]: boolean } = {};
expandedSection: string | null = null;
// Pagination // Pagination
page = 1; page = 1;
@ -318,12 +320,12 @@ export class MerchantConfigView implements OnInit, OnDestroy {
// ==================== GESTION DE L'EXPANSION ==================== // ==================== GESTION DE L'EXPANSION ====================
toggleSection(sectionId: string) { toggleSection(section: string) {
this.expandedSections[sectionId] = !this.expandedSections[sectionId]; this.expandedSection = this.expandedSection === section ? null : section;
} }
isSectionExpanded(sectionId: string): boolean { isSectionExpanded(section: string): boolean {
return !!this.expandedSections[sectionId]; return this.expandedSection === section;
} }
// ==================== ÉDITION DES CONFIGURATIONS ==================== // ==================== ÉDITION DES CONFIGURATIONS ====================
@ -408,11 +410,11 @@ export class MerchantConfigView implements OnInit, OnDestroy {
newConfig: Omit<MerchantConfig, 'id' | 'merchantPartnerId'> = { newConfig: Omit<MerchantConfig, 'id' | 'merchantPartnerId'> = {
name: ConfigType.CUSTOM, name: ConfigType.CUSTOM,
value: '', value: '',
operatorId: null operatorId: 1
}; };
// États pour la modification de rôle // États pour la modification de rôle
editingUserId: string | null = null; editingUserId: number | null = null;
newUserRole: UserRole | null = null; newUserRole: UserRole | null = null;
// ==================== GESTION DES CONFIGURATIONS ==================== // ==================== GESTION DES CONFIGURATIONS ====================
@ -422,7 +424,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
this.newConfig = { this.newConfig = {
name: ConfigType.CUSTOM, name: ConfigType.CUSTOM,
value: '', value: '',
operatorId: null operatorId: 1
}; };
} }
@ -431,7 +433,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
this.newConfig = { this.newConfig = {
name: ConfigType.CUSTOM, name: ConfigType.CUSTOM,
value: '', value: '',
operatorId: null operatorId: 1
}; };
this.error = ''; this.error = '';
} }
@ -459,7 +461,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
this.newConfig = { this.newConfig = {
name: ConfigType.CUSTOM, name: ConfigType.CUSTOM,
value: '', value: '',
operatorId: null operatorId: 1
}; };
this.clearCache(); this.clearCache();
this.cdRef.detectChanges(); this.cdRef.detectChanges();
@ -504,7 +506,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
return this.isSensitiveConfig({ return this.isSensitiveConfig({
name: this.newConfig.name, name: this.newConfig.name,
value: '', value: '',
operatorId: null operatorId: 1
} as MerchantConfig); } as MerchantConfig);
} }
@ -661,7 +663,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
}); });
} }
isEditingUserRole(userId: string): boolean { isEditingUserRole(userId: number): boolean {
return this.editingUserId === userId; return this.editingUserId === userId;
} }
@ -723,7 +725,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
return this.canManageAllMerchants(); return this.canManageAllMerchants();
} }
isEditingConfig(configId: string): boolean { isEditingConfig(configId: number): boolean {
return this.editingConfigId === configId; return this.editingConfigId === configId;
} }
@ -734,7 +736,9 @@ export class MerchantConfigView implements OnInit, OnDestroy {
if (this.canManageAllMerchants()) return true; if (this.canManageAllMerchants()) return true;
return config.merchantPartnerId === this.currentMerchantPartnerId; return true;
//return config.merchantPartnerId === this.currentMerchantPartnerId;
} }
canManageAllMerchants(): boolean { canManageAllMerchants(): boolean {
@ -747,12 +751,14 @@ export class MerchantConfigView implements OnInit, OnDestroy {
if (this.canManageAllMerchants()) return true; if (this.canManageAllMerchants()) return true;
return true;
// PARTNER ne peut modifier que ses propres marchands // PARTNER ne peut modifier que ses propres marchands
return this.merchant.configs?.some(config => // return this.merchant.configs?.some(config =>
config.merchantPartnerId === this.currentMerchantPartnerId // config.merchantPartnerId === this.currentMerchantPartnerId
) || this.merchant.users?.some(user => // ) || this.merchant.users?.some(user =>
user.merchantPartnerId === this.currentMerchantPartnerId // user.merchantPartnerId === this.currentMerchantPartnerId
); // );
} }
// ==================== UTILITAIRES D'AFFICHAGE ==================== // ==================== UTILITAIRES D'AFFICHAGE ====================
@ -781,7 +787,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
return this.isSensitiveConfig(config) || config.value.length > 100; return this.isSensitiveConfig(config) || config.value.length > 100;
} }
toggleSensitiveValue(configId: string) { toggleSensitiveValue(configId: number) {
this.showSensitiveValues[configId] = !this.showSensitiveValues[configId]; this.showSensitiveValues[configId] = !this.showSensitiveValues[configId];
} }
@ -965,7 +971,7 @@ export class MerchantConfigView implements OnInit, OnDestroy {
this.back.emit(); this.back.emit();
} }
requestEditConfig(configId: string) { requestEditConfig(configId: number) {
this.editConfigRequested.emit(configId); this.editConfigRequested.emit(configId);
} }
@ -1028,15 +1034,15 @@ export class MerchantConfigView implements OnInit, OnDestroy {
// ==================== TRACKBY FUNCTIONS ==================== // ==================== TRACKBY FUNCTIONS ====================
trackByConfigId(index: number, config: MerchantConfig): string { trackByConfigId(index: number, config: MerchantConfig): number {
return config.id || `config-${index}`; return config.id || index;
} }
trackByContactId(index: number, contact: TechnicalContact): string { trackByContactId(index: number, contact: TechnicalContact): number {
return contact.id || `contact-${index}`; return contact.id || index;
} }
trackByUserId(index: number, user: MerchantUser): string { trackByUserId(index: number, user: MerchantUser): number {
return user.userId || `user-${index}`; return user.userId || index;
} }
} }

View File

@ -114,7 +114,7 @@ export class MerchantConfigService {
); );
} }
getMerchantById(id: string): Observable<Merchant> { getMerchantById(id: number): Observable<Merchant> {
const numericId = this.convertIdToNumber(id); const numericId = this.convertIdToNumber(id);
console.log(`📥 Loading merchant ${id}`); console.log(`📥 Loading merchant ${id}`);
@ -130,14 +130,14 @@ export class MerchantConfigService {
); );
} }
updateMerchant(id: string, updateMerchantDto: UpdateMerchantDto): Observable<Merchant> { updateMerchant(id: number, updateMerchantDto: UpdateMerchantDto): Observable<Merchant> {
const numericId = this.convertIdToNumber(id); //const numericId = this.convertIdToNumber(id);
const apiDto = this.dataAdapter.convertUpdateMerchantToApi(updateMerchantDto); const apiDto = this.dataAdapter.convertUpdateMerchantToApi(updateMerchantDto);
console.log(`📤 Updating merchant ${id}:`, apiDto); 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), timeout(this.REQUEST_TIMEOUT),
map(apiMerchant => { map(apiMerchant => {
console.log(`✅ Merchant ${id} updated successfully`); 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); const numericId = this.convertIdToNumber(id);
console.log(`🗑️ Deleting merchant ${id}`); console.log(`🗑️ Deleting merchant ${id}`);
@ -164,30 +164,24 @@ export class MerchantConfigService {
// ==================== USER MANAGEMENT ==================== // ==================== USER MANAGEMENT ====================
addUserToMerchant(addUserDto: AddUserToMerchantDto): Observable<MerchantUser> { addUserToMerchant(addUserDto: AddUserToMerchantDto): Observable<MerchantUser> {
// CONVERSION AVEC L'ADAPTER
const apiDto = { console.log('📤 Adding user to merchant:', addUserDto);
...addUserDto,
merchantPartnerId: addUserDto.merchantPartnerId ?
this.convertIdToNumber(addUserDto.merchantPartnerId) : undefined
};
console.log('📤 Adding user to merchant:', apiDto); return this.http.post<ApiMerchantUser>(`${this.baseApiUrl}/users`, addUserDto).pipe(
return this.http.post<ApiMerchantUser>(`${this.baseApiUrl}/users`, apiDto).pipe(
timeout(this.REQUEST_TIMEOUT), timeout(this.REQUEST_TIMEOUT),
map(apiUser => { map(apiUser => {
console.log('✅ User added to merchant successfully'); console.log('✅ User added to merchant successfully');
// ✅ UTILISATION DE L'ADAPTER
return this.dataAdapter.convertApiUserToFrontend(apiUser); return this.dataAdapter.convertApiUserToFrontend(apiUser);
}), }),
catchError(error => this.handleError('addUserToMerchant', error)) catchError(error => this.handleError('addUserToMerchant', error))
); );
} }
getMerchantUsers(merchantId: string): Observable<MerchantUser[]> { getMerchantUsers(merchantId: number): Observable<MerchantUser[]> {
const numericMerchantId = this.convertIdToNumber(merchantId); //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), timeout(this.REQUEST_TIMEOUT),
map(apiMerchant => { map(apiMerchant => {
return (apiMerchant.users || []).map(user => return (apiMerchant.users || []).map(user =>
@ -199,11 +193,11 @@ export class MerchantConfigService {
); );
} }
updateUserRole(merchantId: string, userId: string, updateRoleDto: UpdateUserRoleDto): Observable<MerchantUser> { updateUserRole(merchantId: number, userId: number, updateRoleDto: UpdateUserRoleDto): Observable<MerchantUser> {
const numericMerchantId = this.convertIdToNumber(merchantId); //const merchantId = this.convertIdToNumber(merchantId);
return this.http.patch<ApiMerchantUser>( return this.http.patch<ApiMerchantUser>(
`${this.baseApiUrl}/${numericMerchantId}/users/${userId}/role`, `${this.baseApiUrl}/${merchantId}/users/${userId}/role`,
updateRoleDto updateRoleDto
).pipe( ).pipe(
timeout(this.REQUEST_TIMEOUT), timeout(this.REQUEST_TIMEOUT),
@ -216,10 +210,10 @@ export class MerchantConfigService {
); );
} }
removeUserFromMerchant(merchantId: string, userId: string): Observable<void> { removeUserFromMerchant(merchantId: number, userId: number): Observable<void> {
const numericMerchantId = this.convertIdToNumber(merchantId); //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), timeout(this.REQUEST_TIMEOUT),
map(() => { map(() => {
console.log(`✅ User ${userId} removed from merchant ${merchantId} successfully`); 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( return this.http.get<ApiMerchant[]>(`${this.baseApiUrl}/user/${userId}`).pipe(
timeout(this.REQUEST_TIMEOUT), timeout(this.REQUEST_TIMEOUT),
map(apiMerchants => { map(apiMerchants => {
@ -242,19 +236,19 @@ export class MerchantConfigService {
// ==================== CONFIG MANAGEMENT ==================== // ==================== CONFIG MANAGEMENT ====================
addConfigToMerchant(merchantId: string, config: Omit<MerchantConfig, 'id' | 'merchantPartnerId'>): Observable<MerchantConfig> { addConfigToMerchant(merchantId: number, config: Omit<MerchantConfig, 'id' | 'merchantPartnerId'>): Observable<MerchantConfig> {
const numericMerchantId = this.convertIdToNumber(merchantId); //const merchantId = this.convertIdToNumber(merchantId);
// CONVERSION AVEC L'ADAPTER // CONVERSION AVEC L'ADAPTER
const apiConfig: Omit<ApiMerchantConfig, 'id'> = { const apiConfig: Omit<ApiMerchantConfig, 'id'> = {
...config, ...config,
operatorId: config.operatorId, operatorId: config.operatorId,
merchantPartnerId: numericMerchantId merchantPartnerId: merchantId
}; };
console.log(`📤 Adding config to merchant ${merchantId}:`, apiConfig); 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), timeout(this.REQUEST_TIMEOUT),
map(apiConfig => { map(apiConfig => {
console.log('✅ Config added to merchant successfully'); console.log('✅ Config added to merchant successfully');
@ -265,14 +259,14 @@ export class MerchantConfigService {
); );
} }
updateConfig(configId: string, config: Partial<MerchantConfig>): Observable<MerchantConfig> { updateConfig(configId: number, config: Partial<MerchantConfig>): Observable<MerchantConfig> {
const numericConfigId = this.convertIdToNumber(configId); //const configId = this.convertIdToNumber(configId);
const apiConfig = this.dataAdapter.convertConfigUpdateToApi(config); const apiConfig = this.dataAdapter.convertConfigUpdateToApi(config);
console.log(`📤 Updating config ${configId}:`, apiConfig); 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), timeout(this.REQUEST_TIMEOUT),
map(apiConfig => { map(apiConfig => {
console.log(`✅ Config ${configId} updated successfully`); console.log(`✅ Config ${configId} updated successfully`);
@ -283,12 +277,12 @@ export class MerchantConfigService {
); );
} }
deleteConfig(configId: string): Observable<void> { deleteConfig(configId: number): Observable<void> {
const numericConfigId = this.convertIdToNumber(configId); //const configId = this.convertIdToNumber(configId);
console.log(`🗑️ Deleting config ${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), timeout(this.REQUEST_TIMEOUT),
map(() => { map(() => {
console.log(`✅ Config ${configId} deleted successfully`); 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( return this.getMerchantById(merchantId).pipe(
map(merchant => merchant?.configs ?? []) map(merchant => merchant?.configs ?? [])
); );
@ -358,7 +352,7 @@ export class MerchantConfigService {
return throwError(() => new Error(userMessage)); return throwError(() => new Error(userMessage));
} }
private convertIdToNumber(id: string): number { private convertIdToNumber(id: number): number {
if (!id) { if (!id) {
throw new Error('ID cannot be null or undefined'); throw new Error('ID cannot be null or undefined');
} }

View File

@ -64,8 +64,8 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
// État de l'interface // État de l'interface
activeTab: 'list' | 'profile' = 'list'; activeTab: 'list' | 'profile' = 'list';
selectedMerchantId: string | null = null; selectedMerchantId: number | null = null;
selectedConfigId: string | null = null; selectedConfigId: number | null = null;
// Gestion des permissions // Gestion des permissions
currentUserRole: UserRole | null = null; currentUserRole: UserRole | null = null;
@ -91,8 +91,8 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
selectedMerchantForDelete: Merchant | null = null; selectedMerchantForDelete: Merchant | null = null;
// États pour la gestion des modifications // États pour la gestion des modifications
editingConfigs: { [configId: string]: boolean } = {}; editingConfigs: { [configId: number]: boolean } = {};
editingContacts: { [contactId: string]: boolean } = {}; editingContacts: { [contactId: number]: boolean } = {};
// Références aux templates de modals // Références aux templates de modals
@ViewChild('createMerchantModal') createMerchantModal!: TemplateRef<any>; @ViewChild('createMerchantModal') createMerchantModal!: TemplateRef<any>;
@ -126,8 +126,8 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
selectedMerchantPartnerId: string = ''; selectedMerchantPartnerId: string = '';
// Cache des marchands // Cache des marchands
merchantProfiles: { [merchantId: string]: Merchant } = {}; merchantProfiles: { [merchantId: number]: Merchant } = {};
loadingMerchants: { [merchantId: string]: boolean } = {}; loadingMerchants: { [merchantId: number]: boolean } = {};
ngOnInit() { ngOnInit() {
this.activeTab = 'list'; this.activeTab = 'list';
@ -303,11 +303,13 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
*/ */
canModifyMerchant(merchant: Merchant): boolean { canModifyMerchant(merchant: Merchant): boolean {
if (this.canManageMerchants) return true; if (this.canManageMerchants) return true;
return true;
// PARTNER ne peut modifier que ses propres marchands // PARTNER ne peut modifier que ses propres marchands
// Vérifier via les configs ou users // Vérifier via les configs ou users
return merchant.configs?.some(config => config.merchantPartnerId === this.currentMerchantPartnerId) || //return merchant.configs?.some(config => config.merchantPartnerId === this.currentMerchantPartnerId) ||
merchant.users?.some(user => user.merchantPartnerId === this.currentMerchantPartnerId); // merchant.users?.some(user => user.merchantPartnerId === this.currentMerchantPartnerId);
} }
// ==================== GESTION DES MARCHANDS ==================== // ==================== GESTION DES MARCHANDS ====================
@ -320,8 +322,8 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
} }
} }
onMerchantSelectionChange(selectedMerchantId: string): void { onMerchantSelectionChange(selectedMerchantId: number): void {
this.selectedMerchantPartnerId = selectedMerchantId; //this.selectedMerchantPartnerId = selectedMerchantId;
// Recharger les marchands pour le partenaire sélectionné // Recharger les marchands pour le partenaire sélectionné
if (this.merchantConfigsList) { if (this.merchantConfigsList) {
@ -331,7 +333,7 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
// ==================== GESTION DES ONGLETS ==================== // ==================== 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}` : ''); console.log(`Switching to tab: ${tab}`, merchantId ? `for merchant ${merchantId}` : '');
this.activeTab = tab; 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; if (this.loadingMerchants[merchantId]) return;
this.loadingMerchants[merchantId] = true; this.loadingMerchants[merchantId] = true;
@ -374,7 +376,7 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
// ==================== ÉVÉNEMENTS DES COMPOSANTS ENFANTS ==================== // ==================== ÉVÉNEMENTS DES COMPOSANTS ENFANTS ====================
onMerchantSelected(merchantId: string): void { onMerchantSelected(merchantId: number): void {
this.showTab('profile', merchantId); this.showTab('profile', merchantId);
} }
@ -388,7 +390,7 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
this.openDeleteMerchantModal(merchant.id!); this.openDeleteMerchantModal(merchant.id!);
} }
onEditConfigRequested(configId: string): void { onEditConfigRequested(configId: number): void {
this.openEditMerchantModal(configId); this.openEditMerchantModal(configId);
} }
@ -413,7 +415,7 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
this.openModal(this.createMerchantModal); this.openModal(this.createMerchantModal);
} }
private openEditMerchantModal(merchantId: string): void { private openEditMerchantModal(merchantId: number): void {
this.merchantConfigService.getMerchantById(merchantId) this.merchantConfigService.getMerchantById(merchantId)
.pipe(takeUntil(this.destroy$)) .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) { if (!this.canDeleteMerchants) {
console.warn('User does not have permission to delete merchants'); console.warn('User does not have permission to delete merchants');
return; return;
@ -765,12 +767,12 @@ export class MerchantConfigManagement implements OnInit, OnDestroy {
} }
// Méthodes utilitaires pour le template d'édition // Méthodes utilitaires pour le template d'édition
trackByConfigId(index: number, config: MerchantConfig): string { trackByConfigId(index: number, config: MerchantConfig): number {
return config.id || `new-${index}`; return config.id || index;
} }
trackByContactId(index: number, contact: TechnicalContact): string { trackByContactId(index: number, contact: TechnicalContact): number {
return contact.id || `new-${index}`; return contact.id || index;
} }
// Méthode pour détecter les configs sensibles // Méthode pour détecter les configs sensibles

View File

@ -29,7 +29,7 @@ export class MerchantDataAdapter {
return { return {
...apiMerchant, ...apiMerchant,
id: this.convertIdToString(apiMerchant.id), id: apiMerchant.id, //this.convertIdToString(apiMerchant.id),
configs: (apiMerchant.configs || []).map(config => configs: (apiMerchant.configs || []).map(config =>
this.convertApiConfigToFrontend(config) this.convertApiConfigToFrontend(config)
), ),
@ -48,8 +48,8 @@ export class MerchantDataAdapter {
convertApiConfigToFrontend(apiConfig: ApiMerchantConfig): MerchantConfig { convertApiConfigToFrontend(apiConfig: ApiMerchantConfig): MerchantConfig {
return { return {
...apiConfig, ...apiConfig,
id: this.convertIdToString(apiConfig.id), id: apiConfig.id, //this.convertIdToString(apiConfig.id),
merchantPartnerId: this.convertIdToString(apiConfig.merchantPartnerId) merchantPartnerId: apiConfig.merchantPartnerId // this.convertIdToString(apiConfig.merchantPartnerId)
}; };
} }
@ -59,8 +59,8 @@ export class MerchantDataAdapter {
convertApiContactToFrontend(apiContact: ApiTechnicalContact): TechnicalContact { convertApiContactToFrontend(apiContact: ApiTechnicalContact): TechnicalContact {
return { return {
...apiContact, ...apiContact,
id: this.convertIdToString(apiContact.id), id: apiContact.id, //this.convertIdToString(apiContact.id),
merchantPartnerId: this.convertIdToString(apiContact.merchantPartnerId) merchantPartnerId: apiContact.merchantPartnerId //this.convertIdToString(apiContact.merchantPartnerId)
}; };
} }
@ -160,7 +160,7 @@ export class MerchantDataAdapter {
} }
if (config.merchantPartnerId !== undefined) { if (config.merchantPartnerId !== undefined) {
updateData.merchantPartnerId = this.convertIdToNumber(config.merchantPartnerId); updateData.merchantPartnerId = config.merchantPartnerId //this.convertIdToNumber(config.merchantPartnerId);
} }
return updateData; return updateData;

View File

@ -291,12 +291,12 @@ const routes: Routes = [
// Support & Profile (Tous les utilisateurs authentifiés) // Support & Profile (Tous les utilisateurs authentifiés)
// --------------------------- // ---------------------------
{ {
path: 'dcb-support', path: 'support',
component: Support, component: Support,
canActivate: [authGuard, roleGuard], canActivate: [authGuard, roleGuard],
data: { data: {
title: 'Support', title: 'Support',
module: 'dcb-support' module: 'support'
} }
}, },
{ {

View File

@ -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({ @Component({
selector: 'app-support', 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');
}
}