160 lines
12 KiB
SQL
160 lines
12 KiB
SQL
-- Схема БД проекта «Бюджет» (MariaDB/InnoDB).
|
||
-- Модель согласована в .claude/memory/бюджет_текущий_план.md — сверяться туда при любых изменениях.
|
||
|
||
SET NAMES utf8mb4;
|
||
|
||
CREATE TABLE users (
|
||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
login VARCHAR(64) NOT NULL,
|
||
password VARCHAR(255) NOT NULL,
|
||
name VARCHAR(128) NOT NULL,
|
||
email VARCHAR(128) NULL,
|
||
date_add DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
last_login DATETIME NULL,
|
||
PRIMARY KEY (id),
|
||
UNIQUE KEY uq_users_login (login)
|
||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||
|
||
CREATE TABLE accounts (
|
||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
user_id INT UNSIGNED NOT NULL,
|
||
type_acc ENUM ('cash', 'bank', 'savings') NOT NULL,
|
||
title VARCHAR(128) NOT NULL,
|
||
summa DECIMAL(15, 2) NOT NULL DEFAULT 0,
|
||
currency CHAR(3) NOT NULL DEFAULT 'RUB', -- ISO 4217; курс — Services\CurrencyRate (ЦБ РФ), не хранится тут
|
||
description VARCHAR(255) NULL, -- свободное поле, например "счёт жены" — без отдельной структуры
|
||
color VARCHAR(20) NULL, -- пользовательский акцент счёта (oklch(...) строкой); NULL — цвет по умолчанию для type_acc
|
||
privacy ENUM ('shared', 'personal') NOT NULL DEFAULT 'shared', -- визуальный признак из дизайн-хендоффа; логики доступа за ним нет (мультипользовательский режим не в приоритете)
|
||
include_in_total TINYINT(1) NOT NULL DEFAULT 1, -- чисто ручной переключатель пользователя, не системное правило по типу/валюте (дефолт 0 для savings — просто стартовое значение)
|
||
is_delete TINYINT(1) NOT NULL DEFAULT 0,
|
||
`order` INT NOT NULL DEFAULT 0,
|
||
PRIMARY KEY (id),
|
||
KEY ix_accounts_user_id (user_id),
|
||
CONSTRAINT fk_accounts_user FOREIGN KEY (user_id) REFERENCES users (id)
|
||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||
|
||
-- Ставка savings-счёта хранится историей, а не полем в accounts — старые периоды не должны
|
||
-- меняться задним числом при смене ставки; та же структура пригодится для кредитов позже.
|
||
CREATE TABLE account_rates (
|
||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
account_id INT UNSIGNED NOT NULL,
|
||
rate_percent DECIMAL(5, 2) NOT NULL,
|
||
valid_from DATE NOT NULL,
|
||
valid_to DATE NULL,
|
||
PRIMARY KEY (id),
|
||
KEY ix_account_rates_account_id (account_id),
|
||
CONSTRAINT fk_account_rates_account FOREIGN KEY (account_id) REFERENCES accounts (id)
|
||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||
|
||
-- Необязательная надстройка над savings-счётом: без записи здесь — просто копилка без цели.
|
||
CREATE TABLE goals (
|
||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
account_id INT UNSIGNED NOT NULL,
|
||
target_summa DECIMAL(15, 2) NOT NULL,
|
||
deadline DATE NOT NULL,
|
||
PRIMARY KEY (id),
|
||
UNIQUE KEY uq_goals_account_id (account_id),
|
||
CONSTRAINT fk_goals_account FOREIGN KEY (account_id) REFERENCES accounts (id)
|
||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||
|
||
-- Группа статей — самостоятельная сущность, не статья с детьми. Раньше группой считалась
|
||
-- обычная строка categories, на которую другие строки ссылались через parent_id — это позволяло
|
||
-- одной и той же записи «случайно» становиться то листом (со своим планом в budgets), то группой,
|
||
-- и при добавлении первого ребёнка старый план листа не чистился, оставаясь мусором в budgets.
|
||
-- Разделение на две таблицы делает такое структурно невозможным: budgets.categorie_id может
|
||
-- указывать только на categories (см. ниже), у group_id там просто нет своей строки в budgets.
|
||
CREATE TABLE category_groups (
|
||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
user_id INT UNSIGNED NOT NULL,
|
||
title VARCHAR(128) NOT NULL,
|
||
type ENUM ('income', 'expense') NOT NULL,
|
||
is_delete TINYINT(1) NOT NULL DEFAULT 0,
|
||
`order` INT NOT NULL DEFAULT 0,
|
||
PRIMARY KEY (id),
|
||
KEY ix_category_groups_user_id (user_id),
|
||
CONSTRAINT fk_category_groups_user FOREIGN KEY (user_id) REFERENCES users (id)
|
||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||
|
||
CREATE TABLE categories (
|
||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
user_id INT UNSIGNED NOT NULL,
|
||
group_id INT UNSIGNED NULL, -- NULL — статья без группы (верхний уровень), см. category_groups
|
||
title VARCHAR(128) NOT NULL,
|
||
type ENUM ('income', 'expense') NOT NULL,
|
||
is_delete TINYINT(1) NOT NULL DEFAULT 0, -- архивация вместо удаления, тот же приём что у accounts — сохраняет историю транзакций
|
||
`order` INT NOT NULL DEFAULT 0,
|
||
-- формульные статьи (см. бюджет_текущий_план.md): настраиваются на статье ДОХОДА, ссылаются на
|
||
-- привязанную статью РАСХОДА + дефолтный процент; у остальных категорий оба поля NULL
|
||
linked_expense_categorie_id INT UNSIGNED NULL,
|
||
formula_pct DECIMAL(5, 2) NULL,
|
||
PRIMARY KEY (id),
|
||
KEY ix_categories_user_id (user_id),
|
||
KEY ix_categories_group_id (group_id),
|
||
CONSTRAINT fk_categories_user FOREIGN KEY (user_id) REFERENCES users (id),
|
||
-- удаление группы не должно тянуть за собой статьи — они просто станут без группы
|
||
CONSTRAINT fk_categories_group FOREIGN KEY (group_id) REFERENCES category_groups (id) ON DELETE SET NULL,
|
||
CONSTRAINT fk_categories_linked_expense FOREIGN KEY (linked_expense_categorie_id) REFERENCES categories (id)
|
||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||
|
||
-- categorie_id NULL — обычный transfer (в т.ч. пополнение копилки/цели): деньги просто
|
||
-- переехали между своими счетами, план/факт по категориям это не видит (см. бюджет_текущий_план.md).
|
||
-- account_f_id/account_in_id: income заполняет только _in, expense — только _f, transfer — оба.
|
||
CREATE TABLE transactions (
|
||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
user_id INT UNSIGNED NOT NULL,
|
||
categorie_id INT UNSIGNED NULL,
|
||
account_f_id INT UNSIGNED NULL,
|
||
account_in_id INT UNSIGNED NULL,
|
||
date DATE NOT NULL,
|
||
summa DECIMAL(15, 2) NOT NULL,
|
||
currency CHAR(3) NULL, -- валюта операции (обычно = валюте счёта списания); NULL = рубли
|
||
rate_to_rub DECIMAL(10, 4) NULL, -- курс ЦБ РФ, зафиксированный на момент операции; NULL для рублёвых
|
||
type ENUM ('income', 'expense', 'transfer') NOT NULL,
|
||
PRIMARY KEY (id),
|
||
KEY ix_transactions_user_date (user_id, date),
|
||
KEY ix_transactions_categorie_id (categorie_id),
|
||
KEY ix_transactions_account_f_id (account_f_id),
|
||
KEY ix_transactions_account_in_id (account_in_id),
|
||
CONSTRAINT fk_transactions_user FOREIGN KEY (user_id) REFERENCES users (id),
|
||
CONSTRAINT fk_transactions_categorie FOREIGN KEY (categorie_id) REFERENCES categories (id),
|
||
CONSTRAINT fk_transactions_account_f FOREIGN KEY (account_f_id) REFERENCES accounts (id),
|
||
CONSTRAINT fk_transactions_account_in FOREIGN KEY (account_in_id) REFERENCES accounts (id)
|
||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||
|
||
-- Лог правок/удалений transactions — цепочка снимков (не old/new-пары), достаточно для отката:
|
||
-- откат edit — snapshot предыдущей записи этой же transaction_id; восстановление после delete —
|
||
-- snapshot записи, предшествующей записи delete (сама delete-запись snapshot не хранит); откат
|
||
-- create — просто удалить операцию, snapshot не нужен. Механизм самого отката — не реализован,
|
||
-- здесь только лог.
|
||
CREATE TABLE transaction_history (
|
||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
transaction_id INT UNSIGNED NOT NULL,
|
||
user_id INT UNSIGNED NOT NULL,
|
||
changed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
action ENUM ('create', 'edit', 'delete') NOT NULL,
|
||
snapshot JSON NULL, -- полный слепок transactions на этот момент; NULL для action='delete'
|
||
PRIMARY KEY (id),
|
||
-- без FK на transactions.id намеренно: после физического удаления операции строка transactions
|
||
-- пропадает, а история по ней должна остаться читаемой
|
||
KEY ix_transaction_history_transaction_id (transaction_id),
|
||
CONSTRAINT fk_transaction_history_user FOREIGN KEY (user_id) REFERENCES users (id)
|
||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||
|
||
-- Факт не хранится — считается на лету суммой transactions по категории за месяц.
|
||
-- plan_summ хранится всегда (и у формульных строк тоже) — у формульных пересчитывается и
|
||
-- перезаписывается автоматически при правке суммы/процента статьи-источника (кэш, как accounts.summa).
|
||
CREATE TABLE budgets (
|
||
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
categorie_id INT UNSIGNED NOT NULL,
|
||
plan_summ DECIMAL(15, 2) NOT NULL,
|
||
year SMALLINT UNSIGNED NOT NULL,
|
||
month TINYINT UNSIGNED NOT NULL,
|
||
type ENUM ('income', 'expense') NOT NULL,
|
||
-- каким процентом посчитан plan_summ этого месяца — только для строк формульной статьи расхода,
|
||
-- NULL для обычных строк (см. категорию categories.linked_expense_categorie_id)
|
||
formula_pct_used DECIMAL(5, 2) NULL,
|
||
PRIMARY KEY (id),
|
||
UNIQUE KEY uq_budgets_categorie_period (categorie_id, year, month),
|
||
CONSTRAINT fk_budgets_categorie FOREIGN KEY (categorie_id) REFERENCES categories (id)
|
||
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|