feat(balance): schema migration v9 + service skeleton + AccountsPage (#138) #147

Merged
maximus merged 4 commits from issue-138-bilan-1a into main 2026-04-26 13:25:10 +00:00
4 changed files with 196 additions and 0 deletions
Showing only changes of commit 4c71eaca2d - Show all commits

View file

@ -2,6 +2,9 @@
## [Non publié]
### Ajouté
- **Bilan — fondations du schéma et page Comptes** (route `/balance/accounts`) : première tranche de la nouvelle feature *Bilan*. La migration SQL v9 introduit 5 tables (`balance_categories`, `balance_accounts`, `balance_snapshots`, `balance_snapshot_lines`, `balance_account_transfers`) avec 7 index et seede 7 catégories standard — Encaisse, CELI, REER, Fonds commun, Autre (type simple) + Action et Cryptomonnaie (type coté). La colonne `currency` est verrouillée à `CAD` via une contrainte CHECK au MVP — le support multi-devises arrivera plus tard. La nouvelle page expose deux onglets : *Comptes* (CRUD complet sur les comptes de l'utilisateur, archivage soft plutôt que suppression dure pour préserver les snapshots historiques) et *Catégories* (renommer une catégorie, créer des catégories de type simple, supprimer celles créées par l'utilisateur — les catégories standard sont protégées). Couverture i18n FR/EN complète sous `balance.*`. Snapshots, transferts, rendements et price-fetching premium arriveront dans les prochaines issues ; pour l'instant la route est accessible directement par URL (pas encore d'entrée sidebar) (#138)
### Corrigé
- **Rapport Zoom catégorie** (`/reports/category`) : la liste déroulante du combobox des catégories affiche désormais la liste complète dans un ordre hiérarchique DFS correct — chaque racine est émise avant ses descendants, et les frères et sœurs sont triés par `sort_order` puis nom affiché. Auparavant la liste était triée globalement par `sort_order` (via un `ORDER BY sort_order, name` SQL), ce qui entrelaçait des parents et enfants de sous-arbres différents partageant le même `sort_order`, d'où l'indentation incohérente et l'impression d'arbre cassé. La recherche filtrée (insensible aux accents) conserve le même comportement (#126)

View file

@ -2,6 +2,9 @@
## [Unreleased]
### Added
- **Balance sheet — schema foundation and accounts page** (route `/balance/accounts`): first slice of the upcoming *Bilan* feature. New SQL migration v9 introduces 5 tables (`balance_categories`, `balance_accounts`, `balance_snapshots`, `balance_snapshot_lines`, `balance_account_transfers`) with 7 indexes and seeds 7 standard categories — Cash, TFSA, RRSP, Mutual Fund, Other (simple kind) plus Stock and Crypto (priced kind). The `currency` column is hardcoded to `CAD` via a CHECK constraint at the MVP — multi-currency support will come in a later release. The new accounts page exposes two tabs: *Accounts* (full CRUD over the user's holdings, soft-archive instead of hard delete to preserve historic snapshots) and *Categories* (rename any category, create simple-kind ones, delete user-created ones — seeded categories are protected). The full FR/EN i18n coverage uses keys under `balance.*`. Snapshots, transfers, returns and the price-fetching premium remain to ship in upcoming issues; for now the route is reachable directly via URL (no sidebar entry yet) (#138)
### Fixed
- **Category zoom report** (`/reports/category`): the category combobox dropdown now renders the full list in proper hierarchical DFS order — each root is emitted before its descendants, with siblings sorted by `sort_order` then display name. Previously the list was ordered by `sort_order` globally (from a SQL `ORDER BY sort_order, name`), which interleaved parents and children from different sub-trees that shared the same `sort_order`, producing scrambled indentation and a mis-leading tree. Filtering (accent-insensitive search) still behaves identically (#126)

View file

@ -1449,5 +1449,100 @@
}
}
}
},
"balance": {
"accountsPage": {
"title": "Balance accounts",
"tabs": {
"accounts": "Accounts",
"categories": "Categories"
},
"newAccount": "New account",
"includeArchived": "Show archived accounts",
"empty": "No accounts yet. Click “New account” to start."
},
"account": {
"fields": {
"name": "Name",
"category": "Category",
"symbol": "Symbol",
"currency": "Currency",
"status": "Status",
"actions": "Actions"
},
"status": {
"active": "Active",
"archived": "Archived"
},
"actions": {
"archive": "Archive",
"unarchive": "Restore"
},
"form": {
"createTitle": "New account",
"editTitle": "Edit account",
"category": "Category",
"noCategory": "(no category available)",
"name": "Account name",
"nameRequired": "Name is required.",
"symbol": "Symbol",
"symbolPricedHint": "required for priced categories",
"symbolPlaceholderSimple": "Optional",
"symbolPlaceholderPriced": "e.g. AAPL, BTC-USD",
"notes": "Notes",
"currencyMvpNotice": "At the MVP, all accounts are in CAD. Multi-currency support will land in a later version.",
"save": "Save",
"create": "Create account"
}
},
"category": {
"intro": "Seeded categories (TFSA, RRSP, Cash, etc.) ship with the app. You can create your own for special cases.",
"fields": {
"name": "Name",
"key": "Key",
"kind": "Kind",
"origin": "Origin",
"actions": "Actions"
},
"kind": {
"simple": "Direct amount",
"priced": "Quantity × price"
},
"origin": {
"seeded": "Standard",
"user": "Custom"
},
"actions": {
"create": "New category",
"renamePrompt": "New label for this category",
"deleteConfirm": "Delete this category? This cannot be undone.",
"deleteSeedHint": "Standard categories cannot be deleted."
},
"form": {
"createTitle": "New category",
"key": "Key",
"keyPlaceholder": "e.g. lira, prpp",
"label": "Label",
"labelPlaceholder": "e.g. LIRA, PRPP",
"simpleOnlyNotice": "Priced categories (stocks, crypto) will be available in a future release.",
"create": "Create category"
},
"cash": "Cash",
"tfsa": "TFSA",
"rrsp": "RRSP",
"fund": "Mutual fund",
"other": "Other",
"stock": "Stock",
"crypto": "Crypto"
},
"errors": {
"currency_unsupported": "Only CAD is supported at the MVP.",
"category_seed_protected": "Standard categories cannot be deleted.",
"category_has_accounts": "Cannot delete a category with linked accounts. Move or archive linked accounts first.",
"category_not_found": "Category not found.",
"account_not_found": "Account not found.",
"name_required": "Name is required.",
"kind_invalid": "Invalid category kind."
}
}
}

