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:
2026-09-14 07:50:26 +02:00
parent e3f946f7d4
commit 0e6a97ca61
133 changed files with 462 additions and 503 deletions
@@ -0,0 +1,143 @@
using System.Globalization;
using Microsoft.EntityFrameworkCore;
using SchotzIadKmTracker.Api.Data;
using SchotzIadKmTracker.Api.Models.DTOs;
using SchotzIadKmTracker.Api.Models.Entities;
using SchotzIadKmTracker.Api.Repositories;
using SchotzIadKmTracker.Api.Services.Exceptions;
namespace SchotzIadKmTracker.Api.Services;
/// <inheritdoc />
public class VehiculeService : IVehiculeService
{
private const string DateFormat = "dd/MM/yyyy";
private readonly IVehiculeRepository _repository;
private readonly AppDbContext _context;
public VehiculeService(IVehiculeRepository repository, AppDbContext context)
{
_repository = repository;
_context = context;
}
/// <inheritdoc />
public async Task<IReadOnlyList<VehiculeDto>> GetHistoriqueAsync(CancellationToken cancellationToken = default)
{
var vehicules = await _repository.GetHistoriqueAsync(cancellationToken);
return vehicules.Select(ToDto).ToList();
}
/// <inheritdoc />
public async Task<VehiculeDto> GetVehiculeCourantAsync(CancellationToken cancellationToken = default)
{
var aujourdHui = DateOnly.FromDateTime(DateTime.Today);
var vehicule = await _repository.GetVehiculeCourantAsync(aujourdHui, cancellationToken)
?? throw new VehiculeNotFoundException(
"Aucun véhicule courant : aucun véhicule n'a de date d'effet inférieure ou égale à aujourd'hui.");
return ToDto(vehicule);
}
/// <inheritdoc />
public async Task<VehiculeDto?> GetVehiculeADateAsync(DateOnly date, CancellationToken cancellationToken = default)
{
var vehicule = await _repository.GetVehiculeCourantAsync(date, cancellationToken);
return vehicule is null ? null : ToDto(vehicule);
}
/// <inheritdoc />
public async Task<VehiculeCreeDto> CreateAsync(CreateVehiculeDto dto, CancellationToken cancellationToken = default)
{
var dateEffet = ParseDateEffet(dto.DateEffet);
// Rejet d'une date d'effet dans le futur (Exigence 10.11).
if (dateEffet > DateOnly.FromDateTime(DateTime.Today))
{
throw new DateEffetFutureException("La date d'effet ne peut pas être postérieure à aujourd'hui.");
}
var vehicule = new Vehicule
{
Denomination = dto.Denomination,
Plaque = dto.Plaque,
PuissanceFiscale = dto.PuissanceFiscale,
DateEffet = dateEffet,
};
// Conservation de l'historique : on ajoute sans supprimer/modifier les véhicules existants (Property 22).
await using var transaction = await _context.Database.BeginTransactionAsync(cancellationToken);
try
{
await _repository.AddAsync(vehicule, cancellationToken);
await _context.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
}
catch
{
await transaction.RollbackAsync(cancellationToken);
throw;
}
// Détection des trajets à réaffecter : date >= DateEffet et puissance fiscale différente.
var trajetsConcernes = await _repository.GetTrajetsAReaffecterAsync(
dateEffet, vehicule.PuissanceFiscale, cancellationToken);
return new VehiculeCreeDto(
ToDto(vehicule),
ReaffectationProposee: trajetsConcernes.Count > 0,
NombreTrajetsConcernes: trajetsConcernes.Count);
}
/// <inheritdoc />
public async Task ReaffecterAsync(ReaffectationDto dto, CancellationToken cancellationToken = default)
{
var vehicule = await _repository.GetByIdAsync(dto.VehiculeId, cancellationToken)
?? throw new VehiculeNotFoundException($"Le véhicule d'identifiant {dto.VehiculeId} est introuvable.");
// Réaffectation refusée : aucun changement (Exigence 10.17).
if (!dto.Accepter)
{
return;
}
// Réaffectation acceptée : mise à jour bornée à la date d'effet (Exigence 10.16 / Property 20).
var trajets = await _repository.GetTrajetsAReaffecterAsync(
vehicule.DateEffet, vehicule.PuissanceFiscale, cancellationToken);
if (trajets.Count == 0)
{
return;
}
await using var transaction = await _context.Database.BeginTransactionAsync(cancellationToken);
try
{
var maintenant = DateTime.UtcNow;
foreach (var trajet in trajets)
{
trajet.VehiculeId = vehicule.Id;
trajet.PuissanceFiscale = vehicule.PuissanceFiscale;
trajet.UpdatedAt = maintenant;
}
await _context.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
}
catch
{
await transaction.RollbackAsync(cancellationToken);
throw;
}
}
private static DateOnly ParseDateEffet(string value)
{
// La validation FluentValidation garantit normalement le format ; on reste défensif ici.
return DateOnly.ParseExact(value, DateFormat, CultureInfo.InvariantCulture);
}
private static VehiculeDto ToDto(Vehicule v) =>
new(v.Id, v.Denomination, v.Plaque, v.PuissanceFiscale, v.DateEffet);
}