diff --git a/app/__init__.py b/app/__init__.py index d22d506..124a53c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -36,6 +36,23 @@ _MOIS_FR = ["", "janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"] +_DAY_TYPE_LABELS = { + "WORK": "Travail", + "TT": "Télétravail", + "GARDE": "Garde", + "ASTREINTE": "Astreinte", + "FORMATION": "Formation", + "RTT": "RTT", + "CONGE": "Congé", + "MALADE": "Maladie", + "FERIE": "Férié", +} + + +def _day_type_fr(code): + return _DAY_TYPE_LABELS.get(code, code) + + def _date_fr(d): """Formate une date en français : 'mercredi 11 mars 2026'.""" from datetime import date as date_type @@ -64,6 +81,7 @@ def create_app(config_path=None): db.init_app(app) app.jinja_env.filters["date_fr"] = _date_fr + app.jinja_env.filters["day_type_fr"] = _day_type_fr from app.routes.dashboard import bp as dashboard_bp from app.routes.entries import bp as entries_bp diff --git a/app/routes/dashboard.py b/app/routes/dashboard.py index a716af1..0d1e44a 100644 --- a/app/routes/dashboard.py +++ b/app/routes/dashboard.py @@ -54,6 +54,7 @@ def index(): today=today, today_entry=today_entry, journeys=journeys, + vehicles=vehicles, week_actual_str=minutes_to_str(week_actual), week_balance=week_balance, week_balance_str=minutes_to_str(abs(week_balance)), diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html index 171dd2d..98b2a23 100644 --- a/app/templates/dashboard.html +++ b/app/templates/dashboard.html @@ -19,7 +19,7 @@
- {{ today_entry.day_type }}{% if today_entry.motor_vehicle_id %} · {{ today_entry.motor_vehicle_id }}{% endif %} + {{ today_entry.day_type | day_type_fr }}{% if today_entry.motor_vehicle_id %} · {{ today_entry.motor_vehicle_id }}{% endif %} Modifier → @@ -55,7 +55,7 @@
{% for vehicle_id, km in month_km.items() %}
- {{ vehicle_id | capitalize }} + {{ vehicles[vehicle_id].name if vehicle_id in vehicles else vehicle_id | capitalize }} {{ km }} km
{% endfor %} diff --git a/app/templates/entry_list.html b/app/templates/entry_list.html index 0c54fe8..5e71888 100644 --- a/app/templates/entry_list.html +++ b/app/templates/entry_list.html @@ -30,7 +30,7 @@

{{ entry.date | date_fr }}

