feat: increase RTT from 18 to 19 #1
@@ -21,8 +21,9 @@ python -m venv .venv
|
||||
# Run a single test
|
||||
.venv/bin/python -m pytest tests/test_routes.py::test_create_entry -v
|
||||
|
||||
# Production (Gunicorn)
|
||||
SECRET_KEY=<secret> ./start.sh
|
||||
# Production (systemd) — déployé dans /var/www/tableau-de-bord-pro/
|
||||
sudo systemctl edit --full tableau-de-bord-pro # configurer SECRET_KEY
|
||||
sudo systemctl restart tableau-de-bord-pro
|
||||
|
||||
# Git commit (GPG signing désactivé — pinentry inaccessible dans cet env)
|
||||
git -c commit.gpgsign=false commit -m "..."
|
||||
|
||||
@@ -54,4 +54,4 @@ class LeaveBalance(db.Model):
|
||||
id: so.Mapped[int] = so.mapped_column(primary_key=True)
|
||||
year: so.Mapped[int] = so.mapped_column(sa.Integer, unique=True, nullable=False)
|
||||
conges_total: so.Mapped[int] = so.mapped_column(sa.Integer, default=28)
|
||||
rtt_total: so.Mapped[int] = so.mapped_column(sa.Integer, default=18)
|
||||
rtt_total: so.Mapped[int] = so.mapped_column(sa.Integer, default=19)
|
||||
|
||||
186
docs/plans/2026-03-11-repartition-types-jours.md
Normal file
186
docs/plans/2026-03-11-repartition-types-jours.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# Répartition par type de jour — Carte rapport
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Ajouter une carte dans `/reports/` listant le nombre de jours travaillés par type (Travail, Garde, Formation…) pour l'année sélectionnée, en n'affichant que les types avec au moins 1 occurrence.
|
||||
|
||||
**Architecture:** Une fonction pure `count_day_types(entries)` est ajoutée dans `app/business/time_calc.py` (cohérent avec les autres fonctions de comptage). La route `reports.py` l'appelle et passe le résultat au template. Le template affiche une nouvelle carte avec la liste, en utilisant le filtre `day_type_fr` existant.
|
||||
|
||||
**Tech Stack:** Python 3, Flask, Jinja2, pytest
|
||||
|
||||
---
|
||||
|
||||
## Tâche 1 : Fonction `count_day_types` dans `time_calc.py`
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/business/time_calc.py`
|
||||
- Test: `tests/test_time_calc.py`
|
||||
|
||||
### Étape 1 : Écrire le test qui échoue
|
||||
|
||||
Ajouter à la fin de `tests/test_time_calc.py` :
|
||||
|
||||
```python
|
||||
from app.business.time_calc import count_day_types
|
||||
from app.models import WorkEntry
|
||||
from datetime import date
|
||||
|
||||
|
||||
def test_count_day_types_basic():
|
||||
entries = [
|
||||
WorkEntry(date=date(2025, 1, 2), day_type="WORK"),
|
||||
WorkEntry(date=date(2025, 1, 3), day_type="WORK"),
|
||||
WorkEntry(date=date(2025, 1, 6), day_type="TT"),
|
||||
WorkEntry(date=date(2025, 1, 7), day_type="GARDE"),
|
||||
]
|
||||
result = count_day_types(entries)
|
||||
assert result == {"WORK": 2, "TT": 1, "GARDE": 1}
|
||||
|
||||
|
||||
def test_count_day_types_empty():
|
||||
assert count_day_types([]) == {}
|
||||
|
||||
|
||||
def test_count_day_types_single_type():
|
||||
entries = [
|
||||
WorkEntry(date=date(2025, 2, 1), day_type="RTT"),
|
||||
WorkEntry(date=date(2025, 2, 2), day_type="RTT"),
|
||||
]
|
||||
result = count_day_types(entries)
|
||||
assert result == {"RTT": 2}
|
||||
```
|
||||
|
||||
### Étape 2 : Vérifier que le test échoue
|
||||
|
||||
```bash
|
||||
.venv/bin/python -m pytest tests/test_time_calc.py::test_count_day_types_basic -v
|
||||
```
|
||||
|
||||
Attendu : `FAILED` — `ImportError: cannot import name 'count_day_types'`
|
||||
|
||||
### Étape 3 : Implémenter la fonction
|
||||
|
||||
Ajouter à la fin de `app/business/time_calc.py` :
|
||||
|
||||
```python
|
||||
def count_day_types(entries: list) -> dict[str, int]:
|
||||
"""Retourne un dict {day_type: count} pour une liste d'entrées, sans les zéros."""
|
||||
counts: dict[str, int] = {}
|
||||
for entry in entries:
|
||||
counts[entry.day_type] = counts.get(entry.day_type, 0) + 1
|
||||
return counts
|
||||
```
|
||||
|
||||
### Étape 4 : Vérifier que les tests passent
|
||||
|
||||
```bash
|
||||
.venv/bin/python -m pytest tests/test_time_calc.py -v
|
||||
```
|
||||
|
||||
Attendu : tous `PASSED`.
|
||||
|
||||
### Étape 5 : Commit
|
||||
|
||||
```bash
|
||||
git -c commit.gpgsign=false add app/business/time_calc.py tests/test_time_calc.py
|
||||
git -c commit.gpgsign=false commit -m "feat: count_day_types — compte les jours par type"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tâche 2 : Brancher la fonction dans la route `reports.py`
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/routes/reports.py`
|
||||
|
||||
### Étape 1 : Importer et appeler `count_day_types`
|
||||
|
||||
Dans `app/routes/reports.py`, modifier l'import :
|
||||
|
||||
```python
|
||||
from app.business.travel_calc import compute_km_for_entry, compute_co2_grams, compute_frais_reels
|
||||
from app.business.time_calc import count_day_types
|
||||
```
|
||||
|
||||
Puis, juste avant le `return render_template(...)`, ajouter :
|
||||
|
||||
```python
|
||||
day_type_counts = count_day_types(entries)
|
||||
```
|
||||
|
||||
Et passer la variable au template :
|
||||
|
||||
```python
|
||||
return render_template(
|
||||
"reports.html",
|
||||
year=year,
|
||||
total_km=total_km,
|
||||
total_co2_kg=round(total_co2 / 1000, 2),
|
||||
frais_reels=frais_reels,
|
||||
vehicles=vehicles,
|
||||
day_type_counts=day_type_counts,
|
||||
)
|
||||
```
|
||||
|
||||
### Étape 2 : Vérifier que les tests passent
|
||||
|
||||
```bash
|
||||
.venv/bin/python -m pytest -v
|
||||
```
|
||||
|
||||
Attendu : tous `PASSED`.
|
||||
|
||||
---
|
||||
|
||||
## Tâche 3 : Carte dans le template `reports.html`
|
||||
|
||||
**Files:**
|
||||
- Modify: `app/templates/reports.html`
|
||||
|
||||
### Étape 1 : Ajouter la carte après la carte "Frais réels"
|
||||
|
||||
Insérer avant `{% endblock %}` :
|
||||
|
||||
```html
|
||||
<!-- Répartition par type de jour -->
|
||||
<div class="card card-amber">
|
||||
<p class="card-label">Répartition {{ year }}</p>
|
||||
{% if day_type_counts %}
|
||||
<div class="space-y-2 mt-1">
|
||||
{% for day_type, count in day_type_counts.items() %}
|
||||
<div class="flex items-baseline justify-between">
|
||||
<span class="text-xs" style="color:#8A8278;">{{ day_type | day_type_fr }}</span>
|
||||
<span class="font-data font-semibold text-sm" style="color:var(--ink);">
|
||||
{{ count }}<span class="font-normal text-xs ml-1" style="color:#9A9288;">j</span>
|
||||
</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="text-sm" style="color:#9A9288;">Aucune entrée pour {{ year }}.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
```
|
||||
|
||||
### Étape 2 : Vérifier visuellement
|
||||
|
||||
```bash
|
||||
.venv/bin/python run.py
|
||||
```
|
||||
|
||||
Naviguer sur `http://localhost:5000/reports/` — la carte doit apparaître avec la liste des types.
|
||||
|
||||
### Étape 3 : Vérifier les tests
|
||||
|
||||
```bash
|
||||
.venv/bin/python -m pytest -v
|
||||
```
|
||||
|
||||
Attendu : tous `PASSED`.
|
||||
|
||||
### Étape 4 : Commit
|
||||
|
||||
```bash
|
||||
git -c commit.gpgsign=false add app/routes/reports.py app/templates/reports.html
|
||||
git -c commit.gpgsign=false commit -m "feat: carte répartition par type de jour dans le rapport"
|
||||
```
|
||||
@@ -88,7 +88,7 @@ Une journée peut avoir N plages horaires non-consécutives (ex: 9h-18h + 22h-00
|
||||
| id | INTEGER PK | |
|
||||
| year | INTEGER UNIQUE | Année |
|
||||
| conges_total | INTEGER | Total congés (défaut 28) |
|
||||
| rtt_total | INTEGER | Total RTT (défaut 18) |
|
||||
| rtt_total | INTEGER | Total RTT (défaut 19) |
|
||||
|
||||
Les jours utilisés sont calculés dynamiquement depuis `work_entries`.
|
||||
|
||||
@@ -156,7 +156,7 @@ Toutes les vues sont conçues pour mobile en premier (Tailwind responsive).
|
||||
- **Carte "Aujourd'hui"** : boutons rapides "Arrivée" / "Départ" (horodatage automatique), sélecteur de profil de trajet
|
||||
- **Synthèse semaine courante** (lun-dim) : total heures, écart vs objectif (5 × 7h45 = 38h45)
|
||||
- **Synthèse mensuelle** : jours travaillés, km par véhicule, CO2
|
||||
- **Solde congés/RTT** : jauge visuelle (ex: "12/18 RTT utilisés")
|
||||
- **Solde congés/RTT** : jauge visuelle (ex: "12/19 RTT utilisés")
|
||||
|
||||
### Page Saisie / Édition d'un jour
|
||||
|
||||
|
||||
@@ -454,7 +454,7 @@ class LeaveBalance(db.Model):
|
||||
id: so.Mapped[int] = so.mapped_column(primary_key=True)
|
||||
year: so.Mapped[int] = so.mapped_column(sa.Integer, unique=True, nullable=False)
|
||||
conges_total: so.Mapped[int] = so.mapped_column(sa.Integer, default=28)
|
||||
rtt_total: so.Mapped[int] = so.mapped_column(sa.Integer, default=18)
|
||||
rtt_total: so.Mapped[int] = so.mapped_column(sa.Integer, default=19)
|
||||
```
|
||||
|
||||
**Step 2: Vérifier la création des tables**
|
||||
@@ -769,7 +769,7 @@ def test_get_or_create_balance_creates_default(app):
|
||||
balance = get_or_create_balance(2025)
|
||||
assert balance.year == 2025
|
||||
assert balance.conges_total == 28
|
||||
assert balance.rtt_total == 18
|
||||
assert balance.rtt_total == 19
|
||||
|
||||
|
||||
def test_get_or_create_balance_returns_existing(app):
|
||||
|
||||
2
run.py
2
run.py
@@ -3,4 +3,4 @@ from app import create_app
|
||||
app = create_app()
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(debug=True)
|
||||
app.run(debug=True, host="0.0.0.0")
|
||||
|
||||
@@ -27,7 +27,7 @@ def test_get_or_create_balance_creates_default(app):
|
||||
balance = get_or_create_balance(2025)
|
||||
assert balance.year == 2025
|
||||
assert balance.conges_total == 28
|
||||
assert balance.rtt_total == 18
|
||||
assert balance.rtt_total == 19
|
||||
|
||||
|
||||
def test_get_or_create_balance_returns_existing(app):
|
||||
|
||||
Reference in New Issue
Block a user