Commit graph

9 commits

Author SHA1 Message Date
le king fu
bd1085c148 schema: add migration v17 carrying the full import format on import_sources
All checks were successful
PR Check — Rust / rust (pull_request) Successful in 9m27s
`import_sources` carried only the mechanical CSV settings. The two fields that
decide how an amount is READ -- `amount_mode` and `sign_convention` -- lived
only on `import_config_templates`. That asymmetry is the root bug of this
chantier: restoring a saved source re-inferred the mode from the mapping and
hardcoded `signConvention: "negative_expense"` (useImportWizard.ts:321-323), so
a source configured with positive expenses silently flipped back on its second
import. After v17 both tables carry the same eight format fields.

v17 is strictly additive -- v1 to v16 are untouched, and the diff is pure
insertion. The four columns are defaulted or nullable so the ALTERs are safe on
a populated database:

- amount_mode / sign_convention carry a CHECK, same pattern as v15 on
  balance_accounts.kind. amount_mode admits 'absolute_indicator' from the start
  so the third amount mode ships without another migration, while the database
  still refuses a corrupted value today.
- header_signature stores the normalized header labels seen at the last
  successful import, for drift detection.
- template_id is a provenance tag only, never re-read as format: the eight
  source columns are authoritative, so editing a template changes no linked
  source. ON DELETE SET NULL keeps the source and its format when a template
  goes.

The backfill reproduces exactly the rule the wizard applied on the fly, so no
source changes behaviour on migration. It tests `column_mapping LIKE
'%debitAmount%'` rather than json_extract, so the migration depends on no JSON1
extension in the bundled SQLite. sign_convention is deliberately not
backfilled: its DEFAULT restores precisely the value the code hardcoded, the
only past convention that can be inferred.

The four columns are mirrored into consolidated_schema.sql. They are inert
there on the production path -- that script runs after every migration and only
uses CREATE TABLE IF NOT EXISTS, so new profiles receive them from v17 -- but
it stays the tested reference definition, and a parity test now compares it
against the v1->v17 chain column by column, DEFAULT by DEFAULT, CHECK by CHECK
and FK by FK. Both halves of that test were mutation-checked to confirm they
fail on drift.

5 new tests (111 Rust total): v17 on a populated v16 database with a child row,
the backfill against 5 mapping shapes, the CHECKs, the provenance-tag
semantics, and the consolidated parity.

