Files
SchotzIAD/.kiro/specs/trajet-km-tracker/requirements.md
T
MadJax e3f946f7d4 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.
2026-09-14 02:08:27 +02:00

381 lines
55 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Requirements Document
## Introduction
L'application **SchotzIAD KM Tracker** est une application web **mono-utilisateur** (sans authentification ni gestion de comptes) permettant de saisir et de suivre ses trajets mensuels (jour, adresse de départ, adresse d'arrivée), de calculer automatiquement les kilomètres parcourus par mois, et de consulter une vue annuelle consolidée des kilomètres parcourus.
L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. La saisie des adresses s'appuie sur l'API de la **Base Adresses Nationale (BAN)** (disponible sur data.gouv.fr) pour l'autocomplétion et la résolution des coordonnées géographiques. Le calcul de distance routière utilise l'API **OpenRouteService (ORS)** uniquement lorsque les coordonnées des deux adresses sont connues. Elle cible des utilisateurs souhaitant garder un historique fiable de leurs déplacements, notamment à des fins professionnelles ou de remboursement de frais.
---
## Glossary
- **Application** : L'ensemble du système SchotzIAD KM Tracker (frontend Angular + backend C#).
- **Frontend** : L'interface utilisateur développée en Angular/TypeScript.
- **Backend** : Le serveur d'API développé en C#.
- **Utilisateur** : La personne qui utilise l'application.
- **Trajet** : Un déplacement défini par une date, une adresse de départ et une adresse d'arrivée.
- **Distance** : Le nombre de kilomètres calculés entre l'adresse de départ et l'adresse d'arrivée d'un Trajet.
- **Résumé_Mensuel** : L'agrégation des Trajets et des Distances pour un mois calendaire donné.
- **Résumé_Annuel** : L'agrégation des Résumés_Mensuels pour une année calendaire donnée.
- **Geocodeur** : Le service de la Base Adresses Nationale (BAN), accessible via l'API api.gouv.fr, utilisé pour convertir une adresse textuelle en coordonnées géographiques et proposer des suggestions d'adresses en temps réel lors de la saisie.
- **ORS** : OpenRouteService, l'API externe utilisée exclusivement pour le calcul d'itinéraire routier entre deux points géographiques dont les coordonnées sont déjà connues.
- **BAN** : Base Adresses Nationale, le référentiel officiel des adresses françaises accessible via l'API publique `https://api-adresse.data.gouv.fr`. Fournit l'autocomplétion d'adresses et les coordonnées géographiques (latitude, longitude) sans clé d'authentification.
- **Calculateur_Distance** : Le composant backend responsable du calcul de la Distance entre deux points géographiques.
- **API** : L'interface de programmation exposée par le Backend pour communiquer avec le Frontend.
- **Export_PDF** : La fonctionnalité permettant de générer un fichier PDF contenant les données d'un Résumé_Mensuel ou d'un Résumé_Annuel.
- **Carte** : La visualisation cartographique interactive affichant les itinéraires des Trajets d'une journée, rendue via Leaflet.js sur fond OpenStreetMap.
- **Cache_Adresses** : La table de base de données stockant les couples (adresse textuelle normalisée BAN, coordonnées géographiques) des adresses déjà saisies, afin d'éviter des appels répétés à l'API BAN pour des adresses déjà connues.
- **Carnet_Adresses** : Le répertoire des Adresses_Nommées gérées par l'utilisateur via un menu dédié, permettant de créer, lister, modifier et supprimer des adresses réutilisables identifiées par une Dénomination, afin de simplifier la saisie des Trajets.
- **Adresse_Nommee** : Une adresse enregistrée dans le Carnet_Adresses, caractérisée par une Dénomination unique (libellé, 1 à 100 caractères) et un texte d'adresse (1 à 255 caractères) résolu via l'autocomplétion BAN, avec ses coordonnées géographiques (latitude, longitude) stockées lorsqu'elles sont disponibles.
- **Dénomination** : Le libellé libre et unique (1 à 100 caractères) identifiant une Adresse_Nommee dans le Carnet_Adresses (ex. « Bureau », « Client Dupont », « Domicile »).
- **Profil** : Les paramètres personnels de l'utilisateur, notamment son adresse de domicile.
- **Adresse_Domicile** : L'adresse personnelle de l'utilisateur enregistrée dans son Profil, utilisable en un clic lors de la saisie d'un Trajet.
- **Profil_Configure** : L'état du Profil considéré comme configuré lorsqu'un Profil est enregistré avec un nom et un prénom non vides ET qu'au moins un Véhicule (plaque, Puissance_Fiscale, Date_Effet) est enregistré. À l'inverse, le Profil est dit « non configuré » lorsqu'aucun Profil n'est enregistré avec un nom et un prénom non vides, ou lorsqu'aucun Véhicule n'est enregistré.
- **Onboarding** : Le parcours d'initialisation présenté à la première utilisation de l'Application lorsque le Profil est non configuré, invitant l'utilisateur à renseigner son identité (nom, prénom, matricule), éventuellement son Adresse_Domicile, et au moins un Véhicule avant d'accéder aux fonctionnalités de saisie et de consultation des Trajets.
- **Vue_Consultation** : La vue principale de l'Application permettant la saisie et la consultation des Trajets (Sélecteur_Temporel, zone des Trajets, Panneau_Totaux, Carte).
- **Commentaire** : Un texte optionnel associé à un Trajet, permettant à l'utilisateur d'annoter un déplacement (0 à 500 caractères).
- **Trajet_Précédent** : Le Trajet immédiatement antérieur dans la liste chronologique d'une journée, dont l'adresse d'arrivée peut servir de départ lors d'une insertion.
- **Trajet_Suivant** : Le Trajet immédiatement postérieur dans la liste chronologique d'une journée, dont l'adresse de départ peut servir d'arrivée lors d'une insertion.
- **Position** : Un entier positif associé à chaque Trajet au sein d'une même journée, déterminant son rang dans la liste affichée. L'ordre d'affichage est toujours trié par Position croissante, indépendamment de l'heure de création du Trajet.
- **Sélecteur_Temporel** : Le composant de navigation permettant de choisir successivement une année, un mois puis une journée pour consulter les Trajets correspondants.
- **Panneau_Totaux** : La zone latérale, toujours visible, affichant les totaux kilométriques du jour, du mois sélectionné et de l'année sélectionnée (cumul jusqu'au mois sélectionné inclus).
- **Total_Annuel_Partiel** : Le total kilométrique de l'année sélectionnée cumulant uniquement les Trajets jusqu'au mois sélectionné inclus, sans inclure les mois postérieurs.
- **Véhicule** : Un véhicule de l'utilisateur, caractérisé par une plaque d'immatriculation et une puissance fiscale, avec une date d'effet à partir de laquelle il devient le véhicule courant. L'historique des Véhicules est conservé.
- **Puissance_Fiscale** : La puissance administrative du Véhicule exprimée en chevaux fiscaux (CV), utilisée notamment pour le calcul du barème kilométrique.
- **Véhicule_Courant** : Le Véhicule dont la date d'effet est la plus récente parmi celles antérieures ou égales à la date du jour ; véhicule associé par défaut aux nouveaux Trajets.
- **Date_Effet** : La date (inférieure ou égale à la date du jour) à partir de laquelle un Véhicule devient le Véhicule_Courant.
- **Réaffectation** : L'opération consistant à réassocier des Trajets existants à un Véhicule différent lors d'un changement de véhicule.
- **Bareme_Kilometrique** : Le barème de remboursement kilométrique officiel (type URSSAF) pour une année donnée. Pour chaque Puissance_Fiscale, il comprend trois tranches de kilométrage annuel : jusqu'à 5 000 km, de 5 001 à 20 000 km, et au-delà de 20 000 km. Chaque tranche est définie par un coefficient (€/km) et une base (montant fixe en €, nul pour les tranches 1 et 3). La formule d'une tranche est : `montant = km × coefficient + base`.
- **Tranche_Bareme** : Une ligne du Bareme_Kilometrique pour une (année, Puissance_Fiscale, tranche de kilométrage) donnée : (borne_min_km, borne_max_km, coefficient €/km, base €).
- **Montant_Previsionnel** : Une indication de versement mensuel affichée à titre informatif, calculée comme 80% du Bareme_Kilometrique appliqué au Total_Annuel_Partiel. Ce montant est purement indicatif et n'est jamais utilisé dans le calcul de la Regularisation ; seul le Montant_Regle saisi par l'utilisateur fait foi.
- **Montant_Regle** : Le montant effectivement versé pour un mois donné, saisi librement par l'utilisateur (variable d'un mois à l'autre, forfaitaire ou non). C'est ce montant qui fait foi pour le calcul de la Regularisation.
- **Montant_Reel** : Le montant de remboursement réellement dû pour l'année, calculé en appliquant le Bareme_Kilometrique plein (100%, sans minoration) au total kilométrique annuel complet du Véhicule. La tranche applicable est déterminée par le total kilométrique annuel (et non par les kilomètres d'un mois isolé).
- **Regularisation** : La différence entre le Montant_Reel annuel et le total des Montant_Regle versés sur l'année, indiquant le montant restant à verser (ou à récupérer si négatif).
- **Frais_Reel_Mensuel** : La quote-part mensuelle du Montant_Reel, calculée en appliquant à chaque mois le coefficient de la tranche annuelle applicable (déterminée par le total annuel) : `Frais_Reel_Mensuel = km_du_mois × coefficient_tranche_annuelle`. La somme des douze Frais_Reel_Mensuel plus la base éventuelle de la tranche annuelle est égale au Montant_Reel.
- **Coefficient_Base_Vehicule** : Le coefficient et la base de la tranche du Bareme_Kilometrique correspondant à la Puissance_Fiscale du Véhicule et au total kilométrique annuel, affichés dans l'en-tête des feuilles mensuelles (ex. « Coef. 0,447 / Base 0 »).
---
## Requirements
### Requirement 1: Saisie d'un trajet
**User Story:** En tant qu'utilisateur, je veux saisir un trajet avec une date, une adresse de départ et une adresse d'arrivée, afin de conserver un historique de mes déplacements.
#### Acceptance Criteria
1. THE Frontend SHALL afficher un formulaire de saisie contenant exactement les champs suivants : date du trajet (au format JJ/MM/AAAA), adresse de départ avec autocomplétion BAN (texte libre, 1 à 255 caractères) et adresse d'arrivée avec autocomplétion BAN (texte libre, 1 à 255 caractères).
2. WHEN l'utilisateur soumet le formulaire de saisie avec tous les champs obligatoires valides, THE Frontend SHALL envoyer les données du Trajet au Backend via l'API dans un délai maximum de 3 secondes.
3. WHEN le Backend reçoit les données d'un Trajet valide, THE Backend SHALL persister le Trajet en base de données et retourner une confirmation de création au Frontend.
4. IF le formulaire de saisie est soumis avec un ou plusieurs champs obligatoires manquants ou vides, THEN THE Frontend SHALL afficher, sans soumettre la requête au Backend, un message d'erreur identifiant chaque champ manquant ou vide.
5. IF le formulaire de saisie est soumis avec une date dont le format ne correspond pas à JJ/MM/AAAA ou dont la valeur est une date inexistante, THEN THE Frontend SHALL afficher un message d'erreur indiquant que la date est invalide sans soumettre la requête au Backend.
6. IF le Backend reçoit un Trajet dont la date est absente ou dont le format ne correspond pas à JJ/MM/AAAA, THEN THE Backend SHALL retourner une erreur HTTP 400 avec un message indiquant que la date est absente ou invalide, sans persister le Trajet.
7. IF le Backend reçoit un Trajet dont l'adresse de départ ou l'adresse d'arrivée est absente, vide ou dépasse 255 caractères, THEN THE Backend SHALL retourner une erreur HTTP 400 avec un message identifiant le champ concerné, sans persister le Trajet.
8. WHEN le Backend retourne une confirmation de création, THE Frontend SHALL afficher un message de confirmation de succès et réinitialiser les champs du formulaire de saisie.
9. IF le Frontend ne reçoit pas de réponse du Backend dans un délai de 10 secondes après la soumission, THEN THE Frontend SHALL afficher un message d'erreur indiquant que le serveur est indisponible et conserver les données saisies dans le formulaire.
10. THE Frontend SHALL afficher un champ optionnel "Commentaire" (texte libre, 0 à 500 caractères) dans le formulaire de saisie d'un Trajet.
11. WHEN le Backend persiste un Trajet, THE Backend SHALL stocker le commentaire associé au Trajet si fourni, ou null si absent.
12. IF le Backend reçoit un Trajet dont le commentaire dépasse 500 caractères, THEN THE Backend SHALL retourner une erreur HTTP 400 avec un message indiquant que le commentaire est trop long, sans persister le Trajet.
13. WHEN l'utilisateur saisit au moins 3 caractères dans le champ adresse de départ ou adresse d'arrivée, THE Frontend SHALL interroger l'API BAN (`https://api-adresse.data.gouv.fr/search/`) et afficher une liste de suggestions d'adresses françaises correspondant à la saisie dans un délai de 2 secondes.
14. WHEN l'utilisateur sélectionne une suggestion de l'API BAN, THE Frontend SHALL pré-remplir le champ adresse avec le libellé normalisé de l'adresse sélectionnée et stocker les coordonnées géographiques (latitude, longitude) associées pour transmission au Backend.
15. WHEN l'utilisateur soumet le formulaire avec une adresse sélectionnée via l'API BAN, THE Frontend SHALL transmettre au Backend les coordonnées géographiques (latitude, longitude) de chaque adresse en même temps que le texte de l'adresse.
16. IF l'API BAN est indisponible lors de la saisie, THEN THE Frontend SHALL permettre à l'utilisateur de saisir l'adresse manuellement en texte libre sans suggestion d'autocomplétion, en affichant un avertissement discret indiquant que l'autocomplétion est temporairement indisponible.
17. IF l'utilisateur soumet une adresse saisie manuellement sans passer par l'autocomplétion BAN (coordonnées non disponibles), THEN THE Frontend SHALL afficher un avertissement indiquant que la distance ne pourra pas être calculée automatiquement si les coordonnées sont absentes.
18. THE Frontend SHALL proposer, pour le champ adresse de départ et pour le champ adresse d'arrivée du formulaire de saisie, un choix entre sélectionner une Adresse_Nommee pré-enregistrée du Carnet_Adresses (par sa Dénomination) via un sélecteur et saisir une nouvelle adresse manuellement avec l'autocomplétion BAN.
19. WHEN l'utilisateur sélectionne une Adresse_Nommee (par sa Dénomination) dans le sélecteur associé au champ adresse de départ ou adresse d'arrivée, THE Frontend SHALL pré-remplir ce champ avec le texte d'adresse de l'Adresse_Nommee et associer ses coordonnées géographiques (latitude, longitude) pour transmission au Backend.
---
### Requirement 2: Calcul automatique de la distance
**User Story:** En tant qu'utilisateur, je veux que les kilomètres parcourus soient calculés automatiquement à partir des coordonnées des adresses saisies, afin de ne pas avoir à les calculer manuellement.
#### Acceptance Criteria
1. WHEN le Backend reçoit un Trajet valide dont les coordonnées de départ et d'arrivée sont toutes deux connues, THE Calculateur_Distance SHALL calculer la Distance via l'API ORS en utilisant ces coordonnées.
2. WHEN OpenRouteService (ORS) retourne avec succès la distance routière entre les deux points géographiques d'un Trajet, THE Calculateur_Distance SHALL utiliser cette valeur comme Distance du Trajet, exprimée en kilomètres avec une valeur comprise entre 0,01 km et 99 999,99 km.
3. IF OpenRouteService (ORS) ne parvient pas à calculer la distance routière entre les coordonnées de départ et d'arrivée, THEN THE Calculateur_Distance SHALL calculer la Distance en utilisant la formule orthodromique (Haversine) comme fallback, sans retourner d'erreur à l'utilisateur.
4. IF le Backend reçoit un Trajet dont les coordonnées de départ ou d'arrivée sont absentes (adresse saisie manuellement sans BAN), THEN THE Backend SHALL tenter le géocodage de l'adresse manquante via l'API BAN avant d'appeler ORS ; si le géocodage BAN échoue également, THE Backend SHALL stocker le Trajet avec une Distance nulle et retourner une réponse HTTP 201 avec un avertissement indiquant que la distance n'a pas pu être calculée.
5. WHEN la Distance d'un Trajet est calculée, THE Backend SHALL stocker la valeur de la Distance en kilomètres avec une précision de deux décimales.
6. IF le Calculateur_Distance ne reçoit pas de réponse d'OpenRouteService (ORS) dans un délai de 10 secondes, THE Backend SHALL appliquer le fallback Haversine et stocker la Distance calculée orthodromiquement, sans erreur bloquante pour l'utilisateur.
---
### Requirement 3: Consultation du résumé mensuel
**User Story:** En tant qu'utilisateur, je veux consulter le total des kilomètres parcourus pour un mois donné, afin de suivre mes déplacements mensuels.
#### Acceptance Criteria
1. WHEN l'utilisateur sélectionne un mois (valeur entre 1 et 12 inclus) et une année (valeur entre 2000 et 2100 inclus), THE Frontend SHALL afficher la liste des Trajets enregistrés pour ce mois ainsi que le Résumé_Mensuel correspondant.
2. WHEN le Backend reçoit une requête de Résumé_Mensuel pour un mois (entre 1 et 12 inclus) et une année (entre 2000 et 2100 inclus) donnés, THE Backend SHALL retourner la liste des Trajets de ce mois triés par date croissante puis par Position croissante au sein de chaque journée, avec leur Distance respective et le total kilométrique du mois.
3. WHEN le Backend reçoit une requête de Résumé_Mensuel, THE Backend SHALL calculer le total kilométrique du Résumé_Mensuel en additionnant les Distances, exprimées en kilomètres à deux décimales, de tous les Trajets du mois concerné.
4. IF aucun Trajet n'est enregistré pour le mois sélectionné, THEN THE Backend SHALL retourner un Résumé_Mensuel avec une liste vide et un total kilométrique de zéro.
5. WHEN le Frontend affiche le Résumé_Mensuel, THE Frontend SHALL afficher le total kilométrique mensuel avec une précision de deux décimales.
6. IF le Backend reçoit une requête de Résumé_Mensuel avec un mois hors de la plage 1–12 ou une année hors de la plage 2000–2100, THEN THE Backend SHALL retourner une erreur HTTP 400 avec un message descriptif.
7. IF le Backend retourne une erreur lors d'une requête de Résumé_Mensuel, THEN THE Frontend SHALL afficher un message d'erreur indiquant que le chargement des données a échoué et ne pas afficher de données kilométriques partielles.
8. WHEN le Backend calcule avec succès un Résumé_Mensuel, THE Backend SHALL retourner une réponse HTTP 200 avec le Résumé_Mensuel correspondant.
---
### Requirement 4: Consultation de la vue annuelle
**User Story:** En tant qu'utilisateur, je veux consulter le total des kilomètres parcourus pour une année entière, afin d'avoir une vision globale de mes déplacements annuels.
#### Acceptance Criteria
1. WHEN l'utilisateur sélectionne une année comprise entre 2000 et 2100 inclus, THE Frontend SHALL afficher le Résumé_Annuel présentant le total kilométrique de chaque mois et le total annuel.
2. WHEN le Backend reçoit une requête de Résumé_Annuel pour une année donnée, THE Backend SHALL retourner les douze Résumés_Mensuels de cette année, chacun avec son total kilométrique.
3. WHEN le Backend reçoit une requête de Résumé_Annuel pour une année donnée, THE Backend SHALL calculer le total kilométrique annuel en additionnant les totaux kilométriques des douze Résumés_Mensuels de l'année concernée.
4. IF aucun Trajet n'est enregistré pour un mois d'une année, THEN THE Backend SHALL inclure ce mois dans le Résumé_Annuel avec un total kilométrique de zéro.
5. WHEN le Frontend affiche le Résumé_Annuel, THE Frontend SHALL afficher chaque total kilométrique mensuel et le total annuel avec une précision de deux décimales et une valeur maximale affichable de 999 999,99 km.
6. IF le Backend retourne une erreur lors d'une requête de Résumé_Annuel, THEN THE Frontend SHALL afficher un message d'erreur indiquant que le chargement des données a échoué et ne pas afficher de données kilométriques partielles.
7. THE Backend SHALL calculer le total kilométrique annuel exclusivement en additionnant les totaux kilométriques des douze Résumés_Mensuels de l'année concernée ; aucun kilométrage non rattaché à un mois ne peut être inclus dans le total annuel.
---
### Requirement 5: Modification et suppression d'un trajet
**User Story:** En tant qu'utilisateur, je veux pouvoir modifier ou supprimer un trajet existant, afin de corriger des erreurs de saisie.
#### Acceptance Criteria
1. WHEN l'utilisateur demande la modification d'un Trajet existant, THE Frontend SHALL afficher le formulaire de saisie pré-rempli avec les données actuelles du Trajet dans un délai de 500 ms.
2. WHEN l'utilisateur soumet la modification d'un Trajet avec au moins une adresse modifiée, THE Backend SHALL recalculer la Distance et mettre à jour les données du Trajet avec la nouvelle Distance.
3. WHEN l'utilisateur soumet la modification d'un Trajet sans modifier les adresses, THE Backend SHALL mettre à jour les données du Trajet sans recalculer la Distance.
4. WHEN l'utilisateur demande la suppression d'un Trajet, THE Frontend SHALL afficher une demande de confirmation avant d'envoyer la requête de suppression au Backend.
5. WHEN l'utilisateur confirme la suppression d'un Trajet, THE Backend SHALL supprimer le Trajet de la base de données.
6. IF le Backend reçoit une demande de modification ou de suppression pour un Trajet inexistant, THEN THE Backend SHALL retourner une réponse indiquant que le Trajet demandé n'existe pas et SHALL bloquer toute mise à jour du Résumé_Mensuel associée à cette opération.
7. WHEN un Trajet est modifié ou supprimé, THE Frontend SHALL mettre à jour l'affichage du Résumé_Mensuel dans un délai de 1000 ms sans nécessiter un rechargement complet de la page.
8. IF le recalcul de la Distance échoue lors d'une modification, THEN THE Backend SHALL annuler la modification et retourner une erreur descriptive, en préservant les données existantes du Trajet sans modification.
9. WHEN l'utilisateur soumet la modification d'un Trajet avec un commentaire modifié, THE Backend SHALL mettre à jour le commentaire du Trajet sans déclencher de recalcul de distance.
10. IF un Trajet possède une Distance nulle suite à l'absence de coordonnées, THEN THE Backend SHALL accepter la modification du Trajet et tenter de recalculer la Distance si les nouvelles adresses soumises disposent de coordonnées via le Cache_Adresses ou l'API BAN.
---
### Requirement 6: Persistance et cohérence des données
**User Story:** En tant qu'utilisateur, je veux que mes données de trajets soient persistées de manière fiable, afin de les retrouver à chaque connexion.
#### Acceptance Criteria
1. THE Backend SHALL persister l'ensemble des Trajets dans une base de données relationnelle.
2. WHEN le Backend persiste un Trajet, THE Backend SHALL stocker la date du Trajet, l'adresse de départ (100 caractères maximum), l'adresse d'arrivée (100 caractères maximum) et la Distance calculée (valeur numérique positive exprimée en kilomètres, arrondie à deux décimales).
3. WHEN le Backend persiste un Trajet, IF l'un des champs obligatoires (date, adresse de départ, adresse d'arrivée, Distance) est absent ou invalide, THEN THE Backend SHALL rejeter la persistance et retourner un message d'erreur indiquant le champ manquant ou invalide sans créer d'entrée partielle.
4. WHEN un Trajet est supprimé, THE Backend SHALL mettre à jour le Résumé_Mensuel et le Résumé_Annuel correspondants en garantissant la cohérence des données en priorité ; le délai de 5 secondes est un objectif de performance et non une contrainte stricte lorsque la charge du système l'empêche.
5. THE Backend SHALL garantir que le total d'un Résumé_Mensuel est égal à la somme des Distances de tous les Trajets non supprimés du mois calendaire correspondant (année et mois identiques à la date du Trajet), avec une tolérance de 0,01 km.
6. THE Backend SHALL garantir que le total d'un Résumé_Annuel est égal à la somme des totaux des Résumés_Mensuels des douze mois de l'année calendaire correspondante, avec une tolérance de 0,01 km.
7. IF une opération d'écriture (création ou suppression de Trajet) échoue partiellement, THEN THE Backend SHALL annuler l'intégralité de l'opération et conserver l'état précédent des données sans modification partielle.
8. IF une opération d'écriture (création ou suppression de Trajet) réussit intégralement, THEN THE Backend SHALL ne pas effectuer de rollback de cette opération, quel que soit l'état du système après son exécution.
---
### Requirement 7: Export PDF des résumés
**User Story:** En tant qu'utilisateur, je veux pouvoir exporter en PDF le résumé mensuel ou annuel de mes trajets, afin de conserver une trace imprimable pour mes notes de frais.
#### Acceptance Criteria
1. WHEN l'utilisateur demande l'export PDF d'un Résumé_Mensuel, THE Backend SHALL générer un fichier PDF contenant la liste de tous les Trajets du mois (date, adresse de départ, adresse d'arrivée, distance en km) ainsi que le total kilométrique mensuel.
2. WHEN l'utilisateur demande l'export PDF d'un Résumé_Annuel, THE Backend SHALL générer un fichier PDF contenant le tableau des douze mois (mois, total kilométrique) ainsi que le total kilométrique annuel.
3. WHEN le Backend génère un Export_PDF avec succès, THE Frontend SHALL déclencher le téléchargement automatique du fichier PDF dans le navigateur de l'utilisateur. IF le mécanisme de téléchargement échoue après une génération réussie, THEN THE Frontend SHALL afficher un message d'erreur indiquant que le téléchargement a échoué.
4. THE Backend SHALL nommer le fichier PDF exporté selon le format suivant : `trajets_MM_AAAA.pdf` pour un export mensuel et `trajets_AAAA.pdf` pour un export annuel.
5. IF le Backend ne parvient pas à générer le fichier PDF dans un délai de 15 secondes, THEN THE Backend SHALL retourner une erreur et THE Frontend SHALL afficher un message indiquant que la génération du PDF a échoué.
6. WHEN le Backend génère un Export_PDF, THE Backend SHALL inclure dans le PDF : le titre "Rapport de trajets", la période couverte (mois/année ou année), et le total kilométrique de la période en en-tête du document.
7. WHEN l'utilisateur demande un export PDF (mensuel ou annuel), THE Backend SHALL n'inclure que les Trajets associés au Véhicule sélectionné et SHALL indiquer dans l'en-tête du PDF la plaque d'immatriculation et la Puissance_Fiscale du Véhicule concerné.
8. WHEN le Backend génère un Export_PDF (mensuel ou annuel), THE Backend SHALL inclure dans l'en-tête du document : le nom, le prénom, le matricule de l'utilisateur, ainsi que la plaque d'immatriculation et la Puissance_Fiscale du Véhicule concerné.
9. IF des Trajets de plusieurs Véhicules existent pour la période (mois ou année) demandée, THEN THE Backend SHALL générer un rapport PDF distinct par Véhicule.
10. WHEN le Backend génère un Export_PDF mensuel, THE Backend SHALL faire figurer dans l'en-tête : le Véhicule (plaque, puissance fiscale, coefficient et base de la tranche applicable), la période, l'adresse de départ du Profil, le total kilométrique du mois, le total kilométrique annuel cumulé, le TOTAL IK du mois (kilomètres du mois × coefficient de la tranche annuelle) et le Montant_Regle du mois.
11. WHEN le Backend génère un Export_PDF mensuel, THE Backend SHALL faire figurer la liste des Trajets du mois avec, pour chaque Trajet : la date, le motif (commentaire), l'adresse de départ, l'adresse d'arrivée et la distance en kilomètres.
12. WHEN le Backend génère un Export_PDF annuel (synthèse), THE Backend SHALL faire figurer : le nom et le prénom de l'utilisateur, l'année, l'adresse de domicile, le Bareme_Kilometrique applicable pour l'année, le Véhicule et sa puissance fiscale, la tranche applicable ; puis pour chacun des douze mois : les kilomètres parcourus, le Montant_Regle (frais payé) et le Frais_Reel_Mensuel (frais réel) ; et enfin les totaux : total kilométrique annuel, total réglé, Montant_Reel, et Regularisation (Montant_Reel − total réglé).
---
### Requirement 8: Visualisation des trajets sur une carte
**User Story:** En tant qu'utilisateur, je veux visualiser sur une carte les trajets effectués lors d'une journée donnée, afin d'avoir une représentation géographique de mes déplacements.
#### Acceptance Criteria
1. WHEN l'utilisateur sélectionne une date, THE Frontend SHALL afficher une carte interactive centrée sur la zone géographique couvrant l'ensemble des Trajets de cette journée, ou centrée sur une position par défaut si aucun Trajet n'est disponible.
2. WHEN la carte est affichée pour une journée donnée, THE Frontend SHALL représenter chaque Trajet de la journée par un tracé d'itinéraire routier (polyline) sur la carte, avec un marqueur au point de départ et un marqueur au point d'arrivée.
3. WHEN l'utilisateur survole ou clique sur un tracé de Trajet sur la carte, THE Frontend SHALL afficher une infobulle indiquant l'adresse de départ, l'adresse d'arrivée et la distance du Trajet.
4. IF aucun Trajet n'est enregistré pour la date sélectionnée, THEN THE Frontend SHALL afficher la carte interactive vide avec un message indiquant qu'aucun trajet n'est disponible pour cette journée.
5. IF la géométrie d'itinéraire d'un Trajet n'est pas disponible (coordonnées manquantes ou ORS indisponible), THEN THE Frontend SHALL afficher ce Trajet comme une ligne droite entre le point de départ et le point d'arrivée, avec une indication visuelle que l'itinéraire est approximatif.
6. THE Frontend SHALL permettre à l'utilisateur de zoomer et de se déplacer sur la carte de manière interactive.
7. WHEN le Backend reçoit une requête de trajets pour une journée donnée avec visualisation, THE Backend SHALL retourner pour chaque Trajet les coordonnées géographiques de départ et d'arrivée ainsi que la géométrie de l'itinéraire (liste de points GeoJSON) si disponible dans le Cache_Adresses.
8. IF le service de carte (Leaflet.js ou les tuiles OpenStreetMap) est indisponible ou inaccessible, THEN THE Frontend SHALL afficher un message d'erreur indiquant que la carte ne peut pas être chargée et proposer à l'utilisateur de réessayer.
---
### Requirement 9: Cache et réutilisation des adresses géocodées
**User Story:** En tant qu'utilisateur, je veux que les adresses que j'ai déjà saisies soient mémorisées et réutilisées automatiquement, afin d'éviter des appels répétés à l'API BAN et de gagner du temps lors de la saisie de nouveaux trajets.
#### Acceptance Criteria
1. WHEN l'utilisateur sélectionne une adresse via l'autocomplétion BAN lors de la création ou modification d'un Trajet, THE Backend SHALL stocker dans le Cache_Adresses le libellé normalisé BAN de l'adresse, sa latitude et sa longitude si cette adresse n'y est pas déjà présente.
2. WHEN le Backend reçoit un Trajet dont l'adresse de départ ou d'arrivée correspond exactement à une entrée du Cache_Adresses, THE Backend SHALL utiliser les coordonnées du Cache_Adresses sans appeler l'API BAN.
3. WHEN l'utilisateur saisit au moins 3 caractères dans un champ adresse, THE Frontend SHALL d'abord interroger le Cache_Adresses local via le Backend pour afficher les suggestions d'adresses déjà connues, puis compléter avec les résultats de l'API BAN si le Cache_Adresses retourne moins de 5 suggestions.
4. WHEN l'utilisateur sélectionne une suggestion du Cache_Adresses ou de l'API BAN, THE Frontend SHALL pré-remplir le champ adresse avec le libellé complet et stocker les coordonnées associées pour transmission au Backend.
5. IF le Cache_Adresses contient plus de 1 000 entrées, THEN THE Backend SHALL conserver les 1 000 entrées les plus récemment utilisées et supprimer les plus anciennes (politique LRU).
6. THE Cache_Adresses SHALL stocker pour chaque adresse : le libellé normalisé BAN (255 caractères maximum), la latitude (précision de 6 décimales), la longitude (précision de 6 décimales) et la date de dernière utilisation.
---
### Requirement 10: Profil utilisateur, adresse de domicile et véhicules
**User Story:** En tant qu'utilisateur, je veux gérer mon profil (identité, matricule, adresse de domicile et mes véhicules avec leur puissance fiscale), afin d'associer automatiquement chaque trajet au bon véhicule et de suivre mes kilomètres par véhicule.
#### Acceptance Criteria
1. THE Frontend SHALL afficher une page de profil permettant à l'utilisateur de saisir et de sauvegarder : son nom (1 à 100 caractères), son prénom (1 à 100 caractères), un matricule (1 à 8 caractères maximum), et une Adresse_Domicile (texte libre, 1 à 255 caractères).
2. IF le matricule saisi dépasse 8 caractères, THEN THE Frontend SHALL empêcher la saisie au-delà de 8 caractères et THE Backend SHALL rejeter tout matricule de plus de 8 caractères avec une erreur HTTP 400.
3. WHEN l'utilisateur sauvegarde une Adresse_Domicile, THE Backend SHALL géocoder l'adresse via l'API BAN pour obtenir ses coordonnées et les stocker dans le Profil ainsi que dans le Cache_Adresses ; si le géocodage BAN échoue, THE Backend SHALL persister l'Adresse_Domicile textuelle sans coordonnées.
4. THE Frontend SHALL afficher un bouton "Utiliser mon domicile" dans le formulaire de saisie d'un Trajet, à côté du champ adresse de départ et à côté du champ adresse d'arrivée.
5. WHEN l'utilisateur clique sur le bouton "Utiliser mon domicile" à côté d'un champ adresse, THE Frontend SHALL pré-remplir ce champ avec l'Adresse_Domicile enregistrée dans le Profil.
6. IF aucune Adresse_Domicile n'est enregistrée dans le Profil, THEN THE Frontend SHALL désactiver le bouton "Utiliser mon domicile" et afficher une infobulle indiquant que l'adresse de domicile n'est pas encore configurée.
7. WHEN l'utilisateur modifie l'Adresse_Domicile dans le Profil et sauvegarde, THE Backend SHALL re-géocoder la nouvelle adresse via l'API BAN pour mettre à jour les coordonnées dans le Profil et dans le Cache_Adresses.
8. IF le géocodage BAN de l'Adresse_Domicile échoue, THEN THE Backend SHALL sauvegarder l'Adresse_Domicile textuelle sans coordonnées et THE Frontend SHALL afficher un avertissement indiquant que l'adresse n'a pas pu être vérifiée.
9. THE Frontend SHALL permettre à l'utilisateur de saisir un Véhicule caractérisé par une plaque d'immatriculation (1 à 15 caractères) et une Puissance_Fiscale (entier positif, exprimé en chevaux fiscaux).
10. WHEN l'utilisateur enregistre un nouveau Véhicule, THE Frontend SHALL exiger la saisie d'une Date_Effet inférieure ou égale à la date du jour.
11. IF l'utilisateur saisit une Date_Effet postérieure à la date du jour, THEN THE Frontend SHALL rejeter la saisie et afficher un message indiquant que la date d'effet ne peut pas être dans le futur.
12. WHEN le Backend enregistre un nouveau Véhicule, THE Backend SHALL conserver l'historique complet des Véhicules précédents sans les supprimer.
13. THE Backend SHALL déterminer le Véhicule_Courant comme étant le Véhicule dont la Date_Effet est la plus récente parmi celles inférieures ou égales à la date du jour.
14. WHEN l'utilisateur crée un nouveau Trajet, THE Backend SHALL associer par défaut ce Trajet à la Puissance_Fiscale du Véhicule_Courant à la date du Trajet et stocker cette Puissance_Fiscale sur le Trajet.
15. WHEN l'utilisateur enregistre un nouveau Véhicule avec une Date_Effet donnée, IF il existe des Trajets déjà saisis dont la date est supérieure ou égale à cette Date_Effet et qui sont associés à une Puissance_Fiscale différente de celle du nouveau Véhicule, THEN THE Frontend SHALL proposer à l'utilisateur la Réaffectation de ces Trajets à la Puissance_Fiscale du nouveau Véhicule.
16. WHEN l'utilisateur accepte la Réaffectation, THE Backend SHALL mettre à jour la Puissance_Fiscale (et l'association au Véhicule) de tous les Trajets concernés (date >= Date_Effet) avec celle du nouveau Véhicule.
17. WHEN l'utilisateur refuse la Réaffectation, THE Backend SHALL laisser inchangés les Trajets déjà saisis et associer uniquement les prochains Trajets au nouveau Véhicule.
18. THE Backend SHALL exposer la liste de l'historique des Véhicules avec leur plaque, leur Puissance_Fiscale et leur Date_Effet.
---
### Requirement 11: Insertion d'un trajet entre deux saisies existantes
**User Story:** En tant qu'utilisateur, je veux insérer un nouveau trajet entre deux trajets existants d'une même journée, afin de compléter une journée de déplacements en conservant la continuité des adresses.
#### Acceptance Criteria
1. WHEN l'utilisateur demande l'insertion d'un Trajet entre deux Trajets existants d'une même journée (Trajet_Précédent et Trajet_Suivant), THE Frontend SHALL afficher le formulaire de saisie avec le champ adresse de départ pré-rempli avec l'adresse d'arrivée du Trajet_Précédent et le champ adresse d'arrivée pré-rempli avec l'adresse de départ du Trajet_Suivant.
2. WHEN l'utilisateur choisit d'insérer un Trajet après un Trajet_Précédent sans Trajet_Suivant connu, THE Frontend SHALL afficher le formulaire de saisie avec le champ adresse de départ pré-rempli avec l'adresse d'arrivée du Trajet_Précédent et le champ adresse d'arrivée vide.
3. WHEN l'utilisateur choisit d'insérer un Trajet avant un Trajet_Suivant sans Trajet_Précédent connu, THE Frontend SHALL afficher le formulaire de saisie avec le champ adresse d'arrivée pré-rempli avec l'adresse de départ du Trajet_Suivant et le champ adresse de départ vide.
4. THE Frontend SHALL permettre à l'utilisateur de modifier les adresses de départ et d'arrivée pré-remplies avant de soumettre le formulaire d'insertion.
5. WHEN l'utilisateur soumet le formulaire d'insertion avec tous les champs obligatoires valides, THE Backend SHALL créer le nouveau Trajet avec les mêmes règles de validation et de calcul de distance que la création standard (Exigence 1 et 2).
6. WHEN un Trajet est inséré avec succès, THE Frontend SHALL rafraîchir l'affichage de la liste des Trajets de la journée sans rechargement complet de la page, en positionnant le nouveau Trajet à sa place chronologique dans la liste.
7. THE Backend SHALL maintenir pour chaque Trajet un champ Position (entier positif) représentant son rang dans la liste des Trajets d'une même journée, indépendamment de l'heure de création du Trajet.
8. WHEN l'utilisateur insère un Trajet entre un Trajet_Précédent de position P et un Trajet_Suivant de position P+1 ou plus, THE Backend SHALL attribuer au Trajet inséré une Position comprise entre P et P+1 (valeur fractionnaire ou réordonnancement) de sorte que la liste des Trajets de la journée soit triée par Position croissante et que le Trajet inséré apparaisse bien entre les deux trajets existants.
9. WHEN l'utilisateur réordonne manuellement les Trajets d'une journée (glisser-déposer), THE Backend SHALL mettre à jour les valeurs de Position des Trajets concernés de manière à refléter le nouvel ordre sans modifier les autres données du Trajet.
10. THE Frontend SHALL proposer, dans le formulaire d'insertion, pour le champ adresse de départ et pour le champ adresse d'arrivée (y compris lorsqu'ils sont pré-remplis à partir du Trajet_Précédent ou du Trajet_Suivant), un choix entre sélectionner une Adresse_Nommee pré-enregistrée du Carnet_Adresses (par sa Dénomination) et saisir une nouvelle adresse manuellement avec l'autocomplétion BAN.
11. WHEN l'utilisateur sélectionne une Adresse_Nommee (par sa Dénomination) dans le formulaire d'insertion pour le champ adresse de départ ou adresse d'arrivée, THE Frontend SHALL remplacer le contenu pré-rempli de ce champ par le texte d'adresse de l'Adresse_Nommee et associer ses coordonnées géographiques (latitude, longitude) pour transmission au Backend.
---
### Requirement 12: Navigation temporelle et mise en page
**User Story:** En tant qu'utilisateur, je veux naviguer facilement entre les années, mois et journées ayant des données, et voir en permanence les totaux et la carte, afin de consulter et saisir mes trajets de façon fluide.
#### Acceptance Criteria
1. THE Frontend SHALL afficher un Sélecteur_Temporel permettant de choisir successivement une année, puis un mois, puis une journée.
2. THE Frontend SHALL proposer dans le sélecteur d'année uniquement l'année courante et les années pour lesquelles au moins un Trajet est enregistré.
3. WHEN l'utilisateur sélectionne une année, THE Frontend SHALL afficher les douze mois de cette année et mettre en surbrillance les mois comportant au moins un Trajet.
4. WHEN l'utilisateur sélectionne un mois, THE Frontend SHALL afficher les jours de ce mois et mettre en surbrillance les jours comportant au moins un Trajet.
5. THE Frontend SHALL empêcher la sélection de toute date postérieure à la date courante (aucune date future sélectionnable).
6. THE Frontend SHALL afficher la zone des Trajets de la journée sélectionnée comme zone principale, avec défilement vertical (scroll) lorsque le nombre de Trajets dépasse la hauteur visible.
7. THE Frontend SHALL afficher un Panneau_Totaux latéral toujours visible contenant : le total kilométrique du jour sélectionné, le total kilométrique du mois sélectionné, et le Total_Annuel_Partiel de l'année sélectionnée.
8. THE Panneau_Totaux SHALL calculer le Total_Annuel_Partiel en cumulant uniquement les Trajets de l'année sélectionnée jusqu'au mois sélectionné inclus, sans inclure les mois postérieurs au mois sélectionné.
9. THE Frontend SHALL afficher, sous le Panneau_Totaux, la Carte des Trajets de la journée sélectionnée.
10. THE Frontend SHALL afficher un bouton "Jour précédent" et un bouton "Jour suivant" permettant de naviguer d'une journée à l'autre.
11. WHEN l'utilisateur clique sur "Jour précédent", THE Frontend SHALL sélectionner la journée calendaire immédiatement antérieure et rafraîchir la zone des Trajets, le Panneau_Totaux et la Carte.
12. WHEN l'utilisateur clique sur "Jour suivant", THE Frontend SHALL sélectionner la journée calendaire immédiatement postérieure et rafraîchir la zone des Trajets, le Panneau_Totaux et la Carte.
13. IF la journée suivante est postérieure à la date courante, THEN THE Frontend SHALL désactiver le bouton "Jour suivant".
14. WHEN l'utilisateur change de jour, de mois ou d'année via le Sélecteur_Temporel ou les boutons de navigation, THE Frontend SHALL mettre à jour la zone des Trajets, le Panneau_Totaux et la Carte dans un délai de 1000 ms sans rechargement complet de la page.
15. WHEN le Backend reçoit une requête des années disponibles, THE Backend SHALL retourner la liste des années comportant au moins un Trajet, ainsi que l'année courante si elle n'y figure pas déjà.
16. WHEN le Backend reçoit une requête des jours comportant des Trajets pour un mois et une année donnés, THE Backend SHALL retourner la liste des jours de ce mois pour lesquels au moins un Trajet est enregistré.
17. THE Frontend SHALL afficher un sélecteur de Véhicule permettant de choisir le Véhicule dont les données sont affichées ; par défaut, le Véhicule_Courant est sélectionné.
18. WHEN l'utilisateur sélectionne un Véhicule dans le sélecteur de Véhicule, THE Frontend SHALL filtrer la zone des Trajets, le Panneau_Totaux et la Carte pour n'afficher que les Trajets associés à ce Véhicule.
19. THE Panneau_Totaux SHALL calculer le total du jour, le total du mois et le Total_Annuel_Partiel uniquement à partir des Trajets associés au Véhicule sélectionné.
20. WHEN l'utilisateur change de Véhicule sélectionné, THE Frontend SHALL mettre à jour la zone des Trajets, le Panneau_Totaux et la Carte dans un délai de 1000 ms sans rechargement complet de la page.
---
### Requirement 13: Barème kilométrique et indication de versement
**User Story:** En tant qu'utilisateur, je veux saisir le barème kilométrique officiel par année et par puissance fiscale, et voir une indication de versement mensuel, afin d'estimer mes remboursements en cours d'année.
#### Acceptance Criteria
1. THE Frontend SHALL afficher un menu de saisie du Bareme_Kilometrique par année, permettant de saisir, pour chaque Puissance_Fiscale, les trois tranches de kilométrage : jusqu'à 5 000 km, de 5 001 à 20 000 km, et au-delà de 20 000 km, chacune avec son coefficient (€/km) et sa base (€).
2. WHEN l'utilisateur enregistre une Tranche_Bareme, THE Backend SHALL persister la tranche associée à l'année, à la Puissance_Fiscale et à la borne de kilométrage concernées.
3. WHEN le Backend applique le Bareme_Kilometrique au total kilométrique annuel d'un Véhicule pour une année donnée, THE Backend SHALL sélectionner la tranche dont l'intervalle de kilométrage contient le total annuel, puis calculer le montant selon la formule `montant = total_km_annuel × coefficient + base`.
4. IF aucune Tranche_Bareme n'existe pour la Puissance_Fiscale et l'année d'un calcul, THEN THE Backend SHALL retourner le montant zéro et signaler que le barème n'est pas configuré pour cette combinaison.
5. WHEN l'utilisateur consulte les données d'un mois, THE Frontend SHALL afficher à titre indicatif le Montant_Previsionnel, calculé comme 80% du Bareme_Kilometrique appliqué au Total_Annuel_Partiel du Véhicule sélectionné.
6. THE Frontend SHALL indiquer explicitement que le Montant_Previsionnel (80%) est une simple indication de versement et que le montant réellement versé est celui saisi par l'utilisateur (Montant_Regle).
7. THE Frontend SHALL afficher tous les montants en euros avec une précision de deux décimales.
8. THE Frontend SHALL afficher, dans l'en-tête de chaque feuille mensuelle, le Coefficient_Base_Vehicule (coefficient et base de la tranche applicable selon la puissance fiscale du Véhicule et le total kilométrique annuel).
---
### Requirement 14: Montant réglé mensuel et régularisation annuelle
**User Story:** En tant qu'utilisateur, je veux saisir le montant réellement versé chaque mois et voir la régularisation annuelle, afin de connaître le solde restant à verser en fin d'année.
#### Acceptance Criteria
1. THE Frontend SHALL permettre à l'utilisateur de saisir un Montant_Regle (valeur numérique positive en euros, deux décimales) pour un mois et une année donnés, associé à un Véhicule ; ce montant est saisi librement et peut différer d'un mois à l'autre.
2. WHEN l'utilisateur enregistre un Montant_Regle, THE Backend SHALL persister ce montant associé au mois, à l'année et au Véhicule concernés.
3. IF un Montant_Regle existe déjà pour le même mois, la même année et le même Véhicule, THEN THE Backend SHALL mettre à jour le montant existant plutôt que d'en créer un nouveau.
4. IF l'utilisateur saisit un Montant_Regle négatif, THEN THE Backend SHALL rejeter la saisie avec une erreur HTTP 400.
5. WHEN le Backend calcule le Montant_Reel annuel d'un Véhicule, THE Backend SHALL appliquer le Bareme_Kilometrique plein (100%) au total kilométrique annuel du Véhicule, en utilisant la tranche déterminée par ce total annuel.
6. WHEN le Backend calcule le Frais_Reel_Mensuel de chaque mois, THE Backend SHALL multiplier les kilomètres du mois par le coefficient de la tranche annuelle applicable, de sorte que la somme des douze Frais_Reel_Mensuel plus la base de la tranche annuelle soit égale au Montant_Reel annuel.
7. WHEN le Backend calcule la Regularisation d'un Véhicule pour une année, THE Backend SHALL soustraire le total des Montant_Regle de l'année du Montant_Reel annuel : `Regularisation = Montant_Reel − Total_Regle`.
8. THE Frontend SHALL afficher le Montant_Regle, le Frais_Reel_Mensuel, le Montant_Reel annuel, le total réglé et la Regularisation avec une précision de deux décimales.
---
### Requirement 15: Carnet d'adresses nommées
**User Story:** En tant qu'utilisateur, je veux gérer un carnet d'adresses nommées (créer, lister, modifier, supprimer) et pouvoir réutiliser ces adresses par leur dénomination lors de la saisie d'un trajet, afin de simplifier et d'accélérer la saisie de mes déplacements.
#### Acceptance Criteria
1. THE Frontend SHALL afficher un menu dédié au Carnet_Adresses permettant à l'utilisateur de créer, lister, modifier et supprimer des Adresses_Nommées.
2. THE Frontend SHALL afficher, pour la création ou la modification d'une Adresse_Nommee, un champ Dénomination (texte libre, 1 à 100 caractères) et un champ adresse avec autocomplétion BAN (texte libre, 1 à 255 caractères).
3. WHEN l'utilisateur saisit au moins 3 caractères dans le champ adresse d'une Adresse_Nommee, THE Frontend SHALL interroger l'API BAN après un délai d'inactivité de 300 millisecondes et afficher au maximum 5 suggestions.
4. WHEN l'utilisateur sélectionne une suggestion de l'API BAN lors de la saisie du champ adresse d'une Adresse_Nommee, THE Frontend SHALL pré-remplir le champ avec le libellé normalisé et associer les coordonnées géographiques (latitude, longitude) pour transmission au Backend.
5. WHEN le Backend reçoit une Adresse_Nommee valide, THE Backend SHALL persister en base de données la Dénomination, le texte d'adresse et, lorsqu'elles sont disponibles, les coordonnées géographiques (latitude, longitude).
6. IF le Backend reçoit une Adresse_Nommee dont la Dénomination est absente, vide ou dépasse 100 caractères, THEN THE Backend SHALL retourner une erreur HTTP 400 avec un message indiquant que la Dénomination est absente ou invalide, sans persister l'Adresse_Nommee.
7. IF le Backend reçoit une Adresse_Nommee dont la Dénomination est identique, après normalisation (suppression des espaces de début et de fin, comparaison insensible à la casse et aux accents), à celle d'une Adresse_Nommee déjà enregistrée, THEN THE Backend SHALL retourner une erreur HTTP 400 avec un message indiquant que la Dénomination est déjà utilisée, sans persister l'Adresse_Nommee.
8. IF le Backend reçoit une Adresse_Nommee dont le texte d'adresse est absent, vide ou dépasse 255 caractères, THEN THE Backend SHALL retourner une erreur HTTP 400 avec un message indiquant que l'adresse est absente ou invalide, sans persister l'Adresse_Nommee.
9. WHEN le Backend persiste une Adresse_Nommee dont les coordonnées géographiques sont disponibles, THE Backend SHALL enregistrer le couple (libellé normalisé, latitude, longitude) dans le Cache_Adresses s'il n'y est pas déjà présent, afin que cette adresse soit réutilisable et mise en cache.
10. WHEN le Backend reçoit une Adresse_Nommee dont le texte d'adresse correspond, après normalisation (suppression des espaces de début et de fin, comparaison insensible à la casse et aux accents), à une entrée du Cache_Adresses, THE Backend SHALL réutiliser les coordonnées du Cache_Adresses sans appeler l'API BAN.
11. WHEN l'utilisateur modifie le texte d'adresse d'une Adresse_Nommee existante et enregistre, et que ce texte ne correspond à aucune entrée du Cache_Adresses après normalisation, THE Backend SHALL re-géocoder la nouvelle adresse via l'API BAN dans un délai maximal de 5 secondes afin de rafraîchir les coordonnées géographiques (latitude, longitude) de l'Adresse_Nommee.
12. IF l'API BAN est indisponible ou ne répond pas dans le délai de 5 secondes lors du géocodage ou du re-géocodage d'une Adresse_Nommee, THEN THE Backend SHALL persister l'Adresse_Nommee sans coordonnées géographiques et retourner une réponse indiquant que les coordonnées n'ont pas pu être obtenues.
13. WHEN l'utilisateur demande la suppression d'une Adresse_Nommee, THE Frontend SHALL afficher une demande de confirmation avant d'envoyer la requête de suppression au Backend.
14. WHEN l'utilisateur confirme la suppression d'une Adresse_Nommee, THE Backend SHALL supprimer l'Adresse_Nommee du Carnet_Adresses en conservant inchangés les Trajets déjà enregistrés, qui gardent leur texte d'adresse et leurs coordonnées géographiques stockés.
15. WHEN le Backend a créé, modifié ou supprimé une Adresse_Nommee avec succès, THE Frontend SHALL afficher un message de confirmation indiquant que l'opération a réussi.
16. WHEN le Frontend affiche le formulaire de saisie ou d'insertion d'un Trajet, THE Frontend SHALL proposer la liste des Adresses_Nommées du Carnet_Adresses par leur Dénomination pour le champ adresse de départ et pour le champ adresse d'arrivée.
---
### Requirement 16: Initialisation du profil à la première utilisation
**User Story:** En tant qu'utilisateur, je veux que l'application me présente la saisie de mon profil dès la première connexion, afin de configurer mon identité et mon véhicule avant de commencer à saisir mes trajets.
#### Acceptance Criteria
1. THE Backend SHALL considérer le Profil comme non configuré tant qu'aucun Profil n'est enregistré avec un nom et un prénom non vides, ou tant qu'aucun Véhicule (plaque, Puissance_Fiscale, Date_Effet) n'est enregistré.
2. THE Backend SHALL exposer un indicateur de configuration du Profil (`estConfigure`) valant vrai lorsque le Profil est un Profil_Configure et faux dans le cas contraire.
3. WHEN l'utilisateur ouvre l'Application, WHILE le Profil est non configuré, THE Frontend SHALL présenter le parcours d'Onboarding (formulaire de saisie du Profil) au lieu de la Vue_Consultation.
4. WHILE le Profil est non configuré, THE Frontend SHALL bloquer l'accès à la saisie et à la consultation des Trajets et maintenir l'affichage du parcours d'Onboarding.
5. THE Frontend SHALL exiger, dans le parcours d'Onboarding, la saisie du nom (1 à 100 caractères), du prénom (1 à 100 caractères) et du matricule (1 à 8 caractères) avant d'autoriser la validation du Profil.
6. THE Frontend SHALL exiger, dans le parcours d'Onboarding, la saisie d'au moins un Véhicule comportant une plaque d'immatriculation (1 à 15 caractères), une Puissance_Fiscale (entier positif) et une Date_Effet inférieure ou égale à la date du jour avant d'autoriser la validation du Profil.
7. THE Frontend SHALL présenter, dans le parcours d'Onboarding, un champ Adresse_Domicile optionnel (texte libre, 1 à 255 caractères) que l'utilisateur peut renseigner immédiatement ou compléter ultérieurement via la page de profil.
8. IF l'utilisateur tente de valider le parcours d'Onboarding alors qu'un champ d'identité obligatoire (nom, prénom, matricule) est manquant ou invalide, ou qu'aucun Véhicule valide n'est saisi, THEN THE Frontend SHALL afficher un message d'erreur identifiant chaque élément manquant ou invalide sans soumettre les données au Backend.
9. WHEN l'utilisateur valide le parcours d'Onboarding avec un Profil et au moins un Véhicule valides, THE Backend SHALL persister le Profil et le Véhicule et retourner un indicateur `estConfigure` valant vrai.
10. WHEN le Backend confirme l'enregistrement d'un Profil_Configure, THE Frontend SHALL marquer le Profil comme configuré et naviguer vers la Vue_Consultation dans un délai de 1000 ms.
11. IF le Profil est déjà un Profil_Configure lors de l'ouverture de l'Application, THEN THE Frontend SHALL afficher directement la Vue_Consultation sans présenter le parcours d'Onboarding.
12. IF la persistance du Profil ou du Véhicule échoue au niveau du Backend lors de la soumission du parcours d'Onboarding, THEN THE Frontend SHALL afficher un message d'erreur indiquant l'échec de l'enregistrement, ne pas marquer le Profil comme configuré et conserver les données saisies par l'utilisateur.
13. IF la récupération de l'indicateur `estConfigure` échoue lors de l'ouverture de l'Application, THEN THE Frontend SHALL afficher un message d'erreur indiquant un problème de connexion au serveur et proposer de réessayer, sans présenter la Vue_Consultation.