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,87 @@
using TrajetKmTracker.Api.Models.DTOs;
namespace TrajetKmTracker.Api.Services;
/// <summary>
/// Logique métier de gestion des Trajets. Cette interface couvre pour l'instant la
/// création (tâche 8.2) ; la modification, la suppression et le réordonnancement
/// (tâche 8.5) l'étendront ultérieurement.
/// </summary>
public interface ITrajetService
{
/// <summary>
/// Crée un Trajet à partir de <paramref name="dto"/> :
/// <list type="bullet">
/// <item>parse la date au format JJ/MM/AAAA (la validation de forme est faite par le validateur MVC) ;</item>
/// <item>associe le véhicule : <c>VehiculeId</c> fourni sinon le véhicule courant à la date
/// du trajet, en copiant sa <c>PuissanceFiscale</c> (dénormalisation, Exigence 10.14) ;</item>
/// <item>calcule la distance via <see cref="IDistanceCalculator"/> (BAN puis ORS, fallback Haversine) ;</item>
/// <item>met en cache les adresses résolues via <see cref="IAdresseCacheService"/> ;</item>
/// <item>attribue la position en fin de journée (<c>max(Position)+1.0</c>) ;</item>
/// <item>persiste le Trajet en transaction. Lorsque la distance n'a pas pu être calculée,
/// le Trajet est tout de même persisté avec une Distance nulle et un avertissement (Exigence 2.4).</item>
/// </list>
/// </summary>
/// <param name="dto">Données de création transmises par le contrôleur (déjà validées côté MVC).</param>
/// <param name="cancellationToken">Jeton d'annulation.</param>
/// <returns>Le <see cref="TrajetCreationResult"/> (trajet créé + éventuel avertissement).</returns>
/// <exception cref="Exceptions.VehiculeNotFoundException">
/// Si aucun véhicule n'est applicable à la date du trajet et qu'aucun <c>VehiculeId</c> n'est fourni.
/// </exception>
Task<TrajetCreationResult> CreateAsync(CreateTrajetDto dto, CancellationToken cancellationToken = default);
/// <summary>
/// Modifie un Trajet existant à partir de <paramref name="dto"/> :
/// <list type="bullet">
/// <item>lève <see cref="Exceptions.TrajetNotFoundException"/> (404) si le Trajet est introuvable
/// (Exigence 5.6) ;</item>
/// <item>recalcule la Distance <b>uniquement</b> si au moins une adresse a changé (comparaison sur
/// le texte d'adresse normalisé) ou si le Trajet avait une Distance nulle et que les nouvelles
/// adresses deviennent résolubles (Exigences 5.2, 5.9, 5.10) ; sinon les autres champs (commentaire,
/// véhicule, date) sont mis à jour <b>sans</b> recalcul de distance (Exigence 5.3) ;</item>
/// <item>en cas d'échec du recalcul (coordonnées connues mais calcul impossible), annule la
/// modification (rollback) et lève <see cref="Exceptions.DistanceRecalculException"/> en préservant
/// les données existantes du Trajet (Exigence 5.8) ;</item>
/// <item>met en cache les adresses résolues (Exigences 6.7, 6.8) ;</item>
/// <item>persiste la modification en transaction.</item>
/// </list>
/// </summary>
/// <param name="id">Identifiant du Trajet à modifier.</param>
/// <param name="dto">Données de modification (déjà validées côté MVC).</param>
/// <param name="cancellationToken">Jeton d'annulation.</param>
/// <returns>Le <see cref="TrajetUpdateResult"/> (trajet mis à jour + éventuel avertissement).</returns>
/// <exception cref="Exceptions.TrajetNotFoundException">Si le Trajet n'existe pas.</exception>
/// <exception cref="Exceptions.DistanceRecalculException">Si le recalcul de la distance échoue.</exception>
/// <exception cref="Exceptions.VehiculeNotFoundException">
/// Si le <c>VehiculeId</c> fourni est introuvable, ou qu'aucun véhicule n'est applicable à la date.
/// </exception>
Task<TrajetUpdateResult> UpdateAsync(int id, UpdateTrajetDto dto, CancellationToken cancellationToken = default);
/// <summary>
/// Supprime un Trajet existant de manière transactionnelle (Exigence 5.5).
/// Lève <see cref="Exceptions.TrajetNotFoundException"/> (404) si le Trajet est introuvable (Exigence 5.6).
/// </summary>
/// <param name="id">Identifiant du Trajet à supprimer.</param>
/// <param name="cancellationToken">Jeton d'annulation.</param>
/// <exception cref="Exceptions.TrajetNotFoundException">Si le Trajet n'existe pas.</exception>
Task DeleteAsync(int id, CancellationToken cancellationToken = default);
/// <summary>
/// Applique un réordonnancement manuel des trajets d'une journée : met à jour uniquement la
/// <c>Position</c> des trajets fournis via <see cref="ITrajetRepository.ReordonnerAsync"/>,
/// sans altérer leurs autres champs (Exigence 11.9). Les identifiants inconnus sont ignorés.
/// </summary>
/// <param name="dto">Nouvelles positions des trajets à réordonner.</param>
/// <param name="cancellationToken">Jeton d'annulation.</param>
Task ReordonnerAsync(ReordonnerTrajetsDto dto, CancellationToken cancellationToken = default);
/// <summary>
/// Recalcule la Distance (et la géométrie d'itinéraire mise en cache) de TOUS les trajets
/// via <see cref="IDistanceCalculator"/> (BAN + ORS, fallback Haversine), indépendamment de
/// tout changement d'adresse. Utile pour « réparer » des distances calculées en mode dégradé
/// (ex. clé ORS invalide → distances à vol d'oiseau) une fois ORS de nouveau opérationnel.
/// Chaque trajet est traité de façon indépendante : un échec sur l'un n'interrompt pas les autres.
/// </summary>
/// <returns>Le nombre de trajets dont la distance a effectivement été recalculée.</returns>
Task<int> RecalculerToutesLesDistancesAsync(CancellationToken cancellationToken = default);
}