191 lines
6.9 KiB
Python
191 lines
6.9 KiB
Python
from __future__ import annotations
|
|
|
|
SCHEMA_VERSION = "10"
|
|
|
|
SCHEMA_SQL = """
|
|
PRAGMA journal_mode=WAL;
|
|
PRAGMA foreign_keys=ON;
|
|
|
|
CREATE TABLE IF NOT EXISTS schema_meta (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS accounts (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
type TEXT NOT NULL DEFAULT 'bank',
|
|
institution TEXT NOT NULL DEFAULT '',
|
|
currency TEXT NOT NULL DEFAULT 'KRW',
|
|
active INTEGER NOT NULL DEFAULT 1,
|
|
memo TEXT NOT NULL DEFAULT '',
|
|
account_number_masked TEXT NOT NULL DEFAULT '',
|
|
holder_name TEXT NOT NULL DEFAULT '',
|
|
last_balance INTEGER,
|
|
last_synced_at TEXT NOT NULL DEFAULT '',
|
|
statement_status TEXT NOT NULL DEFAULT 'unknown',
|
|
liquidity_bucket TEXT NOT NULL DEFAULT 'other',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS transactions (
|
|
id TEXT PRIMARY KEY,
|
|
transaction_date TEXT NOT NULL,
|
|
posted_date TEXT NOT NULL DEFAULT '',
|
|
type TEXT NOT NULL,
|
|
amount INTEGER NOT NULL,
|
|
currency TEXT NOT NULL DEFAULT 'KRW',
|
|
category TEXT NOT NULL DEFAULT '',
|
|
subcategory TEXT NOT NULL DEFAULT '',
|
|
merchant TEXT NOT NULL DEFAULT '',
|
|
description TEXT NOT NULL DEFAULT '',
|
|
account_id TEXT NOT NULL DEFAULT '',
|
|
payment_method TEXT NOT NULL DEFAULT '',
|
|
person TEXT NOT NULL DEFAULT '',
|
|
project TEXT NOT NULL DEFAULT '',
|
|
tags TEXT NOT NULL DEFAULT '[]',
|
|
is_fixed INTEGER NOT NULL DEFAULT 0,
|
|
source TEXT NOT NULL DEFAULT 'manual',
|
|
import_batch_id TEXT NOT NULL DEFAULT '',
|
|
external_id TEXT NOT NULL DEFAULT '',
|
|
dedupe_key TEXT NOT NULL DEFAULT '',
|
|
memo TEXT NOT NULL DEFAULT '',
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
transaction_datetime TEXT NOT NULL DEFAULT '',
|
|
counterparty TEXT NOT NULL DEFAULT '',
|
|
balance_after INTEGER,
|
|
branch TEXT NOT NULL DEFAULT '',
|
|
raw_summary TEXT NOT NULL DEFAULT '',
|
|
raw_payload TEXT NOT NULL DEFAULT '',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_transactions_date ON transactions(transaction_date);
|
|
CREATE INDEX IF NOT EXISTS idx_transactions_category ON transactions(category, subcategory);
|
|
CREATE INDEX IF NOT EXISTS idx_transactions_person ON transactions(person);
|
|
CREATE INDEX IF NOT EXISTS idx_transactions_account ON transactions(account_id);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_transactions_dedupe ON transactions(dedupe_key) WHERE dedupe_key <> '';
|
|
|
|
CREATE TABLE IF NOT EXISTS budgets (
|
|
id TEXT PRIMARY KEY,
|
|
month TEXT NOT NULL,
|
|
category TEXT NOT NULL,
|
|
subcategory TEXT NOT NULL DEFAULT '',
|
|
person TEXT NOT NULL DEFAULT '',
|
|
amount INTEGER NOT NULL,
|
|
currency TEXT NOT NULL DEFAULT 'KRW',
|
|
memo TEXT NOT NULL DEFAULT '',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_budgets_month ON budgets(month);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_budgets_unique ON budgets(month, category, subcategory, person);
|
|
|
|
CREATE TABLE IF NOT EXISTS import_batches (
|
|
id TEXT PRIMARY KEY,
|
|
source TEXT NOT NULL,
|
|
filename TEXT NOT NULL DEFAULT '',
|
|
imported_at TEXT NOT NULL,
|
|
row_count INTEGER NOT NULL DEFAULT 0,
|
|
status TEXT NOT NULL DEFAULT 'active',
|
|
memo TEXT NOT NULL DEFAULT ''
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS category_rules (
|
|
id TEXT PRIMARY KEY,
|
|
priority INTEGER NOT NULL DEFAULT 100,
|
|
match_field TEXT NOT NULL,
|
|
match_type TEXT NOT NULL DEFAULT 'contains',
|
|
pattern TEXT NOT NULL,
|
|
amount_min INTEGER,
|
|
amount_max INTEGER,
|
|
day_of_month INTEGER,
|
|
transaction_type TEXT NOT NULL DEFAULT '',
|
|
category TEXT NOT NULL,
|
|
subcategory TEXT NOT NULL DEFAULT '',
|
|
person TEXT NOT NULL DEFAULT '',
|
|
is_fixed INTEGER,
|
|
confidence REAL NOT NULL DEFAULT 1.0,
|
|
active INTEGER NOT NULL DEFAULT 1,
|
|
memo TEXT NOT NULL DEFAULT '',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_category_rules_active_priority ON category_rules(active, priority);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_category_rules_unique ON category_rules(match_field, match_type, pattern, transaction_type, category, subcategory, person);
|
|
|
|
CREATE TABLE IF NOT EXISTS ledger_categories (
|
|
id TEXT PRIMARY KEY,
|
|
category TEXT NOT NULL,
|
|
subcategory TEXT NOT NULL DEFAULT '',
|
|
cashflow_kind TEXT NOT NULL DEFAULT 'expense',
|
|
active INTEGER NOT NULL DEFAULT 1,
|
|
sort_order INTEGER NOT NULL DEFAULT 100,
|
|
memo TEXT NOT NULL DEFAULT '',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_ledger_categories_active_sort ON ledger_categories(active, sort_order, category, subcategory);
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_ledger_categories_unique ON ledger_categories(category, subcategory);
|
|
|
|
CREATE TABLE IF NOT EXISTS manual_assets (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
liquidity_bucket TEXT NOT NULL DEFAULT 'other',
|
|
valuation_amount INTEGER,
|
|
valuation_date TEXT NOT NULL DEFAULT '',
|
|
currency TEXT NOT NULL DEFAULT 'KRW',
|
|
source_note TEXT NOT NULL DEFAULT '',
|
|
memo TEXT NOT NULL DEFAULT '',
|
|
active INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_manual_assets_active_bucket ON manual_assets(active, liquidity_bucket, name);
|
|
CREATE INDEX IF NOT EXISTS idx_manual_assets_valuation_date ON manual_assets(valuation_date);
|
|
|
|
CREATE TABLE IF NOT EXISTS asset_snapshots (
|
|
id TEXT PRIMARY KEY,
|
|
basis_date TEXT NOT NULL,
|
|
source_type TEXT NOT NULL,
|
|
source_id TEXT NOT NULL DEFAULT '',
|
|
name TEXT NOT NULL,
|
|
amount INTEGER,
|
|
currency TEXT NOT NULL DEFAULT 'KRW',
|
|
liquidity_bucket TEXT NOT NULL DEFAULT 'other',
|
|
valuation_source TEXT NOT NULL DEFAULT '',
|
|
source_note TEXT NOT NULL DEFAULT '',
|
|
memo TEXT NOT NULL DEFAULT '',
|
|
snapshot_run_id TEXT NOT NULL DEFAULT '',
|
|
correction_of_run_id TEXT NOT NULL DEFAULT '',
|
|
correction_reason TEXT NOT NULL DEFAULT '',
|
|
is_correction INTEGER NOT NULL DEFAULT 0,
|
|
closed_at TEXT NOT NULL DEFAULT '',
|
|
created_at TEXT NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_asset_snapshots_basis_date ON asset_snapshots(basis_date);
|
|
CREATE INDEX IF NOT EXISTS idx_asset_snapshots_source ON asset_snapshots(source_type, source_id);
|
|
|
|
INSERT OR REPLACE INTO schema_meta(key, value) VALUES ('schema_version', '10');
|
|
"""
|
|
|
|
ACCOUNT_ADDITIVE_COLUMNS: dict[str, str] = {
|
|
"account_number_masked": "TEXT NOT NULL DEFAULT ''",
|
|
"holder_name": "TEXT NOT NULL DEFAULT ''",
|
|
"last_balance": "INTEGER",
|
|
"last_synced_at": "TEXT NOT NULL DEFAULT ''",
|
|
"statement_status": "TEXT NOT NULL DEFAULT 'unknown'",
|
|
"liquidity_bucket": "TEXT NOT NULL DEFAULT 'other'",
|
|
}
|
|
|
|
TRANSACTION_ADDITIVE_COLUMNS: dict[str, str] = {
|
|
"transaction_datetime": "TEXT NOT NULL DEFAULT ''",
|
|
"counterparty": "TEXT NOT NULL DEFAULT ''",
|
|
"balance_after": "INTEGER",
|
|
"branch": "TEXT NOT NULL DEFAULT ''",
|
|
"raw_summary": "TEXT NOT NULL DEFAULT ''",
|
|
"raw_payload": "TEXT NOT NULL DEFAULT ''",
|
|
}
|