using Microsoft.EntityFrameworkCore; using SchotzIadKmTracker.Api.Data; using SchotzIadKmTracker.Api.Models.DTOs; namespace SchotzIadKmTracker.Api.Services; /// public class TotauxService : ITotauxService { private readonly AppDbContext _context; private readonly IVehiculeService _vehiculeService; private readonly IPrecisionDistanceService _precisionDistanceService; public TotauxService( AppDbContext context, IVehiculeService vehiculeService, IPrecisionDistanceService precisionDistanceService) { _context = context; _vehiculeService = vehiculeService; _precisionDistanceService = precisionDistanceService; } /// public async Task GetTotauxAsync( DateOnly date, int? vehiculeId = null, CancellationToken cancellationToken = default) { // Véhicule cible : celui fourni, sinon le Véhicule_Courant (Exigence 12.19). var vehiculeCible = vehiculeId ?? (await _vehiculeService.GetVehiculeCourantAsync(cancellationToken)).Id; // Précision courante (0/1/2 décimales) : lue une fois par appel, stable pour tous les // totaux calculés ci-dessous (Exigences 4.1, 4.2, 4.3). var precision = await _precisionDistanceService.ObtenirAsync(cancellationToken); // Trajets du véhicule pour l'année sélectionnée (Property 21 : filtrage par véhicule). // Distance nullable → 0 lors des sommes (traitée côté client après projection). var trajetsAnnee = await _context.Trajets .AsNoTracking() .Where(t => t.VehiculeId == vehiculeCible && t.Date.Year == date.Year) // Projection : on ramène la distance simple et l'indicateur aller/retour, puis on // calcule la distance EFFECTIVE (doublée si A/R) côté client lors des sommes. .Select(t => new { t.Date, t.Distance, t.AllerRetour }) .ToListAsync(cancellationToken); // Distance comptée (effective) : distance simple arrondie à la précision courante, // PUIS doublée si aller/retour (Exigence 4.2 — arrondi avant doublement), null → 0. decimal Effective(decimal? distance, bool allerRetour) => _precisionDistanceService.ArrondirDistance(distance.GetValueOrDefault(), precision) * (allerRetour ? 2m : 1m); // TotalJour : somme des distances effectives de la journée sélectionnée (Exigence 12.7). var totalJour = trajetsAnnee .Where(t => t.Date == date) .Sum(t => Effective(t.Distance, t.AllerRetour)); // TotalMois : somme des distances effectives du mois de la date sélectionnée (Exigence 12.7). var totalMois = trajetsAnnee .Where(t => t.Date.Month == date.Month) .Sum(t => Effective(t.Distance, t.AllerRetour)); // Total_Annuel_Partiel : cumul de l'année jusqu'au mois sélectionné INCLUS, // exclusion des mois strictement postérieurs (Exigence 12.8, Property 17). var totalAnnuelPartiel = trajetsAnnee .Where(t => t.Date.Month <= date.Month) .Sum(t => Effective(t.Distance, t.AllerRetour)); // Les distances sommées sont déjà arrondies à la précision courante (via // ArrondirDistance) : aucun second arrondi forcé à 2 décimales n'est appliqué ici // (Exigences 5.1, 5.4). return new TotauxDto( Date: date, VehiculeId: vehiculeCible, TotalJour: totalJour, TotalMois: totalMois, TotalAnnuelPartiel: totalAnnuelPartiel); } /// public async Task> GetAnneesDisponiblesAsync(CancellationToken cancellationToken = default) { // Années comportant au moins un Trajet (Exigence 12.15). var annees = await _context.Trajets .AsNoTracking() .Select(t => t.Date.Year) .Distinct() .ToListAsync(cancellationToken); // Ajout de l'année courante si absente. var anneeCourante = DateOnly.FromDateTime(DateTime.Today).Year; if (!annees.Contains(anneeCourante)) { annees.Add(anneeCourante); } annees.Sort(); return annees; } /// public async Task> GetMoisDisponiblesAsync(int annee, CancellationToken cancellationToken = default) { // Mois de l'année comportant au moins un Trajet (Exigence 12.16). var mois = await _context.Trajets .AsNoTracking() .Where(t => t.Date.Year == annee) .Select(t => t.Date.Month) .Distinct() .ToListAsync(cancellationToken); mois.Sort(); return mois; } /// public async Task> GetJoursDisponiblesAsync( int annee, int mois, CancellationToken cancellationToken = default) { // Jours du mois comportant au moins un Trajet (Exigence 12.16). var jours = await _context.Trajets .AsNoTracking() .Where(t => t.Date.Year == annee && t.Date.Month == mois) .Select(t => t.Date.Day) .Distinct() .ToListAsync(cancellationToken); jours.Sort(); return jours; } }