docs(architecture): document balance domain (tables, services, hooks, commands, routes)
Update docs/architecture.md to reflect the Bilan feature: - BDD: 5 new tables + 7 indexes + CHECK + FK invariants (CAD lock, kind invariants, ON DELETE RESTRICT on transaction_id) - Migrations: v8 + v9 added to history - Services: balance.service.ts with its 4 logical sections - Hooks: useBalanceAccounts / useSnapshotEditor / useBalanceOverview - Commands: compute_account_return (1 new) + Phase 5 fetch_price stub - Routing: /balance, /balance/snapshot, /balance/accounts - Components: balance/ folder added in tree Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
5274e51907
commit
4d5a0e2e3b
1 changed files with 63 additions and 8 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
# Architecture technique — Simpl'Résultat
|
# Architecture technique — Simpl'Résultat
|
||||||
|
|
||||||
> Document mis à jour le 2026-04-13 — Version 0.7.3
|
> Document mis à jour le 2026-04-25 — Version 0.8.x (Bilan)
|
||||||
|
|
||||||
## Stack technique
|
## Stack technique
|
||||||
|
|
||||||
|
|
@ -28,6 +28,7 @@ simpl-resultat/
|
||||||
├── src/ # Frontend React/TypeScript
|
├── src/ # Frontend React/TypeScript
|
||||||
│ ├── components/ # 58 composants organisés par domaine
|
│ ├── components/ # 58 composants organisés par domaine
|
||||||
│ │ ├── adjustments/ # 3 composants
|
│ │ ├── adjustments/ # 3 composants
|
||||||
|
│ │ ├── balance/ # 7 composants Bilan (AccountForm, BalanceAccountsTable, BalanceEvolutionChart, BalanceOverviewCard, LinkTransfersModal, SnapshotEditor, SnapshotLineRow)
|
||||||
│ │ ├── budget/ # 5 composants
|
│ │ ├── budget/ # 5 composants
|
||||||
│ │ ├── categories/ # 5 composants
|
│ │ ├── categories/ # 5 composants
|
||||||
│ │ ├── dashboard/ # 2 composants
|
│ │ ├── dashboard/ # 2 composants
|
||||||
|
|
@ -72,7 +73,7 @@ simpl-resultat/
|
||||||
|
|
||||||
## Base de données
|
## Base de données
|
||||||
|
|
||||||
### Tables (13)
|
### Tables (18)
|
||||||
|
|
||||||
| Table | Description |
|
| Table | Description |
|
||||||
|-------|-------------|
|
|-------|-------------|
|
||||||
|
|
@ -89,10 +90,36 @@ simpl-resultat/
|
||||||
| `budget_template_entries` | Catégories et montants dans les modèles |
|
| `budget_template_entries` | Catégories et montants dans les modèles |
|
||||||
| `import_config_templates` | Modèles prédéfinis de config d'import |
|
| `import_config_templates` | Modèles prédéfinis de config d'import |
|
||||||
| `user_preferences` | Préférences applicatives (clé-valeur) |
|
| `user_preferences` | Préférences applicatives (clé-valeur) |
|
||||||
|
| `balance_categories` | Taxonomie des types d'actifs (cash, TFSA, RRSP, fund, stock, crypto, other) — `kind ∈ {simple, priced}`, 7 seedées (`is_seed = 1`) |
|
||||||
|
| `balance_accounts` | Comptes de bilan (rattachés à une catégorie). `currency` hardcodée à `CAD` au MVP via CHECK. `archived_at` pour soft-delete |
|
||||||
|
| `balance_snapshots` | Snapshots datés (`snapshot_date` UNIQUE) — éditer = mettre à jour les lignes, pas dupliquer |
|
||||||
|
| `balance_snapshot_lines` | Une ligne par `(snapshot, compte)`. Stockage denormalisé : pour `simple` `value` seul, pour `priced` `quantity + unit_price + value`. CHECK kind invariants côté SQL |
|
||||||
|
| `balance_account_transfers` | Liaison `transactions ↔ balance_accounts` avec `direction ∈ {in, out}`. Utilisée par le calcul Modified Dietz pour séparer apports et gains |
|
||||||
|
|
||||||
### Index (9)
|
### Index (16)
|
||||||
|
|
||||||
Index sur : `transactions` (date, category, supplier, source, file, parent), `categories` (parent, type), `suppliers` (category, normalized_name), `keywords` (category, keyword), `budget_entries` (year, month), `adjustment_entries` (adjustment_id), `imported_files` (source).
|
Index existants (9) : `transactions` (date, category, supplier, source, file, parent), `categories` (parent, type), `suppliers` (category, normalized_name), `keywords` (category, keyword), `budget_entries` (year, month), `adjustment_entries` (adjustment_id), `imported_files` (source).
|
||||||
|
|
||||||
|
Index Bilan (7, ajoutés en migration v9) :
|
||||||
|
- `idx_balance_accounts_category` (FK lookup catégorie → comptes)
|
||||||
|
- `idx_balance_accounts_active` partiel `WHERE is_active = 1` (filtre liste active)
|
||||||
|
- `idx_balance_snapshot_lines_snapshot` (chargement d'un snapshot)
|
||||||
|
- `idx_balance_snapshot_lines_account` (historique par compte)
|
||||||
|
- `idx_balance_account_transfers_account` (cash flows Modified Dietz par compte)
|
||||||
|
- `idx_balance_account_transfers_transaction` (lookup icône d'attribution dans `TransactionTable`)
|
||||||
|
- `idx_balance_snapshots_date` (sélecteur de période + agrégation chronologique)
|
||||||
|
|
||||||
|
### Invariants Bilan (CHECK + FK)
|
||||||
|
|
||||||
|
- `balance_categories.kind` ∈ `('simple','priced')`
|
||||||
|
- `balance_accounts.currency = 'CAD'` (verrou MVP — v2 lèvera ce CHECK avec table de taux)
|
||||||
|
- `balance_snapshot_lines` : `(quantity, unit_price)` doivent être tous deux NULL (kind simple) OU tous deux NOT NULL (kind priced)
|
||||||
|
- `balance_account_transfers.direction` ∈ `('in','out')` ; UNIQUE `(transaction_id, account_id)` (une transaction ne peut pas être liée deux fois au même compte)
|
||||||
|
- FK `balance_accounts.balance_category_id` → `balance_categories(id)` `ON DELETE RESTRICT` (empêche suppression de catégorie avec comptes liés)
|
||||||
|
- FK `balance_snapshot_lines.snapshot_id` → `balance_snapshots(id)` `ON DELETE CASCADE` (supprimer un snapshot supprime ses lignes)
|
||||||
|
- FK `balance_snapshot_lines.account_id` → `balance_accounts(id)` `ON DELETE RESTRICT` (préserve l'historique)
|
||||||
|
- FK `balance_account_transfers.account_id` → `balance_accounts(id)` `ON DELETE CASCADE`
|
||||||
|
- FK `balance_account_transfers.transaction_id` → `transactions(id)` `ON DELETE RESTRICT` — décision structurante pour la reproductibilité Modified Dietz, voir [ADR 0010](adr/0010-fk-restrict-balance-transfers.md)
|
||||||
|
|
||||||
## Système de migrations
|
## Système de migrations
|
||||||
|
|
||||||
|
|
@ -107,17 +134,19 @@ Les migrations sont définies inline dans `src-tauri/src/lib.rs` via `tauri_plug
|
||||||
| 5 | v5 | Création de `import_config_templates` |
|
| 5 | v5 | Création de `import_config_templates` |
|
||||||
| 6 | v6 | Changement contrainte unique `imported_files` (hash → filename) |
|
| 6 | v6 | Changement contrainte unique `imported_files` (hash → filename) |
|
||||||
| 7 | v7 | Ajout sous-catégories d'assurance (niveau 3) |
|
| 7 | v7 | Ajout sous-catégories d'assurance (niveau 3) |
|
||||||
|
| 8 | v8 | Migration de catégories (cf. release 0.8.x) |
|
||||||
|
| 9 | v9 | Schéma Bilan : 5 tables + 7 index + seed des 7 catégories standard (cash, TFSA, RRSP, fund, stock, crypto, other) |
|
||||||
|
|
||||||
Pour les **nouveaux profils**, le fichier `consolidated_schema.sql` contient le schéma complet avec toutes les migrations pré-appliquées (pas besoin de rejouer les migrations).
|
Pour les **nouveaux profils**, le fichier `consolidated_schema.sql` contient le schéma complet avec toutes les migrations pré-appliquées (pas besoin de rejouer les migrations).
|
||||||
|
|
||||||
## Services TypeScript (17)
|
## Services TypeScript (18)
|
||||||
|
|
||||||
| Service | Responsabilité |
|
| Service | Responsabilité |
|
||||||
|---------|---------------|
|
|---------|---------------|
|
||||||
| `db.ts` | Wrapper de connexion (tauri-plugin-sql) |
|
| `db.ts` | Wrapper de connexion (tauri-plugin-sql) |
|
||||||
| `profileService.ts` | Gestion des profils |
|
| `profileService.ts` | Gestion des profils |
|
||||||
| `categoryService.ts` | CRUD catégories hiérarchiques |
|
| `categoryService.ts` | CRUD catégories hiérarchiques |
|
||||||
| `transactionService.ts` | CRUD et filtrage des transactions |
|
| `transactionService.ts` | CRUD et filtrage des transactions ; détection d'erreurs FK RESTRICT pour transactions liées à un compte de bilan (typed `TransactionLinkedToBalanceError`) |
|
||||||
| `importSourceService.ts` | Configuration des sources d'import |
|
| `importSourceService.ts` | Configuration des sources d'import |
|
||||||
| `importedFileService.ts` | Suivi des fichiers importés |
|
| `importedFileService.ts` | Suivi des fichiers importés |
|
||||||
| `importConfigTemplateService.ts` | Modèles de configuration d'import |
|
| `importConfigTemplateService.ts` | Modèles de configuration d'import |
|
||||||
|
|
@ -131,8 +160,20 @@ Pour les **nouveaux profils**, le fichier `consolidated_schema.sql` contient le
|
||||||
| `logService.ts` | Capture des logs console (buffer circulaire, sessionStorage) |
|
| `logService.ts` | Capture des logs console (buffer circulaire, sessionStorage) |
|
||||||
| `licenseService.ts` | Validation et gestion de la clé de licence (appels commandes Tauri) |
|
| `licenseService.ts` | Validation et gestion de la clé de licence (appels commandes Tauri) |
|
||||||
| `authService.ts` | OAuth2 PKCE / Compte Maximus (appels commandes Tauri auth_*) |
|
| `authService.ts` | OAuth2 PKCE / Compte Maximus (appels commandes Tauri auth_*) |
|
||||||
|
| `balance.service.ts` | Domaine Bilan — service unique avec 4 sections logiques (voir détail ci-dessous) |
|
||||||
|
|
||||||
## Hooks (14)
|
### Service Bilan — `balance.service.ts`
|
||||||
|
|
||||||
|
Un seul service par convention projet (1 service par domaine, splitter seulement > ~400 lignes). Quatre sections logiques distinctes :
|
||||||
|
|
||||||
|
1. **CRUD catégories + comptes** — `listBalanceCategories`, `createBalanceCategory`, `updateBalanceCategory`, `archiveBalanceCategory` (refus si comptes liés via FK RESTRICT, refus si `is_seed = 1`), `listBalanceAccounts`, `createBalanceAccount`, `updateBalanceAccount`, `archiveBalanceAccount`. Le service garde une `BalanceServiceError` typée (`BalanceErrorCode`) pour permettre à la UI d'afficher des messages i18n distincts (`currency_unsupported`, `category_seed_protected`, `category_has_accounts`, etc.).
|
||||||
|
2. **Snapshots + lines** — `listBalanceSnapshots`, `getBalanceSnapshotByDate`, `upsertSnapshot` (création + édition par date), `upsertSnapshotLines` (rewrite-all : DELETE WHERE snapshot_id puis INSERT par ligne — choix simple pour < 20 comptes/snapshot), `deleteSnapshot`, helper `validateLineKindInvariants` exporté pour les tests (kind invariants TS en complément du CHECK SQL ; tolérance `PRICED_VALUE_TOLERANCE = 0.01` pour le match `value ≈ quantity × unit_price`).
|
||||||
|
3. **Returns + transfers** — `linkTransfer`, `unlinkTransfer`, `listAccountTransfers`, `listAllLinkedTransfersForTooltip` (un coup pour la `Map.has(txId)` consommée par l'icône d'attribution dans `TransactionTable`), `computeAccountReturn` (wrapper sur la commande Tauri `compute_account_return` qui lit `db_filename` du profil actif via `loadProfiles()`).
|
||||||
|
4. **Prices** — *(Phase 5, livraison reportée à l'Issue #143)*. La forme prévue : `fetchPrice(symbol, date)` invoquant `fetch_price` (Tauri), avec rate-limit client (1/2s), backoff exponentiel et dedup in-flight. Voir [ADR 0009](adr/0009-proxy-price-fetching-via-maximus-api.md) pour l'architecture proxy.
|
||||||
|
|
||||||
|
Le CRUD passe par `getDb()` + `tauri-plugin-sql` direct, **jamais** via une commande Tauri — convention projet. Les commandes Rust sont réservées au filesystem, OAuth, license, profils, feedback et au seul calcul Modified Dietz (qui a besoin d'arithmétique de dates `chrono`).
|
||||||
|
|
||||||
|
## Hooks (17+)
|
||||||
|
|
||||||
Chaque hook encapsule la logique d'état via `useReducer` :
|
Chaque hook encapsule la logique d'état via `useReducer` :
|
||||||
|
|
||||||
|
|
@ -152,13 +193,16 @@ Chaque hook encapsule la logique d'état via `useReducer` :
|
||||||
| `useCompare` | Rapport Comparables (mode `actual`/`budget`, sous-toggle MoM ↔ YoY, mois de référence explicite avec wrap-around janvier) |
|
| `useCompare` | Rapport Comparables (mode `actual`/`budget`, sous-toggle MoM ↔ YoY, mois de référence explicite avec wrap-around janvier) |
|
||||||
| `useCategoryZoom` | Rapport Zoom catégorie avec rollup sous-catégories |
|
| `useCategoryZoom` | Rapport Zoom catégorie avec rollup sous-catégories |
|
||||||
| `useCartes` | Rapport Cartes (snapshot KPI + sparklines + top movers + budget + saisonnalité via `getCartesSnapshot`) |
|
| `useCartes` | Rapport Cartes (snapshot KPI + sparklines + top movers + budget + saisonnalité via `getCartesSnapshot`) |
|
||||||
|
| `useBalanceAccounts` | Bilan — état de la page `/balance/accounts` : CRUD comptes ET catégories (un seul hook pour les deux onglets, aligné sur la convention "1 hook par page") |
|
||||||
|
| `useSnapshotEditor` | Bilan — cycle de vie d'un snapshot unique (`/balance/snapshot`) : valeurs simple (string) + valeurs priced (`{quantity, unit_price}` strings), prefill depuis snapshot précédent, save (rewrite-all), delete avec double-confirmation par re-saisie de la date |
|
||||||
|
| `useBalanceOverview` | Bilan — page `/balance` : sélecteur de période (`3M / 6M / 1A / 3A / Tout`), série temporelle agrégée, mode chart (`line` / `stacked`), tableau des comptes avec valeurs courantes et Δ% sur la période. Les rendements multi-horizons sont chargés *lazily* dans `BalanceAccountsTable` (un appel `compute_account_return` par cellule) |
|
||||||
| `useDataExport` | Export de données |
|
| `useDataExport` | Export de données |
|
||||||
| `useTheme` | Thème clair/sombre |
|
| `useTheme` | Thème clair/sombre |
|
||||||
| `useUpdater` | Mise à jour de l'application (gated par entitlement licence) |
|
| `useUpdater` | Mise à jour de l'application (gated par entitlement licence) |
|
||||||
| `useLicense` | État de la licence et entitlements |
|
| `useLicense` | État de la licence et entitlements |
|
||||||
| `useAuth` | Authentification Compte Maximus (OAuth2 PKCE, subscription status) |
|
| `useAuth` | Authentification Compte Maximus (OAuth2 PKCE, subscription status) |
|
||||||
|
|
||||||
## Commandes Tauri (35)
|
## Commandes Tauri (36)
|
||||||
|
|
||||||
### `fs_commands.rs` — Système de fichiers (6)
|
### `fs_commands.rs` — Système de fichiers (6)
|
||||||
|
|
||||||
|
|
@ -230,6 +274,14 @@ Module privé appelé uniquement par `auth_commands.rs` et `license_commands.rs`
|
||||||
- Source de vérité : `FEATURE_TIERS` dans `entitlements.rs`. Modifier cette constante pour changer les gates, jamais ailleurs dans le code
|
- Source de vérité : `FEATURE_TIERS` dans `entitlements.rs`. Modifier cette constante pour changer les gates, jamais ailleurs dans le code
|
||||||
- Temporaire : `auto-update` est ouvert à `free` en attendant le serveur de licences (issue #49). À re-gater à `[base, premium]` quand l'activation payante sera live
|
- Temporaire : `auto-update` est ouvert à `free` en attendant le serveur de licences (issue #49). À re-gater à `[base, premium]` quand l'activation payante sera live
|
||||||
|
|
||||||
|
### `balance_commands.rs` — Bilan (1)
|
||||||
|
|
||||||
|
- `compute_account_return(account_id, period_start, period_end, db_filename)` — Calcul Modified Dietz d'un compte sur une période. Ouvre une connexion `rusqlite` courte sur le fichier DB du profil actif, lit le snapshot ≤ `period_start`, le snapshot ≥ `period_end` et tous les `balance_account_transfers` JOIN `transactions` dans la fenêtre, puis appelle `return_calculator::modified_dietz`. Retourne `AccountReturn { value_start, value_end, net_contributions, return_pct, annualized_pct, is_partial, has_no_transfers_warning }`. Voir [ADR 0008](adr/0008-modified-dietz-pour-rendement.md).
|
||||||
|
|
||||||
|
Le module privé `return_calculator.rs` (déclaré dans `commands/mod.rs` mais non exposé comme commande) contient la logique pure Modified Dietz et ses tests `#[cfg(test)] mod tests` co-localisés (TDD, 7 cas : nominal / pas de snapshot début / partial / créé en cours / vidé / sans transferts / annualisation).
|
||||||
|
|
||||||
|
**À venir Phase 5** (Issue #143, BLOCKED par maximus-api Phase 2) : commande `fetch_price(symbol, date)` pour le price-fetching premium via proxy maximus-api. L'architecture est documentée dans l'ADR 0009 ; la livraison est différée jusqu'à ce que le serveur de licences (`maximus-api`) expose l'endpoint `GET /v1/prices`.
|
||||||
|
|
||||||
## Plugins Tauri
|
## Plugins Tauri
|
||||||
|
|
||||||
Ordre d'initialisation dans `lib.rs` (certains plugins ont des contraintes d'ordre) :
|
Ordre d'initialisation dans `lib.rs` (certains plugins ont des contraintes d'ordre) :
|
||||||
|
|
@ -291,6 +343,9 @@ Le routing est défini dans `App.tsx`. Toutes les pages sont englobées par `App
|
||||||
| `/reports/compare` | `ReportsComparePage` | Comparables (MoM / YoY / Réel vs budget) |
|
| `/reports/compare` | `ReportsComparePage` | Comparables (MoM / YoY / Réel vs budget) |
|
||||||
| `/reports/category` | `ReportsCategoryPage` | Zoom catégorie avec rollup + édition contextuelle de mots-clés |
|
| `/reports/category` | `ReportsCategoryPage` | Zoom catégorie avec rollup + édition contextuelle de mots-clés |
|
||||||
| `/reports/cartes` | `ReportsCartesPage` | Tableau de bord KPI avec sparklines, top movers, budget et saisonnalité |
|
| `/reports/cartes` | `ReportsCartesPage` | Tableau de bord KPI avec sparklines, top movers, budget et saisonnalité |
|
||||||
|
| `/balance` | `BalancePage` | Bilan — vue d'ensemble : carte "Aujourd'hui" + Δ% + avertissement bilan pas à jour > 60j, graphique d'évolution (toggle ligne / aire empilée par catégorie), tableau des comptes avec rendements multi-horizons (3M / 1A / depuis création — Modified Dietz) côte-à-côte avec rendement non-ajusté |
|
||||||
|
| `/balance/snapshot` | `SnapshotEditPage` | Saisie / édition d'un snapshot daté. Mode `?date=today` (création) ou `?date=YYYY-MM-DD` (édition, date immutable). Lignes groupées par catégorie : `simple` = champ valeur, `priced` = `quantity` × `unit_price` (`value` calculé read-only). Bouton "Pré-remplir depuis le snapshot précédent". Suppression à double-confirmation par re-saisie de la date |
|
||||||
|
| `/balance/accounts` | `AccountsPage` | CRUD comptes + catégories de bilan (deux onglets). Catégories seedées (`is_seed = 1`) renommables mais non-supprimables ; refus de suppression d'une catégorie avec comptes liés (FK RESTRICT) |
|
||||||
| `/settings` | `SettingsPage` | Paramètres |
|
| `/settings` | `SettingsPage` | Paramètres |
|
||||||
| `/docs` | `DocsPage` | Documentation in-app |
|
| `/docs` | `DocsPage` | Documentation in-app |
|
||||||
| `/changelog` | `ChangelogPage` | Historique des versions (bilingue FR/EN) |
|
| `/changelog` | `ChangelogPage` | Historique des versions (bilingue FR/EN) |
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue