468 lines
16 KiB
TypeScript
468 lines
16 KiB
TypeScript
import { Injectable } from '@angular/core';
|
|
import { HttpClient, HttpParams } from '@angular/common/http';
|
|
import { Observable, of } from 'rxjs';
|
|
import { map, catchError, switchMap } from 'rxjs/operators';
|
|
import { TpeDevice, TpeStatus, TpeType } from '../interfaces/tpe';
|
|
import { Agent, AgentStatus } from '../interfaces/agent';
|
|
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/terminaux';
|
|
|
|
// Interface to match the API response structure for Agent (nested in TPE)
|
|
interface AgentApiResponse {
|
|
id: number,
|
|
code: String,
|
|
profil: String,
|
|
principalCode: String,
|
|
caisseProfile: String,
|
|
statut: AgentStatus,
|
|
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,
|
|
"limiteInferieure": number,
|
|
"limiteSuperieure": number,
|
|
"limiteParTransaction": number,
|
|
"limiteMinAirtime": number,
|
|
"limiteMaxAirtime": number,
|
|
"maxPeripheriques": number,
|
|
"limitId": String,
|
|
"nationalite": String,
|
|
"cni": String,
|
|
"cniDelivreeLe": String,
|
|
"cniDelivreeA": String,
|
|
"residence": String,
|
|
"autreAdresse1": String,
|
|
"statutMarital": String,
|
|
"epoux": String,
|
|
"autreTelephone": String,
|
|
"createdAt": String,
|
|
"updatedAt": String,
|
|
"createdBy": String,
|
|
"terminauxIds": [
|
|
number[]
|
|
]
|
|
}
|
|
|
|
// Interface to match the API response structure
|
|
interface TpeApiResponse {
|
|
id: number,
|
|
numeroSerie: String,
|
|
pointDeVenteId: number,
|
|
statut: TpeStatus,
|
|
derniereConnexion: String,
|
|
versionLogicielle: String,
|
|
typeTerminal: String,
|
|
plateforme: String,
|
|
modeleAppareil: String,
|
|
systemeExploitation: String,
|
|
versionOs: String,
|
|
adresseIp: String,
|
|
adresseMac: String,
|
|
agentConnecteId: number,
|
|
derniereConnexionAgent: String,
|
|
derniereDeconnexionAgent: String,
|
|
journalSession: String
|
|
}
|
|
|
|
// Stats interfaces
|
|
interface CountByStatutResponse {
|
|
[key: string]: number;
|
|
}
|
|
|
|
// Assignment stats is just a number (count of assigned TPEs)
|
|
type AssignesStatsResponse = number;
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
export class TpeService {
|
|
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 statut to interface statut (both use uppercase now)
|
|
private 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 interface statut to API statut (both use uppercase now, so direct return)
|
|
private transformStatutToApi(statut: TpeStatus): string {
|
|
return statut; // Already uppercase, no transformation needed
|
|
}
|
|
|
|
// Transform API Agent response to Agent (lightweight mapping)
|
|
private transformAgent(apiAgent: AgentApiResponse): Agent {
|
|
return {
|
|
id: String(apiAgent.id),
|
|
code: String((apiAgent as any).code || ''),
|
|
profile: String((apiAgent as any).profile || ''),
|
|
principalCode: (apiAgent as any).principalCode ? String((apiAgent as any).principalCode) : undefined,
|
|
caisseProfile: (apiAgent as any).caisseProfile ? String((apiAgent as any).caisseProfile) : undefined,
|
|
statut: apiAgent.statut as AgentStatus,
|
|
zone: (apiAgent as any).zone ? String((apiAgent as any).zone) : undefined,
|
|
kiosk: (apiAgent as any).kiosk ? String((apiAgent as any).kiosk) : undefined,
|
|
fonction: (apiAgent as any).fonction ? String((apiAgent as any).fonction) : undefined,
|
|
dateEmbauche: apiAgent.dateEmbauche ? String(apiAgent.dateEmbauche) : undefined,
|
|
nom: String(apiAgent.nom || ''),
|
|
prenom: String(apiAgent.prenom || ''),
|
|
autresNoms: apiAgent.autresNoms ? String(apiAgent.autresNoms) : undefined,
|
|
dateNaissance: apiAgent.dateNaissance ? String(apiAgent.dateNaissance) : undefined,
|
|
lieuNaissance: apiAgent.lieuNaissance ? String(apiAgent.lieuNaissance) : undefined,
|
|
ville: apiAgent.ville ? String(apiAgent.ville) : undefined,
|
|
adresse: apiAgent.adresse ? String(apiAgent.adresse) : undefined,
|
|
autoriserAides: Boolean(apiAgent.autoriserAides),
|
|
phone: String(apiAgent.phone || ''),
|
|
pin: (apiAgent as any).pin ? String((apiAgent as any).pin) : undefined,
|
|
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 ? String(apiAgent.nationalite) : undefined,
|
|
cni: apiAgent.cni ? String(apiAgent.cni) : undefined,
|
|
cniDelivreeLe: apiAgent.cniDelivreeLe ? String(apiAgent.cniDelivreeLe) : undefined,
|
|
cniDelivreeA: apiAgent.cniDelivreeA ? String(apiAgent.cniDelivreeA) : undefined,
|
|
residence: apiAgent.residence ? String(apiAgent.residence) : undefined,
|
|
autreAdresse1: apiAgent.autreAdresse1 ? String(apiAgent.autreAdresse1) : undefined,
|
|
statutMarital: apiAgent.statutMarital ? String(apiAgent.statutMarital) : undefined,
|
|
epoux: apiAgent.epoux ? String(apiAgent.epoux) : undefined,
|
|
autreTelephone: apiAgent.autreTelephone ? String(apiAgent.autreTelephone) : undefined,
|
|
createdAt: apiAgent.createdAt ? String(apiAgent.createdAt) : undefined,
|
|
updatedAt: apiAgent.updatedAt ? String(apiAgent.updatedAt) : undefined,
|
|
createdBy: apiAgent.createdBy ? String(apiAgent.createdBy) : undefined,
|
|
};
|
|
}
|
|
|
|
// Transform API response to TpeDevice
|
|
private transformTpe(apiTpe: TpeApiResponse): TpeDevice {
|
|
// Map API-specific names to our generic interface where possible
|
|
const serial = (apiTpe as any).numeroSerie || (apiTpe as any).serial || '';
|
|
const imei = (apiTpe as any).imei || serial || '';
|
|
const typeRaw = String((apiTpe as any).typeTerminal || '').toUpperCase();
|
|
const type = typeRaw.includes('POS') ? ('POS' as TpeType) : ('OTHER' as TpeType);
|
|
const marque = (apiTpe as any).plateforme || (apiTpe as any).marque || '';
|
|
const modele = (apiTpe as any).modeleAppareil || (apiTpe as any).modele || '';
|
|
const statut = this.transformStatut(String(apiTpe.statut || 'INVALIDE'));
|
|
// Agent mapping: sometimes API returns an agent object or only an id
|
|
let agent: Agent | undefined = undefined;
|
|
if ((apiTpe as any).agent && typeof (apiTpe as any).agent === 'object' && (apiTpe as any).agent.id) {
|
|
agent = this.transformAgent((apiTpe as any).agent as AgentApiResponse);
|
|
}
|
|
const assigne = Boolean((apiTpe as any).agentConnecteId || (apiTpe as any).assigne);
|
|
|
|
return {
|
|
id: String(apiTpe.id),
|
|
imei: String(imei),
|
|
serial: String(serial),
|
|
type,
|
|
marque: String(marque),
|
|
modele: String(modele),
|
|
statut,
|
|
agent,
|
|
assigne,
|
|
createdAt: (apiTpe as any).createdAt,
|
|
updatedAt: (apiTpe as any).updatedAt,
|
|
};
|
|
}
|
|
|
|
// Transform TpeDevice to API payload (best-effort)
|
|
private transformToApiPayload(tpe: Partial<TpeDevice>): any {
|
|
const payload: any = {};
|
|
if (tpe.imei !== undefined) payload.numeroSerie = tpe.imei;
|
|
if (tpe.serial !== undefined) payload.numeroSerie = tpe.serial;
|
|
if (tpe.type !== undefined) payload.typeTerminal = tpe.type;
|
|
if (tpe.marque !== undefined) payload.plateforme = tpe.marque;
|
|
if (tpe.modele !== undefined) payload.modeleAppareil = tpe.modele;
|
|
if (tpe.statut !== undefined) payload.statut = this.transformStatutToApi(tpe.statut);
|
|
if (tpe.assigne !== undefined) payload.assigne = tpe.assigne;
|
|
return payload;
|
|
}
|
|
|
|
// GET /api/v1/tpes/{id} - Get by ID
|
|
getById(id: string): Observable<TpeDevice | undefined> {
|
|
if (USE_SERVER) {
|
|
return this.http.get<TpeApiResponse>(`${this.apiUrl}/${id}`, { headers: this.getNgrokHeaders() }).pipe(
|
|
map((api) => this.transformTpe(api)),
|
|
catchError((err) => {
|
|
console.error(`Error fetching TPE ${id}:`, err);
|
|
return of(undefined);
|
|
})
|
|
);
|
|
}
|
|
return of(undefined);
|
|
}
|
|
|
|
|
|
// GET /api/v1/tpes - List all
|
|
list(params?: ListParams): Observable<PagedResult<TpeDevice>> {
|
|
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<TpeApiResponse>>(this.apiUrl, {
|
|
params: httpParams,
|
|
headers: this.getNgrokHeaders(),
|
|
})
|
|
.pipe(
|
|
map((list) => {
|
|
const content = (list.content || []).map((api) => this.transformTpe(api));
|
|
return { ...list, content } as PagedResult<TpeDevice>;
|
|
}),
|
|
catchError((err) => {
|
|
console.error('Error fetching TPEs:', err);
|
|
return of(normalizePage<TpeDevice>({ content: [], meta: { total: 0 } }, 1, 10));
|
|
})
|
|
);
|
|
}
|
|
|
|
// POST /api/v1/tpes - Create
|
|
create(payload: Partial<TpeDevice>): Observable<TpeDevice> {
|
|
const apiPayload = this.transformToApiPayload(payload);
|
|
return this.http
|
|
.post<TpeApiResponse>(this.apiUrl, apiPayload, { headers: this.getNgrokHeaders() })
|
|
.pipe(
|
|
map((apiTpe) => this.transformTpe(apiTpe)),
|
|
catchError((err) => {
|
|
console.error('Error creating TPE:', err);
|
|
throw err;
|
|
})
|
|
);
|
|
}
|
|
|
|
// PUT /api/v1/tpes/{id} - Update
|
|
update(id: string, payload: Partial<TpeDevice>): Observable<TpeDevice | undefined> {
|
|
const apiPayload = this.transformToApiPayload(payload);
|
|
return this.http
|
|
.put<TpeApiResponse>(`${this.apiUrl}/${id}`, apiPayload, {
|
|
headers: this.getNgrokHeaders(),
|
|
})
|
|
.pipe(
|
|
map((apiTpe) => this.transformTpe(apiTpe)),
|
|
catchError((err) => {
|
|
console.error(`Error updating TPE ${id}:`, err);
|
|
return of(undefined);
|
|
})
|
|
);
|
|
}
|
|
|
|
// DELETE /api/v1/tpes/{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 TPE ${id}:`, err);
|
|
return of(false);
|
|
})
|
|
);
|
|
}
|
|
return of(false);
|
|
}
|
|
|
|
// PATCH /api/v1/tpes/{id}/statut - Update statut
|
|
updateStatut(id: string, statut: TpeStatus): Observable<TpeDevice | undefined> {
|
|
if (USE_SERVER) {
|
|
return this.http
|
|
.patch<TpeApiResponse>(
|
|
`${this.apiUrl}/${id}/statut`,
|
|
{ statut: this.transformStatutToApi(statut) },
|
|
{ headers: this.getNgrokHeaders() }
|
|
)
|
|
.pipe(
|
|
map((apiTpe) => this.transformTpe(apiTpe)),
|
|
catchError((err) => {
|
|
console.error(`Error updating TPE statut ${id}:`, err);
|
|
return of(undefined);
|
|
})
|
|
);
|
|
}
|
|
return of(undefined);
|
|
}
|
|
|
|
// PATCH /api/v1/tpes/{id}/liberer - Liberate TPE (updates whole TPE, sets assigne to false and statut to DISPONIBLE)
|
|
liberer(id: string): Observable<TpeDevice | undefined> {
|
|
if (USE_SERVER) {
|
|
// First get the current TPE data
|
|
return this.getById(id).pipe(
|
|
switchMap((tpe) => {
|
|
if (!tpe) {
|
|
return of(undefined);
|
|
}
|
|
// Update the whole TPE with assigne set to false and statut to DISPONIBLE
|
|
const updatedTpe = { ...tpe, assigne: false, statut: 'DISPONIBLE' as TpeStatus };
|
|
const apiPayload = this.transformToApiPayload(updatedTpe);
|
|
return this.http
|
|
.patch<TpeApiResponse>(`${this.apiUrl}/liberer/${id}`, apiPayload, {
|
|
headers: this.getNgrokHeaders(),
|
|
})
|
|
.pipe(
|
|
map((apiTpe) => this.transformTpe(apiTpe)),
|
|
catchError((err) => {
|
|
console.error(`Error liberating TPE ${id}:`, err);
|
|
return of(undefined);
|
|
})
|
|
);
|
|
}),
|
|
catchError((err) => {
|
|
console.error(`Error fetching TPE ${id} for liberation:`, err);
|
|
return of(undefined);
|
|
})
|
|
);
|
|
}
|
|
return of(undefined);
|
|
}
|
|
|
|
// PATCH /api/v1/tpes/assigner - Assign TPE
|
|
// Payload: { tpeId: number, agentId: number }
|
|
assigner(id: string, agentId: string): Observable<TpeDevice | undefined> {
|
|
if (USE_SERVER) {
|
|
const payload = {
|
|
tpeId: Number(id),
|
|
agentId: Number(agentId),
|
|
};
|
|
return this.http
|
|
.patch<TpeApiResponse>(`${this.apiUrl}/assigner`, payload, {
|
|
headers: this.getNgrokHeaders(),
|
|
})
|
|
.pipe(
|
|
map((apiTpe) => this.transformTpe(apiTpe)),
|
|
catchError((err) => {
|
|
console.error(`Error assigning TPE ${id}:`, err);
|
|
return of(undefined);
|
|
})
|
|
);
|
|
}
|
|
return of(undefined);
|
|
}
|
|
|
|
// GET /api/v1/tpes/statut/{statut} - List by statut
|
|
getByStatut(statut: TpeStatus): Observable<TpeDevice[]> {
|
|
if (USE_SERVER) {
|
|
const apiStatut = this.transformStatutToApi(statut);
|
|
return this.http
|
|
.get<TpeApiResponse[]>(`${this.apiUrl}/statut/${apiStatut}`, {
|
|
headers: this.getNgrokHeaders(),
|
|
})
|
|
.pipe(
|
|
map((list) => list.map((apiTpe) => this.transformTpe(apiTpe))),
|
|
catchError((err) => {
|
|
console.error(`Error fetching TPEs by statut ${statut}:`, err);
|
|
return of([]);
|
|
})
|
|
);
|
|
}
|
|
return of([]);
|
|
}
|
|
|
|
// GET /api/v1/tpes/stats/count-by-statut - Get count by statut
|
|
getCountByStatut(): Observable<CountByStatutResponse> {
|
|
if (USE_SERVER) {
|
|
return this.http
|
|
.get<CountByStatutResponse>(`${this.apiUrl}/stats/count-by-statut`, {
|
|
headers: this.getNgrokHeaders(),
|
|
})
|
|
.pipe(
|
|
catchError((err) => {
|
|
console.error('Error fetching TPE count by statut:', err);
|
|
return of({});
|
|
})
|
|
);
|
|
}
|
|
return of({});
|
|
}
|
|
|
|
// GET /api/v1/tpes/stats/assignes - Get assignment stats (returns a number)
|
|
getAssignesStats(): Observable<number> {
|
|
if (USE_SERVER) {
|
|
return this.http
|
|
.get<number>(`${this.apiUrl}/stats/assignes`, {
|
|
headers: this.getNgrokHeaders(),
|
|
})
|
|
.pipe(
|
|
catchError((err) => {
|
|
console.error('Error fetching TPE assignment stats:', err);
|
|
return of(0);
|
|
})
|
|
);
|
|
}
|
|
return of(0);
|
|
}
|
|
|
|
// GET /api/v1/tpes/search - Search
|
|
search(query: string): Observable<TpeDevice[]> {
|
|
if (USE_SERVER) {
|
|
return this.http
|
|
.get<TpeApiResponse[]>(`${this.apiUrl}/search`, {
|
|
params: { q: query.trim() },
|
|
headers: this.getNgrokHeaders(),
|
|
})
|
|
.pipe(
|
|
map((list) => list.map((apiTpe) => this.transformTpe(apiTpe))),
|
|
catchError((err) => {
|
|
console.error(`Error searching TPEs with query ${query}:`, err);
|
|
return of([]);
|
|
})
|
|
);
|
|
}
|
|
return of([]);
|
|
}
|
|
|
|
// GET /api/v1/tpes/disponibles - List available TPEs
|
|
getDisponibles(): Observable<TpeDevice[]> {
|
|
if (USE_SERVER) {
|
|
return this.http
|
|
.get<TpeApiResponse[]>(`${this.apiUrl}/disponibles`, { headers: this.getNgrokHeaders() })
|
|
.pipe(
|
|
map((list) => list.map((apiTpe) => this.transformTpe(apiTpe))),
|
|
catchError((err) => {
|
|
console.error('Error fetching available TPEs:', err);
|
|
return of([]);
|
|
})
|
|
);
|
|
}
|
|
return of([]);
|
|
}
|
|
}
|