475 lines
17 KiB
TypeScript
475 lines
17 KiB
TypeScript
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';
|
|
|
|
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;
|
|
profile: 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?: TpeApiResponse[];
|
|
createdAt?: string;
|
|
updatedAt?: string;
|
|
createdBy?: string;
|
|
}
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
export class AgentService {
|
|
private apiUrl = environment.apiBaseUrl + API_BASE;
|
|
|
|
constructor(private http: HttpClient) {}
|
|
|
|
// Helper method to get ngrok bypass headers
|
|
private getNgrokHeaders(): Record<string, string> {
|
|
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[] = [
|
|
'VALIDE',
|
|
'INVALIDE',
|
|
'EN_PANNE',
|
|
'BLOQUE',
|
|
'DISPONIBLE',
|
|
'AFFECTE',
|
|
'EN_MAINTENANCE',
|
|
'HORS_SERVICE',
|
|
'VOLE',
|
|
];
|
|
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,
|
|
profile: apiAgent.profile,
|
|
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?.map((tpe) => {
|
|
const transformed = this.transformTpe(tpe);
|
|
return transformed;
|
|
}),
|
|
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<Agent>): any {
|
|
const payload: any = {};
|
|
if (agent.code !== undefined) payload.code = agent.code;
|
|
if (agent.profile !== undefined) payload.profile = agent.profile;
|
|
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;
|
|
// Include tpes if provided - transform to API format
|
|
if (agent.tpes !== undefined) {
|
|
payload.tpes = agent.tpes.map((tpe) => ({
|
|
id: tpe.id ? Number(tpe.id) : undefined,
|
|
imei: tpe.imei,
|
|
serial: tpe.serial,
|
|
type: tpe.type,
|
|
marque: tpe.marque,
|
|
modele: tpe.modele,
|
|
statut: tpe.statut,
|
|
agent: undefined, // Will be set by backend
|
|
assigne: tpe.assigne,
|
|
createdAt: tpe.createdAt,
|
|
updatedAt: tpe.updatedAt,
|
|
}));
|
|
}
|
|
return payload;
|
|
}
|
|
|
|
// GET /api/v1/agents/{id} - Get by ID
|
|
getById(id: string): Observable<Agent | undefined> {
|
|
if (USE_SERVER) {
|
|
return this.http
|
|
.get<AgentApiResponse>(`${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<PagedResult<Agent>> {
|
|
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<PagedResult<AgentApiResponse>>(this.apiUrl, {
|
|
params: httpParams,
|
|
headers: this.getNgrokHeaders(),
|
|
})
|
|
.pipe(
|
|
map((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<Agent>({ content: [], meta: { total: 0 } }, 1, 10));
|
|
})
|
|
);
|
|
}
|
|
|
|
// POST /api/v1/agents - Create
|
|
create(payload: Omit<Agent, 'id' | 'createdAt' | 'updatedAt'>): Observable<Agent> {
|
|
if (USE_SERVER) {
|
|
const apiPayload = this.transformToApiPayload(payload);
|
|
return this.http
|
|
.post<AgentApiResponse>(this.apiUrl, apiPayload, { headers: this.getNgrokHeaders() })
|
|
.pipe(
|
|
map((apiAgent) => this.transformAgent(apiAgent)),
|
|
catchError((err) => {
|
|
console.error('Error creating agent:', err);
|
|
throw err;
|
|
})
|
|
);
|
|
}
|
|
throw new Error('Server mode is required');
|
|
}
|
|
|
|
// PUT /api/v1/agents/{id} - Update
|
|
update(id: string, payload: Partial<Agent>): Observable<Agent | undefined> {
|
|
if (USE_SERVER) {
|
|
const apiPayload = this.transformToApiPayload(payload);
|
|
return this.http
|
|
.put<AgentApiResponse>(`${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<boolean> {
|
|
if (USE_SERVER) {
|
|
return this.http
|
|
.delete<void>(`${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<Agent[]> {
|
|
if (USE_SERVER) {
|
|
return this.http
|
|
.get<AgentApiResponse[]>(`${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<Agent[]> {
|
|
if (USE_SERVER) {
|
|
return this.http
|
|
.get<AgentApiResponse[]>(`${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<Agent[]> {
|
|
if (USE_SERVER) {
|
|
return this.http
|
|
.get<AgentApiResponse[]>(`${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<Agent | undefined> {
|
|
if (USE_SERVER) {
|
|
return this.http
|
|
.get<AgentApiResponse>(`${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<boolean> {
|
|
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);
|
|
}
|
|
}
|