Refonte export PDF, colonne commentaire et UX résumés

- Export PDF: nouveau nommage AAAAMM/AAAA_Trajets_MATRICULE_PLAQUE.pdf,
  logo carré embarqué en en-tête, km formatés selon Precision_Distance,
  restructuration en-têtes (Domicile déplacé, période/année fusionnées
  au titre)
- Correction bug: le nom de fichier suggéré au téléchargement ne
  remontait pas au frontend (CORS WithExposedHeaders manquant +
  composants résumé ne lisaient pas Content-Disposition)
- Résumé mensuel: colonne Commentaire, sélecteur temporel unifié
  (auto-chargement), km selon précision
- Résumé annuel: bouton Export PDF repositionné, km selon précision
- Panneau totaux: renommage "Remboursement indicatif" + infobulles
  d'aide (icône) au lieu du texte permanent
- Ajout de PdfServiceIntegrationTests (3 tests), 65/65 tests backend OK
- Mise à jour requirements.md et design.md en conséquence
This commit is contained in:
2026-09-28 08:58:23 +02:00
parent 9db3c91471
commit 61298d6384
17 changed files with 614 additions and 266 deletions
+60 -37
View File
@@ -20,7 +20,7 @@
| 4 | Vue annuelle | 12 résumés mensuels + total annuel |
| 5 | Modification / suppression | Édition avec recalcul conditionnel de distance |
| 6 | Persistance et cohérence | Base relationnelle avec invariants d'agrégation et transactions |
| 7 | Export PDF | Fichiers `trajets_MM_AAAA.pdf` et `trajets_AAAA.pdf` |
| 7 | Export PDF | Fichiers `AAAAMM_Trajets_MATRICULE_PLAQUE.pdf` et `AAAA_Trajets_MATRICULE_PLAQUE.pdf` |
| 8 | Visualisation carte | Tracés des itinéraires d'une journée sur carte Leaflet + OpenStreetMap |
| 9 | Cache adresses | Stockage des coordonnées BAN des adresses saisies, autocomplétion BAN + cache local, politique LRU (1 000 entrées) |
| 10 | Profil / adresse domicile | Saisie du domicile, bouton "Utiliser mon domicile" dans le formulaire |
@@ -248,14 +248,16 @@ src/
- Si l'utilisateur tape une adresse sans passer par BAN (pas de sélection), les coordonnées sont absentes et un avertissement discret s'affiche
**`ResumeMensuelComponent`**
- Sélecteurs mois (1–12) et année (2000–2100)
- Affiche la liste des trajets et le total mensuel (2 décimales)
- Bouton "Export PDF mensuel"
- Réutilise le même `SelecteurTemporelComponent` que `ConsultationJourComponent` (sélection successive année puis mois, surbrillance des périodes ayant des données) au lieu de sélecteurs distincts mois/année ; la sélection d'un mois (événement `moisSelectionne`) déclenche automatiquement le chargement du Résumé_Mensuel, sans bouton "Afficher" sur cet écran
- Affiche la liste des trajets, avec une colonne **Commentaire** entre les colonnes Date et Départ (valorisée avec le commentaire du Trajet, ou « — » si absent), et le total mensuel
- Bouton "Export PDF mensuel" positionné à droite du `SelecteurTemporelComponent`
- La Distance de chaque Trajet et le total mensuel sont formatés selon `formatKm()` (pipe `number` alimenté par le signal `precisionDistance` de `ParametresApplicationService`), reflétant Precision_Distance (0, 1 ou 2 décimales) au lieu d'un affichage fixe à 2 décimales — voir spec `precision-distances-km`
**`ResumeAnnuelComponent`**
- Sélecteur année (2000–2100)
- Tableau 12 lignes (mois, total km) + ligne total annuel (2 décimales, max 999 999,99)
- Bouton "Export PDF annuel"
- Sélecteur année (2000–2100) et bouton "Afficher" **inchangés** (pas de remplacement par `SelecteurTemporelComponent` sur cet écran — décision explicite de l'utilisateur, à la différence du Résumé_Mensuel et de la Vue_Consultation)
- Tableau 12 lignes (mois, total km) + ligne total annuel (max 999 999,99)
- Bouton "Export PDF annuel" positionné à droite du formulaire sélecteur/bouton "Afficher" (même agencement visuel que sur le Résumé_Mensuel), sans changement de comportement
- Le total kilométrique de chaque mois et le total annuel sont formatés selon `formatKm()`, reflétant Precision_Distance au lieu d'un affichage fixe à 2 décimales — voir spec `precision-distances-km`
**`TrajetListComponent`**
- Affiche TOUS les trajets du mois, regroupés par journée : chaque groupe/jour a un **en-tête repliable** (jour de la semaine avant la date, ex. « Lundi 15/09/2026 », puis nombre de trajets) ; par défaut tout est replié sauf le dernier jour du mois ayant des trajets.
@@ -326,7 +328,8 @@ src/
- Affiche trois totaux : total du jour ciblé par la navigation, total du mois sélectionné, Total_Annuel_Partiel (cumul de l'année jusqu'au mois sélectionné inclus)
- Reçoit en entrée la date sélectionnée et recalcule les totaux à chaque changement
- Tous les totaux affichés à 2 décimales, calculés par sommation de distances déjà arrondies selon Precision_Distance (voir spec `precision-distances-km`, Requirement 5)
- Affiche également le Montant_Previsionnel_Mensuel (80%, désormais calculé sur le seul mois affiché) et, en dessous, une nouvelle ligne « Montant prévisionnel annuel » présentant le Montant_Previsionnel_Annuel (100%, sur le Total_Annuel_Partiel) — détail complet du calcul dans la spec `precision-distances-km`, Requirements 7 et 8
- Affiche le libellé **« Remboursement indicatif »** (renommé, anciennement « Indication de versement (80%) ») présentant le Montant_Previsionnel_Mensuel (80%, calculé sur le seul mois affiché) et, en dessous, une ligne « Montant prévisionnel annuel » présentant le Montant_Previsionnel_Annuel (100%, sur le Total_Annuel_Partiel) — détail complet du calcul dans la spec `precision-distances-km`, Requirements 7 et 8
- **Infobulles d'aide** : le texte explicatif de chacune de ces deux lignes (« Remboursement indicatif » et « Montant prévisionnel annuel »), auparavant affiché en permanence sous le montant, est retiré du flux visuel et remplacé par un bouton icône (SVG inline `help-circle`, 16×16, `aria-label` dédié) affiché juste après le libellé ; le texte explicatif est porté par l'attribut HTML `title` de ce bouton et s'affiche via l'infobulle native du navigateur au survol ou au focus clavier de l'icône
**`ParametresApplicationComponent`**
- Nouveau composant du Sous_Menu_Application de la Page_Parametrage, positionné entre « Compte » et « Véhicules »
@@ -1537,19 +1540,17 @@ QuestPDF est une bibliothèque .NET open-source (licence MIT pour usage communau
### Structure des documents PDF
#### PDF Mensuel (`trajets_MM_AAAA.pdf`)
#### PDF Mensuel (`AAAAMM_Trajets_MATRICULE_PLAQUE.pdf`)
```
┌────────────────────────────────────────────────────────────────────────────┐
│ FEUILLE D'INDEMNITÉ KILOMÉTRIQUE MENSUELLE │
│ Cécile DOUCET │
├────────────────────────────────────────────────────────────────────────────┤
│ Véhicule : GOLF (ES-036-YF) — 6 CV (Coef. : 0,447 / Base : 0) │
│ Période : JANVIER 2025 Total parcouru sur le mois : 931 Kms │
│ Coef. : 0,447 Total parcouru sur l'année : 931 Kms │
│ Base : 0 TOTAL IK du mois : 416,16 € │
│ Adresse de départ : 29 route des Cascades, 23400 Saint-Pardoux-Morterolles │
│ Réglé : 500,00 € │
│ [Logo] Rapport de trajets │
│ Feuille d'indemnité kilométrique JANVIER 2025 │
│ Utilisateur : Cécile DOUCET — Matricule : AB12CD34 │
│ Domicile : 29 route des Cascades, 23400 Saint-Pardoux-Morterolles │
│ Véhicule : GOLF (ES-036-YF) — 6 CV (Coef. : 0,447 / Base : 0) │
│ Total parcouru sur le mois : 931 Kms TOTAL IK du mois : 416,16 € │
│ Total parcouru sur l'année : 931 Kms Réglé : 500,00 € │
├────────────┬──────────────────────────┬──────────────────────────┬─────────┤
│ Date │ Motifs (commentaire) │ Adresse départ → arrivée │ Kms │
├────────────┼──────────────────────────┼──────────────────────────┼─────────┤
@@ -1558,20 +1559,25 @@ QuestPDF est une bibliothèque .NET open-source (licence MIT pour usage communau
└────────────┴──────────────────────────┴──────────────────────────┴─────────┘
```
> Le « TOTAL IK du mois » = kilomètres du mois × coefficient de la tranche annuelle applicable. Le « Réglé » = Montant_Regle saisi pour le mois. Le motif affiché est le commentaire du Trajet.
> Le titre du document intègre désormais le mois et l'année (« Feuille d'indemnité kilométrique JANVIER 2025 ») ; il n'y a plus de ligne « Période » distincte dans l'en-tête. Le libellé « Adresse de départ » devient « Domicile » et est positionné juste après la ligne Utilisateur/Matricule (au lieu d'être après la ligne Véhicule). Le « TOTAL IK du mois » = kilomètres du mois × coefficient de la tranche annuelle applicable. Le « Réglé » = Montant_Regle saisi pour le mois. Le motif affiché est le commentaire du Trajet. Les kilomètres (mois, année, table des trajets) sont formatés selon Precision_Distance.
#### PDF Annuel (`trajets_AAAA.pdf`)
#### PDF Annuel (`AAAA_Trajets_MATRICULE_PLAQUE.pdf`)
```
┌──────────────────────────────────────────────────────────────────────────────┐
│ SYNTHÈSE 2025 D'INDEMNITÉ KILOMÉTRIQUE │
│ Nom : Cécile DOUCET Année : 2025 │
│ Domicile : 29 route des Cascades, 23400 Saint-Pardoux-Morterolles │
│ [Logo] Rapport de trajets │
│ Synthèse 2025 d'indemnité kilométrique │
│ Utilisateur : Cécile DOUCET — Matricule : AB12CD34 │
│ Domicile : 29 route des Cascades, 23400 Saint-Pardoux-Morterolles │
│ Véhicule : GOLF (ES-036-YF) — 6 CV │
│ Barème applicable : 5001–20000 │
│ Total kilométrique de l'année : 12 241 km │
├──────────────────────────────────────────────────────────────────────────────┤
│ Barème applicable pour l'année (par puissance fiscale) : │
│ PF │ ≤5000 (coef/base) │ 5001–20000 (coef/base) │ >20000 (coef/base) │
│ 6 CV │ 0,665 / 0 │ 0,374 / 1457 │ 0,447 / 0 │
│ Véhicule : GOLF (ES-036-YF) Total Kms : 12 241 Barème applicable : D2 │
│ Barème applicable pour l'année (6 CV) : │
│ Tranche (km) │ Coefficient │ Base (€) │
│ 0 à 5000 │ 0,665 │ 0 │
│ 5001 à 20000 │ 0,374 │ 1457 │
│ 20000 et plus │ 0,447 │ 0 │
├────────────┬───────────────┬───────────────────┬────────────────────────────┤
│ Mois │ Kms parcourus │ Frais payé (réglé)│ Frais réel │
├────────────┼───────────────┼───────────────────┼────────────────────────────┤
@@ -1586,28 +1592,45 @@ QuestPDF est une bibliothèque .NET open-source (licence MIT pour usage communau
└──────────────────────────────────────────────────────────────────────────────┘
```
> Le « Frais réel » mensuel = kilomètres du mois × coefficient de la tranche annuelle. Le « Frais payé » = Montant_Regle saisi. La minoration 80% n'apparaît pas dans la synthèse : elle sert uniquement d'indication de versement en cours d'année.
> La ligne « Année : AAAA », auparavant présente dans l'en-tête, est retirée : elle est redondante avec le titre « Synthèse {Année} d'indemnité kilométrique ». Le « Frais réel » mensuel = kilomètres du mois × coefficient de la tranche annuelle. Le « Frais payé » = Montant_Regle saisi. La minoration 80% n'apparaît pas dans la synthèse : elle sert uniquement d'indication de versement en cours d'année. Les kilomètres (total annuel, table des mois, formule réelle) sont formatés selon Precision_Distance.
> **Rapports distincts par véhicule** : si la période demandée (mois ou année) contient des Trajets associés à plusieurs Véhicules, le backend génère un fichier PDF distinct par Véhicule. Chaque PDF ne contient que les Trajets et montants du Véhicule concerné.
### En-tête obligatoire (Exigence 7.6)
Tous les PDF incluent en en-tête :
1. Titre fixe : **"Rapport de trajets"**
2. Période couverte : `Mois AAAA` pour le mensuel, `AAAA` pour l'annuel
3. Total kilométrique de la période
1. Le **logo carré** de l'Application, en haut à gauche (voir « Logo dans l'en-tête » ci-dessous)
2. Titre fixe : **"Rapport de trajets"**, suivi du titre spécifique intégrant la période (`Feuille d'indemnité kilométrique {Mois} {AAAA}` pour le mensuel, `Synthèse {AAAA} d'indemnité kilométrique` pour l'annuel)
3. Total kilométrique de la période, formaté selon Precision_Distance
### Nommage des fichiers
### Logo dans l'en-tête (Exigences 7.13)
- Mensuel : `trajets_MM_AAAA.pdf` (ex. `trajets_01_2025.pdf`)
- Annuel : `trajets_AAAA.pdf` (ex. `trajets_2025.pdf`)
Le logo carré (`logo-carre.png`, cf. section Charte graphique) est **embarqué comme ressource** dans l'assembly du backend (`Assets/logo-carre.png`, copié depuis les assets du frontend au moment du build) plutôt que lu depuis le système de fichiers, pour rester indépendant du répertoire de travail au runtime. `PdfService` charge ces octets une seule fois via `Assembly.GetManifestResourceStream`, en cache statique, et les insère via `Image().FitWidth()` dans une colonne fixe (45 pt) à gauche de la colonne de texte de l'en-tête, pour les deux rapports (mensuel et annuel). Si la ressource est introuvable (tableau vide), le logo est simplement omis sans faire échouer la génération du PDF.
### Precision_Distance dans les PDF (Exigence 7.12)
`PdfService` récupère la valeur courante de Precision_Distance via `IPrecisionDistanceService` avant le rendu, et l'applique à tous les kilomètres affichés (`FormatKm(valeur, precision)`) : totaux d'en-tête (mois, année), table des Trajets, table de synthèse annuelle, et formule réelle. Comme pour l'affichage Frontend, seul le rendu est affecté ; la Distance brute stockée en base n'est jamais modifiée par ce paramètre (voir spec `precision-distances-km`).
### Restructuration des en-têtes (Exigence 7.9)
- **Mensuel** : le libellé « Adresse de départ » devient **« Domicile »** et est déplacé juste après la ligne Utilisateur/Matricule (même position que sur le rapport annuel), au lieu d'être positionné après la ligne Véhicule. La ligne « Période : MOIS ANNEE » est retirée : la période est désormais intégrée au titre du document (« Feuille d'indemnité kilométrique {MOIS} {ANNEE} », au lieu du titre fixe « Feuille d'indemnité kilométrique mensuelle »).
- **Annuel** : la ligne « Année : AAAA » est retirée de l'en-tête, l'année figurant déjà dans le titre « Synthèse {Année} d'indemnité kilométrique ».
### Nommage des fichiers (Exigence 7.4)
- Mensuel : `AAAAMM_Trajets_MATRICULE_PLAQUE.pdf` (ex. `202501_Trajets_AB12CD34_ES036YF.pdf`)
- Annuel : `AAAA_Trajets_MATRICULE_PLAQUE.pdf` (ex. `2025_Trajets_AB12CD34_ES036YF.pdf`)
- La **plaque** est nettoyée de ses espaces et de ses tirets avant insertion dans le nom de fichier (ex. `ES-036-YF` → `ES036YF`).
- Les segments **Matricule** et **Plaque** (et le séparateur `_` qui les précède) sont **omis** lorsque la donnée correspondante est vide ou absente (Profil sans matricule, ou Véhicule sans plaque) — voir `PdfService.ConstruireNomFichier`.
Le header HTTP de réponse inclut :
```
Content-Disposition: attachment; filename="trajets_01_2025.pdf"
Content-Disposition: attachment; filename="202501_Trajets_AB12CD34_ES036YF.pdf"
Content-Type: application/pdf
```
> **Exposition CORS de `Content-Disposition`** : la politique CORS du backend (`Program.cs`) déclare explicitement `WithExposedHeaders("Content-Disposition")` — sans cet appel, `AllowAnyHeader()` (qui ne couvre que les en-têtes de requête) ne suffit pas à rendre cet en-tête de réponse lisible en cross-origin par le Frontend, qui en a besoin pour proposer le nom de fichier suggéré lors du téléchargement d'un Export_PDF.
### Timeout PDF (Exigence 7.5)
La génération PDF est enveloppée dans un `CancellationToken` avec délai de 15 secondes. En cas de dépassement, le backend retourne `503 Service Unavailable`.
@@ -1984,7 +2007,7 @@ catch
### Property 9: Nommage des fichiers PDF exportés
*Pour tout* export PDF généré — qu'il soit mensuel ou annuel — le nom de fichier inclus dans l'en-tête `Content-Disposition` doit correspondre exactement au format `trajets_MM_AAAA.pdf` (mensuel) ou `trajets_AAAA.pdf` (annuel), où MM et AAAA correspondent aux paramètres de la requête.
*Pour tout* export PDF généré — qu'il soit mensuel ou annuel — le nom de fichier inclus dans l'en-tête `Content-Disposition` doit correspondre exactement au format `AAAAMM_Trajets_MATRICULE_PLAQUE.pdf` (mensuel) ou `AAAA_Trajets_MATRICULE_PLAQUE.pdf` (annuel), où AAAA et MM correspondent aux paramètres de la requête, PLAQUE est la plaque du Véhicule nettoyée de ses espaces et de ses tirets, et les segments MATRICULE / PLAQUE (avec leur séparateur `_`) sont omis lorsque la donnée correspondante est vide ou absente.
**Validates: Requirements 7.4**
@@ -2359,7 +2382,7 @@ public Property EtatProfilConfigureIffNomPrenomEtVehicule() { ... }
- `DistanceCalculatorService`: BAN indisponible → saisie manuelle autorisée, distance null stockée
- `ResumeService`: total mois vide = 0, résumé annuel avec mois manquants = 0
- `ResumeService` / `TotauxService`: calcul TotalAnnuelPartiel (cumul jusqu'au mois inclus, exclusion des mois postérieurs), années disponibles (données + année courante), mois/jours disponibles
- `PdfService`: génération mensuelle, génération annuelle, nommage des fichiers, timeout
- `PdfService`: génération mensuelle, génération annuelle, nommage des fichiers (avec/sans matricule, avec/sans plaque, nettoyage espaces/tirets de la plaque), formatage des km selon Precision_Distance, présence du logo (et comportement si ressource absente), timeout
- `AdresseCacheService`: insertion nouvelle adresse, réutilisation adresse existante, éviction LRU à 1 001 entrées
- `ProfilService`: sauvegarde avec géocodage réussi, sauvegarde avec géocodage échoué (warning sans erreur), mise à jour
- `ProfilService`: validation matricule <= 8 car.