Réorganisation complète de la navigation suite au retour d'usage, plus changement de mot de passe et affichage/masquage des mots de passe saisis. Backend : - Nouvel endpoint POST /api/auth/change-password (ChangePasswordDto, MotDePasseActuelInvalideException, validateur, AuthService.ChangePasswordAsync). Exige l'ancien mot de passe, distinct de la réinitialisation par e-mail. - Endpoint placé dans SessionController (protégé par l'autorisation globale) et NON dans AuthController : AuthController porte [AllowAnonymous] au niveau classe, ce qui rend inopérant un [Authorize] posé sur une seule action (bug de sécurité détecté par test manuel — l'endpoint était accessible sans jeton — puis corrigé par ce déplacement). Frontend : - Page « Compte utilisateur » (ProfilComponent renommé) : section Maintenance extraite vers MaintenanceComponent (route /maintenance) ; nouvelle section de changement de mot de passe (ancien + nouveau + confirmation). - ParametrageComponent (route /parametrage) : menu latéral Compte / Véhicules / Barème + contenu en routes enfants, réutilisant les composants existants. Compte et Véhicules en authGuard seul ; Barème garde profilConfigureGuard. - Barre de navigation : Véhicules et Barème retirés (rejoignent Paramétrage) ; logo passé en format horizontal (icône + texte). - Avatar utilisateur (initiales) à droite de la barre : sous-menu au survol (identité, Paramétrage, Maintenance, Se déconnecter). Menu « Specs » converti en dropdown survolable (Exigences / Conception / Tâches). - Dropdowns : panneau collé au bouton (plus d'espace mort) pour que le survol reste continu jusqu'au panneau — un espace créait une fermeture prématurée. - Barre masquée sur les pages publiques (login/register/forgot/reset-password) même avec un jeton résiduel expiré/invalide (détection via Router events, pas seulement la présence du jeton). - PasswordInputComponent (shared) : bouton œil afficher/masquer, ControlValueAccessor, style aligné sur les champs texte classiques. Appliqué aux 8 champs mot de passe de l'application (login, register, reset-password, compte utilisateur). Specs : requirements.md (exigences 20, 21 + glossaire) et design.md (endpoints, DTO, composants) mis à jour. 54 tests backend au vert.
491 lines
74 KiB
Markdown
491 lines
74 KiB
Markdown
# Requirements Document
|
||
|
||
## Introduction
|
||
|
||
L'application **SchotzIAD KM Tracker** est une application web **mono-utilisateur** protégée par un **compte unique authentifié** (adresse e-mail + mot de passe, session par jeton JWT). Elle permet de saisir et de suivre ses trajets mensuels (jour, adresse de départ, adresse d'arrivée, éventuellement en aller/retour), de calculer automatiquement les kilomètres parcourus par mois, et de consulter une vue annuelle consolidée des kilomètres parcourus. L'application ne gère qu'un seul compte : la première utilisation crée ce compte via un parcours d'inscription, et toute utilisation ultérieure exige une authentification.
|
||
|
||
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 (repliable) permettant de choisir successivement une année puis un mois pour consulter les Trajets correspondants. La journée affichée sur la Carte n'est pas choisie par des boutons de navigation mais suit le survol des Trajets dans la liste.
|
||
- **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 »).
|
||
- **Compte** : L'unique compte de l'Application, identifié par une adresse e-mail (servant de login) et protégé par un mot de passe. L'Application n'autorise l'existence que d'un seul Compte.
|
||
- **Adresse_Email** : L'adresse e-mail de l'utilisateur, utilisée comme identifiant de connexion du Compte et comme destinataire des e-mails de réinitialisation de mot de passe.
|
||
- **Mot_De_Passe** : Le secret d'au moins 8 caractères associé au Compte, stocké uniquement sous forme de hachage salé (jamais en clair).
|
||
- **Session** : L'état authentifié d'un utilisateur, matérialisé par un jeton JWT signé émis à la connexion, transmis par le Frontend à chaque appel protégé du Backend.
|
||
- **JWT** : JSON Web Token signé (HS256) émis par le Backend à la connexion, portant l'identifiant du Compte et une durée de validité configurable (12 heures par défaut).
|
||
- **Jeton_Reinitialisation** : Un jeton à usage unique et à durée de vie limitée, envoyé par e-mail, permettant de définir un nouveau Mot_De_Passe sans connaître l'ancien.
|
||
- **Aller_Retour** : Un indicateur booléen d'un Trajet signifiant que le déplacement est effectué dans les deux sens. Lorsqu'il est vrai, la Distance comptée du Trajet (Distance_Effective) est le double de la distance simple mesurée entre le départ et l'arrivée.
|
||
- **Distance_Effective** : La distance comptée d'un Trajet dans tous les agrégats (liste, totaux, carte, PDF, barème) : égale à la distance simple, ou à son double lorsque le Trajet est un Aller_Retour.
|
||
- **Document_Specification** : Un document de conception du projet consultable en lecture seule dans l'Application : les Exigences (requirements), la Conception technique (design) et le Plan de tâches (tasks), rendus depuis leur source Markdown.
|
||
- **Avatar_Utilisateur** : Le contrôle affiché à l'extrémité droite de la barre de navigation, représentant l'utilisateur par ses initiales (1ère lettre du Prénom + 1ère lettre du Nom). Un survol ou un clic ouvre un sous-menu proposant l'identité affichée, « Paramétrage », « Maintenance » et « Se déconnecter ».
|
||
- **Page_Parametrage** : La page accessible depuis l'Avatar_Utilisateur, présentant un menu latéral (Compte, Véhicules, Barème) et affichant à sa droite la donnée paramétrable sélectionnée.
|
||
- **Page_Maintenance** : La page accessible depuis l'Avatar_Utilisateur regroupant les actions techniques de maintenance de l'Application (ex. recalcul des distances routières).
|
||
|
||
---
|
||
|
||
## 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.
|
||
20. THE Frontend SHALL afficher, dans le formulaire de saisie d'un Trajet, une case à cocher "Aller/Retour" (Aller_Retour) qui, lorsqu'elle est cochée, indique que la Distance comptée du Trajet doit être doublée sans créer de Trajet supplémentaire ni de ligne additionnelle dans les résumés.
|
||
|
||
---
|
||
|
||
### 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 Dénomination libre (ex. « Marque, Modèle », watermark indicatif « Ex. Marque, Modèle »), 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.
|
||
19. WHEN l'utilisateur clique sur le bouton "Utiliser mon domicile" pour le champ adresse d'ARRIVÉE, IF le champ Commentaire est vide, THEN THE Frontend SHALL pré-remplir le Commentaire avec « Retour domicile » ; un Commentaire déjà saisi n'est jamais remplacé.
|
||
|
||
---
|
||
|
||
### 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 le point de fin du Trajet_Précédent — son adresse d'arrivée, ou son adresse de DÉPART si le Trajet_Précédent est un Aller_Retour (retour au point d'origine) — 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 le point de fin du Trajet_Précédent — son adresse d'arrivée, ou son adresse de DÉPART si le Trajet_Précédent est un Aller_Retour — 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.
|
||
12. WHEN l'utilisateur ouvre le formulaire d'insertion à partir d'un Trajet existant, THE Frontend SHALL pré-remplir la date avec la journée de ce Trajet (et non la date affichée sur la Carte), afin d'éviter les erreurs de saisie de date.
|
||
|
||
---
|
||
|
||
### Requirement 12: Navigation temporelle et mise en page
|
||
|
||
**User Story:** En tant qu'utilisateur, je veux naviguer facilement par année et par mois, consulter d'un coup tous les trajets d'un mois regroupés par jour, cibler une journée précise sur la carte, 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 repliable permettant de choisir successivement une année puis un mois ; la sélection ne descend pas jusqu'à la journée. Le libellé de l'en-tête indique le mois et l'année sélectionnés (ex. « Septembre 2026 »).
|
||
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, avec le même code couleur que la journée mise en avant dans la liste (cohérence visuelle).
|
||
4. THE Frontend SHALL empêcher la sélection de tout mois postérieur au mois courant (aucun mois futur sélectionnable).
|
||
5. WHEN l'utilisateur sélectionne un mois, THE Frontend SHALL afficher la liste de TOUS les Trajets de ce mois, regroupés par journée et triés par date croissante puis par Position croissante au sein de chaque journée.
|
||
6. THE Frontend SHALL afficher chaque journée de la liste mensuelle comme un groupe repliable, avec un en-tête indiquant le jour de la semaine avant la date (ex. « Lundi 15/09/2026 ») suivi du nombre de Trajets de la journée ; par défaut toutes les journées sont repliées et, au changement de mois, seule la dernière journée comportant des Trajets est dépliée.
|
||
7. THE Frontend SHALL afficher la liste mensuelle des Trajets comme zone principale, avec défilement vertical (scroll) lorsque le nombre de Trajets dépasse la hauteur visible, tandis que le Sélecteur_Temporel, la navigation par jour, le Panneau_Totaux et la Carte restent visibles.
|
||
8. THE Frontend SHALL afficher un Panneau_Totaux toujours visible contenant : le total kilométrique du jour ciblé sur la Carte, le total kilométrique du mois sélectionné, et le Total_Annuel_Partiel de l'année sélectionnée.
|
||
9. 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é.
|
||
10. WHEN l'utilisateur survole un Trajet dans la liste, THE Frontend SHALL afficher sur la Carte les Trajets de la journée de ce Trajet et mettre le Trajet survolé en évidence sur la Carte ; la date ainsi affichée sert de date par défaut lors de l'ajout ou de l'insertion d'un Trajet.
|
||
11. WHEN l'utilisateur cesse de survoler un Trajet, THE Frontend SHALL conserver sur la Carte la dernière journée survolée (avec l'ensemble de ses Trajets), en retirant seulement la mise en évidence du tracé.
|
||
12. THE Frontend SHALL mettre en évidence, dans la liste, le groupe correspondant à la journée actuellement affichée sur la Carte (surbrillance de l'en-tête de ce groupe).
|
||
13. WHEN un mois est sélectionné, THE Frontend SHALL afficher par défaut sur la Carte la dernière journée du mois comportant des Trajets (ou aucune journée si le mois est vide).
|
||
14. THE Frontend SHALL proposer un bouton « Ouvrir toutes les journées » qui déplie tous les groupes de la liste ; ce bouton n'est affiché que lorsqu'au moins une journée est repliée.
|
||
15. WHEN l'utilisateur change de mois via le Sélecteur_Temporel, survole un Trajet d'une autre journée, ou change de Véhicule, THE Frontend SHALL mettre à jour la liste, le Panneau_Totaux et la Carte concernés dans un délai de 1000 ms sans rechargement complet de la page.
|
||
16. 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à.
|
||
17. WHEN le Backend reçoit une requête des mois comportant des Trajets pour une année donnée, THE Backend SHALL retourner la liste des mois de cette année pour lesquels au moins un Trajet est enregistré.
|
||
18. IF le mois sélectionné ne comporte aucun Trajet, THEN THE Frontend SHALL afficher un unique message « Aucun trajet pour ce mois » et des totaux à zéro, sans afficher de message d'erreur (un mois vide n'est pas une erreur).
|
||
19. 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é.
|
||
20. WHEN l'utilisateur sélectionne un Véhicule dans le sélecteur de Véhicule, THE Frontend SHALL filtrer la liste mensuelle des Trajets, le Panneau_Totaux et la Carte pour n'afficher que les Trajets associés à ce Véhicule.
|
||
21. 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é.
|
||
22. WHEN l'utilisateur change de Véhicule sélectionné, THE Frontend SHALL mettre à jour la liste mensuelle des Trajets, le Panneau_Totaux et la Carte dans un délai de 1000 ms sans rechargement complet de la page.
|
||
23. WHEN un Trajet est créé ou modifié avec une date appartenant à un mois différent du mois affiché, THE Frontend SHALL se repositionner automatiquement sur le mois de ce Trajet, cibler sa journée sur la Carte, et déplier le groupe de cette journée dans la liste afin que le Trajet ajouté soit visible.
|
||
24. THE Frontend SHALL afficher, à l'extrémité droite de l'en-tête de chaque journée, un bouton dédié « Ajouter un trajet sur cette journée » qui ouvre le formulaire de création avec la date de CETTE journée pré-remplie, indépendamment de la journée actuellement affichée sur la Carte (afin d'éviter qu'un survol traversant plusieurs journées ouvertes ne fasse dériver la date visée par l'utilisateur).
|
||
|
||
---
|
||
|
||
### 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, une ou plusieurs Tranches_Bareme définies par une borne minimale de kilométrage, une borne maximale de kilométrage (la dernière tranche pouvant être illimitée, sans borne maximale), un coefficient (€/km sur trois décimales) et une base (€). L'utilisateur peut ajouter plusieurs tranches pour une même Puissance_Fiscale (ex. 0–4 999 km, 5 000–19 999 km, 20 000 km et plus).
|
||
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).
|
||
9. THE Frontend SHALL proposer un bouton « Recopier le dernier barème connu » qui, pour l'année affichée, recopie l'intégralité des Tranches_Bareme de l'année la plus récente antérieure comportant un barème. IF des Tranches_Bareme existent déjà pour l'année affichée, THEN THE Frontend SHALL demander une confirmation avant de les remplacer intégralement (annule et remplace). THE Frontend SHALL rendre ce bouton inaccessible lorsque l'année affichée est celle du dernier barème connu (aucune année antérieure à recopier).
|
||
|
||
---
|
||
|
||
### 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 juste après la création et la connexion de mon Compte, afin de configurer mon identité et mon véhicule avant de commencer à saisir mes trajets.
|
||
|
||
> **Note** : Le parcours d'initialisation se déroule en deux étapes. La première étape (création du Compte : Adresse_Email + Mot_De_Passe, puis connexion) est décrite à l'Exigence 17 (Authentification). La présente exigence décrit la seconde étape (identité, matricule, adresse de domicile optionnelle et Véhicule), présentée une fois l'utilisateur authentifié.
|
||
|
||
#### 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 authentifié 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.
|
||
|
||
---
|
||
|
||
### Requirement 17: Authentification par compte unique
|
||
|
||
**User Story:** En tant qu'utilisateur, je veux protéger l'accès à mon application par un compte (e-mail + mot de passe) avec la possibilité de réinitialiser mon mot de passe, afin que mes données de trajets restent privées.
|
||
|
||
#### Acceptance Criteria
|
||
|
||
1. THE Application SHALL n'autoriser l'existence que d'un seul Compte (application mono-utilisateur).
|
||
2. WHEN aucun Compte n'existe et que l'utilisateur ouvre l'Application, THE Frontend SHALL présenter la page de création de compte (première étape de l'initialisation) au lieu de la page de connexion.
|
||
3. THE Frontend SHALL exiger, à la création du Compte, une Adresse_Email valide (servant de login) et un Mot_De_Passe d'au moins 8 caractères avant d'autoriser la soumission.
|
||
4. WHEN l'utilisateur soumet la création du Compte avec une Adresse_Email valide et un Mot_De_Passe d'au moins 8 caractères, THE Backend SHALL créer le Compte en stockant uniquement un hachage salé du Mot_De_Passe (jamais le mot de passe en clair) et rediriger l'utilisateur vers la page de connexion.
|
||
5. IF un Compte existe déjà lors d'une tentative de création de compte, THEN THE Backend SHALL rejeter la création avec une erreur explicite (un seul Compte autorisé).
|
||
6. WHEN l'utilisateur soumet la page de connexion avec une Adresse_Email et un Mot_De_Passe correspondant au Compte, THE Backend SHALL émettre un JWT signé (HS256) portant l'identifiant du Compte et une durée de validité configurable (12 heures par défaut), et THE Frontend SHALL conserver ce jeton pour authentifier les appels suivants.
|
||
7. IF l'Adresse_Email ou le Mot_De_Passe soumis à la connexion ne correspond pas au Compte, THEN THE Backend SHALL rejeter la connexion avec une erreur d'identifiants invalides, sans indiquer lequel des deux champs est erroné.
|
||
8. THE Backend SHALL protéger tous les endpoints de données (Trajets, résumés, totaux, profil, véhicules, barème, carnet d'adresses, export PDF) en exigeant un JWT valide, et SHALL retourner une erreur HTTP 401 en l'absence de jeton valide.
|
||
9. WHEN le Backend valide un JWT dont le Compte associé n'existe plus, THE Backend SHALL rejeter la requête (jeton invalide) afin qu'un jeton résiduel ne donne pas accès à une application dont le compte a été supprimé.
|
||
10. THE Backend SHALL exposer un endpoint permettant au Frontend de valider la Session courante (existence du Compte et validité du jeton), utilisé par la garde de navigation du Frontend avant d'afficher les vues protégées.
|
||
11. WHEN l'utilisateur demande la réinitialisation de son Mot_De_Passe en fournissant son Adresse_Email, THE Backend SHALL générer un Jeton_Reinitialisation à usage unique et à durée de vie limitée, et l'envoyer par e-mail à l'Adresse_Email du Compte.
|
||
12. IF l'envoi de l'e-mail de réinitialisation n'est pas possible (service e-mail non configuré ou indisponible), THEN THE Backend SHALL journaliser le lien de réinitialisation en repli (fallback) afin que la réinitialisation reste possible, sans faire échouer la demande.
|
||
13. WHEN l'utilisateur suit un Jeton_Reinitialisation valide et fournit un nouveau Mot_De_Passe d'au moins 8 caractères, THE Backend SHALL remplacer le hachage du Mot_De_Passe du Compte et invalider le Jeton_Reinitialisation.
|
||
14. IF le Jeton_Reinitialisation est expiré, déjà utilisé ou inconnu, THEN THE Backend SHALL rejeter la réinitialisation avec une erreur explicite sans modifier le Mot_De_Passe.
|
||
15. THE Backend SHALL exiger la présence d'une clé secrète de signature JWT au démarrage (variable d'environnement obligatoire) et SHALL refuser de démarrer si elle est absente.
|
||
|
||
---
|
||
|
||
### Requirement 18: Trajet aller/retour
|
||
|
||
**User Story:** En tant qu'utilisateur, je veux marquer un trajet comme aller/retour pour compter le double de la distance sans ressaisir le trajet en sens inverse, afin de simplifier la saisie et de gagner de la place à l'affichage.
|
||
|
||
#### Acceptance Criteria
|
||
|
||
1. WHEN l'utilisateur crée ou modifie un Trajet avec l'indicateur Aller_Retour activé, THE Backend SHALL conserver la distance simple mesurée et exposer une Distance_Effective égale au double de cette distance simple.
|
||
2. WHEN l'indicateur Aller_Retour est désactivé, THE Backend SHALL exposer une Distance_Effective égale à la distance simple mesurée.
|
||
3. THE Backend SHALL utiliser la Distance_Effective (et non la distance simple) dans tous les agrégats : total du jour, Résumé_Mensuel, Résumé_Annuel, Total_Annuel_Partiel, calculs de barème et Export_PDF.
|
||
4. THE Frontend SHALL afficher, dans la liste des Trajets, un indicateur visuel « A/R » dans une colonne dédiée en début de ligne pour chaque Trajet Aller_Retour, en maintenant l'alignement des autres colonnes.
|
||
5. WHEN l'utilisateur consulte l'infobulle d'un Trajet Aller_Retour sur la Carte, THE Frontend SHALL faire suivre la distance affichée de la mention « (A/R) ».
|
||
6. THE Backend SHALL persister l'indicateur Aller_Retour de chaque Trajet et le conserver lors des modifications ne portant pas sur ce champ.
|
||
|
||
---
|
||
|
||
### Requirement 19: Consultation des documents de spécification
|
||
|
||
**User Story:** En tant qu'utilisateur, je veux consulter les documents de spécification du projet (exigences, conception technique, plan de tâches) directement dans l'application, afin de disposer d'une référence lisible sans quitter l'outil.
|
||
|
||
#### Acceptance Criteria
|
||
|
||
1. THE Frontend SHALL afficher, dans la barre de navigation principale, un lien « Specs » menant à une page dédiée ; cette page présente un menu latéral listant chaque Document_Specification disponible : « Exigences », « Conception technique » et « Plan de tâches ».
|
||
2. WHEN l'utilisateur sélectionne un Document_Specification dans le menu latéral, THE Frontend SHALL afficher le contenu du document rendu de façon lisible (titres, listes, tableaux, blocs de code) à partir de sa source Markdown, ET SHALL rendre les blocs de diagramme Mermaid (ex. `graph TB`) sous forme de schémas graphiques.
|
||
3. THE Backend SHALL exposer les Documents_Specification en lecture seule via une API, en restreignant les documents accessibles à une liste fermée (exigences, conception, tâches) afin d'empêcher tout accès à un fichier arbitraire.
|
||
4. THE Backend SHALL exiger un utilisateur authentifié pour accéder aux Documents_Specification (mêmes règles d'accès que les autres données de l'Application) ; en l'absence de jeton valide, THE Backend SHALL retourner une erreur HTTP 401.
|
||
5. IF l'utilisateur demande un Document_Specification inconnu ou absent, THEN THE Backend SHALL retourner une erreur HTTP 404 et THE Frontend SHALL afficher un message indiquant que le document est introuvable.
|
||
6. WHERE le fichier d'un Document_Specification n'est pas disponible (ex. plan de tâches incomplet ou absent), THE Frontend SHALL signaler ce document comme indisponible dans le menu latéral tout en affichant les autres documents.
|
||
7. THE Frontend SHALL assainir le contenu HTML issu du rendu Markdown avant affichage, afin d'éviter l'injection de contenu actif.
|
||
8. IF un diagramme Mermaid ne peut pas être rendu (syntaxe non reconnue), THEN THE Frontend SHALL conserver l'affichage du bloc de code brut correspondant sans empêcher le rendu des autres diagrammes ni du reste du document.
|
||
|
||
---
|
||
|
||
### Requirement 20: Changement de mot de passe par un utilisateur authentifié
|
||
|
||
**User Story:** En tant qu'utilisateur authentifié, je veux changer mon mot de passe depuis mon compte, afin de le renouveler sans passer par le parcours de mot de passe oublié.
|
||
|
||
#### Acceptance Criteria
|
||
|
||
1. THE Frontend SHALL afficher, sur la page « Compte utilisateur », un formulaire de changement de Mot_De_Passe distinct du formulaire d'identité, comportant le mot de passe actuel, le nouveau Mot_De_Passe et sa confirmation.
|
||
2. THE Frontend SHALL exiger que le nouveau Mot_De_Passe comporte au moins 8 caractères et corresponde exactement à sa confirmation avant d'autoriser la soumission.
|
||
3. WHEN l'utilisateur soumet le changement de Mot_De_Passe, THE Backend SHALL vérifier que le mot de passe actuel fourni correspond au Compte AVANT de le remplacer ; cette vérification exige un utilisateur authentifié (jeton JWT valide).
|
||
4. IF le mot de passe actuel fourni ne correspond pas au Compte, THEN THE Backend SHALL rejeter le changement avec une erreur HTTP 400 explicite, sans modifier le Mot_De_Passe existant.
|
||
5. WHEN le mot de passe actuel est correct et le nouveau Mot_De_Passe valide, THE Backend SHALL remplacer le hachage du Mot_De_Passe du Compte et THE Frontend SHALL afficher une confirmation de succès.
|
||
6. THE Backend SHALL exposer l'endpoint de changement de mot de passe sur un contrôleur soumis à l'autorisation globale (utilisateur authentifié), et non sur un contrôleur autorisant les accès anonymes, afin qu'un `[Authorize]` posé sur une seule action ne puisse pas être contourné par une déclaration `[AllowAnonymous]` au niveau de la classe.
|
||
|
||
---
|
||
|
||
### Requirement 21: Navigation utilisateur (avatar, paramétrage, maintenance)
|
||
|
||
**User Story:** En tant qu'utilisateur, je veux accéder à mon compte, aux données paramétrables et aux actions de maintenance depuis un menu utilisateur unique, afin de garder la barre de navigation principale légère et centrée sur les fonctionnalités de saisie et de consultation.
|
||
|
||
#### Acceptance Criteria
|
||
|
||
1. THE Frontend SHALL afficher un Avatar_Utilisateur à l'extrémité droite de la barre de navigation, représentant l'utilisateur par ses initiales (1ère lettre du Prénom suivie de la 1ère lettre du Nom, en majuscules) ; à défaut d'identité connue, un caractère générique est affiché.
|
||
2. WHEN l'utilisateur survole ou clique sur l'Avatar_Utilisateur, THE Frontend SHALL afficher un sous-menu présentant le Prénom et le Nom de l'utilisateur, puis les options « Paramétrage », « Maintenance » et « Se déconnecter ».
|
||
3. THE Frontend SHALL fermer le sous-menu de l'Avatar_Utilisateur lorsque le curseur quitte la zone du bouton et du panneau déroulant réunis, sans espace mort entre les deux qui provoquerait une fermeture prématurée lors du déplacement du curseur vers le panneau.
|
||
4. WHEN l'utilisateur sélectionne « Paramétrage », THE Frontend SHALL naviguer vers la Page_Parametrage, affichant par défaut la donnée « Compte » ; WHEN l'utilisateur sélectionne « Maintenance », THE Frontend SHALL naviguer vers la Page_Maintenance ; WHEN l'utilisateur sélectionne « Se déconnecter », THE Frontend SHALL déconnecter l'utilisateur et naviguer vers la page de connexion.
|
||
5. THE Page_Parametrage SHALL présenter un menu latéral avec les entrées « Compte » (identité, matricule, adresse de domicile), « Véhicules » (historique des véhicules) et « Barème » (barème kilométrique), chacune affichant à droite le contenu correspondant sans rechargement complet de la page.
|
||
6. THE Backend SHALL continuer à exiger la configuration du Profil (Exigence 16) pour l'accès à la donnée « Barème » de la Page_Parametrage, mais PAS pour l'accès aux données « Compte » et « Véhicules », afin de ne pas bloquer un utilisateur dont le Profil n'est pas encore configuré.
|
||
7. THE Frontend SHALL masquer entièrement la barre de navigation (y compris l'Avatar_Utilisateur) sur les pages non authentifiées (connexion, inscription, mot de passe oublié, réinitialisation), MÊME SI un jeton résiduel expiré ou invalide est encore présent localement, la présence locale d'un jeton ne garantissant pas la validité réelle de la Session sur ces pages.
|
||
8. THE Frontend SHALL proposer, sur chaque champ de saisie d'un Mot_De_Passe (connexion, inscription, réinitialisation, changement de mot de passe), un contrôle permettant de basculer l'affichage entre masqué et visible, afin que l'utilisateur puisse vérifier la saisie ; ce contrôle SHALL présenter un rendu visuel identique à celui des autres champs de saisie du formulaire.
|