Renommage : TrajetKmTracker -> SchotzIadKmTracker (backend, specs, frontend)
- Backend : projets, solution, namespaces et références TrajetKmTracker.Api/.Tests -> SchotzIadKmTracker.Api/.Tests (dossiers, .csproj, .sln, .http, namespaces C#). GUID UserSecretsId préservé. - Specs : dossier .kiro/specs/trajet-km-tracker -> schotziad-km-tracker et mise à jour des références internes (chemins, tags de propriétés). - Frontend : projet Angular trajet-km-tracker -> schotziad-km-tracker (package.json, angular.json, chemin dist, Dockerfile), titre applicatif 'SchotzIAD KM Tracker'. - Docker et scripts (backend/Dockerfile, frontend/Dockerfile, run-dev.sh) mis à jour. - Suppression du fichier résiduel package.json.bak. Backend (solution) et frontend (build production) compilent sans erreur.
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
using System.Globalization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SchotzIadKmTracker.Api.Models.DTOs;
|
||||
using SchotzIadKmTracker.Api.Repositories;
|
||||
using SchotzIadKmTracker.Api.Services;
|
||||
using SchotzIadKmTracker.Api.Services.Exceptions;
|
||||
|
||||
namespace SchotzIadKmTracker.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Endpoints CRUD des Trajets, réordonnancement manuel et récupération des trajets d'une
|
||||
/// journée enrichis des coordonnées et géométries issues du Cache_Adresses (Exigences 1.3,
|
||||
/// 5.5, 5.6, 8.7, 9.3).
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/trajets")]
|
||||
public class TrajetsController : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// En-tête de réponse portant l'avertissement lorsqu'un Trajet a été enregistré sans
|
||||
/// distance calculable (Exigence 2.4). Le corps reste le <see cref="TrajetDto"/> attendu.
|
||||
/// </summary>
|
||||
private const string WarningHeader = "X-Trajet-Warning";
|
||||
|
||||
private readonly ITrajetService _trajetService;
|
||||
private readonly ITrajetRepository _trajetRepository;
|
||||
private readonly IAdresseCacheService _adresseCacheService;
|
||||
|
||||
public TrajetsController(
|
||||
ITrajetService trajetService,
|
||||
ITrajetRepository trajetRepository,
|
||||
IAdresseCacheService adresseCacheService)
|
||||
{
|
||||
_trajetService = trajetService;
|
||||
_trajetRepository = trajetRepository;
|
||||
_adresseCacheService = adresseCacheService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crée un Trajet (Exigence 1.3). Retourne 201 + <see cref="TrajetDto"/>. Si la distance n'a
|
||||
/// pas pu être calculée faute de coordonnées, la réponse reste 201 et un avertissement est
|
||||
/// ajouté dans l'en-tête <c>X-Trajet-Warning</c> (Exigence 2.4).
|
||||
/// </summary>
|
||||
[HttpPost]
|
||||
[ProducesResponseType(typeof(TrajetDto), StatusCodes.Status201Created)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<TrajetDto>> Create(
|
||||
[FromBody] CreateTrajetDto dto,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var resultat = await _trajetService.CreateAsync(dto, cancellationToken);
|
||||
|
||||
if (resultat.Avertissement && !string.IsNullOrWhiteSpace(resultat.Message))
|
||||
{
|
||||
Response.Headers[WarningHeader] = resultat.Message;
|
||||
}
|
||||
|
||||
return CreatedAtAction(
|
||||
nameof(GetById),
|
||||
new { id = resultat.Trajet.Id },
|
||||
resultat.Trajet);
|
||||
}
|
||||
catch (VehiculeNotFoundException ex)
|
||||
{
|
||||
// Aucun véhicule applicable à la date et aucun VehiculeId fourni.
|
||||
return Problem(detail: ex.Message, statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalcule la distance de TOUS les trajets via OpenRouteService (BAN + ORS), indépendamment
|
||||
/// d'un changement d'adresse. Sert à corriger des distances calculées en mode dégradé (ex. clé
|
||||
/// ORS invalide → distances à vol d'oiseau) une fois ORS de nouveau opérationnel.
|
||||
/// Retourne 200 + le nombre de trajets recalculés.
|
||||
/// </summary>
|
||||
[HttpPost("recalculer-distances")]
|
||||
[ProducesResponseType(typeof(RecalculDistancesResultDto), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<RecalculDistancesResultDto>> RecalculerDistances(
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var nombre = await _trajetService.RecalculerToutesLesDistancesAsync(cancellationToken);
|
||||
return Ok(new RecalculDistancesResultDto(nombre));
|
||||
}
|
||||
|
||||
/// <summary>Récupère un Trajet par identifiant. 404 s'il est introuvable.</summary>
|
||||
[HttpGet("{id:int}")]
|
||||
[ProducesResponseType(typeof(TrajetDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<ActionResult<TrajetDto>> GetById(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
var trajet = await _trajetRepository.GetByIdAsync(id, cancellationToken);
|
||||
if (trajet is null)
|
||||
{
|
||||
return Problem(
|
||||
detail: $"Le trajet {id} n'existe pas.",
|
||||
statusCode: StatusCodes.Status404NotFound);
|
||||
}
|
||||
|
||||
return Ok(new TrajetDto(
|
||||
trajet.Id,
|
||||
trajet.Date,
|
||||
trajet.AdresseDepart,
|
||||
trajet.AdresseArrivee,
|
||||
trajet.Distance,
|
||||
trajet.Commentaire,
|
||||
trajet.Position,
|
||||
trajet.VehiculeId,
|
||||
trajet.PuissanceFiscale));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifie un Trajet (Exigences 5.5, 5.6). Retourne 200 + <see cref="TrajetDto"/>. Un
|
||||
/// avertissement de distance non calculable est porté par l'en-tête <c>X-Trajet-Warning</c>.
|
||||
/// 404 si le Trajet est introuvable ; 503 si le recalcul de distance échoue (Exigence 5.8).
|
||||
/// </summary>
|
||||
[HttpPut("{id:int}")]
|
||||
[ProducesResponseType(typeof(TrajetDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
[ProducesResponseType(StatusCodes.Status503ServiceUnavailable)]
|
||||
public async Task<ActionResult<TrajetDto>> Update(
|
||||
int id,
|
||||
[FromBody] UpdateTrajetDto dto,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var resultat = await _trajetService.UpdateAsync(id, dto, cancellationToken);
|
||||
|
||||
if (resultat.Avertissement && !string.IsNullOrWhiteSpace(resultat.Message))
|
||||
{
|
||||
Response.Headers[WarningHeader] = resultat.Message;
|
||||
}
|
||||
|
||||
return Ok(resultat.Trajet);
|
||||
}
|
||||
catch (TrajetNotFoundException ex)
|
||||
{
|
||||
return Problem(detail: ex.Message, statusCode: StatusCodes.Status404NotFound);
|
||||
}
|
||||
catch (VehiculeNotFoundException ex)
|
||||
{
|
||||
return Problem(detail: ex.Message, statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
catch (DistanceRecalculException ex)
|
||||
{
|
||||
// Échec du recalcul sur des coordonnées connues : modification annulée (rollback),
|
||||
// données existantes préservées (Exigence 5.8). Mappé sur 503 conformément à la table
|
||||
// d'erreurs de la conception (DistanceCalculationException → 503).
|
||||
return Problem(detail: ex.Message, statusCode: StatusCodes.Status503ServiceUnavailable);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Supprime un Trajet (Exigence 5.5). 204 si succès, 404 s'il est introuvable (5.6).</summary>
|
||||
[HttpDelete("{id:int}")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> Delete(int id, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _trajetService.DeleteAsync(id, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
catch (TrajetNotFoundException ex)
|
||||
{
|
||||
return Problem(detail: ex.Message, statusCode: StatusCodes.Status404NotFound);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Réordonne manuellement les trajets d'une journée : met à jour uniquement leurs positions
|
||||
/// sans altérer les autres champs (Exigence 11.9). 204 en cas de succès.
|
||||
/// </summary>
|
||||
[HttpPatch("reordonner")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<IActionResult> Reordonner(
|
||||
[FromBody] ReordonnerTrajetsDto dto,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await _trajetService.ReordonnerAsync(dto, cancellationToken);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retourne les trajets d'une journée enrichis des coordonnées et géométries connues via le
|
||||
/// Cache_Adresses (Exigences 8.7, 9.3). Le paramètre <paramref name="date"/> est attendu au
|
||||
/// format ISO <c>yyyy-MM-dd</c> pour la sûreté du routage. Les coordonnées/géométries absentes
|
||||
/// du cache restent à <c>null</c>.
|
||||
/// </summary>
|
||||
[HttpGet("journee/{date}")]
|
||||
[ProducesResponseType(typeof(IReadOnlyList<TrajetCarteDto>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
public async Task<ActionResult<IReadOnlyList<TrajetCarteDto>>> GetJournee(
|
||||
string date,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!DateOnly.TryParseExact(date, "yyyy-MM-dd", CultureInfo.InvariantCulture,
|
||||
DateTimeStyles.None, out var jour))
|
||||
{
|
||||
return Problem(
|
||||
detail: $"La date '{date}' est invalide. Format attendu : yyyy-MM-dd.",
|
||||
statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
|
||||
var trajets = await _trajetRepository.GetByDateAsync(jour, cancellationToken);
|
||||
|
||||
var resultats = new List<TrajetCarteDto>(trajets.Count);
|
||||
foreach (var trajet in trajets)
|
||||
{
|
||||
var depart = await _adresseCacheService.ObtenirParAdresseAsync(
|
||||
trajet.AdresseDepart, cancellationToken);
|
||||
var arrivee = await _adresseCacheService.ObtenirParAdresseAsync(
|
||||
trajet.AdresseArrivee, cancellationToken);
|
||||
|
||||
// Géométrie de l'itinéraire : portée par le Trajet lui-même (paire départ→arrivée),
|
||||
// seule source correcte. Fallback sur le cache d'adresses pour d'éventuels trajets
|
||||
// hérités dont la géométrie n'a pas encore été recalculée (rétrocompatibilité).
|
||||
var geometrie = trajet.GeometrieJson ?? depart?.GeometrieJson ?? arrivee?.GeometrieJson;
|
||||
|
||||
resultats.Add(new TrajetCarteDto(
|
||||
trajet.Id,
|
||||
trajet.Date,
|
||||
trajet.AdresseDepart,
|
||||
trajet.AdresseArrivee,
|
||||
trajet.Distance ?? 0m,
|
||||
depart?.Latitude,
|
||||
depart?.Longitude,
|
||||
arrivee?.Latitude,
|
||||
arrivee?.Longitude,
|
||||
geometrie));
|
||||
}
|
||||
|
||||
return Ok(resultats);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user