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.
115 lines
4.6 KiB
C#
115 lines
4.6 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using TrajetKmTracker.Api.Models.Entities;
|
|
|
|
namespace TrajetKmTracker.Api.Data;
|
|
|
|
/// <summary>
|
|
/// Contexte EF Core de l'application. Déclare les <see cref="DbSet{TEntity}"/>, les contraintes CHECK,
|
|
/// les index et les index uniques conformément à la section « Schéma SQLite » de la conception.
|
|
/// </summary>
|
|
public class AppDbContext : DbContext
|
|
{
|
|
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
|
|
{
|
|
}
|
|
|
|
public DbSet<Trajet> Trajets => Set<Trajet>();
|
|
public DbSet<Vehicule> Vehicules => Set<Vehicule>();
|
|
public DbSet<Profil> Profil => Set<Profil>();
|
|
public DbSet<AdresseCache> AdresseCache => Set<AdresseCache>();
|
|
public DbSet<AdresseNommee> AdressesNommees => Set<AdresseNommee>();
|
|
public DbSet<TrancheBareme> TranchesBareme => Set<TrancheBareme>();
|
|
public DbSet<MontantRegle> MontantsRegles => Set<MontantRegle>();
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
base.OnModelCreating(modelBuilder);
|
|
|
|
// --- Trajets ---
|
|
modelBuilder.Entity<Trajet>(entity =>
|
|
{
|
|
entity.ToTable("Trajets", t =>
|
|
{
|
|
// Distance nulle autorisée (Exigence 2.4 : distance non calculable) ; sinon strictement positive.
|
|
t.HasCheckConstraint("CK_Trajets_Distance", "\"Distance\" IS NULL OR \"Distance\" > 0");
|
|
t.HasCheckConstraint("CK_Trajets_PuissanceFiscale", "\"PuissanceFiscale\" > 0");
|
|
});
|
|
|
|
entity.HasIndex(t => t.Date, "IX_Trajets_Date");
|
|
entity.HasIndex(t => new { t.Date, t.Position }, "IX_Trajets_Date_Position");
|
|
entity.HasIndex(t => t.VehiculeId, "IX_Trajets_Vehicule");
|
|
|
|
entity.HasOne(t => t.Vehicule)
|
|
.WithMany(v => v.Trajets)
|
|
.HasForeignKey(t => t.VehiculeId)
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
|
|
// --- Vehicules ---
|
|
modelBuilder.Entity<Vehicule>(entity =>
|
|
{
|
|
entity.ToTable("Vehicules", t =>
|
|
t.HasCheckConstraint("CK_Vehicules_PuissanceFiscale", "\"PuissanceFiscale\" > 0"));
|
|
|
|
entity.HasIndex(v => v.DateEffet, "IX_Vehicules_DateEffet");
|
|
});
|
|
|
|
// --- Profil (singleton Id = 1) ---
|
|
modelBuilder.Entity<Profil>(entity =>
|
|
{
|
|
entity.ToTable("Profil");
|
|
entity.HasKey(p => p.Id);
|
|
entity.Property(p => p.Id).ValueGeneratedNever();
|
|
entity.HasIndex(p => p.Id, "UX_Profil_Singleton").IsUnique();
|
|
});
|
|
|
|
// --- AdresseCache (Adresse unique) ---
|
|
modelBuilder.Entity<AdresseCache>(entity =>
|
|
{
|
|
entity.ToTable("AdresseCache");
|
|
entity.HasIndex(a => a.Adresse).IsUnique();
|
|
});
|
|
|
|
// --- AdressesNommees (unicité sur DenominationNormalisee) ---
|
|
modelBuilder.Entity<AdresseNommee>(entity =>
|
|
{
|
|
entity.ToTable("AdressesNommees");
|
|
entity.HasIndex(a => a.DenominationNormalisee, "UX_AdresseNommee_Denomination").IsUnique();
|
|
});
|
|
|
|
// --- TranchesBareme ---
|
|
modelBuilder.Entity<TrancheBareme>(entity =>
|
|
{
|
|
// Convention [KmMin, KmMax[ : borne max EXCLUSIVE, ou NULL = illimité.
|
|
// La contrainte tolère KmMax NULL et impose sinon KmMin < KmMax.
|
|
// Convention [KmMin, KmMax[ : borne max EXCLUSIVE, ou NULL = illimité.
|
|
// Les décimaux sont stockés en TEXT par SQLite : on CAST en REAL pour comparer
|
|
// numériquement (sinon "5000" > "20000" en comparaison lexicographique de chaînes).
|
|
entity.ToTable("TranchesBareme", t =>
|
|
t.HasCheckConstraint(
|
|
"CK_TranchesBareme_Km",
|
|
"\"KmMax\" IS NULL OR CAST(\"KmMin\" AS REAL) < CAST(\"KmMax\" AS REAL)"));
|
|
|
|
entity.HasIndex(t => new { t.Annee, t.PuissanceFiscale }, "IX_TranchesBareme_Annee_PF");
|
|
});
|
|
|
|
// --- MontantsRegles ---
|
|
modelBuilder.Entity<MontantRegle>(entity =>
|
|
{
|
|
entity.ToTable("MontantsRegles", t =>
|
|
{
|
|
t.HasCheckConstraint("CK_MontantsRegles_Montant", "\"Montant\" >= 0");
|
|
t.HasCheckConstraint("CK_MontantsRegles_Mois", "\"Mois\" BETWEEN 1 AND 12");
|
|
});
|
|
|
|
entity.HasIndex(m => new { m.Annee, m.Mois, m.VehiculeId }, "UX_MontantsRegles_Periode_Vehicule")
|
|
.IsUnique();
|
|
|
|
entity.HasOne(m => m.Vehicule)
|
|
.WithMany()
|
|
.HasForeignKey(m => m.VehiculeId)
|
|
.OnDelete(DeleteBehavior.Restrict);
|
|
});
|
|
}
|
|
}
|