Files
SchotzIAD/frontend/src/app/core/services/trajet.service.ts
T
MadJax 61298d6384 Refonte export PDF, colonne commentaire et UX résumés
- Export PDF: nouveau nommage AAAAMM/AAAA_Trajets_MATRICULE_PLAQUE.pdf,
  logo carré embarqué en en-tête, km formatés selon Precision_Distance,
  restructuration en-têtes (Domicile déplacé, période/année fusionnées
  au titre)
- Correction bug: le nom de fichier suggéré au téléchargement ne
  remontait pas au frontend (CORS WithExposedHeaders manquant +
  composants résumé ne lisaient pas Content-Disposition)
- Résumé mensuel: colonne Commentaire, sélecteur temporel unifié
  (auto-chargement), km selon précision
- Résumé annuel: bouton Export PDF repositionné, km selon précision
- Panneau totaux: renommage "Remboursement indicatif" + infobulles
  d'aide (icône) au lieu du texte permanent
- Ajout de PdfServiceIntegrationTests (3 tests), 65/65 tests backend OK
- Mise à jour requirements.md et design.md en conséquence
2026-09-28 08:58:23 +02:00

162 lines
5.8 KiB
TypeScript

import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
import { environment } from '../../../environments/environment';
import {
CreateTrajet,
ReordonnerTrajets,
ResumeAnnuel,
ResumeMensuel,
Totaux,
Trajet,
TrajetCarte,
UpdateTrajet
} from '../models';
/**
* Service HTTP des trajets, résumés, exports PDF et navigation temporelle.
*
* Toutes les routes sont relatives à `environment.apiUrl` (`/api` en prod,
* `http://localhost:8080/api` en dev). Les appels sont soumis au
* `timeoutInterceptor` (10 s, Exigence 1.9).
*/
@Injectable({ providedIn: 'root' })
export class TrajetService {
private readonly http = inject(HttpClient);
private readonly baseUrl = environment.apiUrl;
/** Crée un trajet (POST /api/trajets). */
createTrajet(dto: CreateTrajet): Observable<Trajet> {
return this.http.post<Trajet>(`${this.baseUrl}/trajets`, dto);
}
/** Modifie un trajet (PUT /api/trajets/{id}). */
updateTrajet(id: number, dto: UpdateTrajet): Observable<Trajet> {
return this.http.put<Trajet>(`${this.baseUrl}/trajets/${id}`, dto);
}
/** Supprime un trajet (DELETE /api/trajets/{id}). */
deleteTrajet(id: number): Observable<void> {
return this.http.delete<void>(`${this.baseUrl}/trajets/${id}`);
}
/** Réordonne les trajets d'une journée (PATCH /api/trajets/reordonner). */
reordonner(dto: ReordonnerTrajets): Observable<void> {
return this.http.patch<void>(`${this.baseUrl}/trajets/reordonner`, dto);
}
/**
* Recalcule la distance de tous les trajets via ORS
* (POST /api/trajets/recalculer-distances). Retourne le nombre de trajets recalculés.
*/
recalculerDistances(): Observable<{ trajetsRecalcules: number }> {
return this.http.post<{ trajetsRecalcules: number }>(
`${this.baseUrl}/trajets/recalculer-distances`,
{}
);
}
/**
* Résumé mensuel (GET /api/resumes/mensuel/{annee}/{mois}).
* Filtre optionnel par véhicule ; véhicule courant si omis.
*/
getResumeMensuel(annee: number, mois: number, vehiculeId?: number): Observable<ResumeMensuel> {
return this.http.get<ResumeMensuel>(`${this.baseUrl}/resumes/mensuel/${annee}/${mois}`, {
params: this.vehiculeParams(vehiculeId)
});
}
/**
* Résumé annuel (GET /api/resumes/annuel/{annee}).
* Filtre optionnel par véhicule ; véhicule courant si omis.
*/
getResumeAnnuel(annee: number, vehiculeId?: number): Observable<ResumeAnnuel> {
return this.http.get<ResumeAnnuel>(`${this.baseUrl}/resumes/annuel/${annee}`, {
params: this.vehiculeParams(vehiculeId)
});
}
/**
* Export PDF mensuel (GET /api/exports/mensuel/{annee}/{mois}). Réponse complète
* (et non le seul Blob) afin que l'appelant puisse lire le nom de fichier suggéré
* par le backend depuis l'en-tête `Content-Disposition`.
*/
exportPdfMensuel(annee: number, mois: number, vehiculeId?: number): Observable<HttpResponse<Blob>> {
return this.http.get(`${this.baseUrl}/exports/mensuel/${annee}/${mois}`, {
params: this.vehiculeParams(vehiculeId),
responseType: 'blob',
observe: 'response'
});
}
/**
* Export PDF annuel (GET /api/exports/annuel/{annee}). Réponse complète (et non le
* seul Blob) afin que l'appelant puisse lire le nom de fichier suggéré par le backend
* depuis l'en-tête `Content-Disposition`.
*/
exportPdfAnnuel(annee: number, vehiculeId?: number): Observable<HttpResponse<Blob>> {
return this.http.get(`${this.baseUrl}/exports/annuel/${annee}`, {
params: this.vehiculeParams(vehiculeId),
responseType: 'blob',
observe: 'response'
});
}
/**
* Extrait le nom de fichier suggéré par le backend depuis l'en-tête
* `Content-Disposition: attachment; filename="..."` d'une réponse HTTP.
* Retourne `null` si l'en-tête est absent ou ne contient pas de nom de fichier.
*/
extraireNomFichier(reponse: HttpResponse<Blob>): string | null {
const entete = reponse.headers.get('Content-Disposition');
if (!entete) {
return null;
}
const correspondance = /filename\*?=(?:UTF-8'')?"?([^;"]+)"?/i.exec(entete);
return correspondance ? decodeURIComponent(correspondance[1]) : null;
}
/**
* Trajets d'une journée avec géométries pour la carte
* (GET /api/trajets/journee/{date}). La date est au format ISO (yyyy-MM-dd).
*/
getTrajetsJournee(date: string): Observable<TrajetCarte[]> {
return this.http.get<TrajetCarte[]>(`${this.baseUrl}/trajets/journee/${date}`);
}
/** Années disponibles pour la navigation (GET /api/annees-disponibles). */
getAnneesDisponibles(): Observable<number[]> {
return this.http.get<number[]>(`${this.baseUrl}/annees-disponibles`);
}
/** Mois disponibles d'une année (GET /api/mois-disponibles/{annee}). */
getMoisDisponibles(annee: number): Observable<number[]> {
return this.http.get<number[]>(`${this.baseUrl}/mois-disponibles/${annee}`);
}
/** Jours disponibles d'un mois (GET /api/jours-disponibles/{annee}/{mois}). */
getJoursDisponibles(annee: number, mois: number): Observable<number[]> {
return this.http.get<number[]>(`${this.baseUrl}/jours-disponibles/${annee}/${mois}`);
}
/**
* Totaux jour / mois / année partiel d'un véhicule à une date donnée
* (GET /api/totaux/{date}?vehiculeId={id}). La date est au format ISO.
*/
getTotauxJour(date: string, vehiculeId: number): Observable<Totaux> {
return this.http.get<Totaux>(`${this.baseUrl}/totaux/${date}`, {
params: new HttpParams().set('vehiculeId', vehiculeId)
});
}
/** Construit les paramètres de requête avec un `vehiculeId` optionnel. */
private vehiculeParams(vehiculeId?: number): HttpParams {
let params = new HttpParams();
if (vehiculeId != null) {
params = params.set('vehiculeId', vehiculeId);
}
return params;
}
}