- {{ entry.day_type }} + {{ entry.day_type | day_type_fr }} {% if entry.time_slots %} {{ entry.total_hours_str() }} {% endif %} diff --git a/config.toml b/config.toml index 4a648b4..2ec605e 100644 --- a/config.toml +++ b/config.toml @@ -1,21 +1,21 @@ [vehicles.citadine] -name = "Citadine électrique" +name = "Twingo ZE" fuel = "electric" co2_per_km = 0 cv = 3 type = "moteur" [vehicles.familiale] -name = "Familiale thermique" +name = "Duster" fuel = "diesel" -co2_per_km = 142 +co2_per_km = 135 cv = 5 type = "moteur" [vehicles.moto] -name = "Moto" -fuel = "essence" -co2_per_km = 90 +name = "CE04" +fuel = "electric" +co2_per_km = 0 cv = 3 type = "moteur" diff --git a/docs/plans/2026-03-11-bareme-kilometrique.md b/docs/plans/2026-03-11-bareme-kilometrique.md new file mode 100644 index 0000000..d7eaece --- /dev/null +++ b/docs/plans/2026-03-11-bareme-kilometrique.md @@ -0,0 +1,322 @@ +# Barème kilométrique — Refonte complète + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Corriger le barème kilométrique pour qu'il corresponde au barème fiscal officiel : tranches correctes (0–5 000 / 5 001–20 000 / >20 000 km), CV individuels (3 CV, 4 CV, 5 CV, 6 CV, 7 CV+), et support de la majoration +20 % pour les véhicules électriques. + +**Architecture:** Le barème est stocké dans `config.toml`, chargé via `get_bareme()` dans `config_loader.py`, et utilisé par `compute_frais_reels()` dans `travel_calc.py`. La majoration électrique sera un paramètre booléen de `compute_frais_reels()`, passé depuis `reports.py` en lisant `vehicle["fuel"] == "electric"`. + +**Tech Stack:** Python 3, Flask, TOML, pytest + +--- + +## Contexte + +### Barème officiel 2025 (voitures) + +| Puissance | ≤ 5 000 km | 5 001–20 000 km | > 20 000 km | +|-----------|-------------------|--------------------------|---------------| +| 3 CV max | d × 0,529 | (d × 0,316) + 1 065 | d × 0,370 | +| 4 CV | d × 0,606 | (d × 0,340) + 1 330 | d × 0,407 | +| 5 CV | d × 0,636 | (d × 0,357) + 1 395 | d × 0,427 | +| 6 CV | d × 0,665 | (d × 0,374) + 1 457 | d × 0,447 | +| 7 CV+ | d × 0,697 | (d × 0,394) + 1 515 | d × 0,470 | + +**Électrique :** majoration de +20 % sur le montant final calculé. + +### Problèmes actuels dans config.toml +- Tranches fausses : 0–3 000 / 3 001–6 000 / >6 000 (au lieu de 0–5 000 / 5 001–20 000 / >20 000) +- Groupes CV incorrects : `cv_5` regroupe tout ≤ 5 CV +- Taux incorrects +- Électrique absent + +--- + +## Tâche 1 : Mettre à jour les tests de `compute_frais_reels` + +**Fichier :** `tests/test_travel_calc.py` + +### Étape 1 : Remplacer `TRANCHES_CV5` par les tranches officielles et ajouter `TRANCHES_CV3` + +Remplacer dans `tests/test_travel_calc.py` les constantes de test : + +```python +TRANCHES_CV3 = [ + {"km_max": 5000, "taux": 0.529, "forfait": 0}, + {"km_max": 20000, "taux": 0.316, "forfait": 1065}, + {"km_max": 0, "taux": 0.370, "forfait": 0}, +] + +TRANCHES_CV5 = [ + {"km_max": 5000, "taux": 0.636, "forfait": 0}, + {"km_max": 20000, "taux": 0.357, "forfait": 1395}, + {"km_max": 0, "taux": 0.427, "forfait": 0}, +] +``` + +### Étape 2 : Remplacer les assertions existantes et ajouter les nouveaux tests + +```python +# Remplacer les tests frais_reels existants : +def test_frais_reels_tranche1(): + result = compute_frais_reels(2000, TRANCHES_CV5) + assert abs(result - 1272.0) < 0.01 # 2000 × 0.636 + +def test_frais_reels_tranche2(): + result = compute_frais_reels(10000, TRANCHES_CV5) + assert abs(result - 4965.0) < 0.01 # 10000 × 0.357 + 1395 + +def test_frais_reels_tranche3(): + result = compute_frais_reels(25000, TRANCHES_CV5) + assert abs(result - 10675.0) < 0.01 # 25000 × 0.427 + +# Ajouter les tests électrique : +def test_frais_reels_electrique_majoration_20_pct(): + result = compute_frais_reels(2000, TRANCHES_CV3, electric=True) + assert abs(result - 1269.6) < 0.01 # 2000 × 0.529 × 1.2 + +def test_frais_reels_non_electrique_sans_majoration(): + result = compute_frais_reels(2000, TRANCHES_CV3, electric=False) + assert abs(result - 1058.0) < 0.01 # 2000 × 0.529 +``` + +### Étape 3 : Vérifier que les tests échouent + +```bash +.venv/bin/python -m pytest tests/test_travel_calc.py -v +``` + +Attendu : `FAILED` sur les tests `test_frais_reels_*` (mauvaises valeurs et paramètre `electric` inexistant). + +--- + +## Tâche 2 : Modifier `compute_frais_reels` pour supporter la majoration électrique + +**Fichier :** `app/business/travel_calc.py` + +### Étape 1 : Ajouter le paramètre `electric` + +Modifier la signature et la logique de `compute_frais_reels` : + +```python +def compute_frais_reels(total_km_moteur: float, tranches: list[dict], electric: bool = False) -> float: + """ + Calcule les frais réels fiscaux selon le barème kilométrique. + km_max = 0 signifie "pas de limite" (dernière tranche). + electric=True applique la majoration de 20 % pour véhicules électriques. + """ + if not tranches or total_km_moteur <= 0: + return 0.0 + for tranche in tranches: + km_max = tranche["km_max"] + if km_max == 0 or total_km_moteur <= km_max: + result = total_km_moteur * tranche["taux"] + tranche.get("forfait", 0) + return result * 1.2 if electric else result + last = tranches[-1] + result = total_km_moteur * last["taux"] + last.get("forfait", 0) + return result * 1.2 if electric else result +``` + +### Étape 2 : Vérifier que les tests passent + +```bash +.venv/bin/python -m pytest tests/test_travel_calc.py -v +``` + +Attendu : tous les tests `PASSED`. + +### Étape 3 : Commit + +```bash +git -c commit.gpgsign=false add tests/test_travel_calc.py app/business/travel_calc.py +git -c commit.gpgsign=false commit -m "feat: compute_frais_reels supporte la majoration +20% électrique" +``` + +--- + +## Tâche 3 : Modifier `get_bareme()` pour les CV individuels + +**Fichier :** `app/config_loader.py` + +### Étape 1 : Remplacer la logique de groupement des CV + +Remplacer la fonction `get_bareme` : + +```python +def get_bareme(year: int, cv: int) -> list[dict]: + bareme = current_app.config.get("TOML", {}).get("bareme_kilometrique", {}) + year_data = bareme.get(str(year), {}) + if cv <= 3: + key = "cv_3" + elif cv == 4: + key = "cv_4" + elif cv == 5: + key = "cv_5" + elif cv == 6: + key = "cv_6" + else: + key = "cv_7plus" + return year_data.get(key, {}).get("tranches", []) +``` + +### Étape 2 : Vérifier que les tests existants passent toujours + +```bash +.venv/bin/python -m pytest -v +``` + +Attendu : tous `PASSED` (les tests de routes utilisent la DB en mémoire et le TOML de test — pas le vrai barème). + +--- + +## Tâche 4 : Mettre à jour `reports.py` pour passer le flag électrique + +**Fichier :** `app/routes/reports.py` + +### Étape 1 : Lire le champ `fuel` du véhicule et le passer à `compute_frais_reels` + +Remplacer le bloc `frais_reels` : + +```python +frais_reels = {} +for vehicle_id, km in total_km.items(): + vehicle = vehicles.get(vehicle_id, {}) + cv = vehicle.get("cv") + if cv: + tranches = get_bareme(year, cv) + electric = vehicle.get("fuel") == "electric" + frais_reels[vehicle_id] = round(compute_frais_reels(km, tranches, electric=electric), 2) +``` + +### Étape 2 : Vérifier les tests de routes + +```bash +.venv/bin/python -m pytest tests/test_routes.py -v +``` + +Attendu : tous `PASSED`. + +### Étape 3 : Commit + +```bash +git -c commit.gpgsign=false add app/config_loader.py app/routes/reports.py +git -c commit.gpgsign=false commit -m "feat: get_bareme par CV individuel, majoration électrique dans reports" +``` + +--- + +## Tâche 5 : Corriger le barème dans `config.toml` + +**Fichier :** `config.toml` + +### Étape 1 : Remplacer entièrement la section `bareme_kilometrique` + +Supprimer toutes les lignes `[[bareme_kilometrique.*]]` existantes et les remplacer par : + +```toml +# --- Barème kilométrique voitures 2025 (revenus 2024) --- +# Source : https://www.service-public.gouv.fr/particuliers/actualites/A14686 +# Majoration +20% pour véhicules électriques gérée dans travel_calc.py + +[[bareme_kilometrique.2025.cv_3.tranches]] +km_max = 5000 +taux = 0.529 +forfait = 0 + +[[bareme_kilometrique.2025.cv_3.tranches]] +km_max = 20000 +taux = 0.316 +forfait = 1065 + +[[bareme_kilometrique.2025.cv_3.tranches]] +km_max = 0 +taux = 0.370 +forfait = 0 + +[[bareme_kilometrique.2025.cv_4.tranches]] +km_max = 5000 +taux = 0.606 +forfait = 0 + +[[bareme_kilometrique.2025.cv_4.tranches]] +km_max = 20000 +taux = 0.340 +forfait = 1330 + +[[bareme_kilometrique.2025.cv_4.tranches]] +km_max = 0 +taux = 0.407 +forfait = 0 + +[[bareme_kilometrique.2025.cv_5.tranches]] +km_max = 5000 +taux = 0.636 +forfait = 0 + +[[bareme_kilometrique.2025.cv_5.tranches]] +km_max = 20000 +taux = 0.357 +forfait = 1395 + +[[bareme_kilometrique.2025.cv_5.tranches]] +km_max = 0 +taux = 0.427 +forfait = 0 + +[[bareme_kilometrique.2025.cv_6.tranches]] +km_max = 5000 +taux = 0.665 +forfait = 0 + +[[bareme_kilometrique.2025.cv_6.tranches]] +km_max = 20000 +taux = 0.374 +forfait = 1457 + +[[bareme_kilometrique.2025.cv_6.tranches]] +km_max = 0 +taux = 0.447 +forfait = 0 + +[[bareme_kilometrique.2025.cv_7plus.tranches]] +km_max = 5000 +taux = 0.697 +forfait = 0 + +[[bareme_kilometrique.2025.cv_7plus.tranches]] +km_max = 20000 +taux = 0.394 +forfait = 1515 + +[[bareme_kilometrique.2025.cv_7plus.tranches]] +km_max = 0 +taux = 0.470 +forfait = 0 +``` + +### Étape 2 : Lancer le serveur et vérifier visuellement la page `/reports/` + +```bash +.venv/bin/python run.py +``` + +Naviguer sur `http://localhost:5000/reports/` et vérifier que les frais réels sont calculés (pas d'erreur 500, valeurs cohérentes). + +### Étape 3 : Commit final + +```bash +git -c commit.gpgsign=false add config.toml +git -c commit.gpgsign=false commit -m "fix: barème kilométrique 2025 — tranches et CV corrects, toutes puissances" +``` + +--- + +## Récapitulatif des fichiers modifiés + +| Fichier | Nature | +|---|---| +| `tests/test_travel_calc.py` | Mise à jour des tranches de test et nouvelles assertions électrique | +| `app/business/travel_calc.py` | Paramètre `electric` dans `compute_frais_reels` | +| `app/config_loader.py` | `get_bareme()` avec CV individuels (cv_3 à cv_7plus) | +| `app/routes/reports.py` | Passage du flag `electric` à `compute_frais_reels` | +| `config.toml` | Barème complet et correct avec toutes les puissances 2025 | diff --git a/tests/test_routes.py b/tests/test_routes.py index 53098cf..543246d 100644 --- a/tests/test_routes.py +++ b/tests/test_routes.py @@ -46,7 +46,7 @@ def test_entry_list(client, app): response = client.get("/entries/") assert response.status_code == 200 - assert "TT" in response.text + assert "Télétravail" in response.text def test_reports_page(client):