Files
family_budget/docs/backlog/db.md
2026-02-27 19:08:55 +03:00

8.6 KiB
Raw Blame History

Модель БД (PostgreSQL)

Общие принципы

  • Используется PostgreSQL, развёрнутый локально (например, на Synology).
  • Основные сущности:
    • accounts — банковские счета пользователя;
    • transactions — движения средств по счетам;
    • category_rules — правила автоматической категоризации транзакций (подготовка к SPA-редактору правил).
  • Все суммы хранятся в минорных единицах (копейки) как BIGINT.
  • Время хранится в TIMESTAMPTZ (временная зона сохраняется).

Таблица accounts

Предназначение: хранение информации о счетах, по которым загружаются выписки.

Структура:

  • id BIGSERIAL PRIMARY KEY — внутренний идентификатор счёта в системе.
  • bank TEXT NOT NULL — код/имя банка (например, "VTB").
  • account_number TEXT NOT NULL — номер счёта в банке (как в выписке).
  • currency TEXT NOT NULL — код валюты счёта (например, "RUB").

Ограничения и индексы:

  • Уникальность комбинации (bank, account_number) — один и тот же счёт в банке не должен дублироваться.

Рекомендуемый DDL:

CREATE TABLE accounts (
  id             BIGSERIAL PRIMARY KEY,
  bank           TEXT NOT NULL,
  account_number TEXT NOT NULL,
  currency       TEXT NOT NULL
);

CREATE UNIQUE INDEX ux_accounts_bank_number
  ON accounts(bank, account_number);

Таблица transactions

Предназначение: хранение всех операций по счетам с привязкой к accounts.

Структура:

  • id BIGSERIAL PRIMARY KEY — внутренний идентификатор транзакции.
  • account_id BIGINT NOT NULL — внешний ключ (FK) на accounts(id); каждая транзакция жёстко привязана к одному счёту.
  • operation_at TIMESTAMPTZ NOT NULL — дата и время операции.
  • amount_signed BIGINT NOT NULL — сумма операции в копейках; знак отражает тип движения (приход/расход).
  • commission BIGINT NOT NULL — комиссия по операции в копейках.
  • description TEXT NOT NULL — описание операции из выписки.
  • direction TEXT NOT NULL — направление движения:
    • "income" — приход;
    • "expense" — расход;
    • "transfer" — перевод между своими счетами / на другие свои счета;
    • "internal" — служебные/внутренние движения (опционально, по мере необходимости).
  • fingerprint TEXT NOT NULL — вычисляемый хэш для обеспечения идемпотентности импорта.
  • category_id BIGINT — ссылка на таблицу категорий (будет определена позже).
  • created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() — время создания записи в БД.
  • updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() — время последнего обновления записи.

Ограничения и индексы:

  • Внешний ключ account_id ссылается на accounts(id) и обеспечивает целостность (нельзя создать транзакцию для несуществующего счёта).
  • Уникальный индекс (account_id, fingerprint) обеспечивает идемпотентность: одна и та же операция не может быть загружена дважды.
  • Опционально — CHECK-ограничение на поле direction.

Рекомендуемый DDL:

CREATE TABLE transactions (
  id            BIGSERIAL PRIMARY KEY,
  account_id    BIGINT NOT NULL REFERENCES accounts(id),
  operation_at  TIMESTAMPTZ NOT NULL,
  amount_signed BIGINT NOT NULL,
  commission    BIGINT NOT NULL,
  description   TEXT NOT NULL,
  direction     TEXT NOT NULL,
  fingerprint   TEXT NOT NULL,
  category_id   BIGINT,
  created_at    TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  updated_at    TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE UNIQUE INDEX ux_transactions_account_fingerprint
  ON transactions(account_id, fingerprint);

ALTER TABLE transactions
  ADD CONSTRAINT chk_transactions_direction
  CHECK (direction IN ('income', 'expense', 'transfer', 'internal'));

Таблица category_rules

Предназначение: хранение правил автоматической категоризации транзакций на основе текста описания.

Структура:

  • id BIGSERIAL PRIMARY KEY — идентификатор правила.
  • pattern TEXT NOT NULL — строка-шаблон, вводимая пользователем через SPA (в простом виде).
  • match_type TEXT NOT NULL — тип сопоставления:
    • "contains" — простое вхождение подстроки;
    • "starts_with" — строка начинается с шаблона;
    • "regex" — регулярное выражение (формируется и/или проверяется в коде на основе пользовательского ввода).
  • category_id BIGINT NOT NULL — ссылка на категорию (таблица категорий будет описана отдельно).
  • priority INT NOT NULL DEFAULT 0 — приоритет правила; чем выше число, тем раньше правило применяется при конфликте.
  • is_active BOOLEAN NOT NULL DEFAULT TRUE — активно ли правило.
  • created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() — время создания правила.

Логика приоритета:

  • При авто-категоризации для транзакции ищутся все правила, которые ей соответствуют.
  • Если совпало несколько правил, выбирается правило с максимальным priority.
  • Это позволяет задавать общие правила с низким приоритетом и более точные (например, по конкретным мерчантам) с высоким приоритетом.

Рекомендуемый DDL:

CREATE TABLE category_rules (
  id          BIGSERIAL PRIMARY KEY,
  pattern     TEXT NOT NULL,
  match_type  TEXT NOT NULL,
  category_id BIGINT NOT NULL,
  priority    INT NOT NULL DEFAULT 0,
  is_active   BOOLEAN NOT NULL DEFAULT TRUE,
  created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

Взаимосвязь JSON → БД при импорте

  1. По полям bank и statement.accountNumber ищется или создаётся запись в accounts.

  2. Для каждой транзакции из transactions:

    • преобразуются суммы в копейки (amountSigned, commissionBIGINT);
    • вычисляется fingerprint на основе комбинации полей (например, accountNumber + operationAt + amountSigned + commission + normalizedDescription);
    • определяется direction по знаку суммы и/или шаблонам текста (приход/расход/перевод);
    • выполняется попытка вставки в transactions;
    • при срабатывании уникального ограничения (account_id, fingerprint) запись считается дубликатом и пропускается.
    • При импорте новых транзакций is_category_confirmed всегда = FALSE.
    • При автокатегоризации обычными правилами, если правило “жёсткое” (не маркетплейс) — TRUE, для маркетплейсов — всегда FALSE.
  3. Категория (category_id) пока может оставаться NULL и заполняться на следующих этапах (правила, ИИ-агент, ручное редактирование в SPA).