feat(balance): add asset_type column to balance_categories #169

Closed
opened 2026-04-28 00:50:03 +00:00 by maximus · 0 comments
Owner

Goal

Ajouter une colonne explicite asset_type (stock | crypto) à balance_categories pour permettre à PriceFetchControl de router vers le bon provider (best-effort Yahoo pour les actions, exchanges directs pour la crypto). Supprimer le fallback hardcodé 'stock' dans le wiring de SnapshotEditPage (TODO posé dans PR #167 / issue #158).

Why

Les symboles seuls ne suffisent pas à déterminer le type d'actif. Collisions réelles :

  • ETH = Ethan Allen Interiors (NYSE) ET Ethereum (crypto)
  • MARA = Marathon Digital Holdings (NASDAQ) ET d'autres tickers
  • LINK = un titre minier ET Chainlink (crypto)

Aucune heuristique (préfixe, suffixe d'exchange, casse) ne disambigue de façon fiable. Il faut stocker le type explicitement.

Fichiers concernés

Schema (migration v10)

  • src-tauri/src/lib.rs — ajouter migration version: 10 après la v9 :

    ALTER TABLE balance_categories ADD COLUMN asset_type TEXT
        CHECK(asset_type IS NULL OR asset_type IN ('stock','crypto'));
    -- Backfill pour les seeds de la v2 (clés 'stock' et 'crypto')
    UPDATE balance_categories SET asset_type = 'stock'  WHERE key = 'stock'  AND is_seed = 1;
    UPDATE balance_categories SET asset_type = 'crypto' WHERE key = 'crypto' AND is_seed = 1;
    

    Nullable car non pertinent pour kind = 'simple'. Pour kind = 'priced', l'application l'exige côté form (validation TS) ; pas de CHECK SQL conditionnel pour rester simple.

  • src-tauri/src/database/balance_schema.sql — ajouter la colonne dans CREATE TABLE balance_categories.

  • src-tauri/src/database/consolidated_schema.sql — idem (utilisé pour les nouveaux profils, doit refléter le schema cumulatif).

  • src-tauri/src/database/balance_schema.sql (seed INSERT OR IGNORE) — mettre à jour les 2 lignes priced pour inclure la valeur de asset_type.

Form de création/édition de catégorie

  • Localiser le composant CategoryForm (priced variant) — possiblement src/components/balance/CategoryForm.tsx ou une variante dans src/components/categories/. Issue #140 a livré ce variant en milestone bilan ; vérifier le nom exact.
  • Ajouter un sélecteur asset_type (radio ou dropdown) avec deux options : stock, crypto. Visible et requis uniquement quand kind === 'priced'. i18n keys :
    • balance.category.assetType.label (FR « Type d'actif » / EN "Asset type")
    • balance.category.assetType.stock (FR « Action » / EN "Stock")
    • balance.category.assetType.crypto (FR « Crypto » / EN "Crypto")
    • balance.category.assetType.required (FR « Sélectionne le type d'actif » / EN "Select an asset type")

Service / hook

  • src/hooks/useBalanceCategories.ts (ou équivalent) — étendre le type BalanceCategory avec asset_type: 'stock' | 'crypto' | null.
  • Service de création/édition de catégorie : valider que asset_type est non-null si kind === 'priced'. Reject sinon avec une clé d'erreur i18n.

Wiring dans le snapshot editor

  • src/components/balance/SnapshotLineRow.tsx (ou le fichier qui rend <PriceFetchControl ... />) — supprimer le // TODO: read asset_type from category schema et le hardcoded 'stock'. Lire category.asset_type directement. Si null pour une catégorie priced (rows pré-migration ou edge case), masquer le control (return null) plutôt que de tenter un fetch dans un type indéfini.

Scope

  • Migration v10 (ALTER + backfill seeds) ajoutée dans lib.rs
  • balance_schema.sql et consolidated_schema.sql synchronisés
  • Seeds priced enrichis avec asset_type
  • Type BalanceCategory étendu (asset_type champ)
  • CategoryForm priced : sélecteur radio/dropdown, validation requise
  • i18n keys ajoutées (FR + EN, sous balance.category.assetType.*)
  • SnapshotLineRow (ou équivalent) lit category.asset_type ; supprime le TODO + hardcoded
  • Tests :
    • migration tests : nouveau profil → CHECK passe ; profil existant → backfill correct sur les 2 seeds, NULL pour priced custom
    • form tests : sélecteur visible si priced, caché si simple ; validation rejette priced sans asset_type
    • PriceFetchControl tests : asset_type='stock' → warning best-effort visible ; asset_type='crypto' → pas de warning ; asset_type=null sur priced → control caché
  • CHANGELOG.md + CHANGELOG.fr.md sous [Unreleased] / Added

Critères d'acceptation

  • cargo test migrations vert (incluant un test de backfill)
  • npm test tous verts
  • tsc --noEmit clean
  • Démo manuelle : créer une catégorie priced sans asset_type → form refuse ; sélectionner stock vs crypto → snapshot editor montre le bon comportement de PriceFetchControl
  • Aucun // TODO: asset_type restant dans le code

Décisions à prendre pendant l'implémentation

  • Default pour rows priced custom existantes (post-migration) : laisser NULL. L'app les traite comme « pas de fetch dispo, saisie manuelle uniquement ». Au prochain edit, le user choisit. Alternative rejetée : prompter migration UI au démarrage (trop intrusif pour un solo dev).
  • Validation : SQL CHECK conditionnel ou TS-only ? SQLite n'a pas de CHECK conditionnel propre (CHECK(kind != 'priced' OR asset_type IS NOT NULL) est techniquement faisable mais empêcherait les UPDATE séquentiels). On valide côté TS.

Spec source

reports/DAILY-REPORT-2026-04-27.md — section « Needs-clarification », point 1.
ADR 0011 (Providers best-effort Yahoo) — confirme la séparation stock/crypto pour les providers.

Depends on

  • PRs #162-#168 mergées (issue #158 introduit le TODO supprimé ici).
## Goal Ajouter une colonne explicite `asset_type` (`stock` | `crypto`) à `balance_categories` pour permettre à `PriceFetchControl` de router vers le bon provider (best-effort Yahoo pour les actions, exchanges directs pour la crypto). Supprimer le fallback hardcodé `'stock'` dans le wiring de `SnapshotEditPage` (TODO posé dans PR #167 / issue #158). ## Why Les symboles seuls ne suffisent pas à déterminer le type d'actif. Collisions réelles : - `ETH` = Ethan Allen Interiors (NYSE) ET Ethereum (crypto) - `MARA` = Marathon Digital Holdings (NASDAQ) ET d'autres tickers - `LINK` = un titre minier ET Chainlink (crypto) Aucune heuristique (préfixe, suffixe d'exchange, casse) ne disambigue de façon fiable. Il faut stocker le type explicitement. ## Fichiers concernés ### Schema (migration v10) - `src-tauri/src/lib.rs` — ajouter migration `version: 10` après la v9 : ```sql ALTER TABLE balance_categories ADD COLUMN asset_type TEXT CHECK(asset_type IS NULL OR asset_type IN ('stock','crypto')); -- Backfill pour les seeds de la v2 (clés 'stock' et 'crypto') UPDATE balance_categories SET asset_type = 'stock' WHERE key = 'stock' AND is_seed = 1; UPDATE balance_categories SET asset_type = 'crypto' WHERE key = 'crypto' AND is_seed = 1; ``` Nullable car non pertinent pour `kind = 'simple'`. Pour `kind = 'priced'`, l'application l'exige côté form (validation TS) ; pas de CHECK SQL conditionnel pour rester simple. - `src-tauri/src/database/balance_schema.sql` — ajouter la colonne dans `CREATE TABLE balance_categories`. - `src-tauri/src/database/consolidated_schema.sql` — idem (utilisé pour les nouveaux profils, doit refléter le schema cumulatif). - `src-tauri/src/database/balance_schema.sql` (seed `INSERT OR IGNORE`) — mettre à jour les 2 lignes priced pour inclure la valeur de `asset_type`. ### Form de création/édition de catégorie - Localiser le composant CategoryForm (priced variant) — possiblement `src/components/balance/CategoryForm.tsx` ou une variante dans `src/components/categories/`. Issue #140 a livré ce variant en milestone bilan ; vérifier le nom exact. - Ajouter un sélecteur `asset_type` (radio ou dropdown) avec deux options : `stock`, `crypto`. Visible et **requis** uniquement quand `kind === 'priced'`. i18n keys : - `balance.category.assetType.label` (FR « Type d'actif » / EN "Asset type") - `balance.category.assetType.stock` (FR « Action » / EN "Stock") - `balance.category.assetType.crypto` (FR « Crypto » / EN "Crypto") - `balance.category.assetType.required` (FR « Sélectionne le type d'actif » / EN "Select an asset type") ### Service / hook - `src/hooks/useBalanceCategories.ts` (ou équivalent) — étendre le type `BalanceCategory` avec `asset_type: 'stock' | 'crypto' | null`. - Service de création/édition de catégorie : valider que `asset_type` est non-null si `kind === 'priced'`. Reject sinon avec une clé d'erreur i18n. ### Wiring dans le snapshot editor - `src/components/balance/SnapshotLineRow.tsx` (ou le fichier qui rend `<PriceFetchControl ... />`) — supprimer le `// TODO: read asset_type from category schema` et le hardcoded `'stock'`. Lire `category.asset_type` directement. Si null pour une catégorie priced (rows pré-migration ou edge case), masquer le control (return null) plutôt que de tenter un fetch dans un type indéfini. ## Scope - [ ] Migration v10 (ALTER + backfill seeds) ajoutée dans `lib.rs` - [ ] `balance_schema.sql` et `consolidated_schema.sql` synchronisés - [ ] Seeds priced enrichis avec `asset_type` - [ ] Type `BalanceCategory` étendu (`asset_type` champ) - [ ] CategoryForm priced : sélecteur radio/dropdown, validation requise - [ ] i18n keys ajoutées (FR + EN, sous `balance.category.assetType.*`) - [ ] `SnapshotLineRow` (ou équivalent) lit `category.asset_type` ; supprime le TODO + hardcoded - [ ] Tests : - migration tests : nouveau profil → CHECK passe ; profil existant → backfill correct sur les 2 seeds, NULL pour priced custom - form tests : sélecteur visible si priced, caché si simple ; validation rejette priced sans asset_type - PriceFetchControl tests : asset_type='stock' → warning best-effort visible ; asset_type='crypto' → pas de warning ; asset_type=null sur priced → control caché - [ ] CHANGELOG.md + CHANGELOG.fr.md sous `[Unreleased] / Added` ## Critères d'acceptation - [ ] `cargo test` migrations vert (incluant un test de backfill) - [ ] `npm test` tous verts - [ ] `tsc --noEmit` clean - [ ] Démo manuelle : créer une catégorie priced sans asset_type → form refuse ; sélectionner stock vs crypto → snapshot editor montre le bon comportement de PriceFetchControl - [ ] Aucun `// TODO: asset_type` restant dans le code ## Décisions à prendre pendant l'implémentation - **Default pour rows priced custom existantes (post-migration)** : laisser NULL. L'app les traite comme « pas de fetch dispo, saisie manuelle uniquement ». Au prochain edit, le user choisit. Alternative rejetée : prompter migration UI au démarrage (trop intrusif pour un solo dev). - **Validation : SQL CHECK conditionnel ou TS-only ?** SQLite n'a pas de CHECK conditionnel propre (`CHECK(kind != 'priced' OR asset_type IS NOT NULL)` est techniquement faisable mais empêcherait les UPDATE séquentiels). On valide côté TS. ## Spec source `reports/DAILY-REPORT-2026-04-27.md` — section « Needs-clarification », point 1. ADR 0011 (Providers best-effort Yahoo) — confirme la séparation stock/crypto pour les providers. ## Depends on - PRs #162-#168 mergées (issue #158 introduit le TODO supprimé ici).
maximus added the
status:ready
type:feature
type:schema
source:human
labels 2026-04-28 00:50:03 +00:00
maximus added
status:approved
and removed
status:ready
labels 2026-04-28 23:56:24 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/Simpl-Resultat#169
No description provided.