- 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.
144 lines
5.2 KiB
C#
144 lines
5.2 KiB
C#
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);
|
|
}
|