# Implementation Plan: Précision des distances (km) ## Overview Cette implémentation ajoute un paramètre global `Precision_Distance` (0/1/2 décimales) avec arrondi supérieur (ceiling) systématique côté backend et frontend, refond le calcul du Montant_Previsionnel (mensuel recentré sur le mois affiché + nouveau montant annuel 100%), et propage la cohérence de l'arrondi à la liste des trajets, aux totaux, à la carte, à l'export PDF et au barème kilométrique. L'ordre des tâches suit les dépendances réelles : entité + migration → service de précision pur → intégration dans les services d'agrégation existants → refonte du calcul prévisionnel → cohérence carte/PDF → tests backend → couche frontend (service/util → composant/route → consommateurs) → vérification finale. ## Tasks - [x] 1. Backend — Entité `ParametresApplication` et migration EF Core - Créer `Models/Entities/ParametresApplication.cs` (singleton `Id = 1`, `PrecisionDistanceKm` défaut 2, `UpdatedAt`) - Ajouter `DbSet ParametresApplication` et la configuration `OnModelCreating` (contrainte CHECK `IN (0,1,2)`, clé `ValueGeneratedNever()`, index unique singleton) dans `Data/AppDbContext.cs` - Générer la migration EF Core (`dotnet ef migrations add AjoutParametresApplication`) créant la table avec la contrainte CHECK - _Requirements: 1.1, 1.2_ - [x] 2. Backend — Service de précision et exception métier - [x] 2.1 Créer l'exception `Services/Exceptions/PrecisionDistanceInvalideException.cs` (même modèle que `MontantRegleNegatifException`) - _Requirements: 1.6_ - [x] 2.2 Créer `Services/IPrecisionDistanceService.cs` (`ObtenirAsync`, `DefinirAsync`, `ArrondirDistance`) - _Requirements: 1.1, 1.2, 1.3, 1.4, 1.6, 1.8, 3.1, 3.2, 3.3, 3.4, 3.5, 4.2_ - [x] 2.3 Implémenter `Services/PrecisionDistanceService.cs` - `GetOrCreateAsync` privé créant la ligne singleton par défaut (2) si absente - `ObtenirAsync`/`DefinirAsync` avec validation stricte `{0,1,2}` levant `PrecisionDistanceInvalideException` - `ArrondirDistance` : `Ceiling(distance × 10^precision) / 10^precision` - _Requirements: 1.1, 1.2, 1.5, 1.6, 1.8, 3.1, 3.2, 3.3, 3.4, 3.5_ - [x]* 2.4 Écrire les tests de propriété pour `PrecisionDistanceService` - **Property 1: Arrondi supérieur exact et jamais inférieur à la distance brute** — _Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5_ - **Property 4: Validation stricte de la précision** — _Validates: Requirements 1.6_ - **Property 5: Invariance de la distance brute stockée face à un changement de précision** — _Validates: Requirements 1.5, 1.8_ - [ ]* 2.5 Écrire les tests unitaires `PrecisionDistanceService` - `ObtenirAsync` retourne 2 par défaut quand la table est vide (Exigence 1.2) - `DefinirAsync(3)` lève `PrecisionDistanceInvalideException` (Exigence 1.6, cas concret) - _Requirements: 1.2, 1.6_ - [x] 3. Backend — DTOs et contrôleur `ParametresApplicationController` - [x] 3.1 Créer `Models/DTOs/ParametresApplicationDtos.cs` (`ParametresApplicationDto`, `SavePrecisionDistanceDto`) - _Requirements: 1.3, 1.4_ - [x] 3.2 Créer `Controllers/ParametresApplicationController.cs` (GET/PUT `api/parametres-application`, mapping `PrecisionDistanceInvalideException` → 400) - _Requirements: 1.3, 1.4, 1.6, 1.7_ - [x] 3.3 Enregistrer `IPrecisionDistanceService`/`PrecisionDistanceService` dans `Program.cs` (`AddScoped`, section services métier) - _Requirements: 1.1, 1.3, 1.4_ - [ ]* 3.4 Écrire les tests d'intégration du contrôleur - `PUT /api/parametres-application` avec `precisionDistanceKm = 3` retourne 400 (Exigence 1.6) - `GET`/`PUT /api/parametres-application` sans jeton retourne 401 (Exigence 1.7) - _Requirements: 1.3, 1.4, 1.6, 1.7_ - [x] 4. Checkpoint — Ensure all tests pass - Ensure all tests pass, ask the user if questions arise. - [x] 5. Backend — Intégration de l'arrondi dans les services d'agrégation existants - [x] 5.1 Modifier `Services/ResumeService.cs` : injecter `IPrecisionDistanceService`, appliquer `ArrondirDistance(Distance, precision) × (AllerRetour ? 2 : 1)` par trajet dans `ConstruireResumeMensuel`, exposer `TrajetDto.Distance` déjà arrondi, sommer les valeurs déjà arrondies pour `TotalKm` sans second arrondi - _Requirements: 4.1, 4.2, 4.3, 5.2, 5.3_ - [x] 5.2 Modifier `Services/TotauxService.cs` : injecter `IPrecisionDistanceService`, remplacer la fonction locale `Effective(distance, allerRetour)` par l'arrondi avant doublement, supprimer le `Math.Round(..., 2, ...)` final existant sur `TotalJour`/`TotalMois`/`TotalAnnuelPartiel` - _Requirements: 4.1, 4.2, 4.3, 5.1, 5.4_ - [x] 5.3 Modifier `Services/RemboursementService.cs` : injecter `IPrecisionDistanceService`, construire `kmMoisMap` à partir des distances arrondies par trajet (au lieu de la distance brute), garantissant `totalKmAnnuel` et chaque `KmMois` comme sommes de distances déjà arrondies - _Requirements: 4.1, 4.2, 4.3, 6.3, 6.4, 6.6_ - [x]* 5.4 Écrire le test de propriété pour la sommation - **Property 2: Arrondi avant doublement pour Aller_Retour** — _Validates: Requirements 4.1, 4.2, 4.3_ - **Property 3: Sommation de distances déjà arrondies sans arrondi supplémentaire** — _Validates: Requirements 5.1, 5.2, 5.3, 5.4_ - [x]* 5.5 Écrire le test unitaire de l'exemple chiffré Aller_Retour - Cas 12.30 km, précision 0 → 26 km (et non 25 km), en plus de la Property 2 - _Requirements: 4.2_ - [x] 6. Backend — Refonte de `MontantRegleService.GetMontantPrevisionnelAsync` - [x] 6.1 Créer/modifier le DTO `MontantPrevisionnelDto` (`TotalMoisKm`, `TotalAnnuelPartielKm`, `MontantPrevisionnelMensuel`, `MontantPrevisionnelAnnuel`) dans `Models/DTOs/` - _Requirements: 6.1, 6.2, 7.1, 8.1, 8.2, 8.4_ - [x] 6.2 Modifier `Services/IMontantRegleService.cs` et `Services/MontantRegleService.cs` : injecter `IPrecisionDistanceService`, calculer `totalMoisAffiche`/`totalAnnuelPartiel` à partir des distances arrondies, déterminer la tranche via le cumul annuel (`baremeAnnuel`), appliquer son coefficient/base au total du mois pour `MontantPrevisionnelMensuel` (80%), calculer `MontantPrevisionnelAnnuel` (100% sur le cumul) - _Requirements: 6.1, 6.2, 6.6, 7.1, 7.2, 7.4, 8.1, 8.2, 8.5_ - [x]* 6.3 Écrire les tests de propriété pour le calcul prévisionnel - **Property 6: Montant_Previsionnel_Mensuel dépend uniquement du mois affiché** — _Validates: Requirements 7.1, 7.2_ - **Property 7: Montant_Previsionnel_Annuel égal au barème plein sur le cumul** — _Validates: Requirements 8.2, 8.5_ - [ ]* 6.4 Écrire les tests unitaires de `GetMontantPrevisionnelAsync` - Véhicule introuvable → `VehiculeNotFoundException` (comportement existant inchangé) - Cas limite : trajets avec distance `null` ignorés dans la somme - _Requirements: 6.1, 6.2, 7.1, 8.1, 8.2_ - [x] 7. Backend — Cohérence carte et export PDF - [x] 7.1 Vérifier/corriger l'endpoint trajets/journée (`ITrajetService`/`TrajetsController`, méthode consommée par `TrajetService.getTrajetsJournee`) pour renvoyer la Distance_Effective_Arrondie via `IPrecisionDistanceService` - _Requirements: 9.1, 9.4_ - [x] 7.2 Vérifier `Services/PdfService.cs` (`AssemblerModeleMensuelAsync`, composition des tables mensuelle/annuelle) : confirmer qu'aucun recalcul de distance brute ne subsiste et que le modèle assemblé provient intégralement de `IResumeService`/`IRemboursementService` déjà corrigés - _Requirements: 9.2, 9.3_ - [ ]* 7.3 Écrire les tests de propriété de cohérence carte/PDF - **Property 8: Cohérence de l'infobulle carte avec la Distance_Effective_Arrondie** — _Validates: Requirements 9.1_ - **Property 9: Modèle de données PDF cohérent avec les résumés/régularisations** — _Validates: Requirements 9.2, 9.3_ - [ ]* 7.4 Écrire le test d'intégration de cohérence croisée - Scénario bout-en-bout (SQLite en mémoire) : créer des trajets sur plusieurs mois, changer Precision_Distance, vérifier l'égalité des valeurs entre `ResumeService`, `TotauxService` et `RemboursementService` (Exigence 9.4) - _Requirements: 9.4_ - [ ]* 7.5 Écrire les tests d'intégration de génération PDF - Génération effective d'un PDF mensuel et d'un PDF annuel (1 exemple chacun), vérifier l'absence d'exception après les changements de modèle - _Requirements: 9.2, 9.3_ - [x] 8. Checkpoint — Ensure all tests pass - Ensure all tests pass, ask the user if questions arise. - [x] 9. Frontend — Modèle, service et utilitaire d'arrondi - [x] 9.1 Créer `core/models/parametres-application.model.ts` (`ParametresApplication { precisionDistanceKm: number }`) - _Requirements: 1.3, 1.4_ - [x] 9.2 Créer `core/services/parametres-application.service.ts` (`ParametresApplicationService` : signal partagé `precisionDistance`, `charger()`, `definirPrecisionDistance()`) - _Requirements: 1.3, 1.4, 2.3, 2.4, 2.5_ - [x] 9.3 Créer `shared/utils/arrondir-distance.ts` (`arrondirDistance`, `distanceEffectiveArrondie`), miroir exact de la formule backend - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 4.1, 4.2, 4.3, 5.5_ - [ ]* 9.4 Écrire les tests unitaires de `arrondirDistance`/`distanceEffectiveArrondie` - Cas 12.30 km, précision 0, aller-retour → 26 km (miroir du test backend) - Cas limites par précision (0/1/2) et valeurs proches d'un seuil d'arrondi - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 4.1, 4.2, 4.3_ - [x]* 9.5 Mettre à jour `core/models/bareme.model.ts` et `core/services/bareme.service.ts` avec les nouveaux champs `MontantPrevisionnel` (`totalMoisKm`, `totalAnnuelPartielKm`, `montantPrevisionnelMensuel`, `montantPrevisionnelAnnuel`) - _Requirements: 6.1, 7.1, 8.1, 8.2, 8.4_ - [x] 10. Frontend — Composant `ParametresApplicationComponent` et route/menu - [x] 10.1 Créer `features/parametres-application/parametres-application.component.ts` (+ `.html`, `.scss`) : sélection 0/1/2, présélection de la valeur courante, confirmation de succès, restauration + notification d'erreur en cas d'échec - _Requirements: 2.2, 2.3, 2.4, 2.5_ - [x] 10.2 Ajouter la route `/parametrage/application` dans `app.routes.ts`, entre `compte` et `vehicules`, gardée uniquement par `authGuard` (pas `profilConfigureGuard`) - _Requirements: 2.1, 2.6_ - [x] 10.3 Ajouter le lien « Application » entre « Compte » et « Véhicules » dans `features/parametrage/parametrage.component.html` - _Requirements: 2.1_ - [ ]* 10.4 Écrire les tests unitaires du composant `ParametresApplicationComponent` - Présélection de la valeur courante au chargement (Exigence 2.3) - `choisir()` restaure la sélection précédente et notifie une erreur si l'appel HTTP échoue (Exigence 2.5) - _Requirements: 2.3, 2.4, 2.5_ - [ ]* 10.5 Écrire le test de structure du menu de paramétrage - Vérifier le positionnement du lien « Application » entre « Compte » et « Véhicules » - _Requirements: 2.1_ - [x] 11. Frontend — Chargement initial dans `AppComponent` - [x] 11.1 Modifier `app.component.ts` pour appeler `ParametresApplicationService.charger()` au même moment que le chargement du profil (effect déclenché à l'authentification) - _Requirements: 1.3, 5.5_ - [x] 12. Checkpoint — Ensure all tests pass - Ensure all tests pass, ask the user if questions arise. - [x] 13. Frontend — Adaptation des composants consommateurs - [x] 13.1 Modifier `features/trajet-list/trajet-list.component.ts` (méthode `distanceAffichee`) : formater selon `ParametresApplicationService.precisionDistance()` au lieu d'un arrondi fixe à 2 décimales - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5_ - [x] 13.2 Modifier `features/panneau-totaux/panneau-totaux.component.ts` + `.html` : format dynamique des distances basé sur `precisionDistance()`, ajout de la ligne « Montant prévisionnel annuel » sous l'indication de versement mensuelle, consommation de `montantPrevisionnelMensuel`/`montantPrevisionnelAnnuel` - _Requirements: 5.4, 5.5, 8.1, 8.3, 8.4_ - [x] 13.3 Modifier `features/carte-trajets/carte-trajets.component.ts` (méthode `contenuInfobulle`) : `toFixed(precision)` dynamique basé sur `ParametresApplicationService.precisionDistance()` - _Requirements: 9.1_ - [x] 13.4 Modifier `features/montants-regles/montants-regles.component.ts` : lire `montantPrevisionnelMensuel` au lieu de `montantPrevisionnel` - _Requirements: 7.1, 7.3_ - [ ]* 13.5 Écrire les tests unitaires des composants adaptés - `TrajetListComponent.distanceAffichee` formate selon la précision courante - `PanneauTotauxComponent` affiche la nouvelle ligne « Montant prévisionnel annuel » - `CarteTrajetsComponent.contenuInfobulle` utilise la précision courante - `MontantsReglesComponent` affiche `montantPrevisionnelMensuel` sans erreur de liaison après le renommage du DTO - _Requirements: 3.1, 5.4, 7.1, 7.3, 8.1, 8.3, 8.4, 9.1_ - [x] 14. Checkpoint final — Ensure all tests pass - Ensure all tests pass, ask the user if questions arise. - [x] 15. Vérification finale - [x] 15.1 Exécuter le build backend (`dotnet build`) et le build frontend (`ng build` / `npm run build`), corriger toute erreur de compilation résultant des changements de DTO/modèle - _Requirements: 1.1–9.4 (non-régression globale)_ - [x] 15.2 Exécuter l'ensemble des tests backend (`dotnet test`) et frontend (`ng test` / `vitest --run`), vérifier que toutes les propriétés et tous les tests unitaires/intégration passent - _Requirements: 1.1–9.4 (non-régression globale)_ ## Notes - Les tâches marquées `*` sont optionnelles (tests) et peuvent être ignorées pour un MVP plus rapide, mais restent recommandées vu la présence de 9 Correctness Properties dans le design. - Chaque tâche référence les exigences granulaires concernées pour la traçabilité. - Les checkpoints garantissent une validation incrémentale avant de passer à la couche suivante (backend cœur → calcul prévisionnel → cohérence carte/PDF → frontend). - Les tests de propriété utilisent FsCheck (déjà présent dans `SchotzIadKmTracker.Tests.csproj`), avec au moins 100 itérations par propriété et le tag de traçabilité `// Feature: precision-distances-km, Property {n}: {texte}`. - La tâche 6.2 constitue une rupture de contrat mineure sur `MontantPrevisionnelDto` : les tâches frontend 9.5 et 13.4 doivent être réalisées après la tâche 6.1 pour rester synchronisées avec les nouveaux noms de champs. ## Task Dependency Graph ```json { "waves": [ { "id": 0, "tasks": ["1"] }, { "id": 1, "tasks": ["2.1"] }, { "id": 2, "tasks": ["2.2"] }, { "id": 3, "tasks": ["2.3"] }, { "id": 4, "tasks": ["2.4", "2.5", "3.1"] }, { "id": 5, "tasks": ["3.2"] }, { "id": 6, "tasks": ["3.3"] }, { "id": 7, "tasks": ["3.4"] }, { "id": 8, "tasks": ["5.1", "5.2", "5.3"] }, { "id": 9, "tasks": ["5.4", "5.5", "6.1"] }, { "id": 10, "tasks": ["6.2"] }, { "id": 11, "tasks": ["6.3", "6.4", "7.1"] }, { "id": 12, "tasks": ["7.2"] }, { "id": 13, "tasks": ["7.3", "7.4", "7.5"] }, { "id": 14, "tasks": ["9.1"] }, { "id": 15, "tasks": ["9.2"] }, { "id": 16, "tasks": ["9.3", "9.5"] }, { "id": 17, "tasks": ["9.4", "10.1"] }, { "id": 18, "tasks": ["10.2", "10.3"] }, { "id": 19, "tasks": ["10.4", "10.5", "11.1"] }, { "id": 20, "tasks": ["13.1", "13.2", "13.3", "13.4"] }, { "id": 21, "tasks": ["13.5"] }, { "id": 22, "tasks": ["15.1"] }, { "id": 23, "tasks": ["15.2"] } ] } ```