Files
SchotzIAD/backend/SchotzIadKmTracker.Api/Services/TotauxService.cs
T
MadJax 09c24d5f59 UX consultation, trajets aller/retour et rafraîchissement des totaux
- Totaux : rafraîchissement immédiat du panneau (jour/mois/année + montant
  prévisionnel) après création/modification/suppression/réordonnancement d'un
  trajet, sans changer de jour (ViewChild + méthode publique rafraichir()).
- Trajets Aller/Retour : case à cocher au formulaire ; la distance comptée est
  doublée (distance simple stockée + booléen AllerRetour, distance effective
  utilisée dans tous les agrégats : totaux, résumés, barème, régularisation, PDF).
  Migration TrajetAllerRetour. Badge « A/R » dans une colonne dédiée en début de
  ligne pour garder date/adresses alignées.
- Mise en page de la page Trajets : vue à hauteur fixe où seule la liste défile ;
  colonne gauche figée (sélecteur de date + navigation, totaux, carte) et bande
  fixe (véhicule + bouton d'ajout) au-dessus de la liste. Layout applicatif global
  en pleine hauteur (app.component).
- run-dev.sh : chargement de la clé JWT depuis .env (ou secret de dev aléatoire) et
  Auth__FrontendBaseUrl sur le frontend dev ; rappel du lien de reset journalisé.

Build backend (solution) et frontend (production) OK.
2026-09-15 12:33:10 +02:00

117 lines
4.6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Microsoft.EntityFrameworkCore;
using SchotzIadKmTracker.Api.Data;
using SchotzIadKmTracker.Api.Models.DTOs;
namespace SchotzIadKmTracker.Api.Services;
/// <inheritdoc />
public class TotauxService : ITotauxService
{
private readonly AppDbContext _context;
private readonly IVehiculeService _vehiculeService;
public TotauxService(AppDbContext context, IVehiculeService vehiculeService)
{
_context = context;
_vehiculeService = vehiculeService;
}
/// <inheritdoc />
public async Task<TotauxDto> 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;
// 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 × 2 si aller/retour, null → 0.
static decimal Effective(decimal? distance, bool allerRetour) =>
distance.GetValueOrDefault() * (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));
return new TotauxDto(
Date: date,
VehiculeId: vehiculeCible,
TotalJour: Math.Round(totalJour, 2, MidpointRounding.AwayFromZero),
TotalMois: Math.Round(totalMois, 2, MidpointRounding.AwayFromZero),
TotalAnnuelPartiel: Math.Round(totalAnnuelPartiel, 2, MidpointRounding.AwayFromZero));
}
/// <inheritdoc />
public async Task<IReadOnlyList<int>> 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;
}
/// <inheritdoc />
public async Task<IReadOnlyList<int>> 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;
}
/// <inheritdoc />
public async Task<IReadOnlyList<int>> 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;
}
}