Resolves #323
2026-08-13 12:56:28 -04:00
le king fu
a1c3dafcd0 feat(balance): schema & migrations v14/v15 + types (securities, holdings, account.kind) (#210)
All checks were successful
PR Check / rust (pull_request) Successful in 23m1s
PR Check / frontend (pull_request) Successful in 2m25s
Foundations for 'détail par titre' (Étape 2), purely additive — v1-v13 are
untouched and their checksums stay intact.

v14 (balance_holdings_schema.sql, applied verbatim via BALANCE_HOLDINGS_SCHEMA):
- balance_securities: instrument catalogue, symbol normalized + COLLATE NOCASE
  UNIQUE (no case-dupes), asset_type CHECK ('stock','crypto'), currency CAD.
- balance_snapshot_holdings: per-security breakdown of a snapshot line, FK to
  balance_snapshot_lines (CASCADE) + balance_securities (RESTRICT), value
  denormalized, UNIQUE(snapshot_line_id, security_id) + 2 indexes.

v15 (inline): balance_accounts gains kind ('simple'|'detailed', NOT NULL
DEFAULT 'simple', CHECK) + detailed_since DATE; backfills kind='detailed' on
accounts under a priced category. Two single-column ADDs (SQLite), idempotent.

consolidated_schema.sql brought to parity: 2 tables + 2 indexes + kind/
detailed_since columns + the v15 backfill (no-op for the 4 simple starters,
reproduced for future priced starters).

TS types: BalanceAccountKind, BalanceSecurity, BalanceSnapshotHolding (+
WithSecurity join variant); BalanceAccount gains kind + detailed_since;
BalanceAccountWithCategory exposes both kind and category_kind.

Tests: +9 (v14/v15 via V14_SQL/V15_SQL consts + db_through_v13 helper, plus a
consolidated parity test). cargo test 89 passed, npm build + 552 vitest green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 12:52:55 -04:00
le king fu
5861346eb3 feat(balance): data layer — vehicle_type + custom_label migrations, starters, service (#202)
All checks were successful
PR Check / rust (pull_request) Successful in 22m31s
PR Check / frontend (pull_request) Successful in 2m26s
Bilan axe véhicule (Étape 1) data foundation: separate the fiscal envelope
(now balance_accounts.vehicle_type) from the asset class (the category).

- Migration v12 (additive): add vehicle_type (fiscal enum, nullable) to
  balance_accounts + custom_label to balance_categories; backfill the envelope
  onto ex-tfsa/rrsp accounts (cash stays NULL); defensive recovery of any seed
  i18n_key overwritten by free text (bug I).
- Migration v13 (reclass, conditional/idempotent): re-link ex-tfsa/rrsp accounts
  to the `other` asset class and deactivate the two envelope seeds.
- consolidated_schema.sql: 2 new columns, 5 asset-class seeds (no tfsa/rrsp),
  CELI/REER starters re-pointed to `other` + vehicle_type (avoids NULL FK).
- Types: BalanceVehicleType, custom_label / vehicle_type / category_custom_label.
- Service: normalizeVehicleType + vehicle_type_invalid; CRUD writes the new
  columns; SELECT/JOINs read them back; STARTER_ACCOUNTS + proposeStarterAccounts
  (is_active=1 + vehicle_type) + getStarterCollisions adjusted.
- Tests: Rust chain v9→v13 (snapshot_lines identical, transfers intact, archived
  ex-tfsa covered, seeds deactivated, v13 EXISTS guard, idempotence, CHECK reject)
  + consolidated complete (5 categories, 4 starters, 0 NULL FK); TS service +
  StarterAccountsModal specs. No diff to migrations <= v11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 20:37:56 -04:00
le king fu
445822b792 fix(balance): pre-seed balance_starter_proposed pref for new profiles (S1)
Before this commit, a brand-new profile briefly showed the
StarterAccountsModal even though the 4 starter accounts were already
seeded — the modal rendered 4 collision rows with no actionable choice
before being dismissed. Pre-seeding the pref in consolidated_schema.sql
suppresses the modal on first /balance visit for new profiles entirely.

Existing profiles already running the app are unaffected: they handle
the modal once on their first /balance visit (the pref-write happens on
dismiss). No migration is needed for them.

Suggestion S1 from PR #185 review (#187).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 16:28:04 -04:00
le king fu
cd0a2b826f feat(balance): starter accounts + opt-in modal + ADR 0012
Part 1 — New profiles: seed 4 starter accounts in
consolidated_schema.sql (Compte chèque/CELI/REER/Compte
non-enregistré, currency CAD, is_active=1) right after the
balance_categories seeds. Categories resolved via SELECT subquery
on the seeded `key` values for robustness.

Part 2 — Existing profiles: StarterAccountsModal proposes the same
4 starters at first /balance visit. Default-checked checkboxes,
collision rule (case-insensitive trim name + matching category)
disables matches with a "Déjà présent" tooltip. The atomic helper
`proposeStarterAccounts` wraps the inserts in BEGIN/COMMIT (rolls
back on error). user_preferences.balance_starter_proposed records
{shown_at, accepted} so the modal never reappears, dismissed or
confirmed.

Part 3 — docs/adr/0012-balance-two-level-model.md (Proposed):
captures the future vehicles × compositions model for reflection,
no code change. Numbered 0012 because 0011 was already taken by
the providers-best-effort-yahoo ADR. Linked from architecture.md
ADR table and Bilan section.

Tests: StarterAccountsModal.test.tsx covers STARTER_ACCOUNTS shape,
getStarterCollisions (case-insensitive trim, category-scoped) and
proposeStarterAccounts (insert order, COMMIT, ROLLBACK on failure).
No render tests — mirrors the BalanceOnboardingCard pattern (no
jsdom configured).

Resolves #179
2026-05-02 11:59:45 -04:00
le king fu
3963f552ae feat(balance): add asset_type column to balance_categories
All checks were successful
PR Check / rust (push) Successful in 23m42s
PR Check / frontend (push) Successful in 2m26s
PR Check / rust (pull_request) Successful in 22m55s
PR Check / frontend (pull_request) Successful in 2m24s
Priced balance categories now carry an explicit `asset_type`
('stock' | 'crypto') so PriceFetchControl can route to the right
provider without symbol heuristics. ETH = Ethan Allen NYSE AND
Ethereum crypto are no longer ambiguous.

Migration v10 adds a nullable column and backfills the two seeded
priced categories (key='stock','crypto'). Legacy custom priced rows
stay NULL until the user edits the category — SnapshotLineRow hides
the price-fetch button when asset_type is NULL on a priced row, so
manual entry remains available.

Service-side validation rejects priced creation without asset_type
('asset_type_required') and rejects values outside ('stock','crypto')
('asset_type_invalid'). Simple kind coerces asset_type to NULL.

The CategoryVariant of AccountForm shows the selector only when
kind=priced, requires it on submit, and resets it on kind switch.
i18n keys added under balance.category.assetType.* (FR + EN).

Tests:
- 4 new Rust migration tests in lib.rs (column add, seed backfill,
  legacy row stays NULL, CHECK rejects 'gold')
- 6 new vitest cases on createBalanceCategory + listBalanceAccounts
  asserts c.asset_type AS category_asset_type in the join
- balance-flow integration test updated to pass asset_type='stock'

No new test for SnapshotLineRow render guard — project lacks
@testing-library/react + jsdom; the guard is one boolean expression
covered by manual QA per autopilot decisions in PR #167.

Fixes #169
2026-04-28 19:54:04 -04:00
le king fu
a6787adef0 feat(balance): add migration v9 schema (5 tables, 7 indexes, seed)
Adds the SQL foundation for the Bilan (balance sheet) feature:

- 5 new tables: balance_categories, balance_accounts, balance_snapshots,
  balance_snapshot_lines, balance_account_transfers
- 7 indexes (category, active partial, snapshot, accounts x2, transaction,
  snapshot_date)
- Seed of 7 standard categories (5 simple + 2 priced) marked is_seed=1
- CHECK(currency = 'CAD') on balance_accounts (MVP — v2 lifts the constraint
  with a multi-currency rate table)
- CHECK kind invariants on balance_snapshot_lines (quantity/unit_price both
  NULL OR both NOT NULL)
- FK transaction_id ON DELETE RESTRICT to preserve reproducibility of
  Modified Dietz returns calculated on past periods

Migration v9 is added inline to the lib.rs Vec<Migration> via a new
constant database::BALANCE_SCHEMA backed by balance_schema.sql. The
schema is mirrored in consolidated_schema.sql so brand-new profiles
get the feature preinstalled without replaying v9.

13 new co-located rusqlite tests validate the migration on a fresh
in-memory DB: schema applies cleanly, 7 categories seeded with correct
kinds, CHECK rejects invalid currency/kind/direction, UNIQUE rejects
duplicate snapshot_date / (snapshot_id,account_id) / (transaction_id,
account_id), FK CASCADE on snapshot delete, FK RESTRICT on transaction
delete and on category with linked accounts, seed idempotent on replay.

Refs #138

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:31:50 -04:00
le king fu
bd992f2f94 feat(categories): add v1 IPC seed, i18n keys, and migration v8 (#115)
All checks were successful
PR Check / rust (push) Successful in 22m29s
PR Check / frontend (push) Successful in 2m18s
PR Check / rust (pull_request) Successful in 22m39s
PR Check / frontend (pull_request) Successful in 2m18s
Livraison 1 du milestone spec-refonte-seed-categories-ipc. Applies the
new v1 IPC (Indice des prix à la consommation) taxonomy to freshly
created profiles while leaving existing v2 profiles untouched until the
migration wizard (upcoming issue #121) prompts them to move.

- Migration v8 (additive only):
    - ALTER TABLE categories ADD COLUMN i18n_key TEXT
    - INSERT OR IGNORE user_preferences.categories_schema_version=v2
      (existing profiles tagged as v2 for later migration)
- consolidated_schema.sql rewritten with the full v1 seed and
  categories_schema_version='v1' default for brand-new profiles
- src/data/categoryTaxonomyV1.json bundled as the TS-side source of
  truth (consumed by #116 categoryTaxonomyService next)
- categoriesSeed.* i18n namespace (FR/EN) — 150 entries each
- CategoryTree and CategoryCombobox fall back to the raw `name` when
  i18n_key is null (user-created categories stay literal)
- CategoryTreeNode and CategoryRow gain the i18n_key field end-to-end

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:41:55 -04:00
Le-King-Fu
20cae64f60 feat: add multiple profiles with separate databases and optional PIN (v0.3.0)
Some checks failed
Release / build (windows-latest) (push) Has been cancelled
Each profile gets its own SQLite database file for complete data isolation.
Profile selection screen at launch, sidebar switcher for quick switching,
and optional 4-6 digit PIN for privacy. Existing database becomes the
default profile with seamless upgrade.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 12:54:09 +00:00