using System.Globalization;
using Microsoft.EntityFrameworkCore;
using TrajetKmTracker.Api.Data;
using TrajetKmTracker.Api.Models.DTOs;
using TrajetKmTracker.Api.Models.Entities;
using TrajetKmTracker.Api.Repositories;
using TrajetKmTracker.Api.Services.Exceptions;
namespace TrajetKmTracker.Api.Services;
///
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;
}
///
public async Task> GetHistoriqueAsync(CancellationToken cancellationToken = default)
{
var vehicules = await _repository.GetHistoriqueAsync(cancellationToken);
return vehicules.Select(ToDto).ToList();
}
///
public async Task 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);
}
///
public async Task GetVehiculeADateAsync(DateOnly date, CancellationToken cancellationToken = default)
{
var vehicule = await _repository.GetVehiculeCourantAsync(date, cancellationToken);
return vehicule is null ? null : ToDto(vehicule);
}
///
public async Task 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);
}
///
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);
}