# 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).