SchotzIAD KM Tracker : suivi des trajets kilométriques (Angular + .NET 8)

Application complète de saisie et de suivi des trajets kilométriques avec
calcul de distance routière (OpenRouteService), barème kilométrique, résumés
et régularisation annuelle.

Backend (ASP.NET Core .NET 8, EF Core, SQLite) :
- Trajets (CRUD, réordonnancement, insertion), calcul de distance ORS avec
  fallback Haversine, géométrie d'itinéraire stockée par trajet, recalcul en masse.
- Profil, véhicules (avec dénomination), carnet d'adresses nommées, cache d'adresses.
- Barème kilométrique : tranches multiples par puissance fiscale, borne max
  exclusive avec 'illimité' (KmMax nullable), coefficient à 3 décimales,
  recopie du dernier barème connu.
- Résumés mensuel/annuel, montants réglés, régularisation, export PDF.

Frontend (Angular 22, zoneless + signals) :
- Consultation par jour, navigation temporelle synchronisée, carte Leaflet,
  formulaire de trajet (adresses nommées, domicile, autocomplétion BAN).
- Détection de changement zoneless (indépendante de zone.js).

Hébergement : Docker Compose (nginx + backend), scripts run-dev.sh / run-docker.sh.
This commit is contained in:
2026-09-14 02:08:27 +02:00
commit e3f946f7d4
225 changed files with 38168 additions and 0 deletions
@@ -0,0 +1,137 @@
import { HttpClient, HttpParams } 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}). */
exportPdfMensuel(annee: number, mois: number, vehiculeId?: number): Observable<Blob> {
return this.http.get(`${this.baseUrl}/exports/mensuel/${annee}/${mois}`, {
params: this.vehiculeParams(vehiculeId),
responseType: 'blob'
});
}
/** Export PDF annuel (GET /api/exports/annuel/{annee}). */
exportPdfAnnuel(annee: number, vehiculeId?: number): Observable<Blob> {
return this.http.get(`${this.baseUrl}/exports/annuel/${annee}`, {
params: this.vehiculeParams(vehiculeId),
responseType: 'blob'
});
}
/**
* 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;
}
}