SchotzIAD KM Tracker : suivi des trajets kilométriques (Angular + .NET 8)
Application complète de saisie et de suivi des trajets kilométriques avec calcul de distance routière (OpenRouteService), barème kilométrique, résumés et régularisation annuelle. Backend (ASP.NET Core .NET 8, EF Core, SQLite) : - Trajets (CRUD, réordonnancement, insertion), calcul de distance ORS avec fallback Haversine, géométrie d'itinéraire stockée par trajet, recalcul en masse. - Profil, véhicules (avec dénomination), carnet d'adresses nommées, cache d'adresses. - Barème kilométrique : tranches multiples par puissance fiscale, borne max exclusive avec 'illimité' (KmMax nullable), coefficient à 3 décimales, recopie du dernier barème connu. - Résumés mensuel/annuel, montants réglés, régularisation, export PDF. Frontend (Angular 22, zoneless + signals) : - Consultation par jour, navigation temporelle synchronisée, carte Leaflet, formulaire de trajet (adresses nommées, domicile, autocomplétion BAN). - Détection de changement zoneless (indépendante de zone.js). Hébergement : Docker Compose (nginx + backend), scripts run-dev.sh / run-docker.sh.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TrajetKmTracker.Api.Data;
|
||||
using TrajetKmTracker.Api.Models.Entities;
|
||||
|
||||
namespace TrajetKmTracker.Api.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Implémentation EF Core de <see cref="IAdresseCacheRepository"/> au-dessus d'<see cref="AppDbContext"/>.
|
||||
/// </summary>
|
||||
public class AdresseCacheRepository : IAdresseCacheRepository
|
||||
{
|
||||
private readonly AppDbContext _db;
|
||||
|
||||
public AdresseCacheRepository(AppDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<AdresseCache>> RechercherAsync(
|
||||
string fragmentNormalise,
|
||||
int limite,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// La colonne Adresse stocke déjà la forme normalisée : la comparaison se fait donc
|
||||
// directement sur celle-ci. EF.Functions.Like assure une comparaison indépendante
|
||||
// de la casse côté SQLite.
|
||||
var motif = $"%{fragmentNormalise}%";
|
||||
|
||||
return await _db.AdresseCache
|
||||
.Where(a => EF.Functions.Like(a.Adresse, motif))
|
||||
.OrderByDescending(a => a.DerniereUtilisation)
|
||||
.Take(limite)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<AdresseCache?> ObtenirParAdresseNormaliseeAsync(
|
||||
string adresseNormalisee,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _db.AdresseCache
|
||||
.FirstOrDefaultAsync(a => a.Adresse == adresseNormalisee, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task AjouterAsync(AdresseCache entree, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _db.AdresseCache.AddAsync(entree, cancellationToken);
|
||||
}
|
||||
|
||||
public Task<int> CompterAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _db.AdresseCache.CountAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyList<AdresseCache>> ObtenirPlusAnciennesAsync(
|
||||
int nombre,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (nombre <= 0)
|
||||
{
|
||||
return Array.Empty<AdresseCache>();
|
||||
}
|
||||
|
||||
return await _db.AdresseCache
|
||||
.OrderBy(a => a.DerniereUtilisation)
|
||||
.Take(nombre)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public void Supprimer(IEnumerable<AdresseCache> entrees)
|
||||
{
|
||||
_db.AdresseCache.RemoveRange(entrees);
|
||||
}
|
||||
|
||||
public Task<int> SauvegarderAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _db.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using TrajetKmTracker.Api.Models.Entities;
|
||||
|
||||
namespace TrajetKmTracker.Api.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Accès aux données du Cache_Adresses (table <c>AdresseCache</c>).
|
||||
/// Toutes les correspondances d'adresse s'effectuent sur le texte normalisé
|
||||
/// (voir <see cref="AdresseCacheService"/>).
|
||||
/// </summary>
|
||||
public interface IAdresseCacheRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Retourne les entrées dont l'adresse normalisée contient le fragment normalisé fourni,
|
||||
/// triées de la plus récemment utilisée à la plus ancienne, limitées à <paramref name="limite"/>.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<AdresseCache>> RechercherAsync(
|
||||
string fragmentNormalise,
|
||||
int limite,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Retourne l'entrée correspondant exactement à l'adresse normalisée, ou <c>null</c>.</summary>
|
||||
Task<AdresseCache?> ObtenirParAdresseNormaliseeAsync(
|
||||
string adresseNormalisee,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Ajoute une nouvelle entrée au cache.</summary>
|
||||
Task AjouterAsync(AdresseCache entree, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Compte le nombre total d'entrées du cache.</summary>
|
||||
Task<int> CompterAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retourne les entrées les plus anciennes (par <c>DerniereUtilisation</c> croissante),
|
||||
/// limitées à <paramref name="nombre"/>. Utilisé pour l'éviction LRU.
|
||||
/// </summary>
|
||||
Task<IReadOnlyList<AdresseCache>> ObtenirPlusAnciennesAsync(
|
||||
int nombre,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Supprime les entrées fournies du cache.</summary>
|
||||
void Supprimer(IEnumerable<AdresseCache> entrees);
|
||||
|
||||
/// <summary>Persiste les modifications en attente.</summary>
|
||||
Task<int> SauvegarderAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using TrajetKmTracker.Api.Models.Entities;
|
||||
|
||||
namespace TrajetKmTracker.Api.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Accès aux données des trajets et gestion des positions au sein d'une journée.
|
||||
/// <para>
|
||||
/// La <see cref="Trajet.Position"/> est une valeur décimale (REAL) permettant l'insertion
|
||||
/// sans réordonnancement systématique : ajout en fin de journée → <c>max(Position) + 1.0</c>,
|
||||
/// insertion entre deux positions P₁ et P₂ → <c>(P₁ + P₂) / 2</c>. Lorsque l'écart devient
|
||||
/// trop faible (< <see cref="SeuilRenumerotation"/>), la journée est renumérotée par pas de
|
||||
/// 1.0 en transaction en préservant l'ordre relatif (Property 15, Exigences 11.7/11.8).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Ce repository fournit uniquement les primitives d'accès et de calcul de position ;
|
||||
/// la logique métier (validation, calcul de distance, mise en cache) relève de TrajetService.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface ITrajetRepository
|
||||
{
|
||||
/// <summary>
|
||||
/// Seuil de précision en deçà duquel une renumérotation de la journée est déclenchée.
|
||||
/// Conforme à la conception : écart < 0.001.
|
||||
/// </summary>
|
||||
const decimal SeuilRenumerotation = 0.001m;
|
||||
|
||||
/// <summary>Retourne le trajet par identifiant, ou <c>null</c> s'il n'existe pas.</summary>
|
||||
Task<Trajet?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Retourne les trajets d'une journée triés par <see cref="Trajet.Position"/> croissante.</summary>
|
||||
Task<List<Trajet>> GetByDateAsync(DateOnly date, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Résout le véhicule applicable à la date du trajet et copie sa <c>PuissanceFiscale</c>
|
||||
/// sur le trajet (dénormalisation, Exigence 10.14). Ne modifie ni <c>VehiculeId</c> ni
|
||||
/// <c>PuissanceFiscale</c> si aucun véhicule n'est applicable à cette date.
|
||||
/// Coordonné avec <see cref="Services.IVehiculeService.GetVehiculeADateAsync"/>.
|
||||
/// </summary>
|
||||
Task AppliquerVehiculeADateAsync(Trajet trajet, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Calcule la position d'un trajet ajouté en fin de journée : <c>max(Position) + 1.0</c>
|
||||
/// pour la date, ou <c>1.0</c> si la journée est vide.
|
||||
/// </summary>
|
||||
Task<decimal> CalculerPositionFinDeJourneeAsync(DateOnly date, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Calcule la position d'un trajet inséré entre les positions <paramref name="positionPrecedente"/>
|
||||
/// (P₁) et <paramref name="positionSuivante"/> (P₂) : <c>(P₁ + P₂) / 2</c>.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">Si P₁ n'est pas strictement inférieure à P₂.</exception>
|
||||
decimal CalculerPositionEntre(decimal positionPrecedente, decimal positionSuivante);
|
||||
|
||||
/// <summary>
|
||||
/// Indique si l'écart entre deux positions adjacentes est devenu trop faible
|
||||
/// (< <see cref="SeuilRenumerotation"/>) pour insérer une nouvelle valeur intermédiaire.
|
||||
/// </summary>
|
||||
bool NecessiteRenumerotation(decimal positionPrecedente, decimal positionSuivante);
|
||||
|
||||
/// <summary>Ajoute un trajet (sans persister). L'appelant gère la transaction et l'appel à <see cref="SauvegarderAsync"/>.</summary>
|
||||
Task AddAsync(Trajet trajet, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Marque un trajet existant comme modifié (sans persister).</summary>
|
||||
void Update(Trajet trajet);
|
||||
|
||||
/// <summary>Supprime un trajet (sans persister).</summary>
|
||||
void Delete(Trajet trajet);
|
||||
|
||||
/// <summary>Persiste les changements en attente.</summary>
|
||||
Task<int> SauvegarderAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Applique un réordonnancement manuel : met à jour uniquement la <see cref="Trajet.Position"/>
|
||||
/// des trajets fournis (par identifiant) sans altérer leurs autres champs, en transaction.
|
||||
/// Les identifiants inconnus sont ignorés.
|
||||
/// </summary>
|
||||
Task ReordonnerAsync(
|
||||
IReadOnlyList<(int Id, decimal Position)> positions,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Renumérote les trajets d'une journée par pas de 1.0 (1.0, 2.0, 3.0, …) en préservant
|
||||
/// l'ordre relatif courant (tri par Position puis Id), en transaction. Retourne la liste
|
||||
/// renumérotée triée par Position croissante (Property 15).
|
||||
/// </summary>
|
||||
Task<List<Trajet>> RenumeroterJourneeAsync(DateOnly date, CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using TrajetKmTracker.Api.Models.Entities;
|
||||
|
||||
namespace TrajetKmTracker.Api.Repositories;
|
||||
|
||||
/// <summary>
|
||||
/// Accès aux données des véhicules et aux trajets impactés par une réaffectation.
|
||||
/// L'historique des véhicules est conservé (aucune suppression) — Exigence 10.12 / Property 22.
|
||||
/// </summary>
|
||||
public interface IVehiculeRepository
|
||||
{
|
||||
/// <summary>Retourne l'historique complet des véhicules, trié par Date_Effet décroissante puis Id décroissant.</summary>
|
||||
Task<List<Vehicule>> GetHistoriqueAsync(CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Retourne le véhicule par identifiant, ou <c>null</c> s'il n'existe pas.</summary>
|
||||
Task<Vehicule?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Résout le véhicule courant à une date donnée : Date_Effet la plus récente parmi
|
||||
/// celles <= <paramref name="date"/> (ORDER BY DateEffet DESC, Id DESC LIMIT 1).
|
||||
/// Retourne <c>null</c> si aucun véhicule n'a de Date_Effet <= <paramref name="date"/>.
|
||||
/// </summary>
|
||||
Task<Vehicule?> GetVehiculeCourantAsync(DateOnly date, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>Ajoute un nouveau véhicule (sans persister). L'appelant gère la transaction.</summary>
|
||||
Task AddAsync(Vehicule vehicule, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Retourne les trajets dont la Date est >= <paramref name="dateEffet"/> et dont la
|
||||
/// PuissanceFiscale diffère de <paramref name="puissanceFiscale"/> (candidats à la réaffectation).
|
||||
/// </summary>
|
||||
Task<List<Trajet>> GetTrajetsAReaffecterAsync(
|
||||
DateOnly dateEffet,
|
||||
int puissanceFiscale,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TrajetKmTracker.Api.Data;
|
||||
using TrajetKmTracker.Api.Models.Entities;
|
||||
using TrajetKmTracker.Api.Services;
|
||||
|
||||
namespace TrajetKmTracker.Api.Repositories;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class TrajetRepository : ITrajetRepository
|
||||
{
|
||||
private readonly AppDbContext _context;
|
||||
private readonly IVehiculeService _vehiculeService;
|
||||
|
||||
public TrajetRepository(AppDbContext context, IVehiculeService vehiculeService)
|
||||
{
|
||||
_context = context;
|
||||
_vehiculeService = vehiculeService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Trajet?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Trajets
|
||||
.FirstOrDefaultAsync(t => t.Id == id, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<Trajet>> GetByDateAsync(DateOnly date, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// SQLite ne sait pas trier (ORDER BY) sur une colonne decimal : on ramène les
|
||||
// trajets du jour puis on trie côté client (volume par journée très réduit).
|
||||
var trajets = await _context.Trajets
|
||||
.Where(t => t.Date == date)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return trajets
|
||||
.OrderBy(t => t.Position)
|
||||
.ThenBy(t => t.Id)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task AppliquerVehiculeADateAsync(Trajet trajet, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Résolution du véhicule applicable à la date du trajet (Date_Effet la plus récente <= Date).
|
||||
var vehicule = await _vehiculeService.GetVehiculeADateAsync(trajet.Date, cancellationToken);
|
||||
if (vehicule is null)
|
||||
{
|
||||
// Aucun véhicule applicable à cette date : on laisse la logique métier (TrajetService) décider.
|
||||
return;
|
||||
}
|
||||
|
||||
// Dénormalisation : copie de la puissance fiscale du véhicule au moment de l'association (Exigence 10.14).
|
||||
trajet.VehiculeId = vehicule.Id;
|
||||
trajet.PuissanceFiscale = vehicule.PuissanceFiscale;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<decimal> CalculerPositionFinDeJourneeAsync(DateOnly date, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// SQLite ne sait pas appliquer MAX sur une colonne decimal : on ramène les positions
|
||||
// du jour et on calcule le maximum côté client (volume par journée très réduit).
|
||||
var positions = await _context.Trajets
|
||||
.Where(t => t.Date == date)
|
||||
.Select(t => t.Position)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (positions.Count == 0)
|
||||
{
|
||||
return 1.0m;
|
||||
}
|
||||
|
||||
return positions.Max() + 1.0m;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public decimal CalculerPositionEntre(decimal positionPrecedente, decimal positionSuivante)
|
||||
{
|
||||
if (positionPrecedente >= positionSuivante)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"La position précédente ({positionPrecedente}) doit être strictement inférieure à la position suivante ({positionSuivante}).",
|
||||
nameof(positionPrecedente));
|
||||
}
|
||||
|
||||
return (positionPrecedente + positionSuivante) / 2.0m;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool NecessiteRenumerotation(decimal positionPrecedente, decimal positionSuivante)
|
||||
{
|
||||
return positionSuivante - positionPrecedente < ITrajetRepository.SeuilRenumerotation;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task AddAsync(Trajet trajet, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_context.Trajets.Add(trajet);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Update(Trajet trajet)
|
||||
{
|
||||
_context.Trajets.Update(trajet);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Delete(Trajet trajet)
|
||||
{
|
||||
_context.Trajets.Remove(trajet);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<int> SauvegarderAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return _context.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task ReordonnerAsync(
|
||||
IReadOnlyList<(int Id, decimal Position)> positions,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (positions.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await using var transaction = await _context.Database.BeginTransactionAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
var ids = positions.Select(p => p.Id).ToList();
|
||||
var trajets = await _context.Trajets
|
||||
.Where(t => ids.Contains(t.Id))
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var positionParId = positions.ToDictionary(p => p.Id, p => p.Position);
|
||||
|
||||
foreach (var trajet in trajets)
|
||||
{
|
||||
if (positionParId.TryGetValue(trajet.Id, out var nouvellePosition))
|
||||
{
|
||||
// Seule la Position est modifiée ; aucun autre champ n'est altéré (Exigence 11.9).
|
||||
trajet.Position = nouvellePosition;
|
||||
trajet.UpdatedAt = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
await transaction.CommitAsync(cancellationToken);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync(cancellationToken);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<Trajet>> RenumeroterJourneeAsync(DateOnly date, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using var transaction = await _context.Database.BeginTransactionAsync(cancellationToken);
|
||||
try
|
||||
{
|
||||
// Préservation de l'ordre relatif courant (Position croissante, Id en départage) — Property 15.
|
||||
var trajets = (await _context.Trajets
|
||||
.Where(t => t.Date == date)
|
||||
.ToListAsync(cancellationToken))
|
||||
.OrderBy(t => t.Position)
|
||||
.ThenBy(t => t.Id)
|
||||
.ToList();
|
||||
|
||||
decimal position = 1.0m;
|
||||
foreach (var trajet in trajets)
|
||||
{
|
||||
trajet.Position = position;
|
||||
trajet.UpdatedAt = DateTime.UtcNow;
|
||||
position += 1.0m;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync(cancellationToken);
|
||||
await transaction.CommitAsync(cancellationToken);
|
||||
|
||||
return trajets;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync(cancellationToken);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TrajetKmTracker.Api.Data;
|
||||
using TrajetKmTracker.Api.Models.Entities;
|
||||
|
||||
namespace TrajetKmTracker.Api.Repositories;
|
||||
|
||||
/// <inheritdoc />
|
||||
public class VehiculeRepository : IVehiculeRepository
|
||||
{
|
||||
private readonly AppDbContext _context;
|
||||
|
||||
public VehiculeRepository(AppDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<Vehicule>> GetHistoriqueAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Vehicules
|
||||
.OrderByDescending(v => v.DateEffet)
|
||||
.ThenByDescending(v => v.Id)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Vehicule?> GetByIdAsync(int id, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Vehicules
|
||||
.FirstOrDefaultAsync(v => v.Id == id, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Vehicule?> GetVehiculeCourantAsync(DateOnly date, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// Design SQL : SELECT * FROM Vehicules WHERE DateEffet <= date ORDER BY DateEffet DESC, Id DESC LIMIT 1
|
||||
return await _context.Vehicules
|
||||
.Where(v => v.DateEffet <= date)
|
||||
.OrderByDescending(v => v.DateEffet)
|
||||
.ThenByDescending(v => v.Id)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task AddAsync(Vehicule vehicule, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_context.Vehicules.Add(vehicule);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<Trajet>> GetTrajetsAReaffecterAsync(
|
||||
DateOnly dateEffet,
|
||||
int puissanceFiscale,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _context.Trajets
|
||||
.Where(t => t.Date >= dateEffet && t.PuissanceFiscale != puissanceFiscale)
|
||||
.ToListAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user