docs: adds rules and agents specs
This commit is contained in:
49
docs/agents/agent_architect.md
Normal file
49
docs/agents/agent_architect.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Агент: Архитектор SPA семейного бюджета
|
||||
|
||||
## Контекст
|
||||
|
||||
- Это локальное SPA-приложение для семейного бюджета.
|
||||
- Стек: React + TypeScript (FE), Node.js (BE), PostgreSQL (Synology), Git локально.
|
||||
- Формальные требования и контракт описаны в файлах:
|
||||
- `format.md` — формат импорта JSON 1.0
|
||||
- `db.md` — модель БД (accounts, transactions, category_rules)
|
||||
- `category.md` — категории и алиасы счетов
|
||||
- `api_history.md` — API истории операций
|
||||
- `edit_and_rules.md` — редактирование и правила категорий
|
||||
- `analytics.md` — аналитика и бюджеты
|
||||
- `auth.md` — авторизация и сессии
|
||||
|
||||
## Цели агента
|
||||
|
||||
1. Обеспечить целостную архитектуру проекта (папки, модули, naming, структура API).
|
||||
2. Следить, чтобы Backend и Frontend строго следовали спецификациям из `positionX_*.md`.
|
||||
3. Раскладывать работу на этапы: сначала MVP (импорт + история + базовая категоризация), потом аналитика, бюджеты и т.д.
|
||||
4. Давать чёткие задачи Backend- и Frontend-агентам в виде:
|
||||
- какие файлы создать/изменить,
|
||||
- какие эндпоинты/компоненты реализовать,
|
||||
- какие интерфейсы типов (TypeScript) использовать.
|
||||
|
||||
## Обязательные ограничения
|
||||
|
||||
- Нельзя менять схемы JSON/БД/API, описанные в `./docs/backlog/*.md`, без явной инструкции пользователя.
|
||||
- Не использовать SQL-запросы внутри Postgres-ноды n8n (если интеграция с n8n появится позднее).
|
||||
- Приоритет — надёжность, предсказуемость и отсутствие “магии”.
|
||||
|
||||
## Этапы MVP (для планирования задач)
|
||||
|
||||
1. Импорт JSON 1.0:
|
||||
- Эндпоинт для загрузки файла.
|
||||
- Парсинг JSON, валидация, импорт в Postgres с расчётом fingerprint.
|
||||
2. История операций:
|
||||
- `GET /api/transactions` по спецификации.
|
||||
- FE-таблица с фильтрами/сортировкой/пагинацией.
|
||||
3. Редактирование транзакций и правил:
|
||||
- `PUT /api/transactions/{id}`
|
||||
- CRUD для `category_rules`
|
||||
4. Аналитика (Summary, by-category, timeseries).
|
||||
5. Авторизация и сессии (`/api/auth/*`) и защита всех API.
|
||||
|
||||
Архитектор должен:
|
||||
|
||||
- При запуске нового этапа читать соответствующие `*.md` папке `./docs/backlog`.
|
||||
- Синхронизировать контракты типов между BE и FE (например, общие TypeScript-интерфейсы).
|
||||
87
docs/agents/agent_backend.md
Normal file
87
docs/agents/agent_backend.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# Агент: Backend (Node.js + PostgreSQL)
|
||||
|
||||
## Стек и требования
|
||||
|
||||
- Node.js (Express или Fastify — на усмотрение, но держать код компактным).
|
||||
- БД: PostgreSQL (подключение через `pg` или иной официальный драйвер).
|
||||
- Типизация: TypeScript.
|
||||
- Миграции БД — через миграционный инструмент (например, Knex/Drizzle/TypeORM) или собственные SQL-файлы.
|
||||
|
||||
## Обязательные спецификации (читать целиком)
|
||||
|
||||
- `format.md`
|
||||
- `db.md`
|
||||
- `category.md`
|
||||
- `api_history.md`
|
||||
- `edit_and_rules.md`
|
||||
- `analytics.md`
|
||||
- `auth.md`
|
||||
|
||||
## Основные задачи MVP
|
||||
|
||||
1. Структура проекта
|
||||
|
||||
- Создать каркас BE: `src/app.ts`, `src/routes/*`, `src/db/*`, `src/middleware/*`, `src/services/*`.
|
||||
- Реализовать конфигурацию:
|
||||
- переменные окружения (в т.ч. логин/пароль для авторизации),
|
||||
- строки подключения к Postgres.
|
||||
|
||||
1. Миграции БД
|
||||
|
||||
- Реализовать таблицы и поля строго по `db.md`, `category.md`, `edit_and_rules.md`, `analytics.md`.
|
||||
- Включить все описанные CHECK/UNIQUE/FOREIGN KEY/дополнительные поля (`is_category_confirmed`, `comment`, `alias` для accounts, `budgets` и т.д.).
|
||||
|
||||
1. Авторизация и сессии
|
||||
|
||||
- Реализовать эндпоинты:
|
||||
- `POST /api/auth/login`
|
||||
- `POST /api/auth/logout`
|
||||
- `GET /api/auth/me`
|
||||
- Ввести модель сессий с полями:
|
||||
- `id`, `created_at`, `last_activity_at`, `is_active`
|
||||
- Реализовать middleware, которое:
|
||||
- проверяет наличие и валидность session cookie,
|
||||
- проверяет 3-часовой таймаут бездействия,
|
||||
- обнуляет сессию и отдаёт `401` по истечении таймаута.
|
||||
|
||||
1. Импорт выписки
|
||||
|
||||
- Эндпоинт (например) `POST /api/import/statement`:
|
||||
- Принимает JSON строго формата 1.0 (см. `format.md`).
|
||||
- Валидирует структуру и типы.
|
||||
- Находит или создаёт `accounts` по `bank + accountNumber` и заполняет `alias = NULL`.
|
||||
- Для каждой транзакции:
|
||||
- считает `fingerprint`,
|
||||
- определяет `direction`,
|
||||
- вставляет в `transactions` с учётом уникального индекса `(account_id, fingerprint)`,
|
||||
- по умолчанию `is_category_confirmed = FALSE`, `category_id = NULL`.
|
||||
|
||||
1. История операций
|
||||
|
||||
- Реализовать `GET /api/transactions` по `api_history.md`:
|
||||
- все фильтры и сортировки,
|
||||
- пагинация,
|
||||
- поля `accountAlias`, `categoryName`, `isCategoryConfirmed`, `comment`.
|
||||
|
||||
1. Редактирование транзакций и правила категорий
|
||||
|
||||
- Эндпоинт `PUT /api/transactions/{id}`:
|
||||
- Обновляет `category_id`, `comment`, `is_category_confirmed`.
|
||||
- Эндпоинты для `category_rules`:
|
||||
- создание правила на основе входных данных (pattern, match_type, category_id, priority),
|
||||
- обновление/деактивация,
|
||||
- опционально — применение правила к истории (bulk-обновление транзакций с `is_category_confirmed = FALSE`).
|
||||
|
||||
1. Аналитика
|
||||
|
||||
- Реализовать:
|
||||
- `GET /api/analytics/summary`
|
||||
- `GET /api/analytics/by-category`
|
||||
- `GET /api/analytics/timeseries`
|
||||
по агрегатам, описанным в `analytics.md`.
|
||||
|
||||
## Ограничения
|
||||
|
||||
- Не менять контракты API без явной инструкции пользователя.
|
||||
- Все суммы — в копейках (`BIGINT`), все даты/время — `TIMESTAMPTZ`.
|
||||
- Обязательно обрабатывать ошибки валидации и возвращать понятные сообщения.
|
||||
81
docs/agents/agent_frontend.md
Normal file
81
docs/agents/agent_frontend.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Агент: Frontend (React + TypeScript)
|
||||
|
||||
## Стек
|
||||
|
||||
- React + TypeScript.
|
||||
- Router (React Router или аналог) для экранов: Login, История, Аналитика, Настройки/Правила.
|
||||
- Таблицы и графики — на усмотрение (можно использовать готовые компоненты), но без излишней тяжести.
|
||||
|
||||
## Обязательные спецификации
|
||||
|
||||
- Чтение контрактов API и моделей из:
|
||||
- `api_history.md`
|
||||
- `edit_and_rules.md`
|
||||
- `analytics.md`
|
||||
- `auth.md`
|
||||
- а также модели данных и категорий из `db.md`, `category.md`.
|
||||
|
||||
## Основные экраны
|
||||
|
||||
1. Логин
|
||||
|
||||
- Форма `login/password`, запрос `POST /api/auth/login`.
|
||||
- При успехе — переход на Историю.
|
||||
- При `401` на любых защищённых запросах — редирект на Login.
|
||||
|
||||
1. История операций
|
||||
|
||||
- Таблица с колонками:
|
||||
- Дата,
|
||||
- Счёт (alias),
|
||||
- Сумма,
|
||||
- Описание,
|
||||
- Категория,
|
||||
- (иконка/метка неподтверждённой категории),
|
||||
- (иконка комментария).
|
||||
- Фильтры:
|
||||
- период (from/to),
|
||||
- быстрые предустановки: неделя/месяц/год с перелистыванием,
|
||||
- счёт,
|
||||
- тип движения (приход/расход/перевод),
|
||||
- категория,
|
||||
- строка поиска,
|
||||
- быстрый фильтр по сумме,
|
||||
- флаг `только неподтверждённые`.
|
||||
- Пагинация страницами: 10/50/100.
|
||||
|
||||
- Редактирование транзакции (модальное окно):
|
||||
- выбор категории,
|
||||
- поле комментария,
|
||||
- галочка “Создать правило для похожих транзакций в будущем” (по умолчанию включена),
|
||||
- вызов `PUT /api/transactions/{id}` и соответствующего API для правил (если решено вызывать напрямую).
|
||||
|
||||
1. Аналитика
|
||||
|
||||
- Верхний блок выбора периода: неделя/месяц/год/произвольный + стрелки “←/→” (кроме произвольного).
|
||||
- Фильтры:
|
||||
- счёт,
|
||||
- флаг “только подтверждённые”.
|
||||
- Блок 1: сводка (`/api/analytics/summary`).
|
||||
- Блок 2: график времени (`/api/analytics/timeseries`).
|
||||
- Блок 3: диаграмма/таблица по категориям (`/api/analytics/by-category`).
|
||||
|
||||
1. Настройки (минимум)
|
||||
|
||||
- Список счетов с алиасами:
|
||||
- отображение номера счёта и alias,
|
||||
- возможность поменять alias.
|
||||
- Список категорий (только просмотр).
|
||||
- Список правил (базово: просмотр, активность, приоритет, тип; редактирование можно отложить на потом).
|
||||
|
||||
## Поведение с авторизацией
|
||||
|
||||
- При загрузке приложения:
|
||||
- запрос `GET /api/auth/me`, определение, авторизован ли пользователь.
|
||||
- Для всех запросов к `/api/*`:
|
||||
- при `401` — сброс локального состояния и редирект на Login.
|
||||
|
||||
## Ограничения
|
||||
|
||||
- Не менять контракты API, описанные в соответствующих файлах.
|
||||
- Настройка видимых колонок в истории должна быть предусмотрена (хотя бы на уровне структуры состояния).
|
||||
29
docs/agents/agent_rules_analytics.md
Normal file
29
docs/agents/agent_rules_analytics.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Агент: Правила категорий и аналитика
|
||||
|
||||
## Задачи
|
||||
|
||||
1. Помогать Backend-агенту проектировать и оптимизировать запросы:
|
||||
- авто-категоризация по `category_rules`,
|
||||
- агрегаты для `/api/analytics/*`,
|
||||
- применение правил к прошлым транзакциям.
|
||||
|
||||
2. Следить за тем, чтобы:
|
||||
- все суммы считаются в копейках,
|
||||
- учитывался флаг `is_category_confirmed`,
|
||||
- фильтры по периодам/счётам/категориям корректно отражались в SQL.
|
||||
|
||||
3. Подготавливать “чистые” интерфейсы для FE:
|
||||
- структуры ответов уже определены в `api_history.md`, `analytics.md` — не менять.
|
||||
|
||||
## Контекст
|
||||
|
||||
- Читает:
|
||||
- `db.md`,
|
||||
- `api_history.md`,
|
||||
- `edit_and_rules.md`,
|
||||
- `analytics.md`.
|
||||
|
||||
## Ограничения
|
||||
|
||||
- Не менять схемы и API.
|
||||
- Предлагать решения, которые хорошо работают на локальном PostgreSQL.
|
||||
79
docs/agents/agent_statement_converter.md
Normal file
79
docs/agents/agent_statement_converter.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Агент: Конвертер выписок (PDF/XLSX → JSON 1.0)
|
||||
|
||||
## Контекст
|
||||
|
||||
Цель: по входному файлу выписки (PDF, в будущем XLSX) сформировать JSON строго
|
||||
по схеме 1.0, описанной в `docs/backlog/format.md`, для дальнейшего импорта
|
||||
в SPA через backend-эндпоинт.
|
||||
|
||||
Формат JSON 1.0:
|
||||
|
||||
- `schemaVersion = "1.0"`
|
||||
- `bank = "VTB"` (на старте)
|
||||
- `statement`:
|
||||
- `accountNumber: string`
|
||||
- `currency: string`
|
||||
- `openingBalance: integer` (копейки)
|
||||
- `closingBalance: integer` (копейки)
|
||||
- `exportedAt: string` (ISO 8601 + TZ)
|
||||
- `transactions[]`:
|
||||
- `operationAt: string` (ISO 8601 + TZ)
|
||||
- `amountSigned: integer` (копейки, >0 приход, <0 расход)
|
||||
- `commission: integer` (копейки)
|
||||
- `description: string`
|
||||
|
||||
## Задачи агента
|
||||
|
||||
1. Конвертация PDF → сырые строки
|
||||
|
||||
- Использовать доступный инструмент (локальный ИИ/LLM API или библиотеку),
|
||||
чтобы преобразовать PDF-выписку в структурированный текст/табличное представление:
|
||||
- заголовочная часть (счёт, валюта, баланс на начало/конец, дата выгрузки),
|
||||
- таблица операций (дата/время, сумма, комиссия, описание и т.п.).
|
||||
- Корректно обрабатывать переносы строк в описании операции.
|
||||
|
||||
1. Маппинг полей в JSON 1.0
|
||||
|
||||
- Извлечь из заголовка:
|
||||
- `accountNumber`,
|
||||
- `currency`,
|
||||
- `openingBalance`, `closingBalance`,
|
||||
- `exportedAt` (если нет — использовать время генерации конвертера как суррогат и явно помечать это в комментариях/логах).
|
||||
- Для каждой строки операции:
|
||||
- собрать `operationAt` (дата+время) и добавить таймзону `+03:00`, если она не указана явно;
|
||||
- преобразовать сумму в `amountSigned` (в копейках, с учётом знака прихода/расхода);
|
||||
- выделить `commission` (если в выписке отдельная колонка, иначе `0`);
|
||||
- сформировать `description` как максимально близкое к тексту из выписки поле.
|
||||
|
||||
1. Нормализация сумм
|
||||
|
||||
- Все суммы, которые приходят в формате `12345.67`, должны быть преобразованы в целое число копеек:
|
||||
- `12345.67 → 1234567`.
|
||||
- Важно избегать ошибок округления (использовать работу со строками, а не float).
|
||||
|
||||
1. Проверка целостности (по возможности)
|
||||
|
||||
- При наличии `openingBalance` и `closingBalance`:
|
||||
- проверить, что `openingBalance + Σ(amountSigned) == closingBalance` (с допустимой погрешностью, если есть комиссии/особые операции);
|
||||
- если проверка не проходит, вернуть предупреждение вместе с JSON (или отдельный отчёт).
|
||||
|
||||
1. Выходной результат
|
||||
|
||||
- Агент должен возвращать один JSON-объект строго по схеме 1.0.
|
||||
- Дополнительно может возвращаться диагностическая информация (лог), но она должна быть отделена от финального JSON.
|
||||
|
||||
## Взаимодействие с локальным ИИ
|
||||
|
||||
- Допускается использование API к локальной LLM/модели для:
|
||||
- структурирования текстовых фрагментов из PDF (определение границ колонок, восстановление строк операций),
|
||||
- распознавания сложных описаний, если PDF в виде "сырого текста".
|
||||
- Важно:
|
||||
- всегда явно проверять и постпроцессить результат LLM, чтобы привести его к строгому формату 1.0;
|
||||
- не полагаться на LLM для вычислений сумм — только для парсинга структуры.
|
||||
|
||||
## Ограничения
|
||||
|
||||
- Агент **не взаимодействует** напрямую с БД или HTTP API SPA:
|
||||
- его задача — чистый конвертер файла → JSON.
|
||||
- Нельзя менять схему JSON 1.0 без обновления `docs/backlog/format.md`.
|
||||
- Конвертер должен корректно работать с русским языком и форматами дат/сумм из российских банковских выписок.
|
||||
181
docs/backlog/analytics.md
Normal file
181
docs/backlog/analytics.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Позиция 5: Аналитика расходов и доходов
|
||||
|
||||
## 5.1. Периоды и навигация
|
||||
|
||||
Поддерживаются следующие режимы выбора периода:
|
||||
|
||||
- Быстрый выбор:
|
||||
- Неделя — от начала текущей недели (с понедельника) до текущей даты.
|
||||
- Месяц — от 1-го числа текущего месяца до текущей даты.
|
||||
- Год — от 1 января текущего года до текущей даты.
|
||||
- Произвольный диапазон:
|
||||
- Пользователь задаёт `from` и `to` вручную.
|
||||
|
||||
Навигация:
|
||||
|
||||
- Для режимов Неделя/Месяц/Год доступны кнопки переключения периода:
|
||||
- "предыдущая" / "следующая" неделя;
|
||||
- "предыдущий" / "следующий" месяц;
|
||||
- "предыдущий" / "следующий" год.
|
||||
- При произвольном диапазоне навигация стрелками не используется (только ручная смена дат).
|
||||
|
||||
Фильтры, общие для всех отчётов:
|
||||
|
||||
- `accountId` (или несколько) — фильтр по счёту/счетам.
|
||||
- Флаг "только подтверждённые категории" (`onlyConfirmed`) — учитывать только транзакции с `is_category_confirmed = TRUE`.
|
||||
|
||||
## 5.2. Основные аналитические блоки
|
||||
|
||||
### 1) Сводка периода (Summary)
|
||||
|
||||
Цель: дать быстрый обзор финансов за выбранный период.
|
||||
|
||||
Показатели:
|
||||
|
||||
- Общий расход (`totalExpense`).
|
||||
- Общий доход (`totalIncome`).
|
||||
- Чистый результат (`net = totalIncome - totalExpense`).
|
||||
- Топ-3–5 категорий по расходам (сумма, доля).
|
||||
|
||||
Реализуется через эндпоинт `GET /api/analytics/summary`.
|
||||
|
||||
Параметры:
|
||||
|
||||
- `from`, `to` — границы периода;
|
||||
- `accountId?` — фильтр по счёту (опционально);
|
||||
- `onlyConfirmed?` — учитывать только подтверждённые категории.
|
||||
|
||||
Ответ (идея):
|
||||
|
||||
```json
|
||||
{
|
||||
"totalExpense": 12345600,
|
||||
"totalIncome": 20000000,
|
||||
"net": 7654400,
|
||||
"topCategories": [
|
||||
{ "categoryId": 1, "categoryName": "Продукты", "amount": 4500000, "share": 0.36 },
|
||||
{ "categoryId": 2, "categoryName": "ЖКХ", "amount": 2500000, "share": 0.20 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2) Расходы по категориям
|
||||
|
||||
Цель: понять структуру расходов, какие категории занимают основную долю.
|
||||
|
||||
Реализуется через эндпоинт `GET /api/analytics/by-category`.
|
||||
|
||||
Параметры:
|
||||
|
||||
- `from`, `to`;
|
||||
- `accountId?`;
|
||||
- `onlyConfirmed?`.
|
||||
|
||||
Ответ (идея):
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"categoryId": 1,
|
||||
"categoryName": "Продукты",
|
||||
"amount": 4500000,
|
||||
"txCount": 32,
|
||||
"share": 0.36
|
||||
},
|
||||
{
|
||||
"categoryId": 2,
|
||||
"categoryName": "ЖКХ",
|
||||
"amount": 2500000,
|
||||
"txCount": 5,
|
||||
"share": 0.20
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Использование на фронтенде:
|
||||
|
||||
- круговая диаграмма или bar-chart по категориям;
|
||||
- таблица под диаграммой с суммами, долями и количеством операций.
|
||||
|
||||
### 3) Динамика во времени
|
||||
|
||||
Цель: увидеть изменение доходов/расходов по времени.
|
||||
|
||||
Реализуется через эндпоинт `GET /api/analytics/timeseries`.
|
||||
|
||||
Параметры:
|
||||
|
||||
- `from`, `to`;
|
||||
- `accountId?`;
|
||||
- `categoryId?` — при необходимости анализировать конкретную категорию;
|
||||
- `onlyConfirmed?`;
|
||||
- `granularity=day|week|month` — шаг агрегации.
|
||||
|
||||
Ответ (идея):
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"periodStart": "2026-02-01",
|
||||
"periodEnd": "2026-02-07",
|
||||
"expenseAmount": 3500000,
|
||||
"incomeAmount": 0
|
||||
},
|
||||
{
|
||||
"periodStart": "2026-02-08",
|
||||
"periodEnd": "2026-02-14",
|
||||
"expenseAmount": 4200000,
|
||||
"incomeAmount": 0
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Использование на фронтенде:
|
||||
|
||||
- линейный или столбиковый график: расходы и (опционально) доходы по периодам.
|
||||
|
||||
## 5.3. Задел под бюджеты (лимиты)
|
||||
|
||||
Для поддержки бюджетов по категориям на будущих этапах вводится сущность `budgets`.
|
||||
|
||||
### Таблица `budgets` (идея)
|
||||
|
||||
Поля:
|
||||
|
||||
- `id BIGSERIAL PRIMARY KEY` — идентификатор бюджета.
|
||||
- `category_id BIGINT` — ссылка на категорию (`categories.id`) или `NULL` для общего бюджета по всем категориям.
|
||||
- `period_type TEXT NOT NULL` — тип периода:
|
||||
- `"month"` — месячный бюджет;
|
||||
- `"year"` — годовой бюджет.
|
||||
- `year INT NOT NULL` — год действия бюджета.
|
||||
- `month INT` — номер месяца (1–12), используется если `period_type = 'month'`.
|
||||
- `amount_limit BIGINT NOT NULL` — лимит по расходам в копейках.
|
||||
- `is_active BOOLEAN NOT NULL DEFAULT TRUE` — активен ли бюджет.
|
||||
|
||||
Пример DDL (базовый):
|
||||
|
||||
```sql
|
||||
CREATE TABLE budgets (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
category_id BIGINT,
|
||||
period_type TEXT NOT NULL,
|
||||
year INT NOT NULL,
|
||||
month INT,
|
||||
amount_limit BIGINT NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||
);
|
||||
|
||||
ALTER TABLE budgets
|
||||
ADD CONSTRAINT chk_budgets_period_type
|
||||
CHECK (period_type IN ('month', 'year'));
|
||||
```
|
||||
|
||||
Использование в аналитике:
|
||||
|
||||
- Для выбранного периода (например, текущий месяц) по каждой категории можно:
|
||||
- найти соответствующий бюджет (если задан);
|
||||
- посчитать фактический расход за период;
|
||||
- показать: потрачено / лимит / % выполнения / остаток.
|
||||
|
||||
Это позволит в будущем реализовать экран "Бюджеты", а также подсветку категорий,
|
||||
где расходы близки к лимиту или превышают его.
|
||||
98
docs/backlog/api_history.md
Normal file
98
docs/backlog/api_history.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Позиция 3: API истории операций (GET /api/transactions)
|
||||
|
||||
## Назначение
|
||||
|
||||
Эндпоинт `/api/transactions` предоставляет список транзакций с учётом фильтров, сортировки и пагинации
|
||||
для отображения в SPA (таблица "История операций").
|
||||
|
||||
## Метод и URL
|
||||
|
||||
- Метод: `GET`
|
||||
- URL: `/api/transactions`
|
||||
|
||||
## Параметры запроса (query)
|
||||
|
||||
Все параметры опциональны, если не указано иное.
|
||||
|
||||
- `accountId: number` — идентификатор счёта (`accounts.id`). Если не передан, выбираются транзакции по всем счетам.
|
||||
- `from: string` — дата начала периода (включительно) в формате `YYYY-MM-DD`.
|
||||
- `to: string` — дата конца периода (включительно) в формате `YYYY-MM-DD`.
|
||||
- `direction: string` — направления движения, одно или несколько значений через запятую:
|
||||
- `income` — приход;
|
||||
- `expense` — расход;
|
||||
- `transfer` — переводы между счетами;
|
||||
- `internal` — внутренние движения (если будут использоваться).
|
||||
Пример: `direction=income,expense`.
|
||||
- `categoryId: number` — идентификатор категории (`categories.id`).
|
||||
- `search: string` — строка поиска по полю `description` (поиск по подстроке, регистронезависимый).
|
||||
- `amountMin: number` — минимальная сумма операции в копейках (`BIGINT`).
|
||||
- `amountMax: number` — максимальная сумма операции в копейках (`BIGINT`).
|
||||
- `onlyUnconfirmed: boolean` — если `true`, возвращаются только транзакции с неподтверждённой категорией (`is_category_confirmed = FALSE`).
|
||||
- `sortBy: string` — поле сортировки:
|
||||
- `date` — сортировка по `operation_at`;
|
||||
- `amount` — сортировка по `amount_signed`.
|
||||
- `sortOrder: string` — порядок сортировки: `asc` или `desc`.
|
||||
- `page: number` — номер страницы, начиная с `1`. По умолчанию `1`.
|
||||
- `pageSize: number` — размер страницы (количество записей на странице). Допустимые значения: `10`, `50`, `100`. По умолчанию `50`.
|
||||
|
||||
## Структура ответа
|
||||
|
||||
Ответ содержит массив транзакций и данные пагинации.
|
||||
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 123,
|
||||
"operationAt": "2026-02-26T14:06:57+03:00",
|
||||
"accountId": 1,
|
||||
"accountAlias": "Текущий",
|
||||
"amountSigned": -50000,
|
||||
"commission": 0,
|
||||
"description": "Оплата товаров и услуг. OZON.RU. по карте *2249",
|
||||
"direction": "expense",
|
||||
"categoryId": 5,
|
||||
"categoryName": "Дом",
|
||||
"isCategoryConfirmed": false,
|
||||
"comment": "еда"
|
||||
}
|
||||
],
|
||||
"page": 1,
|
||||
"pageSize": 50,
|
||||
"totalItems": 874,
|
||||
"totalPages": 18
|
||||
}
|
||||
```
|
||||
|
||||
Поля элементов `items`:
|
||||
|
||||
- `id` — идентификатор транзакции (`transactions.id`).
|
||||
- `operationAt` — дата и время операции в формате ISO 8601 (из `transactions.operation_at`).
|
||||
- `accountId` — идентификатор счёта (`accounts.id`).
|
||||
- `accountAlias` — алиас счёта (`accounts.alias`). Если алиас не задан, можно возвращать `null` или сгенерированное значение.
|
||||
- `amountSigned` — сумма операции в копейках.
|
||||
- `commission` — комиссия по операции в копейках.
|
||||
- `description` — описание операции из банка.
|
||||
- `direction` — направление транзакции (`income` / `expense` / `transfer` / `internal`).
|
||||
- `categoryId` — идентификатор категории (`categories.id`), может быть `null`.
|
||||
- `categoryName` — имя категории (`categories.name`), может быть `null`.
|
||||
- `isCategoryConfirmed` — признак того, что категория подтверждена пользователем.
|
||||
- `comment` — пользовательский комментарий к транзакции.
|
||||
|
||||
Поля пагинации:
|
||||
|
||||
- `page` — текущая страница.
|
||||
- `pageSize` — размер страницы.
|
||||
- `totalItems` — общее количество транзакций, удовлетворяющих фильтрам.
|
||||
- `totalPages` — общее количество страниц при заданном `pageSize`.
|
||||
|
||||
## Замечания по реализации
|
||||
|
||||
- Справочные данные (`accountAlias`, `categoryName`) могут возвращаться либо сразу в ответе,
|
||||
либо SPA может подставлять их по `accountId` и `categoryId` из кэшированных справочников,
|
||||
полученных через отдельные эндпоинты (`/api/accounts`, `/api/categories`).
|
||||
- Фильтрация по `search` выполняется по полю `description` (ILIKE `%search%`).
|
||||
- Фильтры по сумме (`amountMin`, `amountMax`) применяются к `amount_signed`.
|
||||
- Параметр `onlyUnconfirmed=true` добавляет условие `is_category_confirmed = FALSE`.
|
||||
- Комбинация `sortBy=date&sortOrder=desc` используется как значение по умолчанию
|
||||
(последние операции сверху).
|
||||
79
docs/backlog/auth.md
Normal file
79
docs/backlog/auth.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Позиция 6: Авторизация и сессии
|
||||
|
||||
## 6.1. Общая модель доступа
|
||||
|
||||
- Приложение предназначено для использования ограниченным числом пользователей (2 человека).
|
||||
- Регистрации пользователей через интерфейс нет.
|
||||
- Учётные данные (логин и пароль) задаются через переменные окружения (`ENV`),
|
||||
например:
|
||||
- `APP_USER_LOGIN`
|
||||
- `APP_USER_PASSWORD`
|
||||
- Ролевой модели нет: все авторизованные пользователи имеют полный доступ
|
||||
ко всем функциям SPA (просмотр, импорт, редактирование, аналитика, управление правилами).
|
||||
|
||||
## 6.2. Авторизация
|
||||
|
||||
Рекомендуемая схема: сессионная авторизация с backend-сессиями и cookie.
|
||||
|
||||
### Эндпоинты
|
||||
|
||||
1. `POST /api/auth/login`
|
||||
- Вход: JSON с полями `login` и `password`.
|
||||
- Backend сравнивает полученные значения с `ENV` (`APP_USER_LOGIN`, `APP_USER_PASSWORD`).
|
||||
- При успешной аутентификации создаётся сессия и клиенту выдаётся session cookie
|
||||
(например, `sid`), привязанная к записи в БД или in-memory хранилищу.
|
||||
|
||||
2. `POST /api/auth/logout`
|
||||
- Инвалидирует текущую сессию (удаляет/помечает как неактивную) и очищает cookie.
|
||||
|
||||
3. `GET /api/auth/me`
|
||||
- Возвращает информацию о том, авторизован ли пользователь (например, `200` + базовый профиль
|
||||
или `401`, если сессия недействительна).
|
||||
|
||||
Все защищённые эндпоинты (`/api/transactions`, `/api/analytics/*`, импорт выписок,
|
||||
управление правилами и т.д.) работают **только** при наличии действующей сессии.
|
||||
|
||||
## 6.3. Сессии и таймаут по бездействию
|
||||
|
||||
Требования:
|
||||
|
||||
- Авто-логаут при длительном бездействии (жёсткий таймаут).
|
||||
- Таймаут бездействия: **3 часа**.
|
||||
|
||||
Реализация:
|
||||
|
||||
- В backend вводится сущность "сессия" (в БД или в памяти) с полями:
|
||||
- `id` — идентификатор сессии;
|
||||
- `created_at` — время создания сессии;
|
||||
- `last_activity_at` — время последней активности по этой сессии;
|
||||
- `is_active` — флаг активности.
|
||||
|
||||
- При каждом запросе с действительным `sid` backend:
|
||||
1. Проверяет, не истёк ли таймаут:
|
||||
- если `now - last_activity_at > 3 часа` — сессия инвалидируется (`is_active = FALSE`),
|
||||
возвращается `401 Unauthorized` и на фронтенде пользователь переводится на экран логина;
|
||||
2. Если таймаут не истёк — обновляет `last_activity_at` текущим временем.
|
||||
|
||||
- Таймаут **жёсткий**:
|
||||
- нет "длинных" сессий;
|
||||
- нет опции "запомнить меня" — после 3 часов полного бездействия требуется повторный логин.
|
||||
|
||||
## 6.4. Интеграция с SPA
|
||||
|
||||
- При открытии приложения SPA выполняет запрос `GET /api/auth/me`:
|
||||
- если ответ `200` — пользователь остаётся в приложении;
|
||||
- если `401` — показывается форма логина.
|
||||
|
||||
- При логине (`POST /api/auth/login`):
|
||||
- при успехе SPA сохраняет состояние "авторизован" и переходит к основному интерфейсу.
|
||||
|
||||
- При получении `401` от любого защищённого API:
|
||||
- SPA сбрасывает локальное состояние пользователя,
|
||||
- перенаправляет на экран логина.
|
||||
|
||||
## 6.5. Связь с другими позициями
|
||||
|
||||
- Все ранее описанные эндпоинты (`/api/transactions`, `/api/analytics/*`, импорт JSON,
|
||||
управление категориями и правилами) доступны только в рамках действующей сессии.
|
||||
- Так как ролевой модели нет, дополнительные ограничения доступа не требуются:
|
||||
оба пользователя имеют одинаковые права.
|
||||
98
docs/backlog/category.md
Normal file
98
docs/backlog/category.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Категории расходов и доходов (MVP)
|
||||
|
||||
## Базовый список категорий
|
||||
|
||||
Используются следующие категории для классификации транзакций:
|
||||
|
||||
- Продукты
|
||||
- Авто
|
||||
- Здоровье
|
||||
- Арчи
|
||||
- ЖКХ
|
||||
- Дом
|
||||
- Проезд
|
||||
- Одежда
|
||||
- Химия
|
||||
- Косметика
|
||||
- Инвестиции
|
||||
- Развлечения
|
||||
- Общепит
|
||||
- Штрафы
|
||||
- Налоги
|
||||
- Подписки
|
||||
- Перевод
|
||||
- Наличные
|
||||
- Подарки
|
||||
- Спорт
|
||||
- Отпуск
|
||||
- Техника
|
||||
- Поступления
|
||||
|
||||
Особенности:
|
||||
|
||||
- Категории на данном этапе одноуровневые (без подкатегорий).
|
||||
- Категории используются как для фильтрации и аналитики, так и для правил авто-категоризации.
|
||||
- Категория `Перевод` используется для переводов (в т.ч. между своими счетами и на другие счета).
|
||||
- Категория `Инвестиции` предназначена для операций, связанных с брокерским счётом/ценными бумагами.
|
||||
- Категория `Поступления` используется для всех видов доходов (зарплата, кешбэк и др.), пока без детализации.
|
||||
|
||||
## Таблица `categories` (PostgreSQL)
|
||||
|
||||
Для хранения категорий используется отдельная таблица `categories`.
|
||||
|
||||
Рекомендуемая структура:
|
||||
|
||||
- `id BIGSERIAL PRIMARY KEY` — идентификатор категории.
|
||||
- `name TEXT NOT NULL` — отображаемое имя категории (из списка выше).
|
||||
- `type TEXT NOT NULL` — тип категории:
|
||||
- `"expense"` — расходная категория;
|
||||
- `"income"` — доходная категория;
|
||||
- `"transfer"` — категории для переводов/движений между собственными счетами.
|
||||
- `is_active BOOLEAN NOT NULL DEFAULT TRUE` — используется ли категория.
|
||||
- `is_category_confirmed BOOLEAN NOT NULL DEFAULT FALSE` — подтверждена ли категория пользователем.
|
||||
- `comment TEXT` — пользовательский комментарий (например, по маркетплейсам).
|
||||
|
||||
Рекомендуемый 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'));
|
||||
|
||||
ALTER TABLE transactions
|
||||
ADD COLUMN is_category_confirmed BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
ALTER TABLE transactions
|
||||
ADD COLUMN comment TEXT;
|
||||
```
|
||||
|
||||
Привязка к транзакциям:
|
||||
|
||||
- В таблице `transactions` поле `category_id` является внешним ключом на `categories(id)`.
|
||||
- При авто- или ручной категоризации транзакции записывается соответствующий `category_id`.
|
||||
|
||||
## Алиасы счетов
|
||||
|
||||
- При первом появлении нового счёта (новая комбинация `bank + account_number`) после загрузки выписки
|
||||
SPA предлагает пользователю ввести человекочитаемый алиас: например, `"Текущий"`, `"Накопительный"`, `"Брокерский"`.
|
||||
- Алиас хранится в таблице `accounts` в отдельном поле, например `alias TEXT`.
|
||||
- В таблице истории операций на фронтенде вместо номера счёта показывается `alias` (с возможностью при наведении/по клику увидеть полный номер).
|
||||
|
||||
Изменённая таблица `accounts` (добавлен алиас):
|
||||
|
||||
```sql
|
||||
ALTER TABLE accounts
|
||||
ADD COLUMN alias TEXT;
|
||||
```
|
||||
|
||||
При создании нового счёта:
|
||||
|
||||
- Если алиас задан пользователем — сохраняем его в `accounts.alias`.
|
||||
- Если пользователь пропустил ввод алиаса — можно задать временный (`"Счёт 1"`, `"Счёт 2"`) и предложить изменить его позже в настройках.
|
||||
147
docs/backlog/db.md
Normal file
147
docs/backlog/db.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Модель БД (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:
|
||||
|
||||
```sql
|
||||
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:
|
||||
|
||||
```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()
|
||||
);
|
||||
|
||||
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:
|
||||
|
||||
```sql
|
||||
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`, `commission` → `BIGINT`);
|
||||
- вычисляется `fingerprint` на основе комбинации полей (например,
|
||||
`accountNumber + operationAt + amountSigned + commission + normalizedDescription`);
|
||||
- определяется `direction` по знаку суммы и/или шаблонам текста (приход/расход/перевод);
|
||||
- выполняется попытка вставки в `transactions`;
|
||||
- при срабатывании уникального ограничения `(account_id, fingerprint)` запись считается дубликатом и пропускается.
|
||||
- При импорте новых транзакций is_category_confirmed всегда = FALSE.
|
||||
- При автокатегоризации обычными правилами, если правило “жёсткое” (не маркетплейс) — TRUE, для маркетплейсов — всегда FALSE.
|
||||
|
||||
3. Категория (`category_id`) пока может оставаться `NULL` и заполняться на следующих этапах
|
||||
(правила, ИИ-агент, ручное редактирование в SPA).
|
||||
141
docs/backlog/edit_and_rules.md
Normal file
141
docs/backlog/edit_and_rules.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Позиция 4: Редактирование транзакций и правила категорий
|
||||
|
||||
## Цели
|
||||
|
||||
- Минимизировать ручной труд при категоризации транзакций.
|
||||
- Обеспечить возможность "дообучения" системы за счёт действий пользователя.
|
||||
- Отдельно обработать сложные случаи (маркетплейсы), где по описанию нельзя однозначно определить категорию.
|
||||
|
||||
## Редактирование транзакций (SPA)
|
||||
|
||||
При открытии истории операций пользователь может открыть карточку редактирования транзакции.
|
||||
|
||||
Доступные действия для одной транзакции:
|
||||
|
||||
- Изменить категорию (выбор из списка категорий `categories`).
|
||||
- При необходимости добавить/изменить пользовательский комментарий (`comment`).
|
||||
- Включить/отключить опцию "Создать правило для похожих транзакций в будущем".
|
||||
|
||||
Ограничения:
|
||||
|
||||
- Описание операции (`description`) **не редактируется** в SPA, чтобы сохранять оригинальные данные из банка.
|
||||
|
||||
Поле `comment` используется только для заметок пользователя и не влияет напрямую на
|
||||
автоматические правила (на старте).
|
||||
|
||||
## Поля в таблице `transactions`
|
||||
|
||||
К ранее описанной структуре добавляются поля:
|
||||
|
||||
- `is_category_confirmed BOOLEAN NOT NULL DEFAULT FALSE` —
|
||||
признак того, что текущая категория транзакции подтверждена пользователем
|
||||
(явно или неявно).
|
||||
- `comment TEXT` — пользовательский комментарий к транзакции.
|
||||
|
||||
Рекомендуемый DDL-дельта:
|
||||
|
||||
```sql
|
||||
ALTER TABLE transactions
|
||||
ADD COLUMN is_category_confirmed BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
ALTER TABLE transactions
|
||||
ADD COLUMN comment TEXT;
|
||||
```
|
||||
|
||||
## Поведение при редактировании транзакции
|
||||
|
||||
При сохранении изменений в SPA backend выполняет:
|
||||
|
||||
1. Обновление транзакции:
|
||||
- устанавливается новое значение `category_id`;
|
||||
- при изменении комментария сохраняется `comment`;
|
||||
- поле `is_category_confirmed` устанавливается в `TRUE`.
|
||||
|
||||
2. Если опция "Создать правило для похожих транзакций в будущем" включена (по умолчанию включена):
|
||||
- создаётся новая запись в `category_rules` или обновляется существующая.
|
||||
|
||||
## Создание правил из транзакций
|
||||
|
||||
В карточке редактирования транзакции отображается блок "Правило для будущих операций":
|
||||
|
||||
- Галочка (переключатель):
|
||||
- по умолчанию **включена**;
|
||||
- отвечает за то, будет ли на основе текущей транзакции создано/обновлено правило.
|
||||
|
||||
- Поле "ключевая строка/шаблон" (pattern):
|
||||
- заполняется автоматически (например, ключевым словом из `description`),
|
||||
но пользователь может его скорректировать;
|
||||
- сохраняется в `category_rules.pattern`.
|
||||
|
||||
- Тип совпадения (match_type): на старте можно использовать только `"contains"`.
|
||||
- В дальнейшем допускается расширение до `"starts_with"` и `"regex"`.
|
||||
|
||||
При сохранении:
|
||||
|
||||
- создаётся правило в `category_rules` с полями:
|
||||
- `pattern` — строка из формы;
|
||||
- `match_type` — тип (на старте `"contains"`);
|
||||
- `category_id` — выбранная категория;
|
||||
- `priority` — например, по умолчанию 100 для правил, созданных пользователем;
|
||||
- `is_active = TRUE`.
|
||||
|
||||
Логика приоритета:
|
||||
|
||||
- Если для одной транзакции подходит несколько правил, выбирается правило
|
||||
с максимальным значением `priority`.
|
||||
|
||||
## Особый случай: маркетплейсы
|
||||
|
||||
Проблема: по описанию вида "OZON", "WILDBERRIES" и т.п. невозможно
|
||||
однозначно определить категорию (там могут быть любые товары).
|
||||
|
||||
Стратегия (MVP):
|
||||
|
||||
- Для маркетплейсов создаются специальные правила, которые:
|
||||
- могут присваивать "предполагаемую" категорию (например, "Дом" или
|
||||
любую другую, выбранную пользователем);
|
||||
- при этом все транзакции, закатегоризированные таким правилом,
|
||||
помечаются как `is_category_confirmed = FALSE`.
|
||||
|
||||
Результат:
|
||||
|
||||
- В истории операций такие транзакции визуально подсвечиваются
|
||||
как требующие внимания пользователя.
|
||||
- Пользователь может:
|
||||
- подтвердить категорию (в этом случае `is_category_confirmed` становится `TRUE`);
|
||||
- изменить категорию и при желании создать новое правило.
|
||||
|
||||
Дополнительно:
|
||||
|
||||
- Для маркетплейсов особенно полезно поле `comment`, куда пользователь может
|
||||
записать, что конкретно было куплено (например, "корм для Арчи", "мебель", "одежда").
|
||||
|
||||
## Применение правил к прошлым транзакциям
|
||||
|
||||
В интерфейсе управления правилами (отдельный раздел SPA) для каждого правила
|
||||
может быть доступна опция:
|
||||
|
||||
- "Применить правило к прошлым транзакциям".
|
||||
|
||||
При активации этой опции backend:
|
||||
|
||||
- находит все транзакции, подходящие под данное правило;
|
||||
- проставляет им `category_id`;
|
||||
- устанавливает `is_category_confirmed = FALSE` (категория считается предварительной).
|
||||
|
||||
Пользователь может затем:
|
||||
|
||||
- отфильтровать операции по `onlyUnconfirmed = true` через `/api/transactions`;
|
||||
- просмотреть и при необходимости скорректировать категории;
|
||||
- после ручного подтверждения транзакции становятся `is_category_confirmed = TRUE`.
|
||||
|
||||
## Связь с API `/api/transactions`
|
||||
|
||||
- Эндпоинт `/api/transactions` возвращает поля `isCategoryConfirmed` и `comment`,
|
||||
что позволяет:
|
||||
- отображать неподтверждённые категории отдельно (подсветка в UI);
|
||||
- предоставлять фильтр "Только неподтверждённые" (`onlyUnconfirmed=true`).
|
||||
|
||||
- При редактировании транзакции через отдельный эндпоинт (например, `PUT /api/transactions/{id}`):
|
||||
- передаются новые значения `categoryId` и/или `comment`;
|
||||
- флаг `isCategoryConfirmed` устанавливается в `true`.
|
||||
82
docs/backlog/format.md
Normal file
82
docs/backlog/format.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Формат импорта выписки (JSON 1.0)
|
||||
|
||||
## Общие принципы
|
||||
|
||||
- На вход SPA принимает файл в формате JSON фиксированной схемы версии `1.0`.
|
||||
- Один JSON-файл = выписка по одному счёту одного банка за произвольный период.
|
||||
- Все суммы хранятся в минорных единицах (копейки) как целые числа.
|
||||
- Дата и время операции передаются в формате ISO 8601 с таймзоной (например, `2026-02-26T14:06:57+03:00`).
|
||||
|
||||
## Структура JSON 1.0
|
||||
|
||||
Корневые поля:
|
||||
|
||||
- `schemaVersion: "1.0"` — версия схемы обменного формата.
|
||||
- `bank: string` — идентификатор банка (например, `"VTB"`).
|
||||
- `statement: object` — заголовок выписки.
|
||||
- `transactions: array` — массив операций.
|
||||
|
||||
### Объект `statement`
|
||||
|
||||
Обязательные поля:
|
||||
|
||||
- `accountNumber: string` — номер счёта в банке (как в выписке). Допускается полный номер.
|
||||
- `currency: string` — код валюты счёта (например, `"RUB"`).
|
||||
- `openingBalance: integer` — баланс на начало периода в копейках.
|
||||
- `closingBalance: integer` — баланс на конец периода в копейках.
|
||||
- `exportedAt: string` — дата и время формирования выписки в формате ISO 8601 с таймзоной.
|
||||
|
||||
Особенности:
|
||||
|
||||
- Период выписки (даты "с" и "по") в JSON не передаётся, так как каждая транзакция содержит свой `operationAt`.
|
||||
- Балансы `openingBalance` и `closingBalance` используются для первичной и периодической сверки остатков, а также при первичной загрузке выписки в пустую БД.
|
||||
|
||||
### Массив `transactions`
|
||||
|
||||
Каждый элемент массива описывает одну транзакцию и имеет следующую структуру:
|
||||
|
||||
Обязательные поля:
|
||||
|
||||
- `operationAt: string` — дата и время операции в формате ISO 8601 с таймзоной.
|
||||
- `amountSigned: integer` — сумма операции в копейках:
|
||||
- `> 0` — приход;
|
||||
- `< 0` — расход.
|
||||
- `commission: integer` — комиссия по операции в копейках (всегда передаётся, даже если `0`).
|
||||
- `description: string` — текстовое описание операции из выписки банка (как есть).
|
||||
|
||||
Особенности:
|
||||
|
||||
- Никакие ID транзакций банка не используются, так как они отсутствуют в выписке.
|
||||
- Поле `accountNumber` внутри транзакции не дублируется — связь идёт через `statement.accountNumber`.
|
||||
- Поле `fingerprint` в JSON не передаётся — оно будет вычисляться на стороне backend при импорте и храниться только в БД.
|
||||
|
||||
## Идемпотентность импорта
|
||||
|
||||
- Для каждой транзакции backend вычисляет `fingerprint` (например, SHA-256 от конкатенации:
|
||||
`accountNumber + operationAt + amountSigned + commission + normalizedDescription`).
|
||||
- В БД вводится уникальное ограничение по паре `(account_id, fingerprint)`,
|
||||
что позволяет безопасно загружать один и тот же файл (или пересекающиеся по периоду файлы) без появления дублей.
|
||||
|
||||
## Пример JSON 1.0
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": "1.0",
|
||||
"bank": "VTB",
|
||||
"statement": {
|
||||
"accountNumber": "40817810825104025611",
|
||||
"currency": "RUB",
|
||||
"openingBalance": 4256167,
|
||||
"closingBalance": 8845938,
|
||||
"exportedAt": "2026-02-27T13:23:00+03:00"
|
||||
},
|
||||
"transactions": [
|
||||
{
|
||||
"operationAt": "2026-02-26T14:06:57+03:00",
|
||||
"amountSigned": -50000,
|
||||
"commission": 0,
|
||||
"description": "Оплата товаров и услуг. PAVELETSKAYA. по карте *8214"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user