diff --git a/.kiro/specs/schotziad-km-tracker/design-requirements.md b/.kiro/specs/schotziad-km-tracker/design-requirements.md new file mode 100644 index 0000000..022b4a8 --- /dev/null +++ b/.kiro/specs/schotziad-km-tracker/design-requirements.md @@ -0,0 +1,263 @@ +# Requirements Document + +## Design Requirements (exigences techniques) + +## Introduction + +Ce document rassemble les **exigences techniques** dérivées des exigences fonctionnelles métier décrites dans `requirements.md`. Là où `requirements.md` exprime l'attendu fonctionnel, le mode opératoire et les règles de gestion fournis par le Product Owner business, le présent document précise les choix techniques associés : rendu visuel détaillé, API externes et leurs URL, codes HTTP précis, délais et débounce, mécanismes de sécurité (jetons, hachage, clés), politique de cache, fallbacks de calcul, rendu cartographique et documentaire. + +Ces exigences sont destinées à être définies et arbitrées **conjointement par le PO business et le PO IT**, dans un second temps, une fois le besoin fonctionnel stabilisé. Elles peuvent évoluer sans remettre en cause le besoin métier tant que le comportement observable décrit dans `requirements.md` est respecté. + +Le document est organisé **en miroir** des exigences de `requirements.md` : chaque section reprend le même numéro et le même titre suivi de « — aspects techniques », afin de faciliter les références croisées. Seules les exigences comportant effectivement des critères techniques apparaissent ici. Les exigences purement métier (Requirements 4, 13, 18) ne figurent pas dans ce document. + +--- + +## Glossary (termes techniques) + +- **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. Support technique du Geocodeur défini dans `requirements.md`. +- **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. +- **Haversine** : Formule orthodromique de calcul de distance à vol d'oiseau entre deux points, utilisée en repli (fallback) lorsque le calcul d'itinéraire routier échoue. +- **Leaflet.js** : Bibliothèque JavaScript de rendu cartographique interactif employée par le Frontend pour la Carte. +- **OpenStreetMap** : Fournisseur des tuiles cartographiques (fond de carte) affichées via Leaflet.js. +- **GeoJSON** : Format d'échange des géométries d'itinéraire (liste de points) transmises par le Backend au Frontend. +- **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). Support technique de la Session définie dans `requirements.md`. +- **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, soumise à une politique de rétention LRU. +- **Mermaid** : Syntaxe de définition de diagrammes (ex. `graph TB`) rendue graphiquement lors de l'affichage des Documents_Specification. + +--- + +## Requirements + +### Requirement 1: Saisie d'un trajet — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 1. + +#### Acceptance Criteria + +1. 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. +2. 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. +3. 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é. +4. 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. +5. 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. +6. 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. +7. WHEN l'utilisateur sélectionne une suggestion de l'API BAN, THE Frontend SHALL stocker les coordonnées géographiques (latitude, longitude) associées à l'adresse sélectionnée pour transmission au Backend. +8. 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. +9. WHEN l'utilisateur sélectionne une Adresse_Nommee dans le sélecteur associé à un champ adresse, THE Frontend SHALL associer les coordonnées géographiques (latitude, longitude) de l'Adresse_Nommee pour transmission au Backend. + +--- + +### Requirement 2: Calcul automatique de la distance — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 2. + +#### 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. +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. IF le Calculateur_Distance ne reçoit pas de réponse d'OpenRouteService (ORS) dans un délai de 10 secondes, THEN 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 — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 3. + +#### Acceptance Criteria + +1. 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. +2. 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 5: Modification et suppression d'un trajet — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 5. + +#### 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 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. +3. IF un Trajet possède une Distance nulle suite à l'absence de coordonnées, THEN THE Backend SHALL tenter de recalculer la Distance en utilisant les coordonnées disponibles via le Cache_Adresses ou l'API BAN pour les nouvelles adresses soumises. + +--- + +### Requirement 6: Persistance et cohérence des données — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 6. + +#### Acceptance Criteria + +1. THE Backend SHALL persister l'ensemble des Trajets dans une base de données relationnelle. +2. WHEN un Trajet est supprimé, THE Backend SHALL viser la mise à jour du Résumé_Mensuel et du Résumé_Annuel correspondants dans un délai de 5 secondes ; ce délai est un objectif de performance et non une contrainte stricte lorsque la charge du système l'empêche, la cohérence des données restant prioritaire. + +--- + +### Requirement 7: Export PDF des résumés — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 7. + +#### Acceptance Criteria + +1. 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é. + +--- + +### Requirement 8: Visualisation des trajets sur une carte — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 8. + +#### Acceptance Criteria + +1. WHEN la carte est affichée pour une journée donnée, THE Frontend SHALL rendre la Carte via Leaflet.js sur un fond de tuiles OpenStreetMap, et représenter chaque Trajet par un tracé polyline avec marqueurs de départ et d'arrivée. +2. 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. +3. 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 la géométrie de l'itinéraire sous forme de liste de points GeoJSON si disponible dans le Cache_Adresses. +4. 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 — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 9. + +#### 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 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. 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). +5. 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 — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 10. + +#### Acceptance Criteria + +1. IF le matricule reçu dépasse 8 caractères, THEN THE Backend SHALL rejeter le matricule avec une erreur HTTP 400. +2. 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. +3. 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. + +--- + +### Requirement 11: Insertion d'un trajet entre deux saisies existantes — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 11. + +#### Acceptance Criteria + +1. WHEN l'utilisateur sélectionne une Adresse_Nommee dans le formulaire d'insertion pour un champ adresse, THE Frontend SHALL associer les coordonnées géographiques (latitude, longitude) de l'Adresse_Nommee pour transmission au Backend. +2. WHERE l'insertion utilise l'autocomplétion pour une nouvelle adresse, THE Frontend SHALL s'appuyer sur l'API BAN pour proposer les suggestions et récupérer les coordonnées, selon les mêmes modalités techniques que le formulaire de saisie (voir Requirement 1). + +--- + +### Requirement 12: Navigation temporelle et mise en page — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 12. + +#### Acceptance Criteria + +1. 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. +2. 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. + +--- + +### Requirement 14: Montant réglé mensuel et régularisation annuelle — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 14. + +#### Acceptance Criteria + +1. IF l'utilisateur saisit un Montant_Regle négatif, THEN THE Backend SHALL rejeter la saisie avec une erreur HTTP 400. + +--- + +### Requirement 15: Carnet d'adresses nommées — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 15. + +#### Acceptance Criteria + +1. 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. +2. 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. +3. 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). +4. 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. +5. 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. +6. 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. +7. 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. +8. 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. +9. 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. +10. 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. +11. WHEN l'utilisateur active la création d'une Adresse_Nommee (bouton « Ajouter une adresse ») ou la modification d'une Adresse_Nommee existante depuis la liste, THE Frontend SHALL présenter le formulaire de saisie dans une fenêtre modale superposée à la liste, et SHALL fermer cette fenêtre modale après un enregistrement réussi, après annulation par l'utilisateur, ou lors d'un clic sur l'arrière-plan de la fenêtre modale. +12. THE Frontend SHALL présenter la liste des Adresses_Nommées sous forme d'une grille de cartes, chaque carte disposant d'un contour et d'une ombre portée. + +--- + +### Requirement 16: Initialisation du profil à la première utilisation — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 16. + +#### Acceptance Criteria + +1. 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. +2. WHEN l'utilisateur valide le parcours d'Onboarding avec un Profil et au moins un Véhicule valides, THE Backend SHALL retourner un indicateur `estConfigure` valant vrai. +3. WHEN le Backend confirme l'enregistrement d'un Profil_Configure, THE Frontend SHALL naviguer vers la Vue_Consultation dans un délai de 1000 ms. +4. 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 — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 17. + +#### Acceptance Criteria + +1. 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). +2. WHEN l'utilisateur soumet la page de connexion avec des identifiants 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. +3. 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. +4. WHEN le Backend valide un JWT dont le Compte associé n'existe plus, THE Backend SHALL rejeter la requête (jeton invalide). +5. 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. +6. 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. +7. WHEN l'utilisateur suit un Jeton_Reinitialisation valide et fournit un nouveau Mot_De_Passe valide, THE Backend SHALL remplacer le hachage du Mot_De_Passe du Compte et invalider le Jeton_Reinitialisation. +8. 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 19: Consultation des documents de spécification — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 19. + +#### Acceptance Criteria + +1. WHEN l'utilisateur sélectionne un Document_Specification dans le menu latéral, THE Frontend SHALL rendre le contenu à partir de sa source Markdown (titres, listes, tableaux, blocs de code), ET SHALL rendre les blocs de diagramme Mermaid (ex. `graph TB`) sous forme de schémas graphiques. +2. 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. +3. THE Backend SHALL exiger un utilisateur authentifié (JWT valide) pour accéder aux Documents_Specification ; en l'absence de jeton valide, THE Backend SHALL retourner une erreur HTTP 401. +4. IF l'utilisateur demande un Document_Specification inconnu ou absent, THEN THE Backend SHALL retourner une erreur HTTP 404. +5. THE Frontend SHALL assainir le contenu HTML issu du rendu Markdown avant affichage, afin d'éviter l'injection de contenu actif. +6. 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é — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 20. + +#### Acceptance Criteria + +1. WHEN l'utilisateur soumet le changement de Mot_De_Passe, THE Backend SHALL exiger un jeton JWT valide pour la vérification du mot de passe actuel. +2. 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. +3. 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) — aspects techniques + +> Exigence fonctionnelle : voir `requirements.md`, Requirement 21. + +#### Acceptance Criteria + +1. 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. +2. 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. diff --git a/.kiro/specs/schotziad-km-tracker/requirements.md b/.kiro/specs/schotziad-km-tracker/requirements.md index d26bc39..c0f0ef5 100644 --- a/.kiro/specs/schotziad-km-tracker/requirements.md +++ b/.kiro/specs/schotziad-km-tracker/requirements.md @@ -2,32 +2,32 @@ ## 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 **SchotzIAD KM Tracker** est une application web **mono-utilisateur** protégée par un **compte unique authentifié** (adresse e-mail + mot de passe). 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. +L'application permet de gérer un profil (identité, matricule, adresse de domicile, véhicules), un carnet d'adresses nommées réutilisables, un barème kilométrique par année et par puissance fiscale, la saisie du montant réellement versé chaque mois et le calcul de la régularisation annuelle. Elle propose l'export PDF des résumés, la visualisation cartographique des trajets d'une journée et la consultation des documents de spécification du projet. Elle cible des utilisateurs souhaitant garder un historique fiable de leurs déplacements, notamment à des fins professionnelles ou de remboursement de frais. + +> **Note** : Ce document exprime le besoin **fonctionnel métier** (attendu fonctionnel, mode opératoire, règles de gestion) fourni par le Product Owner business. Les exigences **techniques** dérivées (rendu visuel détaillé, choix techniques, API externes, codes HTTP, délais, sécurité, cache) sont rassemblées dans le document `design-requirements.md`, à préciser conjointement par le PO business et le PO IT. Chaque exigence renvoie, lorsque pertinent, à son pendant technique dans `design-requirements.md`. --- ## 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#. +- **Application** : L'ensemble du système SchotzIAD KM Tracker. +- **Frontend** : L'interface utilisateur de l'Application. +- **Backend** : Le serveur d'API de l'Application. - **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. +- **Geocodeur** : Le service d'adresses officiel 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. - **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. +- **Carte** : La visualisation cartographique interactive affichant les itinéraires des Trajets d'une journée. +- **Cache_Adresses** : Le stockage des couples (adresse textuelle normalisée, coordonnées géographiques) des adresses déjà saisies, afin d'éviter des recherches répétées 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. +- **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 d'adresses, 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. @@ -56,13 +56,12 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. - **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). +- **Mot_De_Passe** : Le secret d'au moins 8 caractères associé au Compte, jamais conservé en clair. +- **Session** : L'état authentifié d'un utilisateur, établi à la connexion et vérifié à chaque accès protégé. - **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. +- **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). - **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). @@ -75,28 +74,29 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (autocomplétion d'adresses, transmission des coordonnées, délais) sont décrites dans `design-requirements.md`, Requirement 1. + #### 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. +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 (texte libre, 1 à 255 caractères) et adresse d'arrivée avec autocomplétion (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. 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. +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 refuser la persistance et signaler que la date est absente ou invalide. +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 refuser la persistance et signaler le champ concerné. 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. +9. IF le Frontend ne reçoit pas de réponse du Backend 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. +11. WHEN le Backend persiste un Trajet, THE Backend SHALL stocker le commentaire associé au Trajet si fourni, ou aucune valeur si absent. +12. IF le Backend reçoit un Trajet dont le commentaire dépasse 500 caractères, THEN THE Backend SHALL refuser la persistance et signaler que le commentaire est trop long. +13. WHEN l'utilisateur saisit une adresse de départ ou d'arrivée, THE Frontend SHALL proposer une liste de suggestions d'adresses françaises correspondant à la saisie. +14. WHEN l'utilisateur sélectionne une suggestion d'adresse, THE Frontend SHALL pré-remplir le champ adresse avec le libellé normalisé de l'adresse sélectionnée. +15. IF l'autocomplétion d'adresses est indisponible lors de la saisie, THEN THE Frontend SHALL permettre à l'utilisateur de saisir l'adresse manuellement en texte libre sans suggestion, en affichant un avertissement discret indiquant que l'autocomplétion est temporairement indisponible. +16. IF l'utilisateur soumet une adresse saisie manuellement sans passer par l'autocomplétion (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. +17. 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. +18. 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. +19. 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. --- @@ -104,14 +104,14 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (API ORS, fallback Haversine, délais) sont décrites dans `design-requirements.md`, Requirement 2. + #### 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. +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 routière entre ces coordonnées. +2. WHEN le calcul de la distance routière entre les deux points géographiques d'un Trajet aboutit, 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 le Backend reçoit un Trajet dont les coordonnées de départ ou d'arrivée sont absentes (adresse saisie manuellement), THEN THE Backend SHALL stocker le Trajet avec une Distance nulle et signaler, sans bloquer l'enregistrement, que la distance n'a pas pu être calculée. +4. 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. --- @@ -119,6 +119,8 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (codes HTTP) sont décrites dans `design-requirements.md`, Requirement 3. + #### 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. @@ -126,9 +128,8 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. 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. +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 refuser la requête et retourner 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. --- @@ -152,18 +153,20 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **User Story:** En tant qu'utilisateur, je veux pouvoir modifier ou supprimer un trajet existant, afin de corriger des erreurs de saisie. +> Les exigences techniques associées (délais, recalcul via cache/géocodage) sont décrites dans `design-requirements.md`, Requirement 5. + #### 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. +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. 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. +6. IF le Backend reçoit une demande de modification ou de suppression pour un Trajet inexistant, THEN THE Backend SHALL signaler 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 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. +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. --- @@ -171,12 +174,14 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (base relationnelle, objectif de performance) sont décrites dans `design-requirements.md`, Requirement 6. + #### Acceptance Criteria -1. THE Backend SHALL persister l'ensemble des Trajets dans une base de données relationnelle. +1. THE Backend SHALL persister l'ensemble des Trajets de manière durable. 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. +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é. 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. @@ -188,20 +193,21 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (délai de génération, mécanisme de téléchargement) sont décrites dans `design-requirements.md`, Requirement 7. + #### 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é). +5. 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. +6. 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é. +7. 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é. +8. 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. +9. 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. +10. 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. +11. 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é). --- @@ -209,31 +215,32 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (Leaflet.js, OpenStreetMap, géométrie GeoJSON) sont décrites dans `design-requirements.md`, Requirement 8. + #### 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. +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 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. +5. IF la géométrie d'itinéraire d'un Trajet n'est pas disponible (coordonnées manquantes ou itinéraire 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. +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 si disponible. +8. IF le service de carte 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. +**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 recherches répétées et de gagner du temps lors de la saisie de nouveaux trajets. + +> Les exigences techniques associées (interrogation d'abord du cache local, politique LRU, précision de stockage) sont décrites dans `design-requirements.md`, Requirement 9. #### 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. +1. WHEN l'utilisateur sélectionne une adresse via l'autocomplétion lors de la création ou modification d'un Trajet, THE Backend SHALL mémoriser dans le Cache_Adresses le libellé normalisé de l'adresse et ses coordonnées 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 nouvelle recherche externe. +3. WHEN l'utilisateur sélectionne une suggestion d'adresse, THE Frontend SHALL pré-remplir le champ adresse avec le libellé complet et conserver les coordonnées associées pour transmission au Backend. --- @@ -241,16 +248,18 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (géocodage, code HTTP de rejet du matricule) sont décrites dans `design-requirements.md`, Requirement 10. + #### 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. +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. +3. WHEN l'utilisateur sauvegarde une Adresse_Domicile, THE Backend SHALL déterminer ses coordonnées géographiques et les stocker dans le Profil ; si les coordonnées ne peuvent pas être obtenues, 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. +7. WHEN l'utilisateur modifie l'Adresse_Domicile dans le Profil et sauvegarde, THE Backend SHALL déterminer à nouveau les coordonnées de la nouvelle adresse pour mettre à jour le Profil. +8. IF la détermination des coordonnées 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. @@ -269,6 +278,8 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (autocomplétion) sont décrites dans `design-requirements.md`, Requirement 11. + #### 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. @@ -280,8 +291,8 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. 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. +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. +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 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. --- @@ -290,6 +301,8 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (délais de mise à jour) sont décrites dans `design-requirements.md`, Requirement 12. + #### 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 »). @@ -306,14 +319,14 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. 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. +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 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. +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 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). @@ -341,12 +354,14 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (code HTTP de rejet d'un montant négatif) sont décrites dans `design-requirements.md`, Requirement 14. + #### 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. +4. IF l'utilisateur saisit un Montant_Regle négatif, THEN THE Backend SHALL rejeter la saisie. 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`. @@ -358,25 +373,20 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (autocomplétion et débounce, codes HTTP, normalisation, mise en cache, délais de géocodage, fenêtre modale) sont décrites dans `design-requirements.md`, Requirement 15. + #### 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. -17. WHEN l'utilisateur active la création d'une Adresse_Nommee (bouton « Ajouter une adresse ») ou la modification d'une Adresse_Nommee existante depuis la liste, THE Frontend SHALL présenter le formulaire de saisie dans une fenêtre modale superposée à la liste, et SHALL fermer cette fenêtre modale après un enregistrement réussi, après annulation par l'utilisateur, ou lors d'un clic sur l'arrière-plan de la fenêtre modale. +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 (texte libre, 1 à 255 caractères). +3. WHEN le Backend reçoit une Adresse_Nommee valide, THE Backend SHALL persister la Dénomination, le texte d'adresse et, lorsqu'elles sont disponibles, les coordonnées géographiques (latitude, longitude). +4. 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 refuser la persistance et signaler que la Dénomination est absente ou invalide. +5. IF le Backend reçoit une Adresse_Nommee dont la Dénomination est identique à celle d'une Adresse_Nommee déjà enregistrée, THEN THE Backend SHALL refuser la persistance et signaler que la Dénomination est déjà utilisée. +6. 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 refuser la persistance et signaler que l'adresse est absente ou invalide. +7. 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. +8. 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. +9. 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. +10. 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. --- @@ -385,22 +395,23 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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é. +> +> Les exigences techniques associées (indicateur `estConfigure`, délai de navigation) sont décrites dans `design-requirements.md`, Requirement 16. #### 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. +2. 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. +3. 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. +4. 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. +5. 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. +6. 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. +7. 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. +8. 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 considérer le Profil comme configuré. +9. WHEN le Backend confirme l'enregistrement d'un Profil_Configure, THE Frontend SHALL marquer le Profil comme configuré et naviguer vers la Vue_Consultation. +10. 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. +11. 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. +12. IF la récupération de l'état de configuration du Profil é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. --- @@ -408,23 +419,23 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (JWT HS256, durée 12h, hachage salé, codes HTTP, endpoint de validation de Session, journalisation du lien de réinitialisation, clé secrète obligatoire au démarrage) sont décrites dans `design-requirements.md`, Requirement 17. + #### 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. +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 sans jamais conserver 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. +6. WHEN l'utilisateur soumet la page de connexion avec une Adresse_Email et un Mot_De_Passe correspondant au Compte, THE Backend SHALL établir une Session authentifiée, et THE Frontend SHALL conserver l'état de connexion 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. +8. THE Backend SHALL protéger tous les accès aux données (Trajets, résumés, totaux, profil, véhicules, barème, carnet d'adresses, export PDF) en exigeant une Session valide. +9. WHEN le Backend valide une Session dont le Compte associé n'existe plus, THE Backend SHALL rejeter la requête afin qu'une Session résiduelle ne donne pas accès à une application dont le compte a été supprimé. +10. THE Backend SHALL permettre au Frontend de valider la Session courante (existence du Compte et validité de la Session), fonctionnalité utilisée 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. +12. 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 Mot_De_Passe du Compte et invalider le Jeton_Reinitialisation. +13. 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. --- @@ -447,16 +458,15 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (rendu Markdown, diagrammes Mermaid, liste fermée de documents, code HTTP, authentification, assainissement HTML) sont décrites dans `design-requirements.md`, Requirement 19. + #### 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. +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). +3. THE Backend SHALL exposer les Documents_Specification en lecture seule, en restreignant les documents accessibles à une liste fermée (exigences, conception, tâches). +4. IF l'utilisateur demande un Document_Specification inconnu ou absent, THEN THE Frontend SHALL afficher un message indiquant que le document est introuvable. +5. 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. --- @@ -464,14 +474,15 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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é. +> Les exigences techniques associées (code HTTP de rejet, contrainte d'implémentation du contrôleur d'autorisation) sont décrites dans `design-requirements.md`, Requirement 20. + #### 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. +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é. +4. IF le mot de passe actuel fourni ne correspond pas au Compte, THEN THE Backend SHALL rejeter le changement de manière 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 Mot_De_Passe du Compte et THE Frontend SHALL afficher une confirmation de succès. --- @@ -479,6 +490,8 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. **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. +> Les exigences techniques associées (masquage de la barre malgré un jeton résiduel, contrôle d'affichage du mot de passe) sont décrites dans `design-requirements.md`, Requirement 21. + #### 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é. @@ -487,5 +500,3 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#. 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.