256 lines
12 KiB
Markdown
256 lines
12 KiB
Markdown
# Правила категоризации (CRUD /api/category-rules)
|
||
|
||
## Назначение
|
||
|
||
Набор эндпоинтов для управления правилами автоматической категоризации транзакций.
|
||
Правила глобальные — без привязки к конкретному пользователю (userId не используется).
|
||
|
||
## Общие требования
|
||
|
||
- Все эндпоинты требуют действующей сессии (cookie `sid`), иначе `401 Unauthorized`.
|
||
- Формат JSON-ответов использует `camelCase`.
|
||
- В MVP поддерживается только `matchType = "contains"`. Значения `"starts_with"` и `"regex"` зарезервированы для будущих версий.
|
||
|
||
---
|
||
|
||
## 1) GET /api/category-rules — список правил
|
||
|
||
Назначение: отображение списка правил на экране настроек (просмотр, активация/деактивация).
|
||
|
||
### Параметры запроса (query)
|
||
|
||
Все параметры опциональны.
|
||
|
||
- `isActive: boolean` — фильтр по активности:
|
||
- Параметр не передан → все правила (активные и неактивные).
|
||
- `isActive=true` → только активные (`is_active = TRUE`).
|
||
- `isActive=false` → только неактивные (`is_active = FALSE`).
|
||
- `categoryId: number` — фильтр по категории (`category_rules.category_id`).
|
||
- `search: string` — поиск по подстроке в поле `pattern` (регистронезависимый, `ILIKE '%search%'`).
|
||
|
||
### Поведение сортировки
|
||
|
||
По умолчанию backend возвращает правила, отсортированные по `priority DESC`, затем по `created_at DESC`.
|
||
|
||
### Ответ (200 OK)
|
||
|
||
Массив объектов правил.
|
||
|
||
```json
|
||
[
|
||
{
|
||
"id": 1,
|
||
"pattern": "PYATEROCHKA",
|
||
"matchType": "contains",
|
||
"categoryId": 1,
|
||
"categoryName": "Продукты",
|
||
"priority": 100,
|
||
"requiresConfirmation": false,
|
||
"isActive": true,
|
||
"createdAt": "2026-02-20T12:00:00+03:00"
|
||
}
|
||
]
|
||
```
|
||
|
||
Поля:
|
||
|
||
- `id: number` — идентификатор правила.
|
||
- `pattern: string` — строка-шаблон.
|
||
- `matchType: string` — тип сопоставления (`"contains"` в MVP).
|
||
- `categoryId: number` — ID категории.
|
||
- `categoryName: string` — имя категории (JOIN с `categories`).
|
||
- `priority: number` — приоритет правила.
|
||
- `requiresConfirmation: boolean` — требуется ли ручное подтверждение категории.
|
||
- `isActive: boolean` — активно ли правило.
|
||
- `createdAt: string` — дата создания в формате ISO 8601.
|
||
|
||
### Ошибки
|
||
|
||
- `401 Unauthorized` — нет действующей сессии.
|
||
|
||
---
|
||
|
||
## 2) POST /api/category-rules — создание правила
|
||
|
||
Назначение: создание нового правила категоризации (из экрана настроек или при редактировании транзакции).
|
||
|
||
### Тело запроса
|
||
|
||
```json
|
||
{
|
||
"pattern": "PYATEROCHKA",
|
||
"matchType": "contains",
|
||
"categoryId": 1,
|
||
"priority": 100,
|
||
"requiresConfirmation": false
|
||
}
|
||
```
|
||
|
||
Поля:
|
||
|
||
- `pattern: string` — **обязательное**. Строка-шаблон для сопоставления с `description` транзакций.
|
||
- `matchType: string` — **опциональное**, по умолчанию `"contains"`. В MVP принимается только `"contains"`.
|
||
- `categoryId: number` — **обязательное**. ID категории из таблицы `categories`.
|
||
- `priority: number` — **опциональное**, по умолчанию `100`. Целое число. Дефолт 100 означает, что пользовательские правила по умолчанию приоритетнее общих (seed/системных) правил с низким приоритетом.
|
||
- `requiresConfirmation: boolean` — **опциональное**, по умолчанию `false`.
|
||
|
||
### Валидация
|
||
|
||
- `pattern`:
|
||
- Не может быть пустым после `trim`.
|
||
- Максимальная длина — 200 символов (после `trim`).
|
||
- `matchType`:
|
||
- Если передан, должен быть строго `"contains"` (MVP).
|
||
- Неизвестные значения → `422`.
|
||
- `categoryId`:
|
||
- Должен ссылаться на существующую активную категорию.
|
||
- Несуществующая или неактивная категория → `422`.
|
||
- `priority`:
|
||
- Целое число от `0` до `1000`.
|
||
|
||
### Ответ (201 Created)
|
||
|
||
Созданный объект правила (структура как в `GET`).
|
||
|
||
```json
|
||
{
|
||
"id": 5,
|
||
"pattern": "PYATEROCHKA",
|
||
"matchType": "contains",
|
||
"categoryId": 1,
|
||
"categoryName": "Продукты",
|
||
"priority": 100,
|
||
"requiresConfirmation": false,
|
||
"isActive": true,
|
||
"createdAt": "2026-02-28T15:30:00+03:00"
|
||
}
|
||
```
|
||
|
||
### Ошибки
|
||
|
||
| Код | Ситуация |
|
||
|-----|-----------------------------------------------------------------------------|
|
||
| 400 | Структурно невалидные данные (пустой pattern, отсутствуют обязательные поля) |
|
||
| 401 | Нет действующей сессии |
|
||
| 422 | Ошибка валидации значений (неизвестный matchType, категория не найдена и пр.)|
|
||
|
||
---
|
||
|
||
## 3) PATCH /api/category-rules/{id} — частичное обновление правила
|
||
|
||
Назначение: изменение параметров правила, в том числе активация/деактивация.
|
||
|
||
### Параметры
|
||
|
||
- `id: number` — идентификатор правила (path-параметр).
|
||
|
||
### Тело запроса
|
||
|
||
Частичное обновление: передаются только изменяемые поля.
|
||
|
||
```json
|
||
{
|
||
"pattern": "PEREKRESTOK",
|
||
"categoryId": 1,
|
||
"priority": 150,
|
||
"requiresConfirmation": true,
|
||
"isActive": false
|
||
}
|
||
```
|
||
|
||
Допустимые поля для обновления:
|
||
|
||
- `pattern: string` — новый шаблон (валидация как при создании).
|
||
- `categoryId: number` — новая категория (должна существовать и быть активной).
|
||
- `priority: number` — новый приоритет (целое, `0`–`1000`).
|
||
- `requiresConfirmation: boolean` — обновление флага.
|
||
- `isActive: boolean` — активация/деактивация правила.
|
||
|
||
Поле `matchType` в MVP не обновляется. Если клиент передал `matchType` — возвращается `422` с сообщением `"matchType update is not supported in MVP"`, чтобы фронтенд не считал, что значение было применено.
|
||
|
||
### Валидация
|
||
|
||
- Правило с указанным `id` должно существовать, иначе `404`.
|
||
- Если передано поле `matchType` — `422` (обновление не поддерживается в MVP).
|
||
- К переданным полям применяются те же правила валидации, что и при создании.
|
||
- Если ни одно допустимое поле не передано — `400`.
|
||
|
||
### Ответ (200 OK)
|
||
|
||
Обновлённый объект правила (структура как в `GET`).
|
||
|
||
### Ошибки
|
||
|
||
| Код | Ситуация |
|
||
|-----|-----------------------------------------------------------------------|
|
||
| 400 | Структурно невалидные данные или ни одного допустимого поля |
|
||
| 401 | Нет действующей сессии |
|
||
| 404 | Правило не найдено |
|
||
| 422 | Ошибка валидации (категория не найдена, передан matchType и пр.) |
|
||
|
||
---
|
||
|
||
## 4) POST /api/category-rules/{id}/apply — применение правила к прошлым транзакциям
|
||
|
||
Назначение: ретроактивное применение конкретного правила к существующим транзакциям.
|
||
|
||
### Параметры
|
||
|
||
- `id: number` — идентификатор правила (path-параметр).
|
||
|
||
### Тело запроса
|
||
|
||
Отсутствует.
|
||
|
||
### Логика обработки
|
||
|
||
1. Правило загружается из БД. Если не найдено — `404`. Если неактивно (`is_active = FALSE`) — `422`.
|
||
2. Выполняется поиск транзакций, подходящих под данное правило:
|
||
- Сопоставление `pattern` с `description` (регистронезависимый `ILIKE '%pattern%'` для `matchType = "contains"`).
|
||
- Затрагиваются только транзакции, у которых:
|
||
- `category_id IS NULL`, **или**
|
||
- `is_category_confirmed = FALSE`.
|
||
- Транзакции с `is_category_confirmed = TRUE` **не затрагиваются** — пользовательские подтверждения неприкосновенны.
|
||
3. Для найденных транзакций:
|
||
- Устанавливается `category_id` из правила.
|
||
- `is_category_confirmed` устанавливается в `FALSE` (категория считается предварительной, требует подтверждения пользователем).
|
||
- Обновляется `updated_at`.
|
||
|
||
### Ответ (200 OK)
|
||
|
||
```json
|
||
{
|
||
"applied": 12
|
||
}
|
||
```
|
||
|
||
Поля:
|
||
|
||
- `applied: number` — количество транзакций, к которым было применено правило.
|
||
|
||
### Ошибки
|
||
|
||
| Код | Ситуация |
|
||
|-----|-----------------------------------------------|
|
||
| 401 | Нет действующей сессии |
|
||
| 404 | Правило не найдено |
|
||
| 422 | Правило неактивно |
|
||
|
||
---
|
||
|
||
## Связь с импортом
|
||
|
||
При импорте выписки (`POST /api/import/statement`) автокатегоризация выполняется автоматически:
|
||
|
||
- Ко всем импортированным транзакциям (с `category_id = NULL`) применяются все активные правила.
|
||
- Если для транзакции подходит несколько правил, выбирается правило с максимальным `priority`.
|
||
- `is_category_confirmed` определяется флагом `requires_confirmation` сработавшего правила.
|
||
|
||
Подробнее см. `api_import.md`.
|
||
|
||
## Связь с редактированием транзакций
|
||
|
||
При редактировании транзакции (`PUT /api/transactions/{id}`) с включённой опцией «Создать правило» фронтенд отправляет отдельный запрос `POST /api/category-rules` с заполненными полями `pattern`, `categoryId` и т.д.
|
||
|
||
Подробнее см. `edit_and_rules.md`.
|