View file

@ -1449,5 +1449,100 @@
}
}
}
},
"balance": {
"accountsPage": {
"title": "Comptes du bilan",
"tabs": {
"accounts": "Comptes",
"categories": "Catégories"
},
"newAccount": "Nouveau compte",
"includeArchived": "Afficher les comptes archivés",
"empty": "Aucun compte pour l'instant. Cliquez sur « Nouveau compte » pour commencer."
},
"account": {
"fields": {
"name": "Nom",
"category": "Catégorie",
"symbol": "Symbole",
"currency": "Devise",
"status": "Statut",
"actions": "Actions"
},
"status": {
"active": "Actif",
"archived": "Archivé"
},
"actions": {
"archive": "Archiver",
"unarchive": "Restaurer"
},
"form": {
"createTitle": "Nouveau compte",
"editTitle": "Modifier le compte",
"category": "Catégorie",
"noCategory": "(aucune catégorie disponible)",
"name": "Nom du compte",
"nameRequired": "Le nom est obligatoire.",
"symbol": "Symbole",
"symbolPricedHint": "obligatoire pour cette catégorie cotée",
"symbolPlaceholderSimple": "Optionnel",
"symbolPlaceholderPriced": "ex. AAPL, BTC-USD",
"notes": "Notes",
"currencyMvpNotice": "Au MVP, tous les comptes sont en CAD. Le support multi-devises arrivera dans une version ultérieure.",
"save": "Enregistrer",
"create": "Créer le compte"
}
},
"category": {
"intro": "Les catégories seedées (CELI, REER, Encaisse, etc.) sont fournies par l'application. Vous pouvez en créer de nouvelles pour vos cas particuliers.",
"fields": {
"name": "Nom",
"key": "Clé",
"kind": "Type",
"origin": "Origine",
"actions": "Actions"
},
"kind": {
"simple": "Montant direct",
"priced": "Quantité × prix"
},
"origin": {
"seeded": "Standard",
"user": "Personnalisée"
},
"actions": {
"create": "Nouvelle catégorie",
"renamePrompt": "Nouveau libellé pour cette catégorie",
"deleteConfirm": "Supprimer cette catégorie ? Cette action est irréversible.",
"deleteSeedHint": "Les catégories standard ne peuvent pas être supprimées."
},
"form": {
"createTitle": "Nouvelle catégorie",
"key": "Clé",
"keyPlaceholder": "ex. ferr, rpdb",
"label": "Libellé",
"labelPlaceholder": "ex. FERR, RPDB",
"simpleOnlyNotice": "Les catégories cotées (actions, crypto) seront disponibles dans une prochaine version.",
"create": "Créer la catégorie"
},
"cash": "Encaisse",
"tfsa": "CELI",
"rrsp": "REER",
"fund": "Fonds commun",
"other": "Autre",
"stock": "Action",
"crypto": "Cryptomonnaie"
},
"errors": {
"currency_unsupported": "Seul le CAD est supporté au MVP.",
"category_seed_protected": "Les catégories standard ne peuvent pas être supprimées.",
"category_has_accounts": "Impossible de supprimer une catégorie avec des comptes liés. Déplacez ou archivez d'abord les comptes liés.",
"category_not_found": "Catégorie introuvable.",
"account_not_found": "Compte introuvable.",
"name_required": "Le nom est obligatoire.",
"kind_invalid": "Type de catégorie invalide."
}
}
}