fix: align docs and code with actual DB schema and format spec
- Rewrite db.md as canonical schema: add categories, sessions tables; add alias to accounts, is_category_confirmed/comment to transactions, FK references to categories(id); mark budgets as post-MVP - Fix account masking to use fixed 6 asterisks (code + docs) - Remove budgets from MVP requirements in agent_backend.md - Add explicit 'not in MVP' note to analytics.md budgets section - Fix test_Statement.json: convert amounts to kopecks (integers), remove fingerprint fields (computed by backend) Made-with: Cursor
This commit is contained in:
@@ -136,6 +136,8 @@
|
||||
|
||||
## 5.3. Задел под бюджеты (лимиты)
|
||||
|
||||
> **Не входит в MVP.** Этот раздел описывает будущую функциональность; таблица `budgets` и связанная логика реализуются после MVP.
|
||||
|
||||
Для поддержки бюджетов по категориям на будущих этапах вводится сущность `budgets`.
|
||||
|
||||
### Таблица `budgets` (идея)
|
||||
|
||||
@@ -175,7 +175,7 @@ accountNumber|operationAt|amountSigned|commission|normalizedDescription
|
||||
{
|
||||
"accountId": 1,
|
||||
"isNewAccount": true,
|
||||
"accountNumberMasked": "408178**********5611",
|
||||
"accountNumberMasked": "408178******5611",
|
||||
"imported": 28,
|
||||
"duplicatesSkipped": 3,
|
||||
"totalInFile": 31
|
||||
@@ -197,8 +197,8 @@ accountNumber|operationAt|amountSigned|commission|normalizedDescription
|
||||
|
||||
- Первые 6 символов остаются открытыми.
|
||||
- Последние 4 символа остаются открытыми.
|
||||
- Промежуточные символы заменяются на `*`.
|
||||
- Пример: `40817810825104025611` → `408178**********5611`.
|
||||
- Промежуточные символы заменяются фиксированным набором из 6 символов `******` (количество звёздочек не раскрывает длину номера).
|
||||
- Пример: `40817810825104025611` → `408178******5611`.
|
||||
|
||||
Маскирование применяется в ответе этого эндпоинта и в `GET /api/accounts`.
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
- Используется PostgreSQL, развёрнутый локально (например, на Synology).
|
||||
- Основные сущности:
|
||||
- `accounts` — банковские счета пользователя;
|
||||
- `categories` — категории расходов, доходов и переводов;
|
||||
- `transactions` — движения средств по счетам;
|
||||
- `category_rules` — правила автоматической категоризации транзакций (подготовка к SPA-редактору правил).
|
||||
- `category_rules` — правила автоматической категоризации транзакций;
|
||||
- `sessions` — серверные сессии авторизации.
|
||||
- Все суммы хранятся в минорных единицах (копейки) как `BIGINT`.
|
||||
- Время хранится в `TIMESTAMPTZ` (временная зона сохраняется).
|
||||
|
||||
@@ -20,6 +22,8 @@
|
||||
- `bank TEXT NOT NULL` — код/имя банка (например, `"VTB"`).
|
||||
- `account_number TEXT NOT NULL` — номер счёта в банке (как в выписке).
|
||||
- `currency TEXT NOT NULL` — код валюты счёта (например, `"RUB"`).
|
||||
- `alias TEXT` — человекочитаемый алиас счёта (например, `"Текущий"`, `"Накопительный"`).
|
||||
При создании счёта через импорт `alias = NULL`; пользователь задаёт его позже через SPA.
|
||||
|
||||
Ограничения и индексы:
|
||||
|
||||
@@ -32,21 +36,57 @@ CREATE TABLE accounts (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
bank TEXT NOT NULL,
|
||||
account_number TEXT NOT NULL,
|
||||
currency TEXT NOT NULL
|
||||
currency TEXT NOT NULL,
|
||||
alias TEXT
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX ux_accounts_bank_number
|
||||
ON accounts(bank, account_number);
|
||||
```
|
||||
|
||||
## Таблица `categories`
|
||||
|
||||
Предназначение: хранение категорий для классификации транзакций.
|
||||
|
||||
Структура:
|
||||
|
||||
- `id BIGSERIAL PRIMARY KEY` — идентификатор категории.
|
||||
- `name TEXT NOT NULL` — отображаемое имя категории (например, `"Продукты"`, `"ЖКХ"`).
|
||||
- `type TEXT NOT NULL` — тип категории:
|
||||
- `"expense"` — расходная категория;
|
||||
- `"income"` — доходная категория;
|
||||
- `"transfer"` — переводы между собственными счетами.
|
||||
- `is_active BOOLEAN NOT NULL DEFAULT TRUE` — используется ли категория.
|
||||
|
||||
Ограничения:
|
||||
|
||||
- CHECK-ограничение: `type IN ('expense', 'income', 'transfer')`.
|
||||
|
||||
Рекомендуемый DDL:
|
||||
|
||||
```sql
|
||||
CREATE TABLE categories (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
|
||||
ALTER TABLE categories
|
||||
ADD CONSTRAINT chk_categories_type
|
||||
CHECK (type IN ('expense', 'income', 'transfer'));
|
||||
```
|
||||
|
||||
Начальный набор из 23 категорий заполняется seed-миграцией (см. `category.md`).
|
||||
|
||||
## Таблица `transactions`
|
||||
|
||||
Предназначение: хранение всех операций по счетам с привязкой к `accounts`.
|
||||
Предназначение: хранение всех операций по счетам с привязкой к `accounts` и `categories`.
|
||||
|
||||
Структура:
|
||||
|
||||
- `id BIGSERIAL PRIMARY KEY` — внутренний идентификатор транзакции.
|
||||
- `account_id BIGINT NOT NULL` — внешний ключ (FK) на `accounts(id)`;
|
||||
- `account_id BIGINT NOT NULL REFERENCES accounts(id)` —
|
||||
каждая транзакция жёстко привязана к одному счёту.
|
||||
- `operation_at TIMESTAMPTZ NOT NULL` — дата и время операции.
|
||||
- `amount_signed BIGINT NOT NULL` — сумма операции в копейках; знак отражает тип движения (приход/расход).
|
||||
@@ -57,31 +97,37 @@ CREATE UNIQUE INDEX ux_accounts_bank_number
|
||||
- `"expense"` — расход;
|
||||
- `"transfer"` — перевод между своими счетами / на другие свои счета.
|
||||
- `fingerprint TEXT NOT NULL` — вычисляемый хэш для обеспечения идемпотентности импорта.
|
||||
- `category_id BIGINT` — ссылка на таблицу категорий (будет определена позже).
|
||||
- `category_id BIGINT REFERENCES categories(id)` — ссылка на категорию; `NULL` для некатегоризированных транзакций.
|
||||
- `is_category_confirmed BOOLEAN NOT NULL DEFAULT FALSE` —
|
||||
признак того, что категория подтверждена пользователем (явно или неявно через правило без `requires_confirmation`).
|
||||
- `comment TEXT` — пользовательский комментарий к транзакции.
|
||||
- `created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` — время создания записи в БД.
|
||||
- `updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` — время последнего обновления записи.
|
||||
|
||||
Ограничения и индексы:
|
||||
|
||||
- Внешний ключ `account_id` ссылается на `accounts(id)` и обеспечивает целостность (нельзя создать транзакцию для несуществующего счёта).
|
||||
- Внешний ключ `account_id` ссылается на `accounts(id)`.
|
||||
- Внешний ключ `category_id` ссылается на `categories(id)`.
|
||||
- Уникальный индекс `(account_id, fingerprint)` обеспечивает идемпотентность: одна и та же операция не может быть загружена дважды.
|
||||
- Опционально — CHECK-ограничение на поле `direction`.
|
||||
- CHECK-ограничение: `direction IN ('income', 'expense', 'transfer')`.
|
||||
|
||||
Рекомендуемый DDL:
|
||||
|
||||
```sql
|
||||
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()
|
||||
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 REFERENCES categories(id),
|
||||
is_category_confirmed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
comment TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX ux_transactions_account_fingerprint
|
||||
@@ -105,7 +151,7 @@ ALTER TABLE transactions
|
||||
- `"starts_with"` — строка начинается с шаблона;
|
||||
- `"regex"` — регулярное выражение (формируется и/или проверяется в коде на основе пользовательского ввода).
|
||||
- MVP: при создании правила принимается только `"contains"`. Расширение до `"starts_with"` | `"regex"` запланировано.
|
||||
- `category_id BIGINT NOT NULL` — ссылка на категорию (таблица категорий будет описана отдельно).
|
||||
- `category_id BIGINT NOT NULL REFERENCES categories(id)` — ссылка на категорию.
|
||||
- `priority INT NOT NULL DEFAULT 0` — приоритет правила; чем выше число, тем раньше правило применяется при конфликте.
|
||||
- `requires_confirmation BOOLEAN NOT NULL DEFAULT FALSE` —
|
||||
если `TRUE`, транзакции, категоризированные этим правилом, получают `is_category_confirmed = FALSE`
|
||||
@@ -127,7 +173,7 @@ CREATE TABLE category_rules (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
pattern TEXT NOT NULL,
|
||||
match_type TEXT NOT NULL,
|
||||
category_id BIGINT NOT NULL,
|
||||
category_id BIGINT NOT NULL REFERENCES categories(id),
|
||||
priority INT NOT NULL DEFAULT 0,
|
||||
requires_confirmation BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
@@ -135,19 +181,46 @@ CREATE TABLE category_rules (
|
||||
);
|
||||
```
|
||||
|
||||
## Таблица `sessions`
|
||||
|
||||
Предназначение: хранение серверных сессий авторизации с поддержкой таймаута по бездействию.
|
||||
|
||||
Структура:
|
||||
|
||||
- `id TEXT PRIMARY KEY` — идентификатор сессии (UUID).
|
||||
- `created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` — время создания сессии.
|
||||
- `last_activity_at TIMESTAMPTZ NOT NULL DEFAULT NOW()` — время последней активности; обновляется при каждом запросе с действительной сессией.
|
||||
- `is_active BOOLEAN NOT NULL DEFAULT TRUE` — флаг активности; устанавливается в `FALSE` при логауте или истечении таймаута.
|
||||
|
||||
Рекомендуемый DDL:
|
||||
|
||||
```sql
|
||||
CREATE TABLE sessions (
|
||||
id TEXT PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
last_activity_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
```
|
||||
|
||||
## Таблица `budgets` (после MVP)
|
||||
|
||||
> **Не входит в MVP.** Таблица запланирована для поддержки бюджетов/лимитов по категориям
|
||||
> на будущих этапах. Подробности см. в `analytics.md`, секция 5.3.
|
||||
|
||||
## Взаимосвязь JSON → БД при импорте
|
||||
|
||||
1. По полям `bank` и `statement.accountNumber` ищется или создаётся запись в `accounts`.
|
||||
Если счёт создан впервые, `alias = NULL`.
|
||||
2. Для каждой транзакции из `transactions`:
|
||||
- преобразуются суммы в копейки (`amountSigned`, `commission` → `BIGINT`);
|
||||
- суммы в JSON 1.0 уже в копейках — записываются как есть;
|
||||
- вычисляется `fingerprint` на основе комбинации полей (например,
|
||||
`accountNumber + operationAt + amountSigned + commission + normalizedDescription`);
|
||||
- определяется `direction`:
|
||||
- `amountSigned > 0` → `"income"`;
|
||||
- `amountSigned < 0` → `"expense"`;
|
||||
- `"transfer"` — определяется по фиксированным ключевым фразам банка в `description`
|
||||
(например, для ВТБ: "Перевод", "между счетами" и т.п.).
|
||||
(например, для ВТБ: "Перевод между своими счетами", "Внутри ВТБ" и т.п.).
|
||||
Если ключевые фразы не сработали — остаётся `"income"` / `"expense"` по знаку суммы;
|
||||
- выполняется попытка вставки в `transactions`;
|
||||
- при срабатывании уникального ограничения `(account_id, fingerprint)` запись считается дубликатом и пропускается;
|
||||
|
||||
Reference in New Issue
Block a user