feat: increase RTT from 18 to 19
- Update LeaveBalance model default rtt_total to 19 - Update all tests to verify 19 RTT instead of 18 - Update documentation (design and technical plan) - Update run.py to bind to 0.0.0.0 for external access - Update CLAUDE.md deployment instructions Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,8 +21,9 @@ python -m venv .venv
|
|||||||
# Run a single test
|
# Run a single test
|
||||||
.venv/bin/python -m pytest tests/test_routes.py::test_create_entry -v
|
.venv/bin/python -m pytest tests/test_routes.py::test_create_entry -v
|
||||||
|
|
||||||
# Production (Gunicorn)
|
# Production (systemd) — déployé dans /var/www/tableau-de-bord-pro/
|
||||||
SECRET_KEY=<secret> ./start.sh
|
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 commit (GPG signing désactivé — pinentry inaccessible dans cet env)
|
||||||
git -c commit.gpgsign=false commit -m "..."
|
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)
|
id: so.Mapped[int] = so.mapped_column(primary_key=True)
|
||||||
year: so.Mapped[int] = so.mapped_column(sa.Integer, unique=True, nullable=False)
|
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)
|
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 | |
|
| id | INTEGER PK | |
|
||||||
| year | INTEGER UNIQUE | Année |
|
| year | INTEGER UNIQUE | Année |
|
||||||
| conges_total | INTEGER | Total congés (défaut 28) |
|
| 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`.
|
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
|
- **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 semaine courante** (lun-dim) : total heures, écart vs objectif (5 × 7h45 = 38h45)
|
||||||
- **Synthèse mensuelle** : jours travaillés, km par véhicule, CO2
|
- **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
|
### Page Saisie / Édition d'un jour
|
||||||
|
|
||||||
|
|||||||
@@ -454,7 +454,7 @@ class LeaveBalance(db.Model):
|
|||||||
id: so.Mapped[int] = so.mapped_column(primary_key=True)
|
id: so.Mapped[int] = so.mapped_column(primary_key=True)
|
||||||
year: so.Mapped[int] = so.mapped_column(sa.Integer, unique=True, nullable=False)
|
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)
|
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**
|
**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)
|
balance = get_or_create_balance(2025)
|
||||||
assert balance.year == 2025
|
assert balance.year == 2025
|
||||||
assert balance.conges_total == 28
|
assert balance.conges_total == 28
|
||||||
assert balance.rtt_total == 18
|
assert balance.rtt_total == 19
|
||||||
|
|
||||||
|
|
||||||
def test_get_or_create_balance_returns_existing(app):
|
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()
|
app = create_app()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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)
|
balance = get_or_create_balance(2025)
|
||||||
assert balance.year == 2025
|
assert balance.year == 2025
|
||||||
assert balance.conges_total == 28
|
assert balance.conges_total == 28
|
||||||
assert balance.rtt_total == 18
|
assert balance.rtt_total == 19
|
||||||
|
|
||||||
|
|
||||||
def test_get_or_create_balance_returns_existing(app):
|
def test_get_or_create_balance_returns_existing(app):
|
||||||
|
|||||||
Reference in New Issue
Block a user