Files
mousehold/db/migrations/0001_initial.sql
2026-06-28 13:48:15 +02:00

324 lines
9.7 KiB
SQL

-- M0: Local household ledger skeleton.
-- SQLite-compatible baseline schema. The SQLAlchemy models are the runtime source for dev
-- create_all(); this file documents the first relational shape for migration tooling.
CREATE TABLE IF NOT EXISTS households (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
default_currency TEXT NOT NULL DEFAULT 'EUR',
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
household_id TEXT NOT NULL REFERENCES households(id),
display_name TEXT NOT NULL,
email TEXT,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS login_identities (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id),
provider TEXT NOT NULL,
provider_subject TEXT NOT NULL,
email TEXT,
display_name TEXT,
created_at TEXT NOT NULL,
UNIQUE(provider, provider_subject)
);
CREATE TABLE IF NOT EXISTS local_sessions (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL REFERENCES users(id),
household_id TEXT NOT NULL REFERENCES households(id),
token_hash TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL,
last_seen_at TEXT NOT NULL,
expires_at TEXT NOT NULL,
revoked_at TEXT
);
CREATE INDEX IF NOT EXISTS ix_local_session_token
ON local_sessions(token_hash);
CREATE INDEX IF NOT EXISTS ix_local_session_user
ON local_sessions(user_id, expires_at);
CREATE TABLE IF NOT EXISTS accounts (
id TEXT PRIMARY KEY,
household_id TEXT NOT NULL REFERENCES households(id),
owner_user_id TEXT REFERENCES users(id),
name TEXT NOT NULL,
account_type TEXT NOT NULL,
institution_name TEXT,
currency TEXT NOT NULL DEFAULT 'EUR',
visibility_mode TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS raw_observations (
id TEXT PRIMARY KEY,
household_id TEXT NOT NULL REFERENCES households(id),
source_type TEXT NOT NULL,
source_account_id TEXT REFERENCES accounts(id),
owner_user_id TEXT REFERENCES users(id),
observed_at TEXT,
received_at TEXT NOT NULL,
raw_hash TEXT NOT NULL,
source_reference TEXT,
provider_transaction_id TEXT,
message_id TEXT,
raw_text TEXT,
raw_file_path TEXT,
raw_payload_json TEXT,
parsed_json TEXT,
parser_name TEXT,
parser_version TEXT,
confidence NUMERIC(14, 2) NOT NULL DEFAULT 1.00,
status TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS ix_observation_dedupe
ON raw_observations(household_id, source_type, raw_hash);
CREATE INDEX IF NOT EXISTS ix_observation_external_refs
ON raw_observations(provider_transaction_id, message_id);
CREATE TABLE IF NOT EXISTS categories (
id TEXT PRIMARY KEY,
household_id TEXT NOT NULL REFERENCES households(id),
parent_id TEXT REFERENCES categories(id),
name TEXT NOT NULL,
path TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1
);
CREATE TABLE IF NOT EXISTS projects (
id TEXT PRIMARY KEY,
household_id TEXT NOT NULL REFERENCES households(id),
name TEXT NOT NULL,
description TEXT,
start_date TEXT,
end_date TEXT,
default_split_rule_id TEXT,
status TEXT NOT NULL,
currency TEXT NOT NULL DEFAULT 'EUR',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS financial_events (
id TEXT PRIMARY KEY,
household_id TEXT NOT NULL REFERENCES households(id),
source_account_id TEXT REFERENCES accounts(id),
payment_account_id TEXT REFERENCES accounts(id),
payer_user_id TEXT REFERENCES users(id),
event_type TEXT NOT NULL,
status TEXT NOT NULL,
event_date TEXT NOT NULL,
booking_date TEXT,
due_date TEXT,
amount NUMERIC(14, 2) NOT NULL,
currency TEXT NOT NULL DEFAULT 'EUR',
merchant TEXT,
counterparty TEXT,
description TEXT,
category_id TEXT REFERENCES categories(id),
project_id TEXT REFERENCES projects(id),
created_by_user_id TEXT REFERENCES users(id),
fingerprint TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS ix_events_household_status_date
ON financial_events(household_id, status, event_date);
CREATE TABLE IF NOT EXISTS event_observation_links (
id TEXT PRIMARY KEY,
event_id TEXT NOT NULL REFERENCES financial_events(id),
observation_id TEXT NOT NULL REFERENCES raw_observations(id),
relation TEXT NOT NULL,
confidence NUMERIC(14, 2) NOT NULL DEFAULT 1.00,
created_at TEXT NOT NULL,
UNIQUE(event_id, observation_id, relation)
);
CREATE TABLE IF NOT EXISTS event_relations (
id TEXT PRIMARY KEY,
from_event_id TEXT NOT NULL REFERENCES financial_events(id),
to_event_id TEXT NOT NULL REFERENCES financial_events(id),
relation_type TEXT NOT NULL,
confidence NUMERIC(14, 2) NOT NULL DEFAULT 1.00,
created_by TEXT NOT NULL DEFAULT 'system',
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS split_lines (
id TEXT PRIMARY KEY,
event_id TEXT NOT NULL REFERENCES financial_events(id),
user_id TEXT NOT NULL REFERENCES users(id),
share_amount NUMERIC(14, 2) NOT NULL,
share_percent NUMERIC(14, 2),
reason TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tags (
id TEXT PRIMARY KEY,
household_id TEXT NOT NULL REFERENCES households(id),
name TEXT NOT NULL,
UNIQUE(household_id, name)
);
CREATE TABLE IF NOT EXISTS event_tags (
event_id TEXT NOT NULL REFERENCES financial_events(id),
tag_id TEXT NOT NULL REFERENCES tags(id),
PRIMARY KEY(event_id, tag_id)
);
CREATE TABLE IF NOT EXISTS project_participants (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id),
user_id TEXT NOT NULL REFERENCES users(id),
role TEXT NOT NULL,
default_share_percent NUMERIC(14, 2),
UNIQUE(project_id, user_id)
);
CREATE TABLE IF NOT EXISTS project_budget_lines (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id),
category_id TEXT REFERENCES categories(id),
name TEXT NOT NULL,
budgeted_amount NUMERIC(14, 2) NOT NULL DEFAULT 0.00,
committed_amount_cached NUMERIC(14, 2),
paid_amount_cached NUMERIC(14, 2),
sort_order INTEGER NOT NULL DEFAULT 0,
notes TEXT
);
CREATE TABLE IF NOT EXISTS project_planned_items (
id TEXT PRIMARY KEY,
project_id TEXT NOT NULL REFERENCES projects(id),
budget_line_id TEXT REFERENCES project_budget_lines(id),
description TEXT NOT NULL,
estimated_amount NUMERIC(14, 2) NOT NULL DEFAULT 0.00,
committed_amount NUMERIC(14, 2),
expected_date TEXT,
due_date TEXT,
expected_payer_account_id TEXT REFERENCES accounts(id),
split_rule_id TEXT,
planning_status TEXT NOT NULL,
payment_status TEXT NOT NULL,
reconciliation_status TEXT NOT NULL,
settlement_status TEXT NOT NULL,
notes TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS payment_lines (
id TEXT PRIMARY KEY,
planned_item_id TEXT REFERENCES project_planned_items(id),
event_id TEXT REFERENCES financial_events(id),
amount NUMERIC(14, 2) NOT NULL,
currency TEXT NOT NULL DEFAULT 'EUR',
date TEXT NOT NULL,
paid_by_user_id TEXT REFERENCES users(id),
payment_account_id TEXT REFERENCES accounts(id),
source_observation_id TEXT REFERENCES raw_observations(id),
status TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS recurring_commitments (
id TEXT PRIMARY KEY,
household_id TEXT NOT NULL REFERENCES households(id),
provider TEXT NOT NULL,
description TEXT,
amount_expected NUMERIC(14, 2) NOT NULL,
amount_min NUMERIC(14, 2),
amount_max NUMERIC(14, 2),
currency TEXT NOT NULL DEFAULT 'EUR',
interval TEXT NOT NULL,
next_due_date TEXT NOT NULL,
payer_account_id TEXT REFERENCES accounts(id),
split_rule_id TEXT,
category_id TEXT REFERENCES categories(id),
project_id TEXT REFERENCES projects(id),
notice_period TEXT,
renewal_date TEXT,
status TEXT NOT NULL,
matching_rule_json TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS promised_payments (
id TEXT PRIMARY KEY,
household_id TEXT NOT NULL REFERENCES households(id),
related_event_id TEXT REFERENCES financial_events(id),
provider TEXT NOT NULL,
amount NUMERIC(14, 2) NOT NULL,
currency TEXT NOT NULL DEFAULT 'EUR',
created_date TEXT NOT NULL,
due_date TEXT NOT NULL,
expected_funding_account_id TEXT REFERENCES accounts(id),
responsible_user_id TEXT REFERENCES users(id),
status TEXT NOT NULL,
source_observation_id TEXT REFERENCES raw_observations(id),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS import_connections (
id TEXT PRIMARY KEY,
household_id TEXT NOT NULL REFERENCES households(id),
owner_user_id TEXT NOT NULL REFERENCES users(id),
connection_type TEXT NOT NULL,
display_name TEXT NOT NULL,
status TEXT NOT NULL,
last_sync_at TEXT,
last_error TEXT,
secret_storage_ref TEXT,
runs_locally INTEGER NOT NULL DEFAULT 1,
connector_config_json TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS import_runs (
id TEXT PRIMARY KEY,
household_id TEXT NOT NULL REFERENCES households(id),
connection_id TEXT REFERENCES import_connections(id),
actor_user_id TEXT REFERENCES users(id),
source_type TEXT,
status TEXT NOT NULL DEFAULT 'running',
imported_count INTEGER NOT NULL DEFAULT 0,
duplicate_count INTEGER NOT NULL DEFAULT 0,
created_event_count INTEGER NOT NULL DEFAULT 0,
skipped_count INTEGER NOT NULL DEFAULT 0,
error TEXT,
summary_json TEXT,
started_at TEXT NOT NULL,
finished_at TEXT
);
CREATE INDEX IF NOT EXISTS ix_import_runs_household_started
ON import_runs(household_id, started_at);
CREATE INDEX IF NOT EXISTS ix_import_runs_connection_started
ON import_runs(connection_id, started_at);
CREATE TABLE IF NOT EXISTS audit_log (
id TEXT PRIMARY KEY,
household_id TEXT NOT NULL REFERENCES households(id),
actor_user_id TEXT REFERENCES users(id),
action TEXT NOT NULL,
entity_type TEXT NOT NULL,
entity_id TEXT,
details_json TEXT,
created_at TEXT NOT NULL
);