- Nouveau parametre global Precision_Distance (sous-menu Application de la Page_Parametrage, entre Compte et Vehicules), persiste via une entite singleton ParametresApplication, expose par GET/PUT /api/parametres-application - Arrondi superieur (ceiling) applique a l'affichage et aux agregats : trajet simple, aller/retour (arrondi puis doublement), totaux jour/mois/annee, carte, export PDF et bareme kilometrique - jamais a la distance brute stockee - Refonte du Montant_Previsionnel : le montant mensuel (80%) porte desormais sur le seul mois affiche (tranche determinee par le cumul annuel) ; nouvelle ligne Montant_Previsionnel_Annuel (100%, sans minoration, sur le cumul) - Backend : PrecisionDistanceService (ArrondirDistance), integration dans ResumeService/TotauxService/RemboursementService/MontantRegleService, migration EF Core ; 7 tests de propriete (FsCheck) + tests existants, 62/62 verts - Frontend : ParametresApplicationService (signal partage), composant de parametrage, fonction miroir arrondirDistance, adaptation de la liste des trajets, du panneau des totaux, de la carte et des montants regles - Specs : nouvelle spec precision-distances-km (requirements/design/tasks) ; mise a jour de requirements.md, design.md et design-requirements.md de schotziad-km-tracker (Requirement 22, renvois croises, Montant_Previsionnel) - Suppression du fichier chromium .deb suivi par erreur dans le depot
145 lines
6.0 KiB
C#
145 lines
6.0 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using SchotzIadKmTracker.Api.Models.Entities;
|
|
|
|
namespace SchotzIadKmTracker.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<ParametresApplication> ParametresApplication => Set<ParametresApplication>();
|
|
public DbSet<AdresseCache> AdresseCache => Set<AdresseCache>();
|
|
public DbSet<AdresseNommee> AdressesNommees => Set<AdresseNommee>();
|
|
public DbSet<TrancheBareme> TranchesBareme => Set<TrancheBareme>();
|
|
public DbSet<MontantRegle> MontantsRegles => Set<MontantRegle>();
|
|
public DbSet<Compte> Comptes => Set<Compte>();
|
|
public DbSet<ResetPasswordToken> ResetPasswordTokens => Set<ResetPasswordToken>();
|
|
|
|
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();
|
|
});
|
|
|
|
// --- ParametresApplication (singleton Id = 1) ---
|
|
modelBuilder.Entity<ParametresApplication>(entity =>
|
|
{
|
|
entity.ToTable("ParametresApplication", t =>
|
|
t.HasCheckConstraint("CK_ParametresApplication_PrecisionDistanceKm", "\"PrecisionDistanceKm\" IN (0, 1, 2)"));
|
|
entity.HasKey(p => p.Id);
|
|
entity.Property(p => p.Id).ValueGeneratedNever();
|
|
entity.HasIndex(p => p.Id, "UX_ParametresApplication_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);
|
|
});
|
|
|
|
// --- Comptes (singleton Id = 1) ---
|
|
modelBuilder.Entity<Compte>(entity =>
|
|
{
|
|
entity.ToTable("Comptes");
|
|
entity.HasKey(c => c.Id);
|
|
entity.Property(c => c.Id).ValueGeneratedNever();
|
|
entity.HasIndex(c => c.Email, "UX_Comptes_Email").IsUnique();
|
|
});
|
|
|
|
// --- ResetPasswordTokens (jetons de réinitialisation à usage unique) ---
|
|
modelBuilder.Entity<ResetPasswordToken>(entity =>
|
|
{
|
|
entity.ToTable("ResetPasswordTokens");
|
|
entity.HasIndex(t => t.TokenHash, "UX_ResetTokens_TokenHash").IsUnique();
|
|
entity.HasIndex(t => t.Expiration, "IX_ResetTokens_Expiration");
|
|
});
|
|
}
|
|
}
|