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; /// 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); }