66 lines
3.6 KiB
Markdown
66 lines
3.6 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
# Setup (first time)
|
|
python -m venv .venv
|
|
.venv/bin/pip install -r requirements.txt
|
|
|
|
# Run dev server
|
|
.venv/bin/python run.py
|
|
|
|
# Run all tests
|
|
.venv/bin/python -m pytest
|
|
|
|
# Run a single test file
|
|
.venv/bin/python -m pytest tests/test_time_calc.py -v
|
|
|
|
# Run a single test
|
|
.venv/bin/python -m pytest tests/test_routes.py::test_create_entry -v
|
|
|
|
# Production (Gunicorn)
|
|
SECRET_KEY=<secret> ./start.sh
|
|
|
|
# Git commit (GPG signing désactivé — pinentry inaccessible dans cet env)
|
|
git -c commit.gpgsign=false commit -m "..."
|
|
```
|
|
|
|
## Variables d'environnement
|
|
|
|
- `SECRET_KEY` : requis en production (défaut `dev-secret-change-in-prod` en dev)
|
|
|
|
## Architecture
|
|
|
|
Flask app using the factory pattern (`create_app()` in `app/__init__.py`). The DB is SQLite via SQLAlchemy, stored in `instance/worklog.db`. All vehicle/journey/tax configuration lives in `config.toml` (loaded at startup into `app.config["TOML"]`), not in the database.
|
|
|
|
**Data flow:**
|
|
- `config.toml` → `app/config_loader.py` → accessed via `get_vehicles()`, `get_journeys()`, `get_bareme(year, cv)`
|
|
- `app/models.py` defines `WorkEntry` (one row per day), `TimeSlot` (N plages horaires per entry), `LeaveBalance` (annual quotas)
|
|
- `app/business/` contains pure functions with no Flask dependencies: `time_calc.py` (minutes/reference), `travel_calc.py` (km, CO2, frais réels), `leave_calc.py` (solde congés/RTT)
|
|
- Routes in `app/routes/` use business functions and config_loader, then render Jinja2 templates
|
|
|
|
**Key domain rules:**
|
|
- Day types: `WORK | TT | GARDE | ASTREINTE | FORMATION | RTT | CONGE | MALADE | FERIE`
|
|
- Types without journey: `TT, MALADE, CONGE, RTT, FERIE` (see `day_types_without_journey()`)
|
|
- Work reference: 7h45 (465 min) for WORK/TT/FORMATION, 10h (600 min) for GARDE, 0 for absences
|
|
- `total_minutes()` on `WorkEntry` sums `TimeSlot` durations, handles midnight crossing
|
|
- Frais réels: uses `bareme_kilometrique` tranches from config.toml; `km_max = 0` means "no upper limit"
|
|
|
|
**Frontend:** Tailwind CSS and HTMX loaded from CDN in `base.html`. No build step. JavaScript is inline in templates (only `entry_form.html` has JS for dynamic time slots and journey section visibility).
|
|
|
|
**Auth:** Handled entirely by HAProxy upstream. The app has no authentication.
|
|
|
|
**Tests:** `tests/conftest.py` provides `app` and `client` fixtures using an in-memory SQLite DB and a temporary TOML config file. Business logic tests (`test_time_calc.py`, `test_travel_calc.py`) have no Flask dependencies and need no fixtures.
|
|
|
|
## Gotchas
|
|
|
|
- **Pas de migration de schéma** : l'app utilise `db.create_all()` uniquement (pas d'Alembic). Tout changement de modèle nécessite de supprimer `instance/worklog.db` en dev, ou une migration manuelle en prod.
|
|
- **Barème kilométrique** : les tranches dans `config.toml` sont à mettre à jour manuellement chaque année (section `[bareme_kilometrique.YYYY]`).
|
|
- **`datetime.utcnow()` deprecated** : les modèles utilisent `datetime.utcnow` (warning sur Python 3.14+). À remplacer par `datetime.now(UTC)` lors d'une prochaine évolution des modèles.
|
|
- **Dates en français** : `strftime` utilise la locale système (anglais). Utiliser le filtre Jinja2 `{{ date | date_fr }}` défini dans `app/__init__.py`.
|
|
- **`db.get_engine()` deprecated** en Flask-SQLAlchemy 3.x → utiliser `db.engine`.
|
|
- **Migration `_migrate_db`** : vérifier l'existence de la table avant `ALTER TABLE` — SQLite peut avoir un fichier DB sans tables (ex: premier démarrage avec `instance/worklog.db` vide).
|