Precision configurable des distances (arrondi superieur 0/1/2 decimales)
- Nouveau parametre global Precision_Distance (sous-menu Application de la Page_Parametrage, entre Compte et Vehicules), persiste via une entite singleton ParametresApplication, expose par GET/PUT /api/parametres-application - Arrondi superieur (ceiling) applique a l'affichage et aux agregats : trajet simple, aller/retour (arrondi puis doublement), totaux jour/mois/annee, carte, export PDF et bareme kilometrique - jamais a la distance brute stockee - Refonte du Montant_Previsionnel : le montant mensuel (80%) porte desormais sur le seul mois affiche (tranche determinee par le cumul annuel) ; nouvelle ligne Montant_Previsionnel_Annuel (100%, sans minoration, sur le cumul) - Backend : PrecisionDistanceService (ArrondirDistance), integration dans ResumeService/TotauxService/RemboursementService/MontantRegleService, migration EF Core ; 7 tests de propriete (FsCheck) + tests existants, 62/62 verts - Frontend : ParametresApplicationService (signal partage), composant de parametrage, fonction miroir arrondirDistance, adaptation de la liste des trajets, du panneau des totaux, de la carte et des montants regles - Specs : nouvelle spec precision-distances-km (requirements/design/tasks) ; mise a jour de requirements.md, design.md et design-requirements.md de schotziad-km-tracker (Requirement 22, renvois croises, Montant_Previsionnel) - Suppression du fichier chromium .deb suivi par erreur dans le depot
This commit is contained in:
Binary file not shown.
@@ -4,6 +4,7 @@ import { toSignal } from '@angular/core/rxjs-interop';
|
||||
import { catchError, filter, map, of, startWith } from 'rxjs';
|
||||
|
||||
import { AuthService } from './core/services/auth.service';
|
||||
import { ParametresApplicationService } from './core/services/parametres-application.service';
|
||||
import { ProfilService } from './core/services/profil.service';
|
||||
|
||||
/**
|
||||
@@ -28,6 +29,7 @@ export class AppComponent {
|
||||
|
||||
private readonly authService = inject(AuthService);
|
||||
private readonly profilService = inject(ProfilService);
|
||||
private readonly parametresService = inject(ParametresApplicationService);
|
||||
private readonly router = inject(Router);
|
||||
|
||||
/** Chemin de la route active, mis à jour à chaque navigation terminée. */
|
||||
@@ -89,6 +91,12 @@ export class AppComponent {
|
||||
this.prenom.set(profil?.prenom ?? null);
|
||||
this.nom.set(profil?.nom ?? null);
|
||||
});
|
||||
// Charge la précision de distance globale ; en cas d'échec, le signal
|
||||
// `precisionDistance` reste à sa valeur par défaut (2) sans bloquer l'application.
|
||||
this.parametresService
|
||||
.charger()
|
||||
.pipe(catchError(() => of(null)))
|
||||
.subscribe();
|
||||
} else {
|
||||
this.prenom.set(null);
|
||||
this.nom.set(null);
|
||||
|
||||
@@ -13,6 +13,7 @@ import { MontantsReglesComponent } from './features/montants-regles/montants-reg
|
||||
import { MaintenanceComponent } from './features/maintenance/maintenance.component';
|
||||
import { OnboardingComponent } from './features/onboarding/onboarding.component';
|
||||
import { ParametrageComponent } from './features/parametrage/parametrage.component';
|
||||
import { ParametresApplicationComponent } from './features/parametres-application/parametres-application.component';
|
||||
import { ProfilComponent } from './features/profil/profil.component';
|
||||
import { ResumeAnnuelComponent } from './features/resume-annuel/resume-annuel.component';
|
||||
import { ResumeMensuelComponent } from './features/resume-mensuel/resume-mensuel.component';
|
||||
@@ -29,10 +30,11 @@ import { VehiculesComponent } from './features/vehicules/vehicules.component';
|
||||
* et du premier véhicule — comportement inchangé).
|
||||
* `authGuard` est vérifiée en premier ; on n'y applique pas `profilConfigureGuard`
|
||||
* pour ne pas piéger un utilisateur partiellement configuré.
|
||||
* - `parametrage` (menu latéral + routes enfants `compte` / `vehicules` / `bareme`,
|
||||
* accessible depuis le sous-menu de l'avatar utilisateur) : `parametrage` lui-même
|
||||
* et ses enfants `compte` / `vehicules` ne portent que `authGuard` (mêmes raisons
|
||||
* que ci-dessus) ; seul l'enfant `bareme` ajoute `profilConfigureGuard`.
|
||||
* - `parametrage` (menu latéral + routes enfants `compte` / `application` / `vehicules` /
|
||||
* `bareme`, accessible depuis le sous-menu de l'avatar utilisateur) : `parametrage`
|
||||
* lui-même et ses enfants `compte` / `application` / `vehicules` ne portent que
|
||||
* `authGuard` (mêmes raisons que ci-dessus) ; seul l'enfant `bareme` ajoute
|
||||
* `profilConfigureGuard`.
|
||||
* - Routes de données PROTEGEES par `[authGuard, profilConfigureGuard]` :
|
||||
* `consultation`, `carnet-adresses`, `resume-mensuel`, `resume-annuel`,
|
||||
* `montants-regles`. L'authentification est contrôlée d'abord, puis la
|
||||
@@ -64,8 +66,8 @@ export const routes: Routes = [
|
||||
canActivate: [authGuard]
|
||||
},
|
||||
|
||||
// Paramétrage : menu latéral (Compte, Véhicules, Barème) + contenu en routes enfants.
|
||||
// - 'compte' et 'vehicules' : authGuard SEUL (pas profilConfigureGuard, pour ne pas
|
||||
// Paramétrage : menu latéral (Compte, Application, Véhicules, Barème) + contenu en routes enfants.
|
||||
// - 'compte', 'application' et 'vehicules' : authGuard SEUL (pas profilConfigureGuard, pour ne pas
|
||||
// piéger un utilisateur dont le profil n'est pas encore configuré).
|
||||
// - 'bareme' : authGuard + profilConfigureGuard (comportement inchangé).
|
||||
{
|
||||
@@ -75,6 +77,7 @@ export const routes: Routes = [
|
||||
children: [
|
||||
{ path: '', redirectTo: 'compte', pathMatch: 'full' },
|
||||
{ path: 'compte', component: ProfilComponent },
|
||||
{ path: 'application', component: ParametresApplicationComponent },
|
||||
{ path: 'vehicules', component: VehiculesComponent },
|
||||
{ path: 'bareme', component: BaremeComponent, canActivate: [profilConfigureGuard] }
|
||||
]
|
||||
|
||||
@@ -34,9 +34,10 @@ export interface MontantPrevisionnel {
|
||||
annee: number;
|
||||
mois: number;
|
||||
vehiculeId: number;
|
||||
totalMoisKm: number;
|
||||
totalAnnuelPartielKm: number;
|
||||
montantBaremePlein: number; // barème plein sur le total partiel
|
||||
montantPrevisionnel: number; // = 80% (indicatif uniquement)
|
||||
montantPrevisionnelMensuel: number;
|
||||
montantPrevisionnelAnnuel: number;
|
||||
}
|
||||
|
||||
/** Ligne mensuelle de la synthèse annuelle — miroir de LigneSyntheseMoisDto. */
|
||||
|
||||
@@ -9,3 +9,4 @@ export * from './bareme.model';
|
||||
export * from './adresse.model';
|
||||
export * from './auth.model';
|
||||
export * from './spec.model';
|
||||
export * from './parametres-application.model';
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
// Modèle ParametresApplication — miroir de ParametresApplicationDtos.cs.
|
||||
|
||||
/** Paramètres globaux de l'application — miroir de ParametresApplicationDto. */
|
||||
export interface ParametresApplication {
|
||||
precisionDistanceKm: number;
|
||||
}
|
||||
@@ -9,3 +9,4 @@ export * from './profil.service';
|
||||
export * from './bareme.service';
|
||||
export * from './remboursement.service';
|
||||
export * from './spec.service';
|
||||
export * from './parametres-application.service';
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable, inject, signal } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { ParametresApplication } from '../models';
|
||||
|
||||
/**
|
||||
* Service HTTP des paramètres globaux de l'application.
|
||||
*
|
||||
* Porte le signal partagé `precisionDistance`, chargé une fois puis mis à jour après
|
||||
* modification — mécanisme central de propagation de la précision d'arrondi des distances
|
||||
* à toute l'application (liste des trajets, totaux, carte, PDF, barème).
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ParametresApplicationService {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly baseUrl = environment.apiUrl;
|
||||
|
||||
/** Précision courante (0, 1 ou 2), partagée par toute l'application. Défaut 2 avant premier chargement. */
|
||||
readonly precisionDistance = signal<0 | 1 | 2>(2);
|
||||
|
||||
/** Charge la précision courante depuis le backend et met à jour le signal partagé (GET /api/parametres-application). */
|
||||
charger(): Observable<ParametresApplication> {
|
||||
return this.http.get<ParametresApplication>(`${this.baseUrl}/parametres-application`).pipe(
|
||||
tap((p) => this.precisionDistance.set(p.precisionDistanceKm as 0 | 1 | 2))
|
||||
);
|
||||
}
|
||||
|
||||
/** Modifie la précision et met à jour le signal partagé en cas de succès (PUT /api/parametres-application). */
|
||||
definirPrecisionDistance(valeur: 0 | 1 | 2): Observable<ParametresApplication> {
|
||||
return this.http
|
||||
.put<ParametresApplication>(`${this.baseUrl}/parametres-application`, {
|
||||
precisionDistanceKm: valeur
|
||||
})
|
||||
.pipe(tap((p) => this.precisionDistance.set(p.precisionDistanceKm as 0 | 1 | 2)));
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import { Subscription } from 'rxjs';
|
||||
|
||||
import { TrajetCarte } from '../../core/models';
|
||||
import { TrajetService } from '../../core/services';
|
||||
import { ParametresApplicationService } from '../../core/services/parametres-application.service';
|
||||
|
||||
/**
|
||||
* Palette de couleurs distinctes attribuée aux tracés successifs d'une journée.
|
||||
@@ -72,6 +73,7 @@ type EtatCarte = 'chargement' | 'ok' | 'vide' | 'erreur';
|
||||
export class CarteTrajetsComponent implements AfterViewInit, OnChanges, OnDestroy {
|
||||
private readonly trajetService = inject(TrajetService);
|
||||
private readonly cdr = inject(ChangeDetectorRef);
|
||||
private readonly parametresService = inject(ParametresApplicationService);
|
||||
|
||||
/** Date de la journée à visualiser, au format ISO (yyyy-MM-dd). */
|
||||
@Input() date: string | null = null;
|
||||
@@ -419,7 +421,9 @@ export class CarteTrajetsComponent implements AfterViewInit, OnChanges, OnDestro
|
||||
*/
|
||||
private contenuInfobulle(trajet: TrajetCarte): string {
|
||||
const distanceBase =
|
||||
trajet.distance != null ? `${trajet.distance.toFixed(2)} km` : 'distance non calculée';
|
||||
trajet.distance != null
|
||||
? `${trajet.distance.toFixed(this.parametresService.precisionDistance())} km`
|
||||
: 'distance non calculée';
|
||||
// Précise l'aller/retour après la distance (la distance affichée est déjà la
|
||||
// distance effective doublée côté API).
|
||||
const distance = trajet.allerRetour ? `${distanceBase} (A/R)` : distanceBase;
|
||||
|
||||
@@ -156,7 +156,7 @@ export class MontantsReglesComponent implements OnInit {
|
||||
}
|
||||
}
|
||||
previsionnels.forEach((p, index) => {
|
||||
lignes[index].previsionnel = p ? p.montantPrevisionnel : null;
|
||||
lignes[index].previsionnel = p ? p.montantPrevisionnelMensuel : null;
|
||||
});
|
||||
this.lignes.set(lignes);
|
||||
},
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
<dl class="panneau-totaux__liste">
|
||||
<div class="panneau-totaux__ligne">
|
||||
<dt>Total du jour</dt>
|
||||
<dd>{{ (totaux()?.totalJour ?? 0) | number: '1.2-2' }} km</dd>
|
||||
<dd>{{ (totaux()?.totalJour ?? 0) | number: formatKm() }} km</dd>
|
||||
</div>
|
||||
<div class="panneau-totaux__ligne">
|
||||
<dt>Total du mois</dt>
|
||||
<dd>{{ (totaux()?.totalMois ?? 0) | number: '1.2-2' }} km</dd>
|
||||
<dd>{{ (totaux()?.totalMois ?? 0) | number: formatKm() }} km</dd>
|
||||
</div>
|
||||
<div class="panneau-totaux__ligne panneau-totaux__ligne--fort">
|
||||
<dt>Total annuel partiel</dt>
|
||||
<dd>{{ (totaux()?.totalAnnuelPartiel ?? 0) | number: '1.2-2' }} km</dd>
|
||||
<dd>{{ (totaux()?.totalAnnuelPartiel ?? 0) | number: formatKm() }} km</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
||||
@@ -27,13 +27,28 @@
|
||||
Indication de versement (80%)
|
||||
</span>
|
||||
<span class="panneau-totaux__previsionnel-montant">
|
||||
{{ montantPrevisionnel.montantPrevisionnel | number: '1.2-2' }} €
|
||||
{{ montantPrevisionnel.montantPrevisionnelMensuel | number: '1.2-2' }} €
|
||||
</span>
|
||||
</div>
|
||||
<p class="panneau-totaux__previsionnel-note">
|
||||
Simple indication de versement, calculée à 80% du barème sur le total
|
||||
annuel partiel. Le montant réellement versé est le montant réglé que
|
||||
vous saisissez.
|
||||
Simple indication de versement, calculée à 80% du barème sur le mois
|
||||
affiché. Le montant réellement versé est le montant réglé que vous
|
||||
saisissez.
|
||||
</p>
|
||||
</div>
|
||||
<div class="panneau-totaux__previsionnel">
|
||||
<div class="panneau-totaux__ligne panneau-totaux__ligne--fort">
|
||||
<span class="panneau-totaux__previsionnel-libelle">
|
||||
Montant prévisionnel annuel
|
||||
</span>
|
||||
<span class="panneau-totaux__previsionnel-montant">
|
||||
{{ montantPrevisionnel.montantPrevisionnelAnnuel | number: '1.2-2' }} €
|
||||
</span>
|
||||
</div>
|
||||
<p class="panneau-totaux__previsionnel-note">
|
||||
Projection à 100% du barème sur le total annuel partiel, sans
|
||||
minoration. Cette projection ne se substitue pas au montant réel
|
||||
calculé en fin d'année.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import { Subscription } from 'rxjs';
|
||||
|
||||
import { MontantPrevisionnel, Totaux } from '../../core/models';
|
||||
import { BaremeService } from '../../core/services/bareme.service';
|
||||
import { ParametresApplicationService } from '../../core/services/parametres-application.service';
|
||||
import { TrajetService } from '../../core/services/trajet.service';
|
||||
|
||||
/**
|
||||
@@ -22,18 +23,21 @@ import { TrajetService } from '../../core/services/trajet.service';
|
||||
*
|
||||
* Reçoit en entrée une `date` (ISO `yyyy-MM-dd`) et un `vehiculeId`, puis affiche :
|
||||
* - le total du jour, le total du mois et le Total_Annuel_Partiel (cumul de
|
||||
* l'année jusqu'au mois sélectionné inclus), tous à deux décimales
|
||||
* (Exigences 12.7, 12.8) ;
|
||||
* - le Montant_Previsionnel minoré à 80%, calculé sur le Total_Annuel_Partiel du
|
||||
* véhicule sélectionné, présenté explicitement comme une simple indication de
|
||||
* l'année jusqu'au mois sélectionné inclus), formatés dynamiquement selon la
|
||||
* précision courante des distances (0, 1 ou 2 décimales — Exigences 5.4, 5.5) ;
|
||||
* - le Montant_Previsionnel_Mensuel minoré à 80%, calculé sur le seul mois affiché
|
||||
* du véhicule sélectionné, présenté explicitement comme une simple indication de
|
||||
* versement — le montant réellement versé étant le Montant_Regle saisi par
|
||||
* l'utilisateur (Exigences 13.5, 13.6). Les montants sont exprimés en euros à
|
||||
* deux décimales (Exigence 13.7).
|
||||
* l'utilisateur (Exigences 13.5, 13.6) ;
|
||||
* - le Montant_Previsionnel_Annuel, projection à 100% (sans minoration) sur le
|
||||
* Total_Annuel_Partiel (Exigences 8.1, 8.3, 8.4).
|
||||
* Les montants sont exprimés en euros à deux décimales, indépendamment de la
|
||||
* précision des distances (Exigence 13.7, 8.4).
|
||||
*
|
||||
* Les totaux et le montant prévisionnel sont recalculés à chaque changement de
|
||||
* `date` ou de `vehiculeId` (recalcul au changement, sans rechargement complet).
|
||||
*
|
||||
* _Requirements: 12.7, 12.8, 13.5, 13.6, 13.7, 13.8_
|
||||
* _Requirements: 5.4, 5.5, 8.1, 8.3, 8.4, 12.7, 12.8, 13.5, 13.6, 13.7, 13.8_
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-panneau-totaux',
|
||||
@@ -47,6 +51,7 @@ export class PanneauTotauxComponent implements OnChanges, OnDestroy {
|
||||
private readonly trajetService = inject(TrajetService);
|
||||
private readonly baremeService = inject(BaremeService);
|
||||
private readonly cdr = inject(ChangeDetectorRef);
|
||||
readonly parametresService = inject(ParametresApplicationService);
|
||||
|
||||
/** Date sélectionnée au format ISO (`yyyy-MM-dd`). */
|
||||
@Input() date: string | null = null;
|
||||
@@ -140,6 +145,15 @@ export class PanneauTotauxComponent implements OnChanges, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Chaîne de format `number` (`1.{p}-{p}`) basée sur la précision courante des
|
||||
* distances (0, 1 ou 2 décimales), pour un affichage dynamique des totaux en km.
|
||||
*/
|
||||
formatKm(): string {
|
||||
const precision = this.parametresService.precisionDistance();
|
||||
return `1.${precision}-${precision}`;
|
||||
}
|
||||
|
||||
/** Année (nombre) extraite d'une date ISO `yyyy-MM-dd`. */
|
||||
private anneeDe(dateIso: string): number {
|
||||
return Number(dateIso.slice(0, 4));
|
||||
|
||||
@@ -10,6 +10,13 @@
|
||||
>
|
||||
Compte
|
||||
</a>
|
||||
<a
|
||||
class="parametrage__lien"
|
||||
routerLink="/parametrage/application"
|
||||
routerLinkActive="parametrage__lien--actif"
|
||||
>
|
||||
Application
|
||||
</a>
|
||||
<a
|
||||
class="parametrage__lien"
|
||||
routerLink="/parametrage/vehicules"
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<section class="parametres-application">
|
||||
<h1>Application</h1>
|
||||
|
||||
<h2>Nombre de décimales des distances (km)</h2>
|
||||
|
||||
<div class="choix-precision" role="group" aria-label="Nombre de décimales des distances (km)">
|
||||
@for (valeur of precisionsDisponibles; track valeur) {
|
||||
<button
|
||||
type="button"
|
||||
class="btn-precision"
|
||||
[class.btn-precision--actif]="precisionSelectionnee() === valeur"
|
||||
[disabled]="enregistrement()"
|
||||
[attr.aria-pressed]="precisionSelectionnee() === valeur"
|
||||
(click)="choisir(valeur)"
|
||||
>
|
||||
{{ valeur }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (enregistrement()) {
|
||||
<p class="enregistrement-en-cours">Enregistrement en cours…</p>
|
||||
}
|
||||
|
||||
<p class="aide">
|
||||
L'arrondi appliqué aux distances est toujours supérieur (jamais vers le bas), quelle que
|
||||
soit la précision choisie.
|
||||
</p>
|
||||
</section>
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
.parametres-application {
|
||||
padding: 1.5rem;
|
||||
max-width: 720px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.1rem;
|
||||
margin: 1.5rem 0 0.75rem;
|
||||
}
|
||||
|
||||
.choix-precision {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.btn-precision {
|
||||
padding: 0.55rem 1.25rem;
|
||||
background: #fff;
|
||||
color: var(--couleur-bleu);
|
||||
border: 1px solid var(--couleur-bleu);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.95rem;
|
||||
min-width: 3rem;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
border-color: var(--couleur-orange);
|
||||
color: var(--couleur-orange);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-precision--actif {
|
||||
background: var(--couleur-bleu);
|
||||
color: #fff;
|
||||
border-color: var(--couleur-bleu);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: var(--couleur-orange);
|
||||
border-color: var(--couleur-orange);
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-precision:disabled {
|
||||
opacity: 0.55;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.enregistrement-en-cours {
|
||||
margin-top: 0.75rem;
|
||||
color: #777;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.aide {
|
||||
margin-top: 1rem;
|
||||
color: #555;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnInit, inject, signal } from '@angular/core';
|
||||
|
||||
import { NotificationService } from '../../core/services/notification.service';
|
||||
import { ParametresApplicationService } from '../../core/services/parametres-application.service';
|
||||
|
||||
/**
|
||||
* Paramétrage global de l'application : précision d'arrondi des distances (km).
|
||||
*
|
||||
* - Présélectionne la valeur courante au chargement (Exigence 2.3).
|
||||
* - Applique le choix immédiatement (optimiste) puis confirme ou restaure/notifie
|
||||
* en cas d'échec (Exigences 2.4, 2.5).
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-parametres-application',
|
||||
standalone: true,
|
||||
imports: [CommonModule],
|
||||
templateUrl: './parametres-application.component.html',
|
||||
styleUrl: './parametres-application.component.scss'
|
||||
})
|
||||
export class ParametresApplicationComponent implements OnInit {
|
||||
private readonly parametresService = inject(ParametresApplicationService);
|
||||
private readonly notification = inject(NotificationService);
|
||||
|
||||
/** Valeur sélectionnée (0, 1 ou 2 décimales). */
|
||||
readonly precisionSelectionnee = signal<0 | 1 | 2>(2);
|
||||
|
||||
/** true pendant l'appel d'enregistrement. */
|
||||
readonly enregistrement = signal(false);
|
||||
|
||||
/** Valeurs de précision proposées (0, 1 ou 2 décimales). */
|
||||
readonly precisionsDisponibles: readonly (0 | 1 | 2)[] = [0, 1, 2];
|
||||
|
||||
ngOnInit(): void {
|
||||
this.precisionSelectionnee.set(this.parametresService.precisionDistance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sélectionne une nouvelle précision et l'enregistre côté serveur.
|
||||
* Mise à jour optimiste du signal local, restaurée en cas d'échec (Exigence 2.5).
|
||||
*/
|
||||
choisir(valeur: 0 | 1 | 2): void {
|
||||
if (valeur === this.precisionSelectionnee() || this.enregistrement()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const precedente = this.precisionSelectionnee();
|
||||
this.precisionSelectionnee.set(valeur);
|
||||
this.enregistrement.set(true);
|
||||
|
||||
this.parametresService.definirPrecisionDistance(valeur).subscribe({
|
||||
next: () => {
|
||||
this.enregistrement.set(false);
|
||||
this.notification.success('Précision des distances enregistrée.');
|
||||
},
|
||||
error: () => {
|
||||
this.enregistrement.set(false);
|
||||
this.precisionSelectionnee.set(precedente);
|
||||
this.notification.error("La modification n'a pas pu être enregistrée.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
|
||||
import { ReordonnerTrajets, Trajet, TrajetPosition } from '../../core/models';
|
||||
import { NotificationService } from '../../core/services/notification.service';
|
||||
import { ParametresApplicationService } from '../../core/services/parametres-application.service';
|
||||
import { TrajetService } from '../../core/services/trajet.service';
|
||||
import { ServeurIndisponibleError } from '../../core/interceptors/timeout.interceptor';
|
||||
import { ConfirmDialogComponent } from '../../shared/components/confirm-dialog/confirm-dialog.component';
|
||||
@@ -87,6 +88,7 @@ export interface GroupeJour {
|
||||
export class TrajetListComponent {
|
||||
private readonly trajetService = inject(TrajetService);
|
||||
private readonly notifications = inject(NotificationService);
|
||||
private readonly parametresService = inject(ParametresApplicationService);
|
||||
|
||||
/**
|
||||
* Trajets du mois. Ils sont regroupés par journée (`groupes`), chaque groupe
|
||||
@@ -295,12 +297,12 @@ export class TrajetListComponent {
|
||||
return index === groupe.trajets.length - 1;
|
||||
}
|
||||
|
||||
/** Distance formatée à 2 décimales, ou « — » si non calculée (Exigence 2.4). */
|
||||
/** Distance formatée selon la précision courante, ou « — » si non calculée (Exigence 2.4). */
|
||||
distanceAffichee(trajet: Trajet): string {
|
||||
if (trajet.distance == null) {
|
||||
return '—';
|
||||
}
|
||||
return `${trajet.distance.toFixed(2)} km`;
|
||||
return `${trajet.distance.toFixed(this.parametresService.precisionDistance())} km`;
|
||||
}
|
||||
|
||||
/** Identité stable pour le suivi de la liste (drag & drop). */
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Arrondit une distance brute (km) au supérieur (ceiling) à la précision donnée.
|
||||
* Miroir exact de PrecisionDistanceService.ArrondirDistance côté backend.
|
||||
*/
|
||||
export function arrondirDistance(distanceBrute: number, precision: 0 | 1 | 2): number {
|
||||
const facteur = Math.pow(10, precision);
|
||||
return Math.ceil(distanceBrute * facteur) / facteur;
|
||||
}
|
||||
|
||||
/** Distance_Effective_Arrondie : arrondi de la distance simple PUIS doublement si aller/retour. */
|
||||
export function distanceEffectiveArrondie(
|
||||
distanceBrute: number | null,
|
||||
allerRetour: boolean,
|
||||
precision: 0 | 1 | 2
|
||||
): number | null {
|
||||
if (distanceBrute == null) {
|
||||
return null;
|
||||
}
|
||||
const arrondie = arrondirDistance(distanceBrute, precision);
|
||||
return allerRetour ? arrondie * 2 : arrondie;
|
||||
}
|
||||
Reference in New Issue
Block a user