# Правила категоризации (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`.