Files
family_budget/docs/backlog/api_rules.md
2026-03-02 00:30:56 +03:00

256 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Правила категоризации (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`.