feat: réutilisation des coordonnées déjà géocodées (domicile, insertion de trajets)

Expose les coordonnées déjà géocodées dans ProfilDto
(DomicileLatitude/Longitude) et TrajetDto (DepartLatitude/Longitude,
ArriveeLatitude/Longitude) afin que le Frontend les réutilise sans
déclencher un nouveau géocodage BAN :

- Bouton « Utiliser mon domicile » (trajet-form) : utilise désormais
  les coordonnées du Profil au lieu de forcer un re-géocodage.
- Actions « insérer avant/après » (trajet-list) : transmettent les
  coordonnées déjà connues du point de fin repris au formulaire
  d'insertion.

Le résumé mensuel (ResumeService) résout les coordonnées via le
Cache_Adresses pour chaque trajet (nouvelle méthode
ConstruireResumeMensuelAvecCoordonneesAsync) ; le résumé annuel reste
inchangé pour éviter des lookups de cache inutiles sur une vue qui
n'affiche pas le détail des trajets.

Met à jour design.md et design-requirements.md (Requirements 10 et 11)
pour refléter le nouveau contrat d'API.
This commit is contained in:
2026-10-13 20:52:04 +02:00
parent 7695deae10
commit d015ca1c98
13 changed files with 170 additions and 35 deletions
@@ -145,5 +145,6 @@ public class ProfilService : IProfilService
}
private static ProfilDto ToDto(Profil p) =>
new(p.Nom, p.Prenom, p.Matricule, p.AdresseDomicile, p.DomicileGeocode);
new(p.Nom, p.Prenom, p.Matricule, p.AdresseDomicile, p.DomicileGeocode,
p.DomicileLatitude, p.DomicileLongitude);
}
@@ -16,11 +16,16 @@ public class ResumeService : IResumeService
private readonly AppDbContext _context;
private readonly IPrecisionDistanceService _precisionDistanceService;
private readonly IAdresseCacheService _adresseCacheService;
public ResumeService(AppDbContext context, IPrecisionDistanceService precisionDistanceService)
public ResumeService(
AppDbContext context,
IPrecisionDistanceService precisionDistanceService,
IAdresseCacheService adresseCacheService)
{
_context = context;
_precisionDistanceService = precisionDistanceService;
_adresseCacheService = adresseCacheService;
}
/// <inheritdoc />
@@ -33,7 +38,10 @@ public class ResumeService : IResumeService
var precision = await _precisionDistanceService.ObtenirAsync(cancellationToken);
var trajets = await ChargerTrajetsDuMoisAsync(annee, mois, vehiculeId, cancellationToken);
return ConstruireResumeMensuel(annee, mois, trajets, precision);
// Coordonnées incluses : ce résumé alimente l'écran de consultation/saisie des Trajets,
// dont l'insertion (« Insérer avant/après ») réutilise les coordonnées déjà géocodées
// pour éviter un re-géocodage BAN.
return await ConstruireResumeMensuelAvecCoordonneesAsync(annee, mois, trajets, precision, cancellationToken);
}
/// <inheritdoc />
@@ -115,6 +123,33 @@ public class ResumeService : IResumeService
return new ResumeMensuelDto(annee, mois, trajetsDto, totalKm);
}
/// <summary>
/// Variante de <see cref="ConstruireResumeMensuel"/> enrichissant chaque <see cref="TrajetDto"/>
/// des coordonnées de ses adresses, résolues via le Cache_Adresses (même mécanisme que
/// <c>TrajetsController.GetJournee</c>). Réservée au résumé MENSUEL (écran de consultation/
/// saisie des Trajets, dont l'insertion réutilise ces coordonnées) : le résumé ANNUEL continue
/// d'utiliser <see cref="ConstruireResumeMensuel"/> sans lookup, les totaux annuels n'ayant pas
/// besoin du détail des coordonnées de chaque trajet (évite 2×N lookups cache inutiles).
/// </summary>
private async Task<ResumeMensuelDto> ConstruireResumeMensuelAvecCoordonneesAsync(
int annee, int mois, IReadOnlyList<Trajet> trajets, int precision, CancellationToken cancellationToken)
{
var distancesArrondies = trajets.Select(t => (t, distance: DistanceEffectiveArrondie(t, precision))).ToList();
var trajetsDto = new List<TrajetDto>(distancesArrondies.Count);
foreach (var (t, distance) in distancesArrondies)
{
var depart = await _adresseCacheService.ObtenirParAdresseAsync(t.AdresseDepart, cancellationToken);
var arrivee = await _adresseCacheService.ObtenirParAdresseAsync(t.AdresseArrivee, cancellationToken);
trajetsDto.Add(ToDto(t, distance, depart?.Latitude, depart?.Longitude, arrivee?.Latitude, arrivee?.Longitude));
}
var totalKm = distancesArrondies.Sum(x => x.distance ?? 0m);
return new ResumeMensuelDto(annee, mois, trajetsDto, totalKm);
}
/// <summary>
/// Calcule la Distance_Effective_Arrondie d'un trajet : arrondi (Precision_Distance) appliqué
/// à la distance SIMPLE brute AVANT doublement aller/retour (Exigence 4.1, 4.2, 4.3). null si la
@@ -149,7 +184,13 @@ public class ResumeService : IResumeService
}
}
private static TrajetDto ToDto(Trajet t, decimal? distanceEffectiveArrondie) =>
private static TrajetDto ToDto(
Trajet t,
decimal? distanceEffectiveArrondie,
double? departLatitude = null,
double? departLongitude = null,
double? arriveeLatitude = null,
double? arriveeLongitude = null) =>
new(
t.Id,
t.Date,
@@ -162,5 +203,9 @@ public class ResumeService : IResumeService
t.Position,
t.VehiculeId,
t.PuissanceFiscale,
t.AllerRetour);
t.AllerRetour,
departLatitude,
departLongitude,
arriveeLatitude,
arriveeLongitude);
}
@@ -98,7 +98,7 @@ public class TrajetService : ITrajetService
}
return new TrajetCreationResult(
Trajet: ToDto(trajet),
Trajet: ToDto(trajet, distanceResult),
Avertissement: distanceResult.Avertissement,
Message: distanceResult.Avertissement ? distanceResult.Message : null);
}
@@ -224,7 +224,7 @@ public class TrajetService : ITrajetService
}
return new TrajetUpdateResult(
Trajet: ToDto(trajet),
Trajet: ToDto(trajet, distanceResult),
Avertissement: avertissement,
Message: messageAvertissement);
}
@@ -438,7 +438,15 @@ public class TrajetService : ITrajetService
return parsed;
}
private static TrajetDto ToDto(Trajet t) =>
/// <summary>
/// Construit le <see cref="TrajetDto"/> de retour, enrichi des coordonnées déjà résolues
/// par <paramref name="distanceResult"/> (calcul de distance effectué dans le même appel),
/// afin que le frontend puisse les réutiliser sans re-géocoder (ex. insertion d'un trajet
/// reprenant cette adresse). <c>null</c> si aucun calcul n'a eu lieu dans cet appel (adresses
/// inchangées à la modification) : aucune régression, ce DTO n'étant pas la source utilisée
/// pour l'insertion (voir ResumeService, qui résout les coordonnées via le Cache_Adresses).
/// </summary>
private static TrajetDto ToDto(Trajet t, DistanceCalculationResult? distanceResult = null) =>
new(
t.Id,
t.Date,
@@ -450,5 +458,9 @@ public class TrajetService : ITrajetService
t.Position,
t.VehiculeId,
t.PuissanceFiscale,
t.AllerRetour);
t.AllerRetour,
distanceResult?.DepartLatitude,
distanceResult?.DepartLongitude,
distanceResult?.ArriveeLatitude,
distanceResult?.ArriveeLongitude);
}