feat: budget-tracker — spec, constitution, data-model, API contracts, plan
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
<!--
|
||||
Sync Impact Report
|
||||
- Version change: N/A → 1.0.0 (initial ratification)
|
||||
- Added principles: I. API-First, II. Data Integrity, III. Test Coverage,
|
||||
IV. Simplicity & Pragmatism, V. Docker-First Deployment
|
||||
- Added sections: Technical Stack Constraints, Development Workflow, Governance
|
||||
- Removed sections: none
|
||||
- Templates requiring updates:
|
||||
- .specify/templates/plan-template.md ✅ compatible (Constitution Check present)
|
||||
- .specify/templates/spec-template.md ✅ compatible (priorities, acceptance scenarios)
|
||||
- .specify/templates/tasks-template.md ✅ compatible (phased approach, test-optional)
|
||||
- Follow-up TODOs: none
|
||||
-->
|
||||
|
||||
# Budget Tracker Constitution
|
||||
|
||||
## Core Principles
|
||||
|
||||
### I. API-First Design
|
||||
|
||||
Le backend FastAPI expose une API REST documentee (OpenAPI/Swagger) qui
|
||||
constitue le contrat unique entre frontend et backend.
|
||||
|
||||
- Chaque endpoint DOIT etre defini dans un schema OpenAPI avant implementation.
|
||||
- Le frontend React consomme exclusivement l'API REST ; aucun acces direct
|
||||
a la base de donnees depuis le frontend.
|
||||
- Les reponses DOIVENT suivre un format JSON coherent avec codes HTTP
|
||||
standards (200, 201, 400, 404, 422, 500).
|
||||
- La validation des entrees DOIT utiliser les modeles Pydantic de FastAPI.
|
||||
|
||||
### II. Data Integrity
|
||||
|
||||
Les donnees financieres sont critiques et DOIVENT etre exactes et coherentes.
|
||||
|
||||
- Tous les montants DOIVENT etre stockes en centimes (entiers) pour eviter
|
||||
les erreurs d'arrondi en virgule flottante.
|
||||
- Les operations affectant le solde DOIVENT etre transactionnelles (ACID).
|
||||
- Chaque transaction DOIT avoir une date, un montant, une categorie et un type
|
||||
(revenu ou depense).
|
||||
- Les suppressions DOIVENT etre des soft-deletes (champ deleted_at) pour
|
||||
garantir la tracabilite de l'historique.
|
||||
|
||||
### III. Test Coverage
|
||||
|
||||
Les tests couvrent la logique metier critique sans imposer un TDD strict.
|
||||
|
||||
- Les calculs financiers (soldes, totaux, budgets) DOIVENT avoir des tests
|
||||
unitaires.
|
||||
- Les endpoints API DOIVENT avoir des tests d'integration avec une base de
|
||||
donnees de test.
|
||||
- Les tests DOIVENT etre executables via `pytest` en une seule commande.
|
||||
- Le coverage minimum cible est 80% sur la logique metier (services/).
|
||||
|
||||
### IV. Simplicity & Pragmatism
|
||||
|
||||
Pas de sur-ingenierie. Chaque couche d'abstraction DOIT etre justifiee.
|
||||
|
||||
- YAGNI : ne pas implementer de fonctionnalite "au cas ou".
|
||||
- Pas de pattern Repository si l'acces direct SQLAlchemy suffit.
|
||||
- Pas de microservices : monolithe backend + SPA frontend.
|
||||
- Les librairies tierces sont preferees a du code custom quand elles
|
||||
resolvent exactement le probleme (ex: Chart.js pour les graphiques).
|
||||
- Le code DOIT etre lisible par un developpeur junior sans documentation
|
||||
supplementaire.
|
||||
|
||||
### V. Docker-First Deployment
|
||||
|
||||
L'application DOIT etre deployable via `docker compose up` sans
|
||||
configuration manuelle.
|
||||
|
||||
- Un fichier `docker-compose.yml` a la racine DOIT orchestrer backend,
|
||||
frontend et PostgreSQL.
|
||||
- Les variables d'environnement DOIVENT etre centralisees dans un `.env`
|
||||
(avec `.env.example` versionne).
|
||||
- Les migrations de base de donnees DOIVENT s'executer automatiquement
|
||||
au demarrage du conteneur backend.
|
||||
- Le frontend DOIT etre servi en mode production via un build statique
|
||||
(nginx ou equivalent).
|
||||
|
||||
## Technical Stack Constraints
|
||||
|
||||
- **Backend** : Python 3.12+, FastAPI, SQLAlchemy 2.0 (async), Alembic
|
||||
- **Frontend** : React 18+, Tailwind CSS, Vite, Chart.js (ou Recharts)
|
||||
- **Base de donnees** : PostgreSQL 16+
|
||||
- **Tests** : pytest (backend), Vitest (frontend)
|
||||
- **Conteneurisation** : Docker, Docker Compose v2
|
||||
- **Langue du code** : anglais (variables, fonctions, fichiers)
|
||||
- **Langue de la documentation** : francais
|
||||
- Les dependances DOIVENT etre epinglees (versions exactes dans
|
||||
requirements.txt / package.json)
|
||||
|
||||
## Development Workflow
|
||||
|
||||
- Le code DOIT etre formate automatiquement (Ruff pour Python, Prettier
|
||||
pour JS/TS).
|
||||
- Chaque commit DOIT passer le linting et les tests avant merge.
|
||||
- Les branches suivent le pattern : `feature/xxx`, `fix/xxx`, `chore/xxx`.
|
||||
- Les migrations Alembic DOIVENT etre auto-generees puis relues
|
||||
manuellement avant commit.
|
||||
- Le fichier `docker-compose.yml` DOIT inclure un service de
|
||||
developpement avec hot-reload (backend + frontend).
|
||||
|
||||
## Governance
|
||||
|
||||
Cette constitution est le document de reference pour toutes les decisions
|
||||
techniques et architecturales du projet Budget Tracker.
|
||||
|
||||
- Tout changement architectural DOIT etre valide contre les principes
|
||||
ci-dessus avant implementation.
|
||||
- Les amendements a cette constitution requierent : (1) une justification
|
||||
ecrite, (2) une mise a jour du numero de version, (3) une propagation
|
||||
aux artefacts dependants.
|
||||
- Le versionnement suit le Semantic Versioning : MAJOR pour les
|
||||
changements incompatibles, MINOR pour les ajouts, PATCH pour les
|
||||
clarifications.
|
||||
- En cas de conflit entre pragmatisme et rigueur, le principe IV
|
||||
(Simplicity & Pragmatism) prevaut sauf pour les donnees financieres
|
||||
(principe II).
|
||||
|
||||
**Version**: 1.0.0 | **Ratified**: 2026-03-15 | **Last Amended**: 2026-03-15
|
||||
@@ -0,0 +1,247 @@
|
||||
# Contrats API REST — Budget Tracker
|
||||
|
||||
**Version** : 1.0.0 | **Base URL** : `/api/v1` | **Format** : JSON
|
||||
|
||||
## Conventions
|
||||
|
||||
- Tous les montants sont en **centimes** (integer)
|
||||
- Authentification : `Authorization: Bearer <access_token>`
|
||||
- Dates : ISO 8601 (`YYYY-MM-DD`)
|
||||
- Mois : `YYYY-MM`
|
||||
- Soft-delete : les ressources supprimées retournent 404
|
||||
- Erreurs : `{"detail": "message d'erreur"}`
|
||||
|
||||
---
|
||||
|
||||
## Authentification
|
||||
|
||||
### POST /auth/register
|
||||
Créer un compte utilisateur.
|
||||
|
||||
**Body** :
|
||||
```json
|
||||
{"email": "user@example.com", "password": "...", "full_name": "Jean Dupont"}
|
||||
```
|
||||
**Réponse 201** :
|
||||
```json
|
||||
{"id": "uuid", "email": "user@example.com", "full_name": "Jean Dupont"}
|
||||
```
|
||||
|
||||
### POST /auth/login
|
||||
Obtenir les tokens JWT.
|
||||
|
||||
**Body** : `application/x-www-form-urlencoded` : `username=email&password=...`
|
||||
|
||||
**Réponse 200** :
|
||||
```json
|
||||
{"access_token": "...", "refresh_token": "...", "token_type": "bearer"}
|
||||
```
|
||||
|
||||
### POST /auth/refresh
|
||||
Renouveler l'access token.
|
||||
|
||||
**Body** : `{"refresh_token": "..."}`
|
||||
**Réponse 200** : `{"access_token": "...", "token_type": "bearer"}`
|
||||
|
||||
### POST /auth/logout
|
||||
Invalider le refresh token.
|
||||
**Réponse 204** : no content
|
||||
|
||||
---
|
||||
|
||||
## Transactions
|
||||
|
||||
### GET /transactions
|
||||
Lister les transactions (paginées, filtrables).
|
||||
|
||||
**Query params** : `?month=2026-03&category_id=uuid&type=expense&page=1&per_page=20`
|
||||
|
||||
**Réponse 200** :
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"amount_cents": 4500,
|
||||
"type": "expense",
|
||||
"description": "Courses Leclerc",
|
||||
"category": {"id": "uuid", "name": "Alimentation", "color": "#4CAF50"},
|
||||
"transaction_date": "2026-03-15",
|
||||
"created_at": "2026-03-15T10:30:00Z"
|
||||
}
|
||||
],
|
||||
"total": 42,
|
||||
"page": 1,
|
||||
"per_page": 20
|
||||
}
|
||||
```
|
||||
|
||||
### POST /transactions
|
||||
Créer une transaction.
|
||||
|
||||
**Body** :
|
||||
```json
|
||||
{
|
||||
"amount_cents": 4500,
|
||||
"type": "expense",
|
||||
"category_id": "uuid",
|
||||
"description": "Courses Leclerc",
|
||||
"transaction_date": "2026-03-15"
|
||||
}
|
||||
```
|
||||
**Réponse 201** : transaction complète
|
||||
|
||||
### GET /transactions/{id}
|
||||
Détail d'une transaction. **Réponse 200** : transaction complète
|
||||
|
||||
### PUT /transactions/{id}
|
||||
Modifier une transaction. **Body** : mêmes champs que POST. **Réponse 200** : transaction mise à jour
|
||||
|
||||
### DELETE /transactions/{id}
|
||||
Soft-delete. **Réponse 204** : no content
|
||||
|
||||
---
|
||||
|
||||
## Catégories
|
||||
|
||||
### GET /categories
|
||||
Lister les catégories de l'utilisateur (y compris les défauts système).
|
||||
|
||||
**Query** : `?type=expense`
|
||||
|
||||
**Réponse 200** :
|
||||
```json
|
||||
[
|
||||
{"id": "uuid", "name": "Alimentation", "type": "expense", "color": "#4CAF50", "icon": "shopping-cart", "is_default": true}
|
||||
]
|
||||
```
|
||||
|
||||
### POST /categories
|
||||
Créer une catégorie personnalisée.
|
||||
|
||||
**Body** : `{"name": "Loisirs", "type": "expense", "color": "#9C27B0", "icon": "gamepad"}`
|
||||
**Réponse 201** : catégorie créée
|
||||
|
||||
### PUT /categories/{id}
|
||||
Modifier une catégorie. **Réponse 200** : catégorie mise à jour
|
||||
|
||||
### DELETE /categories/{id}
|
||||
Soft-delete. Erreur 409 si des transactions y sont liées.
|
||||
|
||||
---
|
||||
|
||||
## Budgets (Enveloppes)
|
||||
|
||||
### GET /budgets
|
||||
Lister les budgets du mois courant ou d'un mois donné.
|
||||
|
||||
**Query** : `?month=2026-03`
|
||||
|
||||
**Réponse 200** :
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": "uuid",
|
||||
"category": {"id": "uuid", "name": "Alimentation"},
|
||||
"month": "2026-03",
|
||||
"limit_cents": 30000,
|
||||
"spent_cents": 12500,
|
||||
"remaining_cents": 17500,
|
||||
"percentage_used": 41.7
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### POST /budgets
|
||||
Définir un budget pour une catégorie/mois.
|
||||
|
||||
**Body** : `{"category_id": "uuid", "month": "2026-03", "limit_cents": 30000}`
|
||||
**Réponse 201** : budget créé
|
||||
|
||||
### PUT /budgets/{id}
|
||||
Modifier la limite d'un budget. **Réponse 200** : budget mis à jour
|
||||
|
||||
### DELETE /budgets/{id}
|
||||
Supprimer un budget. **Réponse 204**
|
||||
|
||||
---
|
||||
|
||||
## Dashboard
|
||||
|
||||
### GET /dashboard
|
||||
KPIs du mois courant ou d'un mois donné.
|
||||
|
||||
**Query** : `?month=2026-03`
|
||||
|
||||
**Réponse 200** :
|
||||
```json
|
||||
{
|
||||
"month": "2026-03",
|
||||
"balance_cents": 150000,
|
||||
"total_income_cents": 250000,
|
||||
"total_expense_cents": 100000,
|
||||
"by_category": [
|
||||
{"category_id": "uuid", "name": "Alimentation", "total_cents": 45000, "percentage": 45.0}
|
||||
],
|
||||
"monthly_trend": [
|
||||
{"month": "2026-01", "income_cents": 240000, "expense_cents": 110000},
|
||||
{"month": "2026-02", "income_cents": 245000, "expense_cents": 95000},
|
||||
{"month": "2026-03", "income_cents": 250000, "expense_cents": 100000}
|
||||
],
|
||||
"budget_alerts": [
|
||||
{"category_id": "uuid", "name": "Loisirs", "percentage_used": 92.0, "status": "warning"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Historique
|
||||
|
||||
### GET /history
|
||||
Résumé mensuel navigable.
|
||||
|
||||
**Query** : `?year=2026`
|
||||
|
||||
**Réponse 200** :
|
||||
```json
|
||||
[
|
||||
{
|
||||
"month": "2026-03",
|
||||
"income_cents": 250000,
|
||||
"expense_cents": 100000,
|
||||
"balance_cents": 150000,
|
||||
"transaction_count": 42
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Export
|
||||
|
||||
### GET /export/csv
|
||||
Exporter les transactions en CSV.
|
||||
|
||||
**Query** : `?month=2026-03` (optionnel — tout exporter si absent)
|
||||
**Réponse 200** : `Content-Type: text/csv`, fichier `transactions_2026-03.csv`
|
||||
|
||||
### GET /export/pdf
|
||||
Exporter le rapport mensuel en PDF.
|
||||
|
||||
**Query** : `?month=2026-03`
|
||||
**Réponse 200** : `Content-Type: application/pdf`, fichier `rapport_2026-03.pdf`
|
||||
|
||||
---
|
||||
|
||||
## Codes d'erreur
|
||||
|
||||
| Code | Signification |
|
||||
|------|--------------|
|
||||
| 400 | Données invalides (validation Pydantic) |
|
||||
| 401 | Token manquant ou expiré |
|
||||
| 403 | Ressource appartenant à un autre utilisateur |
|
||||
| 404 | Ressource introuvable (ou soft-deleted) |
|
||||
| 409 | Conflit (doublon, contrainte métier) |
|
||||
| 422 | Erreur de validation (détail par champ) |
|
||||
| 500 | Erreur serveur |
|
||||
@@ -0,0 +1,175 @@
|
||||
# Modèle de Données — Budget Tracker
|
||||
|
||||
## Principes directeurs
|
||||
- Tous les montants sont stockés en **centimes** (INTEGER) — principe II de la Constitution
|
||||
- Les suppressions sont des **soft-deletes** via `deleted_at` — principe II (tracabilité)
|
||||
- Les identifiants sont des **UUID v4** — évite l'exposition de séquences prédictibles
|
||||
- Les timestamps sont en **UTC** sans timezone stockée
|
||||
|
||||
---
|
||||
|
||||
## Entités
|
||||
|
||||
### User
|
||||
| Champ | Type | Contraintes |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | PK, auto (gen_random_uuid()) |
|
||||
| email | VARCHAR(255) | UNIQUE, NOT NULL |
|
||||
| hashed_password | VARCHAR(255) | NOT NULL |
|
||||
| full_name | VARCHAR(100) | NOT NULL |
|
||||
| is_active | BOOLEAN | DEFAULT true, NOT NULL |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||
|
||||
**Notes** :
|
||||
- `email` indexé (recherche lors de l'authentification)
|
||||
- `hashed_password` : bcrypt, jamais exposé en API
|
||||
- `is_active` : permet de désactiver un compte sans le supprimer
|
||||
|
||||
---
|
||||
|
||||
### Category
|
||||
| Champ | Type | Contraintes |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | PK, auto |
|
||||
| user_id | UUID | FK → User.id, NOT NULL |
|
||||
| name | VARCHAR(50) | NOT NULL |
|
||||
| type | ENUM(income, expense) | NOT NULL |
|
||||
| color | VARCHAR(7) | NULL, format hex (#RRGGBB) |
|
||||
| icon | VARCHAR(50) | NULL, nom d'icône (ex: "shopping-cart") |
|
||||
| is_default | BOOLEAN | DEFAULT false, NOT NULL |
|
||||
| deleted_at | TIMESTAMP | NULL = actif |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||
|
||||
**Notes** :
|
||||
- `is_default = true` : catégories système créées au premier login (Alimentation, Transport, Logement, Loisirs, Santé, Revenus)
|
||||
- Les catégories par défaut ont `user_id` de l'utilisateur propriétaire — pas de catégories globales partagées (simplicité)
|
||||
- Index : `(user_id, deleted_at)` pour les requêtes de listing
|
||||
- Contrainte : impossible de supprimer si transactions actives liées (contrôlé en service, pas en DB)
|
||||
|
||||
---
|
||||
|
||||
### Transaction
|
||||
| Champ | Type | Contraintes |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | PK, auto |
|
||||
| user_id | UUID | FK → User.id, NOT NULL |
|
||||
| category_id | UUID | FK → Category.id, NOT NULL |
|
||||
| amount_cents | INTEGER | NOT NULL, CHECK (amount_cents > 0) |
|
||||
| type | ENUM(income, expense) | NOT NULL |
|
||||
| description | VARCHAR(255) | NULL autorisé |
|
||||
| transaction_date | DATE | NOT NULL |
|
||||
| deleted_at | TIMESTAMP | NULL = actif |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||
|
||||
**Notes** :
|
||||
- `amount_cents > 0` : le signe est porté par `type`, jamais par le montant
|
||||
- `transaction_date` : date métier saisie par l'utilisateur (≠ `created_at` technique)
|
||||
- Index : `(user_id, transaction_date, deleted_at)` pour les requêtes par période
|
||||
- Index : `(user_id, category_id, deleted_at)` pour les agrégats par catégorie
|
||||
- Le soft-delete (`deleted_at IS NOT NULL`) masque la transaction de l'UI sans la détruire
|
||||
|
||||
---
|
||||
|
||||
### Budget
|
||||
| Champ | Type | Contraintes |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | PK, auto |
|
||||
| user_id | UUID | FK → User.id, NOT NULL |
|
||||
| category_id | UUID | FK → Category.id, NOT NULL |
|
||||
| month | CHAR(7) | NOT NULL, format YYYY-MM |
|
||||
| limit_cents | INTEGER | NOT NULL, CHECK (limit_cents > 0) |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||
| UNIQUE | | (user_id, category_id, month) |
|
||||
|
||||
**Notes** :
|
||||
- `month` en CHAR(7) format ISO `YYYY-MM` : simple, triable lexicographiquement, pas de confusion timezone
|
||||
- La contrainte UNIQUE garantit un seul budget par catégorie par mois par utilisateur
|
||||
- Index implicite sur la contrainte UNIQUE
|
||||
- Pas de soft-delete : un budget supprimé est réellement supprimé (pas de données financières dans l'entité elle-même)
|
||||
|
||||
---
|
||||
|
||||
### RefreshToken
|
||||
| Champ | Type | Contraintes |
|
||||
|-------|------|-------------|
|
||||
| id | UUID | PK, auto |
|
||||
| user_id | UUID | FK → User.id, NOT NULL |
|
||||
| token_hash | VARCHAR(255) | UNIQUE, NOT NULL |
|
||||
| expires_at | TIMESTAMP | NOT NULL |
|
||||
| revoked_at | TIMESTAMP | NULL = actif |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT now() |
|
||||
|
||||
**Notes** :
|
||||
- Seul le hash du token est stocké (SHA-256), jamais le token brut
|
||||
- Permet la révocation explicite (logout) et l'invalidation par rotation
|
||||
- Nettoyage périodique des tokens expirés recommandé (tâche cron ou au login)
|
||||
|
||||
---
|
||||
|
||||
## Relations
|
||||
|
||||
```
|
||||
User 1 ──────────────────────────── N Category
|
||||
User 1 ──────────────────────────── N Transaction
|
||||
User 1 ──────────────────────────── N Budget
|
||||
User 1 ──────────────────────────── N RefreshToken
|
||||
Category 1 ──────────────────────── N Transaction
|
||||
Category 1 ──────────────────────── N Budget
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Requêtes critiques et leurs index
|
||||
|
||||
### Solde courant d'un utilisateur
|
||||
```sql
|
||||
SELECT
|
||||
SUM(CASE WHEN type = 'income' THEN amount_cents ELSE -amount_cents END)
|
||||
FROM transactions
|
||||
WHERE user_id = :uid AND deleted_at IS NULL;
|
||||
```
|
||||
→ Index : `(user_id, deleted_at)`
|
||||
|
||||
### Résumé mensuel (revenus/dépenses d'un mois)
|
||||
```sql
|
||||
SELECT type, SUM(amount_cents)
|
||||
FROM transactions
|
||||
WHERE user_id = :uid
|
||||
AND transaction_date >= :month_start
|
||||
AND transaction_date < :month_end
|
||||
AND deleted_at IS NULL
|
||||
GROUP BY type;
|
||||
```
|
||||
→ Index : `(user_id, transaction_date, deleted_at)`
|
||||
|
||||
### Consommation d'un budget (dépenses catégorie × mois)
|
||||
```sql
|
||||
SELECT SUM(t.amount_cents)
|
||||
FROM transactions t
|
||||
JOIN budgets b ON b.category_id = t.category_id
|
||||
WHERE t.user_id = :uid
|
||||
AND t.category_id = :cat_id
|
||||
AND t.type = 'expense'
|
||||
AND t.transaction_date >= :month_start
|
||||
AND t.transaction_date < :month_end
|
||||
AND t.deleted_at IS NULL;
|
||||
```
|
||||
→ Index : `(user_id, category_id, deleted_at)`
|
||||
|
||||
---
|
||||
|
||||
## Valeurs par défaut — Catégories système
|
||||
|
||||
| Nom | Type | Couleur | Icône |
|
||||
|-----|------|---------|-------|
|
||||
| Alimentation | expense | #22c55e | utensils |
|
||||
| Transport | expense | #3b82f6 | car |
|
||||
| Logement | expense | #f59e0b | home |
|
||||
| Loisirs | expense | #a855f7 | gamepad-2 |
|
||||
| Santé | expense | #ef4444 | heart-pulse |
|
||||
| Revenus | income | #10b981 | trending-up |
|
||||
|
||||
Créées automatiquement lors du premier login utilisateur (dans la logique de service, pas en seed DB globale).
|
||||
@@ -0,0 +1,265 @@
|
||||
# Plan d'Implémentation — Budget Tracker
|
||||
|
||||
**Feature** : `003-budget-tracker-core`
|
||||
**Date** : 2026-03-15
|
||||
**Durée estimée** : 13 jours de travail
|
||||
**Stack** : FastAPI + SQLAlchemy 2.0 async + Alembic / React 18 + Vite + Tailwind + Recharts / PostgreSQL 16 / Docker Compose
|
||||
|
||||
---
|
||||
|
||||
## Vérification Constitution
|
||||
|
||||
| Principe | Conformité | Notes |
|
||||
|----------|-----------|-------|
|
||||
| I. API-First | ✅ | Endpoints définis dans `contracts/api.md`, Pydantic pour la validation |
|
||||
| II. Data Integrity | ✅ | Montants en centimes (int), soft-delete, transactions ACID |
|
||||
| III. Test Coverage | ✅ | pytest backend, Vitest frontend, ≥80% logique métier |
|
||||
| IV. Simplicity | ✅ | Monolithe, pas de microservices, librairies standard |
|
||||
| V. Docker-First | ✅ | docker-compose.yml, migrations auto au démarrage, build statique nginx |
|
||||
|
||||
---
|
||||
|
||||
## Phase 0 — Setup & Infrastructure (1 jour)
|
||||
|
||||
### Objectif
|
||||
Disposer d'un environnement de développement fonctionnel avec hot-reload.
|
||||
|
||||
### Tâches
|
||||
|
||||
1. **Init repo Git** : `.gitignore`, `README.md`, structure dossiers (`backend/`, `frontend/`, `docker/`)
|
||||
2. **Docker Compose dev** :
|
||||
- Service `db` : PostgreSQL 16, volume persistant, init script
|
||||
- Service `backend` : Python 3.12, hot-reload uvicorn, montage code source
|
||||
- Service `frontend` : Node 22, Vite dev server, montage code source
|
||||
3. **Backend scaffold** :
|
||||
- FastAPI app factory (`backend/app/main.py`)
|
||||
- Config via Pydantic Settings (`.env` → `backend/app/config.py`)
|
||||
- SQLAlchemy 2.0 async engine + session factory
|
||||
- Alembic init (`backend/alembic/`)
|
||||
- Ruff config (`pyproject.toml`)
|
||||
- `requirements.txt` avec versions pinned
|
||||
4. **Frontend scaffold** :
|
||||
- `npm create vite@latest` avec template React + TypeScript
|
||||
- Tailwind CSS setup
|
||||
- Prettier config
|
||||
- Structure dossiers (`src/components/`, `src/pages/`, `src/api/`, `src/hooks/`)
|
||||
5. **`.env.example`** avec toutes les variables documentées
|
||||
6. **Smoke test** : `docker compose up` → backend répond `GET /health`, frontend affiche une page
|
||||
|
||||
### Livrables
|
||||
- `docker-compose.yml` + `docker-compose.override.yml` (dev)
|
||||
- Backend qui démarre, frontend qui affiche, PostgreSQL qui tourne
|
||||
- Premier commit : `chore: initial project setup`
|
||||
|
||||
### Critères de validation
|
||||
- [ ] `docker compose up` fonctionne sans erreur
|
||||
- [ ] `GET /health` retourne `{"status": "ok"}`
|
||||
- [ ] Frontend accessible sur `localhost:5173`
|
||||
- [ ] Ruff et Prettier passent sans erreur
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 — Backend Core (3 jours)
|
||||
|
||||
### Objectif
|
||||
API REST fonctionnelle avec auth, transactions et catégories.
|
||||
|
||||
### Jour 1 : Modèles & Auth
|
||||
|
||||
1. **Modèles SQLAlchemy** : `User`, `Category`, `Transaction`, `Budget`, `RefreshToken`
|
||||
- UUIDs, `created_at`/`updated_at` auto, `deleted_at` pour soft-delete
|
||||
- `amount_cents` INTEGER, contrainte CHECK > 0
|
||||
2. **Migrations Alembic** : `alembic revision --autogenerate -m "initial models"`
|
||||
3. **Auth JWT** :
|
||||
- Endpoint `POST /auth/register` (hash bcrypt, validation email unique)
|
||||
- Endpoint `POST /auth/login` (access token 15min + refresh token 7j)
|
||||
- Endpoint `POST /auth/refresh`
|
||||
- Endpoint `POST /auth/logout` (invalidation refresh token)
|
||||
- Middleware `get_current_user` pour les routes protégées
|
||||
4. **Seed catégories par défaut** : Alimentation, Transport, Logement, Santé, Loisirs, Divers (dépenses) + Salaire, Freelance, Remboursement (revenus)
|
||||
|
||||
### Jour 2 : CRUD Transactions & Catégories
|
||||
|
||||
1. **Transactions** :
|
||||
- `GET /transactions` : pagination, filtres (mois, catégorie, type), tri par date desc
|
||||
- `POST /transactions` : validation Pydantic, isolation par user_id
|
||||
- `PUT /transactions/{id}` : vérification ownership
|
||||
- `DELETE /transactions/{id}` : soft-delete (set `deleted_at`)
|
||||
2. **Catégories** :
|
||||
- `GET /categories` : catégories user + catégories système (`is_default`)
|
||||
- `POST /categories` : personnalisées uniquement
|
||||
- `PUT /categories/{id}` : pas de modif des catégories système
|
||||
- `DELETE /categories/{id}` : refus 409 si transactions liées actives
|
||||
3. **Services layer** : séparer la logique métier des routes (pas de requêtes SQL dans les endpoints)
|
||||
|
||||
### Jour 3 : Tests Backend
|
||||
|
||||
1. **Fixtures pytest** : base de test PostgreSQL (ou SQLite async pour la vitesse), factory pour User/Transaction/Category
|
||||
2. **Tests unitaires** :
|
||||
- Calcul de solde (revenu - dépense, centimes)
|
||||
- Soft-delete : la transaction n'apparaît plus mais existe en base
|
||||
- Validation montant > 0, date pas dans le futur (si décidé)
|
||||
3. **Tests d'intégration** :
|
||||
- Auth : register → login → token valide → accès protégé
|
||||
- CRUD transactions : create → list → update → delete → list vérifie absence
|
||||
- Filtrage par mois et catégorie
|
||||
- Isolation multi-user : user A ne voit pas les transactions de user B
|
||||
4. **CI** : `pytest --cov=app/services --cov-fail-under=80`
|
||||
|
||||
### Livrables
|
||||
- API complète (auth + transactions + catégories)
|
||||
- Suite de tests avec coverage ≥80% services
|
||||
- Documentation OpenAPI auto (`/docs`)
|
||||
|
||||
---
|
||||
|
||||
## Phase 2 — Frontend Core (3 jours)
|
||||
|
||||
### Objectif
|
||||
SPA React fonctionnelle avec auth et gestion des transactions.
|
||||
|
||||
### Jour 4 : Auth & Layout
|
||||
|
||||
1. **Client API** : module `src/api/client.ts` avec intercepteur Axios/fetch, gestion auto du refresh token
|
||||
2. **Auth store** : Context React ou Zustand léger (user, tokens, login/logout)
|
||||
3. **Pages auth** : Login, Register avec validation côté client
|
||||
4. **Layout principal** : Sidebar navigation (Dashboard, Transactions, Budgets, Historique), header avec nom user + logout
|
||||
5. **Route guard** : redirect vers login si non authentifié
|
||||
|
||||
### Jour 5 : Liste & Formulaire Transactions
|
||||
|
||||
1. **Page Transactions** :
|
||||
- Tableau avec colonnes : date, description, catégorie (badge couleur), montant, type (icône revenu/dépense)
|
||||
- Pagination
|
||||
- Filtres : mois (date picker), catégorie (select), type (toggle)
|
||||
- Bouton supprimer avec confirmation
|
||||
2. **Formulaire Transaction** (modal ou page) :
|
||||
- Champs : montant (€, converti en centimes), date, type (toggle revenu/dépense), catégorie (select), description
|
||||
- Validation temps réel
|
||||
- Mode création et édition
|
||||
3. **TanStack Query** : cache serveur, invalidation après mutation
|
||||
|
||||
### Jour 6 : Catégories & Polish
|
||||
|
||||
1. **Gestion catégories** : page settings ou modal, CRUD catégories personnalisées avec couleur et icône
|
||||
2. **Feedback UI** : toasts de confirmation (ajout, suppression), skeleton loading, états vides
|
||||
3. **Responsive** : mobile-first, sidebar collapsible sur mobile
|
||||
4. **Tests Vitest** : composants critiques (formulaire transaction, affichage montant centimes→euros)
|
||||
|
||||
### Livrables
|
||||
- SPA complète : auth + transactions + catégories
|
||||
- Responsive mobile/desktop
|
||||
- Tests composants critiques
|
||||
|
||||
---
|
||||
|
||||
## Phase 3 — Features Avancées (4 jours)
|
||||
|
||||
### Jour 7 : Dashboard & Graphiques
|
||||
|
||||
1. **Endpoints backend** :
|
||||
- `GET /dashboard?month=YYYY-MM` : solde, totaux, répartition par catégorie, tendance 6 mois, alertes budget
|
||||
2. **Page Dashboard** :
|
||||
- Cards KPI : solde courant, revenus du mois, dépenses du mois, solde net
|
||||
- Graphique camembert : répartition dépenses par catégorie (Recharts PieChart)
|
||||
- Graphique barres : tendance revenus/dépenses sur 6 mois (Recharts BarChart)
|
||||
- Alertes budgets dépassés (badges warning/danger)
|
||||
3. **Navigation mensuelle** : boutons mois précédent/suivant sur le dashboard
|
||||
|
||||
### Jour 8 : Budgets (Enveloppes)
|
||||
|
||||
1. **Endpoints backend** :
|
||||
- `GET /budgets?month=YYYY-MM` : budgets avec calcul `spent_cents` et `remaining_cents`
|
||||
- `POST /budgets` : création avec contrainte unicité (user, category, month)
|
||||
- `PUT /budgets/{id}`, `DELETE /budgets/{id}`
|
||||
2. **Page Budgets** :
|
||||
- Liste des enveloppes du mois avec barre de progression (vert/orange/rouge)
|
||||
- Formulaire ajout/édition budget (catégorie, limite en €)
|
||||
- Copie des budgets du mois précédent (bouton "Reconduire")
|
||||
3. **Tests** : calcul consommation budget, reconduction, alertes seuil
|
||||
|
||||
### Jour 9 : Historique Mensuel
|
||||
|
||||
1. **Endpoint backend** : `GET /history?year=YYYY` → résumé par mois
|
||||
2. **Page Historique** :
|
||||
- Tableau annuel : mois, revenus, dépenses, solde, nb transactions
|
||||
- Clic sur un mois → redirige vers Dashboard du mois sélectionné
|
||||
- Sélecteur d'année
|
||||
3. **Graphique annuel** : courbe revenus/dépenses sur 12 mois (Recharts LineChart)
|
||||
|
||||
### Jour 10 : Export CSV & PDF
|
||||
|
||||
1. **Endpoint CSV** : `GET /export/csv?month=YYYY-MM` → streaming CSV avec headers
|
||||
2. **Endpoint PDF** : `GET /export/pdf?month=YYYY-MM` → WeasyPrint, template Jinja2 rapport mensuel
|
||||
- En-tête avec titre + mois + date d'export
|
||||
- Tableau des transactions
|
||||
- Résumé par catégorie
|
||||
- Solde
|
||||
3. **Boutons export** dans la page Transactions et le Dashboard
|
||||
4. **Tests** : validité CSV (parsable), PDF non vide
|
||||
|
||||
### Livrables
|
||||
- Dashboard avec graphiques interactifs
|
||||
- Budgets enveloppes avec alertes
|
||||
- Historique annuel navigable
|
||||
- Export CSV + PDF fonctionnels
|
||||
|
||||
---
|
||||
|
||||
## Phase 4 — Finalisation & Production (2 jours)
|
||||
|
||||
### Jour 11 : Docker Production
|
||||
|
||||
1. **Dockerfile backend** : multi-stage (build deps → runtime slim)
|
||||
2. **Dockerfile frontend** : multi-stage (build Vite → nginx:alpine)
|
||||
3. **docker-compose.prod.yml** :
|
||||
- Backend : gunicorn + uvicorn workers
|
||||
- Frontend : nginx avec compression gzip, cache headers
|
||||
- PostgreSQL avec backup volume
|
||||
- Health checks sur tous les services
|
||||
4. **Entrypoint backend** : exécute `alembic upgrade head` au démarrage
|
||||
5. **Sécurité** : CORS restrictif, rate limiting (slowapi), HTTPS-ready headers
|
||||
|
||||
### Jour 12 : Documentation & Qualité
|
||||
|
||||
1. **README.md** complet :
|
||||
- Description du projet
|
||||
- Prérequis (Docker, Docker Compose)
|
||||
- Quick start : `cp .env.example .env && docker compose up`
|
||||
- Architecture (schéma simplifié)
|
||||
- Endpoints API (lien vers `/docs`)
|
||||
2. **OpenAPI** : vérifier que la doc auto est complète et lisible
|
||||
3. **Tests e2e légers** : script Bash qui lance le stack, crée un user, ajoute une transaction, vérifie le dashboard
|
||||
4. **Cleanup** : supprimer code mort, vérifier tous les TODO, linting final
|
||||
|
||||
### Livrables
|
||||
- Docker Compose production-ready
|
||||
- README complet
|
||||
- Suite complète de tests (unit + integ + e2e)
|
||||
- Projet prêt à déployer
|
||||
|
||||
---
|
||||
|
||||
## Artefacts générés
|
||||
|
||||
| Fichier | Description |
|
||||
|---------|-------------|
|
||||
| `spec.md` | Spécification fonctionnelle (7 user stories, 20 exigences) |
|
||||
| `research.md` | Décisions techniques avec rationale |
|
||||
| `data-model.md` | 5 entités, index, contraintes |
|
||||
| `contracts/api.md` | Contrats API REST (21 endpoints) |
|
||||
| `plan.md` | Ce plan d'implémentation |
|
||||
|
||||
## Risques identifiés
|
||||
|
||||
| Risque | Impact | Mitigation |
|
||||
|--------|--------|-----------|
|
||||
| WeasyPrint lourd en Docker | Image backend volumineuse | Multi-stage build, layer caching |
|
||||
| Performances dashboard sur gros volume | Requêtes lentes >10k transactions | Index dédiés (voir `data-model.md`), pagination |
|
||||
| Complexité auth JWT refresh | Bugs de déconnexion intempestive | Tests d'intégration auth exhaustifs |
|
||||
| Recharts bundle size | Frontend lourd | Tree-shaking Vite, lazy loading des pages graphiques |
|
||||
|
||||
## Prochaines étapes
|
||||
|
||||
1. **`/speckit.tasks`** — Découper ce plan en tâches atomiques assignables
|
||||
2. **`/speckit.implement`** — Lancer l'implémentation phase par phase
|
||||
@@ -0,0 +1,100 @@
|
||||
# Recherche & Décisions Techniques — Budget Tracker
|
||||
|
||||
## Authentification
|
||||
- **Décision** : JWT (tokens access 15min + refresh 7j) via `python-jose` + `passlib[bcrypt]`
|
||||
- **Rationale** : stateless, compatible SPA React, aucune session serveur à gérer, facile à dockeriser
|
||||
- **Alternatives** :
|
||||
- Session cookies (plus simple mais couplé au serveur, complexe en CORS)
|
||||
- OAuth2 externe / Keycloak (overkill pour usage personnel)
|
||||
- FastAPI-Users (bibliothèque haut niveau — ajoute de la magie, préférence pour la transparence)
|
||||
|
||||
## Base de données
|
||||
- **Décision** : PostgreSQL 16
|
||||
- **Rationale** : ACID natif (critique pour données financières), types UUID, ENUM, support transactionnel, standard de l'industrie
|
||||
- **Alternatives** :
|
||||
- SQLite (suffisant en dev, mais limité en concurrence et types)
|
||||
- MySQL (moins bon support UUID/ENUM natif)
|
||||
|
||||
## ORM & Migrations
|
||||
- **Décision** : SQLAlchemy 2.0 (mode async) + Alembic
|
||||
- **Rationale** : async natif pour FastAPI, migrations versionées et réversibles, mature et bien documenté
|
||||
- **Alternatives** :
|
||||
- Tortoise ORM (async natif mais moins mature, moins d'intégrations)
|
||||
- SQLModel (wrapper SQLAlchemy+Pydantic — moins de contrôle sur les modèles)
|
||||
- Prisma (Node.js uniquement)
|
||||
|
||||
## Framework Backend
|
||||
- **Décision** : FastAPI 0.111+
|
||||
- **Rationale** : OpenAPI/Swagger auto-généré (conforme principe I de la Constitution), validation Pydantic intégrée, async natif, typage strict
|
||||
- **Alternatives** :
|
||||
- Django REST Framework (sync-first, plus lourd)
|
||||
- Flask (pas d'async natif, validation manuelle)
|
||||
|
||||
## Framework Frontend
|
||||
- **Décision** : React 18 + TypeScript + Vite
|
||||
- **Rationale** : écosystème riche, SPA adaptée à une app de gestion, typage fort réduit les erreurs sur les montants financiers
|
||||
- **Alternatives** :
|
||||
- Next.js (SSR inutile pour usage authentifié perso)
|
||||
- Vue 3 (viable mais écosystème plus réduit)
|
||||
|
||||
## Styling Frontend
|
||||
- **Décision** : Tailwind CSS 3 + shadcn/ui
|
||||
- **Rationale** : utilitaires CSS rapides, shadcn/ui fournit composants accessibles (formulaires, modales, tableaux) sans dépendance lourde
|
||||
- **Alternatives** :
|
||||
- MUI / Ant Design (plus lourds, style moins personnalisable)
|
||||
- CSS Modules (trop verbeux pour une SPA)
|
||||
|
||||
## Graphiques Frontend
|
||||
- **Décision** : Recharts
|
||||
- **Rationale** : composants React purs (pas de D3 direct), bon support responsive, Tailwind-compatible, léger (~180 ko gzip)
|
||||
- **Alternatives** :
|
||||
- Chart.js + react-chartjs-2 (moins idiomatic React, state management externe)
|
||||
- Nivo (très complet mais plus lourd, over-engineering pour 2 types de graphiques)
|
||||
- Victory (moins maintenu)
|
||||
|
||||
## Gestion d'état Frontend
|
||||
- **Décision** : TanStack Query (React Query) v5
|
||||
- **Rationale** : cache serveur, invalidation automatique après mutations (soldes recalculés), gestion loading/error intégrée — évite un store global Redux pour des données essentiellement serveur
|
||||
- **Alternatives** :
|
||||
- Redux Toolkit (overkill, état global inutile si les données viennent de l'API)
|
||||
- SWR (moins de fonctionnalités sur les mutations)
|
||||
- Zustand (utile pour état local UI uniquement, complémentaire)
|
||||
|
||||
## Export CSV
|
||||
- **Décision** : module `csv` Python stdlib
|
||||
- **Rationale** : aucune dépendance additionnelle, suffisant pour colonnes tabulaires de transactions, streaming possible via `StreamingResponse` FastAPI
|
||||
- **Alternatives** :
|
||||
- pandas (overkill, dépendance lourde)
|
||||
|
||||
## Export PDF
|
||||
- **Décision** : WeasyPrint
|
||||
- **Rationale** : rendu HTML→PDF côté serveur, templates Jinja2 réutilisables, CSS support correct, rendu fidèle
|
||||
- **Alternatives** :
|
||||
- ReportLab (API bas niveau, verbeux pour des tableaux)
|
||||
- pdfkit (dépendance système wkhtmltopdf, difficile à dockeriser)
|
||||
- Reportlab + xhtml2pdf (moins stable)
|
||||
|
||||
## Stockage des montants
|
||||
- **Décision** : entiers en centimes (`amount_cents INTEGER`)
|
||||
- **Rationale** : conforme au principe II de la Constitution — élimine toute erreur d'arrondi IEEE 754 sur les opérations financières (ex : 0.1 + 0.2 ≠ 0.3 en float)
|
||||
- **Règle** : la conversion euros↔centimes se fait uniquement aux frontières (serialisation Pydantic et affichage React)
|
||||
|
||||
## Soft Delete
|
||||
- **Décision** : champ `deleted_at TIMESTAMP NULL` sur `Transaction` et `Category`
|
||||
- **Rationale** : conforme au principe II (tracabilité historique), permet l'audit et la restauration éventuelle
|
||||
- **Implémentation** : filtre `WHERE deleted_at IS NULL` systématique dans les requêtes ; index partiel recommandé
|
||||
|
||||
## Conteneurisation
|
||||
- **Décision** : Docker Compose (backend + frontend + PostgreSQL)
|
||||
- **Rationale** : conforme au principe V de la Constitution, reproductibilité dev/prod, un seul `docker compose up`
|
||||
- **Structure** :
|
||||
- `backend/` — image Python 3.12-slim
|
||||
- `frontend/` — build Vite servi par Nginx
|
||||
- `db/` — PostgreSQL 16 officiel
|
||||
|
||||
## Tests Backend
|
||||
- **Décision** : pytest + httpx (client async) + pytest-asyncio + base de données de test dédiée
|
||||
- **Rationale** : conforme au principe III (80% coverage services/), tests d'intégration sur vraie BDD (pas de mock SQLAlchemy)
|
||||
- **Alternatives** :
|
||||
- unittest (moins expressif)
|
||||
- mocks SQLAlchemy (rejeté — risque de divergence mock/prod)
|
||||
@@ -0,0 +1,187 @@
|
||||
# Feature Specification: Budget Tracker Core
|
||||
|
||||
**Feature Branch**: `003-budget-tracker-core`
|
||||
**Created**: 2026-03-15
|
||||
**Status**: Draft
|
||||
**Input**: User description: "App web de suivi de budget personnel : suivi des revenus et dépenses, catégorisation, tableaux de bord, gestion de budgets par enveloppe, export CSV/PDF, historique mensuel, authentification utilisateur."
|
||||
|
||||
## User Scenarios & Testing *(mandatory)*
|
||||
|
||||
### User Story 1 - Saisie et consultation des transactions (Priority: P1)
|
||||
|
||||
En tant qu'utilisateur authentifié, je veux ajouter, modifier et supprimer des transactions (revenus ou dépenses) afin de maintenir un historique fidèle de mes mouvements financiers.
|
||||
|
||||
**Why this priority**: C'est le cœur fonctionnel de l'application. Sans la capacité d'enregistrer des transactions, aucune autre fonctionnalité (budget, graphiques, export) n'a de sens. Livrer uniquement cette story constitue déjà un journal financier minimaliste opérationnel.
|
||||
|
||||
**Independent Test**: Peut être testé indépendamment en ajoutant une transaction via le formulaire, en vérifiant son apparition dans la liste, puis en la modifiant et en la supprimant — sans aucune autre fonctionnalité active.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** un utilisateur authentifié sur la page "Transactions", **When** il soumet le formulaire avec un montant (ex: 45,90 €), une date, un type "dépense" et une description, **Then** la transaction apparaît en tête de liste avec le bon montant, la bonne date et le bon type.
|
||||
2. **Given** une transaction existante, **When** l'utilisateur clique sur "Modifier" et change le montant à 50,00 €, **Then** la liste affiche immédiatement le montant mis à jour et le solde courant est recalculé.
|
||||
3. **Given** une transaction existante, **When** l'utilisateur clique sur "Supprimer" et confirme la suppression, **Then** la transaction disparaît de la liste et le solde est recalculé en conséquence ; la transaction reste présente en base (soft-delete) et n'est plus visible dans l'interface.
|
||||
4. **Given** un formulaire de saisie, **When** l'utilisateur soumet sans montant ou avec un montant négatif, **Then** un message d'erreur explicite est affiché et aucune transaction n'est créée.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 - Tableau de bord avec solde et résumé mensuel (Priority: P2)
|
||||
|
||||
En tant qu'utilisateur authentifié, je veux voir en un coup d'œil mon solde courant, le total de mes revenus et dépenses du mois, ainsi qu'un graphique de répartition des dépenses par catégorie, afin de comprendre rapidement ma situation financière.
|
||||
|
||||
**Why this priority**: Le tableau de bord transforme les données brutes en information utile. Il constitue la page d'accueil naturelle de l'application et justifie la valeur perçue de l'outil dès la première visite. Dépend de P1 (transactions existantes) mais peut être rendu statique avec des données de seed.
|
||||
|
||||
**Independent Test**: Peut être testé indépendamment en pré-chargeant une base avec des transactions de test et en vérifiant que les KPIs et graphiques affichent des valeurs cohérentes avec les données injectées.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** un utilisateur avec des transactions en mars 2026, **When** il accède au tableau de bord, **Then** il voit le solde courant (revenus − dépenses depuis l'origine), le total des revenus du mois, le total des dépenses du mois et le solde net du mois.
|
||||
2. **Given** un utilisateur avec des dépenses réparties sur 3 catégories, **When** il consulte le tableau de bord, **Then** un graphique en camembert ou en barres affiche la répartition des dépenses par catégorie pour le mois courant, avec les montants et pourcentages visibles au survol.
|
||||
3. **Given** un utilisateur sans transaction ce mois-ci, **When** il consulte le tableau de bord, **Then** les KPIs affichent 0 € et le graphique affiche un état vide avec un message d'invitation à saisir des transactions.
|
||||
4. **Given** un utilisateur sur le tableau de bord, **When** il navigue vers le mois précédent via les contrôles de navigation, **Then** tous les KPIs et graphiques se mettent à jour pour refléter la période sélectionnée.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 - Catégorisation des transactions (Priority: P3)
|
||||
|
||||
En tant qu'utilisateur authentifié, je veux assigner chaque transaction à une catégorie (alimentation, transport, loisirs, etc.) et gérer mes propres catégories personnalisées, afin d'analyser mes dépenses par poste.
|
||||
|
||||
**Why this priority**: La catégorisation enrichit les transactions et débloque les analyses (graphiques, budgets par enveloppe). Les catégories par défaut permettent un onboarding rapide ; la personnalisation répond aux besoins individuels. Dépend de P1.
|
||||
|
||||
**Independent Test**: Peut être testé indépendamment en créant une catégorie personnalisée, en l'assignant à une transaction, et en vérifiant que la transaction apparaît bien filtrée par cette catégorie.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** un utilisateur sur la page "Catégories", **When** il crée une catégorie "Abonnements" avec une couleur et une icône, **Then** la catégorie est disponible dans le sélecteur lors de la création/modification d'une transaction.
|
||||
2. **Given** une catégorie utilisée par au moins une transaction, **When** l'utilisateur tente de la supprimer, **Then** un avertissement l'informe que X transactions utilisent cette catégorie et lui propose de les réassigner avant suppression.
|
||||
3. **Given** un utilisateur sur la liste des transactions, **When** il filtre par catégorie "Alimentation", **Then** seules les transactions de cette catégorie sont affichées, avec le total filtré visible.
|
||||
4. **Given** un nouveau compte utilisateur, **When** il accède pour la première fois à l'application, **Then** un ensemble de catégories par défaut (Alimentation, Transport, Logement, Loisirs, Santé, Revenus) est déjà disponible.
|
||||
|
||||
---
|
||||
|
||||
### User Story 4 - Gestion des budgets par enveloppe (Priority: P4)
|
||||
|
||||
En tant qu'utilisateur authentifié, je veux définir un budget maximum mensuel par catégorie (ex : 300 € pour "Alimentation") et visualiser ma consommation en temps réel, afin de respecter mes objectifs de dépenses.
|
||||
|
||||
**Why this priority**: La gestion budgétaire est la fonctionnalité de pilotage financier. Elle différencie l'application d'un simple journal de comptes. Nécessite P1 et P3 pour être utile.
|
||||
|
||||
**Independent Test**: Peut être testé indépendamment en créant un budget pour une catégorie, en ajoutant des transactions dans cette catégorie et en vérifiant que la barre de progression reflète le bon taux de consommation.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** un utilisateur sur la page "Budgets", **When** il crée un budget de 300 € pour la catégorie "Alimentation" pour mars 2026, **Then** une carte budget apparaît avec une barre de progression à 0 % et le plafond affiché.
|
||||
2. **Given** un budget "Alimentation" de 300 € et 180 € de dépenses déjà enregistrées dans cette catégorie ce mois-ci, **When** l'utilisateur consulte la page "Budgets", **Then** la barre de progression indique 60 % (180/300 €), avec le montant restant affiché (120 €).
|
||||
3. **Given** un budget dont les dépenses dépassent le plafond, **When** l'utilisateur consulte la page "Budgets", **Then** la barre de progression passe au rouge, le dépassement est affiché en négatif (ex : −30 €) et une alerte visuelle attire l'attention.
|
||||
4. **Given** un budget défini pour mars 2026, **When** avril 2026 commence, **Then** le système crée automatiquement un nouveau budget pour avril avec le même plafond, ou invite l'utilisateur à reconduire ses budgets.
|
||||
|
||||
---
|
||||
|
||||
### User Story 5 - Historique mensuel navigable (Priority: P5)
|
||||
|
||||
En tant qu'utilisateur authentifié, je veux naviguer mois par mois dans mon historique de transactions, afin de retrouver et analyser mes dépenses passées.
|
||||
|
||||
**Why this priority**: La navigation temporelle est essentielle pour le suivi sur le long terme. Elle complète les fonctionnalités de base sans en être un prérequis. Dépend de P1.
|
||||
|
||||
**Independent Test**: Peut être testé indépendamment en naviguant vers un mois passé contenant des transactions seed et en vérifiant que seules les transactions de ce mois s'affichent avec les bons totaux.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** un utilisateur sur la page "Transactions", **When** il clique sur "< Mois précédent", **Then** la liste affiche uniquement les transactions du mois précédent, avec le sélecteur de période mis à jour.
|
||||
2. **Given** un utilisateur sur un mois passé, **When** il clique sur "Mois suivant" jusqu'au mois courant, **Then** les données du mois courant s'affichent correctement.
|
||||
3. **Given** un utilisateur souhaitant aller à une date précise, **When** il sélectionne "Janvier 2025" dans le sélecteur de mois, **Then** la liste de transactions et les statistiques correspondantes s'affichent instantanément.
|
||||
|
||||
---
|
||||
|
||||
### User Story 6 - Export des données (Priority: P6)
|
||||
|
||||
En tant qu'utilisateur authentifié, je veux exporter mes transactions en CSV ou en PDF pour un mois donné ou pour toute la période, afin de les utiliser dans un tableur ou de les archiver.
|
||||
|
||||
**Why this priority**: L'export est une fonctionnalité de sortie de données sans dépendance critique sur les autres features. Elle répond à un besoin de portabilité et de conformité (déclaration fiscale, remboursements). Dépend de P1.
|
||||
|
||||
**Independent Test**: Peut être testé indépendamment en déclenchant un export CSV pour le mois courant et en vérifiant que le fichier téléchargé contient les colonnes et données attendues.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** un utilisateur avec des transactions en mars 2026, **When** il clique sur "Exporter CSV" pour mars 2026, **Then** un fichier `transactions_2026-03.csv` est téléchargé avec les colonnes : date, description, catégorie, type, montant.
|
||||
2. **Given** un utilisateur souhaitant un export PDF, **When** il clique sur "Exporter PDF" pour mars 2026, **Then** un fichier `rapport_2026-03.pdf` est généré avec le résumé mensuel (solde, totaux), le graphique de répartition et le détail des transactions.
|
||||
3. **Given** un mois sans aucune transaction, **When** l'utilisateur tente un export, **Then** un message lui indique qu'il n'y a pas de données à exporter et aucun fichier n'est généré.
|
||||
4. **Given** un export en cours, **When** le fichier dépasse 10 000 lignes, **Then** l'export est déclenché en tâche de fond et l'utilisateur est notifié (ou redirigé vers un lien de téléchargement) à la fin du traitement.
|
||||
|
||||
---
|
||||
|
||||
### User Story 7 - Authentification utilisateur (Priority: P7)
|
||||
|
||||
En tant qu'utilisateur, je veux créer un compte et me connecter de façon sécurisée, afin que mes données financières soient privées et isolées des autres utilisateurs.
|
||||
|
||||
**Why this priority**: L'authentification est un prérequis à l'isolation des données en mode multi-utilisateurs. En mode instance unique (un seul utilisateur), elle peut être différée. Placée en P7 car l'ensemble des fonctionnalités P1–P6 peut être développé et testé avec un utilisateur fictif en seed.
|
||||
|
||||
**Independent Test**: Peut être testé indépendamment en créant un compte, en se déconnectant, en tentant d'accéder à une route protégée (redirection attendue vers login), puis en se reconnectant.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** un visiteur non authentifié, **When** il accède à n'importe quelle route protégée, **Then** il est redirigé vers la page de connexion.
|
||||
2. **Given** un formulaire d'inscription, **When** l'utilisateur saisit un email valide et un mot de passe d'au moins 8 caractères, **Then** son compte est créé, il est automatiquement connecté et redirigé vers le tableau de bord.
|
||||
3. **Given** un utilisateur avec un compte existant, **When** il se connecte avec ses identifiants corrects, **Then** un token JWT est émis, stocké côté client, et il est redirigé vers le tableau de bord.
|
||||
4. **Given** un utilisateur connecté, **When** son token expire ou qu'il clique sur "Déconnexion", **Then** le token est invalidé, la session est détruite et il est redirigé vers la page de connexion.
|
||||
5. **Given** deux utilisateurs distincts avec leurs propres transactions, **When** chacun consulte son tableau de bord, **Then** aucun ne voit les données de l'autre.
|
||||
|
||||
---
|
||||
|
||||
### Edge Cases
|
||||
|
||||
- Que se passe-t-il si l'utilisateur saisit un montant avec plus de 2 décimales (ex : 10,999 €) ? → arrondi à 2 décimales côté API avant stockage en centimes.
|
||||
- Comment le système gère-t-il un montant de 0 € ? → rejeté avec un message d'erreur (une transaction à 0 n'a pas de sens métier).
|
||||
- Que se passe-t-il si l'utilisateur change la devise en cours de route ? → la devise est fixée par instance (paramètre de configuration), pas modifiable par transaction.
|
||||
- Que se passe-t-il lors de la suppression d'un compte utilisateur ? → soft-delete du compte ; les données associées sont conservées 30 jours avant purge définitive.
|
||||
- Comment le système gère-t-il une date de transaction dans le futur ? → autorisée (prévisionnel), affichée avec une mention "À venir" dans la liste.
|
||||
- Que se passe-t-il si deux utilisateurs soumettent simultanément une modification sur la même transaction ? → la dernière écriture gagne (last-write-wins) ; la cohérence est garantie par les transactions ACID PostgreSQL.
|
||||
- Que se passe-t-il si le budget d'une catégorie est supprimé alors que des transactions y sont rattachées ? → les transactions subsistent sans budget associé ; elles n'apparaissent plus dans la vue "Budgets" mais restent visibles dans "Transactions".
|
||||
- Comment le système gère-t-il un export PDF avec plus de 500 transactions ? → génération asynchrone côté serveur avec lien de téléchargement différé.
|
||||
- Que se passe-t-il si PostgreSQL est indisponible ? → l'API répond 503 Service Unavailable avec un message générique ; aucune donnée partielle n'est exposée.
|
||||
|
||||
## Requirements *(mandatory)*
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: Le système DOIT permettre à un utilisateur authentifié de créer, lire, modifier et supprimer (soft-delete) des transactions financières.
|
||||
- **FR-002**: Chaque transaction DOIT obligatoirement contenir : un montant (> 0), une date, un type (revenu ou dépense), et une catégorie.
|
||||
- **FR-003**: Les montants DOIVENT être stockés en centimes (entiers) pour éviter les erreurs d'arrondi en virgule flottante.
|
||||
- **FR-004**: Le système DOIT calculer et exposer le solde courant de l'utilisateur (somme des revenus − somme des dépenses non supprimées depuis l'origine).
|
||||
- **FR-005**: Le système DOIT permettre de filtrer les transactions par période (mois/année), par catégorie et par type.
|
||||
- **FR-006**: Le système DOIT fournir un tableau de bord mensuel exposant : solde courant, total revenus du mois, total dépenses du mois, solde net du mois, répartition des dépenses par catégorie.
|
||||
- **FR-007**: Le système DOIT permettre à un utilisateur de créer, modifier et supprimer des catégories personnalisées avec un nom, une couleur et une icône facultative.
|
||||
- **FR-008**: Le système DOIT fournir un ensemble de catégories par défaut pré-chargées pour tout nouveau compte (Alimentation, Transport, Logement, Loisirs, Santé, Revenus).
|
||||
- **FR-009**: La suppression d'une catégorie utilisée par des transactions DOIT être bloquée jusqu'à réassignation ou suppression des transactions associées.
|
||||
- **FR-010**: Le système DOIT permettre de définir un budget maximum mensuel par catégorie (couple catégorie × mois).
|
||||
- **FR-011**: Le système DOIT calculer en temps réel le taux de consommation de chaque budget (dépenses de la catégorie sur le mois / plafond défini).
|
||||
- **FR-012**: Le système DOIT alerter visuellement l'utilisateur lorsqu'un budget est dépassé (taux > 100 %).
|
||||
- **FR-013**: Le système DOIT permettre la navigation mensuelle dans l'historique des transactions et des budgets.
|
||||
- **FR-014**: Le système DOIT permettre l'export des transactions d'une période donnée au format CSV avec les colonnes : date, description, catégorie, type, montant (en euros).
|
||||
- **FR-015**: Le système DOIT permettre l'export d'un rapport mensuel au format PDF incluant le résumé financier, le graphique de répartition et le détail des transactions.
|
||||
- **FR-016**: Le système DOIT gérer l'authentification des utilisateurs via email + mot de passe avec émission d'un token JWT.
|
||||
- **FR-017**: Toutes les routes de l'API DOIVENT être protégées par authentification à l'exception des endpoints d'inscription et de connexion.
|
||||
- **FR-018**: Les données de chaque utilisateur DOIVENT être strictement isolées : aucun utilisateur ne peut accéder aux données d'un autre.
|
||||
- **FR-019**: Le système DOIT exposer une API REST documentée (OpenAPI/Swagger) pour l'ensemble des fonctionnalités.
|
||||
- **FR-020**: Le système DOIT être déployable via `docker compose up` sans configuration manuelle au-delà du fichier `.env`.
|
||||
|
||||
### Key Entities
|
||||
|
||||
- **User** : Compte utilisateur. Attributs : `id`, `email`, `hashed_password`, `created_at`, `deleted_at`. Un utilisateur possède des transactions, des catégories et des budgets.
|
||||
- **Transaction** : Mouvement financier unitaire. Attributs : `id`, `user_id`, `amount_cents` (entier), `type` (income | expense), `date`, `description`, `category_id`, `created_at`, `updated_at`, `deleted_at`.
|
||||
- **Category** : Classification d'une transaction. Attributs : `id`, `user_id` (null pour les catégories système), `name`, `color`, `icon`, `created_at`, `deleted_at`. Une catégorie appartient à un utilisateur ou est une catégorie globale partagée.
|
||||
- **Budget** : Enveloppe de dépense mensuelle par catégorie. Attributs : `id`, `user_id`, `category_id`, `month` (YYYY-MM), `limit_cents` (entier), `created_at`, `updated_at`. Relation : un budget est lié à une catégorie et à un mois calendaire.
|
||||
- **ExportJob** *(optionnel — exports asynchrones larges)* : Traçabilité des exports générés. Attributs : `id`, `user_id`, `type` (csv | pdf), `period`, `status` (pending | done | error), `file_url`, `created_at`.
|
||||
|
||||
## Success Criteria *(mandatory)*
|
||||
|
||||
### Measurable Outcomes
|
||||
|
||||
- **SC-001**: Un utilisateur peut saisir une transaction complète (montant, date, catégorie, type, description) en moins de 30 secondes depuis la page principale.
|
||||
- **SC-002**: Le tableau de bord se charge et affiche des données à jour en moins de 2 secondes pour un compte comportant jusqu'à 10 000 transactions.
|
||||
- **SC-003**: 100 % des calculs de solde, totaux et taux de consommation de budget sont couverts par des tests unitaires passants.
|
||||
- **SC-004**: La couverture de tests sur le dossier `services/` (logique métier backend) est supérieure ou égale à 80 %.
|
||||
- **SC-005**: L'ensemble de l'application (backend + frontend + base de données) démarre correctement via `docker compose up` en moins de 5 minutes sur une machine vierge disposant de Docker.
|
||||
- **SC-006**: Tous les endpoints API répondent avec les codes HTTP corrects (200/201 pour les succès, 400/422 pour les erreurs de validation, 401 pour les accès non authentifiés, 404 pour les ressources introuvables) — vérifiable via les tests d'intégration.
|
||||
- **SC-007**: Un export CSV pour un mois contenant jusqu'à 1 000 transactions se génère et se télécharge en moins de 5 secondes.
|
||||
- **SC-008**: Zéro donnée d'un utilisateur A n'est accessible depuis le compte d'un utilisateur B, vérifié par des tests d'intégration dédiés à l'isolation.
|
||||
- **SC-009**: Le tableau de bord affiche un graphique de répartition des dépenses par catégorie lisible pour 1 à 15 catégories distinctes.
|
||||
- **SC-010**: L'interface est utilisable sur desktop (≥ 1280 px) et sur mobile (≥ 375 px) sans défilement horizontal ni éléments tronqués sur les vues principales (tableau de bord, transactions, budgets).
|
||||
Reference in New Issue
Block a user