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>
68 lines
3.6 KiB
SQL
68 lines
3.6 KiB
SQL
-- Balance sheet — détail par titre (Bilan Étape 2) — Migration v14
|
|
-- Created: 2026-06-06
|
|
-- Issue: #210 (Bilan détail #1 — schema & migrations v14/v15 + types)
|
|
--
|
|
-- Purely additive: two new tables enabling a single account to hold many
|
|
-- securities at a given snapshot date instead of one denormalized value.
|
|
-- Conventions aligned with balance_schema.sql / consolidated_schema.sql:
|
|
-- - INTEGER PRIMARY KEY AUTOINCREMENT
|
|
-- - REAL for monetary amounts / quantities (matches transactions.amount)
|
|
-- - snake_case
|
|
-- - FK with explicit ON DELETE policies
|
|
-- - DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP for timestamps
|
|
--
|
|
-- Design notes (spec-decisions-bilan-detail-titres.md + review caveats):
|
|
-- - balance_securities.symbol is the natural key, normalized upper/trim by
|
|
-- the service layer; COLLATE NOCASE UNIQUE prevents case-duplicates
|
|
-- ('aapl' vs 'AAPL') at the SQL level (SEC/ARCH review caveat).
|
|
-- - balance_snapshot_holdings references balance_snapshot_lines(id) rather
|
|
-- than (snapshot_id, account_id): the lines table already guarantees one
|
|
-- row per (snapshot, account) via UNIQUE(snapshot_id, account_id), so the
|
|
-- line id uniquely identifies that pair (ARCH review: keep the line FK).
|
|
-- - value is denormalized (= quantity * unit_price) so reports stay
|
|
-- reproducible without re-fetching prices — same rationale as
|
|
-- balance_snapshot_lines.value.
|
|
|
|
|
|
-- =========================================================================
|
|
-- balance_securities — catalogue of investable instruments (stock | crypto)
|
|
-- =========================================================================
|
|
-- One row per security/coin the user holds in a detailed account. `symbol` is
|
|
-- stored normalized (upper/trim) with COLLATE NOCASE so duplicates differing
|
|
-- only by case are impossible. `asset_type` mirrors balance_categories so the
|
|
-- price-fetch flow can route per security.
|
|
CREATE TABLE IF NOT EXISTS balance_securities (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
symbol TEXT NOT NULL COLLATE NOCASE UNIQUE,
|
|
name TEXT,
|
|
currency TEXT NOT NULL DEFAULT 'CAD',
|
|
asset_type TEXT NOT NULL CHECK (asset_type IN ('stock','crypto')),
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
|
|
-- =========================================================================
|
|
-- balance_snapshot_holdings — one row per (snapshot line, security)
|
|
-- =========================================================================
|
|
-- The per-title breakdown of a detailed account's snapshot line. CASCADE on
|
|
-- snapshot_line_id wipes holdings when the parent line is removed; RESTRICT on
|
|
-- security_id blocks deleting a security still referenced by history. `value`
|
|
-- is stored denormalized (= quantity * unit_price) for reproducible reports.
|
|
CREATE TABLE IF NOT EXISTS balance_snapshot_holdings (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
snapshot_line_id INTEGER NOT NULL REFERENCES balance_snapshot_lines(id) ON DELETE CASCADE,
|
|
security_id INTEGER NOT NULL REFERENCES balance_securities(id) ON DELETE RESTRICT,
|
|
quantity REAL NOT NULL,
|
|
unit_price REAL NOT NULL,
|
|
value REAL NOT NULL,
|
|
book_cost REAL,
|
|
price_source TEXT,
|
|
price_fetched_at DATETIME,
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE (snapshot_line_id, security_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_balance_snapshot_holdings_line ON balance_snapshot_holdings(snapshot_line_id);
|
|
CREATE INDEX IF NOT EXISTS idx_balance_snapshot_holdings_security ON balance_snapshot_holdings(security_id);
|