import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { Observable, of, forkJoin } from 'rxjs'; import { map, catchError, switchMap } from 'rxjs/operators'; import { Agent, AgentStatus } from '../interfaces/agent'; import { TpeDevice, TpeStatus, TpeType } from '../interfaces/tpe'; import { environment } from 'src/environments/environment.development'; import { normalizePage } from '@shared/paging/normalize-page'; import { ListParams, PagedResult } from '@shared/paging/paging'; import { ServicesUtils } from './services-utils'; const USE_SERVER = true; const API_BASE = '/api/agents'; // Interface to match the API response structure for TPE (nested in Agent) // Note: When TPE is nested in Agent's tpes array, the agent field might be omitted or be a reference interface TpeApiResponse { id: number; imei: string; serial: string; type: string; marque: string; modele: string; statut: string; agent?: any; // Can be Agent object or string reference, we'll handle it in transformTpe assigne: boolean; createdAt?: string; updatedAt?: string; } // Interface to match the API response structure interface AgentApiResponse { id: number; code: string; profil: string; principalCode?: string; caisseProfile?: string; statut: string; zone?: string; kiosk?: string; fonction?: string; dateEmbauche?: string; nom: string; prenom: string; autresNoms?: string; dateNaissance?: string; lieuNaissance?: string; ville?: string; adresse?: string; autoriserAides?: boolean; phone: string; pin?: string; limiteInferieure?: number; limiteSuperieure?: number; limiteParTransaction?: number; limiteMinAirtime?: number; limiteMaxAirtime?: number; maxPeripheriques?: number; limitId?: number; nationalite?: string; cni?: string; cniDelivreeLe?: string; cniDelivreeA?: string; residence?: string; autreAdresse1?: string; statutMarital?: string; epoux?: string; autreTelephone?: string; tpes?: TpeDevice; createdAt?: string; updatedAt?: string; createdBy?: string; } @Injectable({ providedIn: 'root' }) export class AgentService { private apiUrl = environment.apiBaseUrl + API_BASE; constructor(private http: HttpClient, private serviceUtils:ServicesUtils) {} // Helper method to get ngrok bypass headers private getNgrokHeaders(): Record { const isNgrok = environment.apiBaseUrl.includes('ngrok-free.app') || environment.apiBaseUrl.includes('ngrok.io') || environment.apiBaseUrl.includes('ngrok'); return isNgrok ? { 'ngrok-skip-browser-warning': 'true' } : {}; } // Transform API TPE response to TpeDevice // private transformTpe(apiTpe: TpeApiResponse): TpeDevice { // const transformStatut = (apiStatut: string): TpeStatus => { // const upperStatut = apiStatut.toUpperCase() as TpeStatus; // const validStatuses: TpeStatus[] = [ // 'ACTIF', // 'HORS_SERVICE' // ]; // return validStatuses.includes(upperStatut) ? upperStatut : 'INVALIDE'; // }; // // Transform agent if it's an object (not just a string reference) // let transformedAgent: Agent | undefined = undefined; // if (apiTpe.agent && typeof apiTpe.agent === 'object' && apiTpe.agent.id) { // // If agent is a full object, transform it // transformedAgent = this.transformAgent(apiTpe.agent as any); // } // return { // id: String(apiTpe.id), // imei: apiTpe.imei, // serial: apiTpe.serial, // type: apiTpe.type as TpeType, // marque: apiTpe.marque, // modele: apiTpe.modele, // statut: transformStatut(apiTpe.statut), // agent: transformedAgent, // assigne: apiTpe.assigne, // createdAt: apiTpe.createdAt, // updatedAt: apiTpe.updatedAt, // }; // } // Transform API response to Agent private transformAgent(apiAgent: AgentApiResponse): Agent { return { id: String(apiAgent.id), code: apiAgent.code, profil: apiAgent.profil, principalCode: apiAgent.principalCode, caisseProfile: apiAgent.caisseProfile, statut: apiAgent.statut as AgentStatus, zone: apiAgent.zone, kiosk: apiAgent.kiosk, fonction: apiAgent.fonction, dateEmbauche: apiAgent.dateEmbauche, nom: apiAgent.nom, prenom: apiAgent.prenom, autresNoms: apiAgent.autresNoms, dateNaissance: apiAgent.dateNaissance, lieuNaissance: apiAgent.lieuNaissance, ville: apiAgent.ville, adresse: apiAgent.adresse, autoriserAides: apiAgent.autoriserAides, phone: apiAgent.phone, pin: apiAgent.pin, limiteInferieure: apiAgent.limiteInferieure, limiteSuperieure: apiAgent.limiteSuperieure, limiteParTransaction: apiAgent.limiteParTransaction, limiteMinAirtime: apiAgent.limiteMinAirtime, limiteMaxAirtime: apiAgent.limiteMaxAirtime, maxPeripheriques: apiAgent.maxPeripheriques, limitId: apiAgent.limitId ? String(apiAgent.limitId) : undefined, nationalite: apiAgent.nationalite, cni: apiAgent.cni, cniDelivreeLe: apiAgent.cniDelivreeLe, cniDelivreeA: apiAgent.cniDelivreeA, residence: apiAgent.residence, autreAdresse1: apiAgent.autreAdresse1, statutMarital: apiAgent.statutMarital, epoux: apiAgent.epoux, autreTelephone: apiAgent.autreTelephone, tpes: apiAgent.tpes , createdAt: apiAgent.createdAt, updatedAt: apiAgent.updatedAt, createdBy: apiAgent.createdBy, }; } // Helper method to convert date string to LocalDateTime format (YYYY-MM-DDTHH:mm:ss) private formatDateForApi(dateStr: string | undefined): string | undefined { if (!dateStr) return undefined; // If already in ISO format with time, return as is if (dateStr.includes('T') || dateStr.includes(' ')) { return dateStr; } // If only date (YYYY-MM-DD), add time component if (dateStr.match(/^\d{4}-\d{2}-\d{2}$/)) { return `${dateStr}T00:00:00`; } return dateStr; } // Transform Agent to API payload private transformToApiPayload(agent: Partial): any { const payload: any = {}; if (agent.code !== undefined) payload.code = agent.code; if (agent.profil !== undefined) payload.profil = agent.profil; if (agent.principalCode !== undefined) payload.principalCode = agent.principalCode; if (agent.caisseProfile !== undefined) payload.caisseProfile = agent.caisseProfile; if (agent.statut !== undefined) payload.statut = agent.statut; if (agent.zone !== undefined) payload.zone = agent.zone; if (agent.kiosk !== undefined) payload.kiosk = agent.kiosk; if (agent.fonction !== undefined) payload.fonction = agent.fonction; if (agent.dateEmbauche !== undefined) payload.dateEmbauche = this.formatDateForApi(agent.dateEmbauche); if (agent.nom !== undefined) payload.nom = agent.nom; if (agent.prenom !== undefined) payload.prenom = agent.prenom; if (agent.autresNoms !== undefined) payload.autresNoms = agent.autresNoms; if (agent.dateNaissance !== undefined) payload.dateNaissance = this.formatDateForApi(agent.dateNaissance); if (agent.lieuNaissance !== undefined) payload.lieuNaissance = agent.lieuNaissance; if (agent.ville !== undefined) payload.ville = agent.ville; if (agent.adresse !== undefined) payload.adresse = agent.adresse; if (agent.autoriserAides !== undefined) payload.autoriserAides = agent.autoriserAides; if (agent.phone !== undefined) payload.phone = agent.phone; if (agent.pin !== undefined) payload.pin = agent.pin; if (agent.limiteInferieure !== undefined) payload.limiteInferieure = agent.limiteInferieure; if (agent.limiteSuperieure !== undefined) payload.limiteSuperieure = agent.limiteSuperieure; if (agent.limiteParTransaction !== undefined) payload.limiteParTransaction = agent.limiteParTransaction; if (agent.limiteMinAirtime !== undefined) payload.limiteMinAirtime = agent.limiteMinAirtime; if (agent.limiteMaxAirtime !== undefined) payload.limiteMaxAirtime = agent.limiteMaxAirtime; if (agent.maxPeripheriques !== undefined) payload.maxPeripheriques = agent.maxPeripheriques; if (agent.limitId !== undefined) payload.limitId = agent.limitId ? Number(agent.limitId) : undefined; if (agent.nationalite !== undefined) payload.nationalite = agent.nationalite; if (agent.cni !== undefined) payload.cni = agent.cni; if (agent.cniDelivreeLe !== undefined) payload.cniDelivreeLe = this.formatDateForApi(agent.cniDelivreeLe); if (agent.cniDelivreeA !== undefined) payload.cniDelivreeA = agent.cniDelivreeA; if (agent.residence !== undefined) payload.residence = agent.residence; if (agent.autreAdresse1 !== undefined) payload.autreAdresse1 = agent.autreAdresse1; if (agent.statutMarital !== undefined) payload.statutMarital = agent.statutMarital; if (agent.epoux !== undefined) payload.epoux = agent.epoux; if (agent.autreTelephone !== undefined) payload.autreTelephone = agent.autreTelephone; if (agent.createdBy !== undefined) payload.createdBy = agent.createdBy; return payload; } // GET /api/v1/agents/{id} - Get by ID getById(id: string): Observable { if (USE_SERVER) { return this.http .get(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() }) .pipe( map((apiAgent) => this.transformAgent(apiAgent)), catchError((err) => { console.error(`Error fetching agent ${id}:`, err); return of(undefined); }) ); } return of(undefined); } // GET /api/v1/agents - List all list(params: ListParams): Observable> { // let httpParams = new HttpParams(); // if (params) { // if (params.page) httpParams = httpParams.set('page', params.page.toString()); // if (params.size) httpParams = httpParams.set('perPage', params.size.toString()); // if (params.search) httpParams = httpParams.set('search', params.search); // if (params.sortKey) httpParams = httpParams.set('sortKey', params.sortKey); // if (params.sortDir) httpParams = httpParams.set('sortDir', params.sortDir); // } return this.http .get>(this.apiUrl, { params: this.serviceUtils.getParamsFromModel(params), headers: this.getNgrokHeaders(), }) .pipe( map((res) => { console.log(res); const agents = res.content.map((apiAgent) => { const transformed = this.transformAgent(apiAgent); return transformed; }); // If pagination params provided, return paginated result const resAgent = { ...res, content: agents } // Otherwise return all as single page return resAgent; }), catchError((err) => { console.error('Error fetching agents:', err); return of(normalizePage({ content: [], meta: { total: 0 } }, 1, 10)); }) ); } // POST /api/agents - Create create(payload: Omit): Observable { const apiPayload = this.transformToApiPayload(payload); return this.http .post(this.apiUrl, apiPayload, { headers: this.getNgrokHeaders() }) .pipe( map((apiAgent) => this.transformAgent(apiAgent)), catchError((err) => { console.error('Error creating agent:', err); throw err; }) ); } // PUT /api/v1/agents/{id} - Update update(id: string, payload: Partial): Observable { if (USE_SERVER) { const apiPayload = this.transformToApiPayload(payload); return this.http .put(`${this.apiUrl}/${id}`, apiPayload, { headers: this.getNgrokHeaders(), }) .pipe( map((apiAgent) => this.transformAgent(apiAgent)), catchError((err) => { console.error(`Error updating agent ${id}:`, err); return of(undefined); }) ); } return of(undefined); } // DELETE /api/v1/agents/{id} - Delete delete(id: string): Observable { if (USE_SERVER) { return this.http .delete(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() }) .pipe( map(() => true), catchError((err) => { console.error(`Error deleting agent ${id}:`, err); return of(false); }) ); } return of(false); } // GET /api/v1/agents/ville/{ville} - List by ville getByVille(ville: string): Observable { if (USE_SERVER) { return this.http .get(`${this.apiUrl}/ville/${ville}`, { headers: this.getNgrokHeaders(), }) .pipe( map((list) => list.map((apiAgent) => this.transformAgent(apiAgent))), catchError((err) => { console.error(`Error fetching agents by ville ${ville}:`, err); return of([]); }) ); } return of([]); } // GET /api/v1/agents/statut/{statut} - List by statut getByStatut(statut: AgentStatus): Observable { if (USE_SERVER) { return this.http .get(`${this.apiUrl}/statut/${statut}`, { headers: this.getNgrokHeaders(), }) .pipe( map((list) => list.map((apiAgent) => this.transformAgent(apiAgent))), catchError((err) => { console.error(`Error fetching agents by statut ${statut}:`, err); return of([]); }) ); } return of([]); } // GET /api/v1/agents/search - Search by nom or prenom search(query: string): Observable { if (USE_SERVER) { return this.http .get(`${this.apiUrl}/search`, { params: { q: query.trim() }, headers: this.getNgrokHeaders(), }) .pipe( map((list) => list.map((apiAgent) => this.transformAgent(apiAgent))), catchError((err) => { console.error(`Error searching agents with query ${query}:`, err); return of([]); }) ); } return of([]); } // GET /api/v1/agents/code/{code} - Get by code getByCode(code: string): Observable { if (USE_SERVER) { return this.http .get(`${this.apiUrl}/code/${code}`, { headers: this.getNgrokHeaders() }) .pipe( map((apiAgent) => this.transformAgent(apiAgent)), catchError((err) => { console.error(`Error fetching agent by code ${code}:`, err); return of(undefined); }) ); } return of(undefined); } // Helper method to update all agents' limitId to a new default limit // This is used when a limit is set as default updateAllAgentsLimitId(limitId: string): Observable { if (USE_SERVER) { // Get all agents first return this.list({ page: 1, size: 10000, search: '', sortKey: 'code', sortDir: 'asc', } as any).pipe( switchMap((result) => { const agents = result.content; if (agents.length === 0) { return of(true); } // Update each agent's limitId in parallel const updateObservables = agents.map((agent) => this.update(agent.id, { limitId }).pipe( catchError((err) => { console.error(`Error updating agent ${agent.id} limitId:`, err); return of(undefined); }) ) ); // Wait for all updates to complete return forkJoin(updateObservables).pipe( map(() => true), catchError((err) => { console.error('Error updating all agents limitId:', err); return of(false); }) ); }), catchError((err) => { console.error('Error fetching agents for limitId update:', err); return of(false); }) ); } return of(false); } }