# Смарт-действия — Администрирование

Раздел описывает автоматизацию в 1Форме: смарт-правила (событие → условие → пакет действий), смарт-доступ, очереди событий, потоки сообщений и смарт-скрипты. Здесь собрано, где всё это настраивается — редактор пакетов действий и смарт-правил, конструктор условий, смарт-скрипты — и на что обратить внимание при настройке.

### Ограничение: 1F Certificate Edition

В сертифицированной редакции **1F Certificate Edition** (сборка для ФСТЭК) подсистема смарт-скриптов и смарт-действий полностью исключена из состава продукта. Административные страницы настройки смарт-правил, пакетов действий и редактора смарт-скриптов в этой редакции недоступны. Для функциональности автоматизации используйте полную редакцию.


## Механизмы администрирования

### Автоадминка (dbadmin)

Часть настроек смарт-действий ведётся через формы автоадминки над таблицами БД:

| Alias формы | Название | Таблица БД | Полей | Секций | Папка |
|-------------|----------|-----------|-------|--------|-------|
| `smart-access` | Смарт-доступ | dbo.SmartAccess | 5 | 1 | Общая бизнес-логика |
| `smart-access-subcategories` | Смарт доступ — Категории | dbo.SmartAccessSubcategories | 4 | 1 | Служебное |
| `smart-access-trigger-extparams` | Смарт доступ — Пересчет при смене ДП | dbo.SmartAccessTriggerExtParams | 4 | 1 | Служебное |
| `smart-access-trigger-groups` | Смарт доступ — Пересчет при добавлении в группу | dbo.SmartAccessTriggerGroups | 4 | 1 | Служебное |
| `queues` | Очереди событий | dbo.Queues | 6 | 1 | Общая бизнес-логика |
| `custom-events` | Произвольные события | dbo.CustomEvents | 5 | 1 | Служебное |
| `message-flows` | Потоки | dbo.MessageFlows | 7 | 1 | Общая бизнес-логика |
| `custom-queue-actions-2` | События (Custom Queue Actions) | dbo.CustomQueueActions | 10 | 1 | Служебное |

### Отдельные SPA-страницы (в дереве администрирования)

Ключевые операции вынесены на отдельные страницы интерфейса администрирования:

| Alias формы | Название | Url | Таблица БД | Папка |
|-------------|----------|-----|-----------|-------|
| `general-smart` | Общие смарты | `/administration/smart-packs` | dbo.SmartExpressions | Общая бизнес-логика |
| `all-smart` | Все смарты | `/administration/smart-packs?globalEventsOnly=false` | dbo.SmartExpressions | Общая бизнес-логика |
| `custom-queue-actions` | Привязка действий к событиям очереди | `/administration/queues` | dbo.CustomQueueActions | Общая бизнес-логика |

Заголовки столбцов гридов страницы «Автоматизация» следуют языку интерфейса: подписи берутся из общих ресурсных файлов, а там, где перевода на язык пользователя ещё нет, подставляется английская подпись — пустых заголовков не бывает.

Помимо страниц, зарегистрированных в дереве автоадминки, существует страница редактирования одного смарт-правила:

| Маршрут | Назначение | Версия |
|---------|------------|--------|
| `/administration/smart-rule/:id` (`?subcatId=...&tab=pack|settings`) | Редактор смарт-правила: вкладки «Пакет действий» / «Настройки правила», смена пакета через модалку | с 2.268.358 |

### Admin API контроллеры

#### Редактор пакетов действий

API редактора пакетов действий:

| Маршрут | Методы | Назначение |
|---------|--------|------------|
| `/api/admin/smart/packs` | GET, POST, DELETE | CRUD пакетов, список по категории |
| `/api/admin/smart/packs-on-events` | GET, POST, DELETE | CRUD смарт-правил (событие → пакет) |
| `/api/admin/smart/packs` | GET, POST, DELETE | Схема/содержимое пакета, действия внутри пакета |

**admin-MCP:** Редактор пакетов действий также доступен через admin-MCP-сервер `/mcp-admin-api-actions-pack-editor` (7 инструментов) — MCP-обёртку над `ActionsPackEditorController` для Анфисы-настройщика и внешних MCP-клиентов. Значения параметров действий передаются как `JsonElement` (`System.Text.Json`) и автоматически десериализуются в нативные типы контролов при сохранении. Пустые параметры (без заполненных `Value`, `ExpressionID`, `ScriptId`, `PreDefinedParameterID`) автоматически исключаются из записи.

#### Конструктор условий и выражений

API конструктора условий и смарт-выражений:

| Маршрут | Методы | Назначение |
|---------|--------|------------|
| `/api/admin/smart/query` | GET, POST | Конструктор запросов |
| `/api/admin/smart/query-constructor` | GET, POST | Конструктор TSQL-условий |
| `/api/admin/smart/expressions` | GET, POST, DELETE | CRUD смарт-выражений |
| `/api/admin/smart/expression-editor` | GET, POST | Редактор выражений (схема интерфейса) |
| `/api/admin/smart/expression-constructor` | GET, POST | Конструктор выражений |
| `/api/admin/smart/expression-test-cases` | GET, POST, DELETE | Тест-кейсы для выражений |

#### Редактор смарт-скриптов

API редактора смарт-скриптов:

| Маршрут | Методы | Назначение |
|---------|--------|------------|
| `/api/admin/smart/scripts/editor` | GET, POST, DELETE | Редактор смарт-скриптов: получение, сохранение, проверка компиляции, выполнение, удаление, параметры действий |

#### Доступ и скрипты

API смарт-доступа, скриптов и расписаний:

| Маршрут | Методы | Назначение |
|---------|--------|------------|
| `/api/admin/smart/access` | GET, POST, PUT, DELETE | CRUD смарт-доступов (только суперадминистраторам через `Permissions.IsUserGod`). С 2.268.346 (#2093905, коммит `4083898ea0`) в `SmartAccessController` добавлены honest CRUD-методы — `POST` (create, возвращает `int Id`), `PUT /{smartAccessId}` (update основных полей), `DELETE /{smartAccessId}`, `GET /{smartAccessId}`, `GET` (list). До этого создание шло только через auto-admin форму 44 (`POST /api-core/data-source-form/44/action`); форма 44 сохранена для backward compatibility со SPA до отдельной миграции. DTO: `SmartAccessCreateDto` (`Name`, `ActionId`, `UsersSmartExpressionId`, опц. `Triggers: SmartAccessAdditionalSettingsDto`), `SmartAccessUpdateDto` (основные поля; **триггеры обновляются отдельно** через [`additional-settings`](#смарт-доступ-smartaccess) — не через `PUT`), `SmartAccessDto` (`Id`, `Name`, `ActionId`, `UsersSmartExpressionId`). MCP-инструменты `/mcp-admin-api-smart-access` автогенерируются: `smart_access_create`, `smart_access_update`, `smart_access_delete`, `smart_access_get`, `smart_access_list` (в дополнение к существовавшим `calculate_access`, `get_additional_settings`, `delete_smart_access_tasks`). |
| `/api/admin/smart/scripts` | GET, POST, DELETE | Смарт-скрипты (JS/TSQL) |
| `/api/admin/smart/recurrences` | GET, POST, PUT | Расписание повторяющихся правил |

## Ключевые настройки

### Смарт-выражения (SmartExpressions)

**Где настраивается:** SPA-страница `/administration/smart-packs` (пункт `general-smart` в дереве автоадминки) / Admin API (`/api/admin/smart/expressions`)
**Таблица БД:** `dbo.SmartExpressions`

Смарт-выражения — универсальный механизм, используемый и как условия (фильтры), и как правила автоматизации (событие + пакет действий).

| Поле | Тип | Что контролирует |
|------|-----|------------------|
| `Content` | nvarchar(max) | JSON-содержимое выражения (дерево условий) |
| `Name` | nvarchar | Название выражения (отображается в интерфейсе) |
| `SubcatID` | int | Привязка к категории (NULL = глобальное) |
| `IsFilter` | bit | Тип: фильтр (true) или правило автоматизации (false) |
| `EventID` | int | Событие-триггер (для правил) |
| `ParameterValue` | nvarchar | Значение параметра события (уточнение триггера) |
| `NoContextMode` | int | Режим без контекста задачи |
| `AvailAsSmartSearch` | bit | Доступно как смарт-поиск |
| `TSqlContent` | nvarchar(max) | TSQL-представление выражения |
| `Public` | bit | Публичное (видимо другим администраторам) |
| `OwnerUserId` | int | Владелец выражения |
| `QueryBuilderState` | nvarchar(max) | Состояние визуального конструктора |
| `OriginID` | int | Происхождение (системное / пользовательское) |
| `IsDeleted` | bit | Мягкое удаление |

**Зависимости между полями:**

- `IsFilter = 1` → `EventID` и `ParameterValue` не используются **при вычислении фильтра** (это фильтр, не правило; в записи БД поля могут быть заполнены)
- `IsFilter = 0` → `EventID` обязателен (определяет триггер)
- `SubcatID = NULL` → глобальное правило/фильтр
- `NoContextMode` → влияет на доступность действий, требующих контекст задачи

**Что происходит при выполнении:**

- `Content` (дерево условий) вычисляется при срабатывании;
- `TSqlContent` используется для серверного выполнения (прямой SQL);
- `EventID` определяет, на какое событие срабатывает правило (см. [Смарт-фильтры](https://help.1forma.ru/domains/smart-filters/admin.md)).

#### Массовая компиляция смарт-выражений (с 2.268.379)

На странице `/administration/smart-packs` (компоненты `admin-smart-grid`, `mass-compile-dialog`, `smart-compile-result`) администратор может перекомпилировать сразу группу смарт-выражений или смарт-фильтров — например, после смены коннекшна PG или массового импорта. Кнопка «Скомпилировать» открывает диалог `MassCompileDialogComponent`, наследующий текущий контекст грида: вкладку (`isFilter` = «Смарт-выражения» / «Смарт-фильтры»), категорию (`subcatIds`) и режим «только глобальные» (`subcatIndependent` при `globalEventsOnly`). `origin: 2` задаёт источник пользовательских выражений.

В диалоге доступны фильтры: «Категория», «Поиск по названию», «Только нескомпилированные» (колонка `compiledColumn` — признак `IsCompiled` из `SmartExpressionDto`). Запуск — кнопками «Все по фильтру» либо «Выделенные»; перед стартом показывается подтверждение `massCompileConfirm` с числом выражений.

Фильтрация в гриде диалога работает только по колонкам «Название» и «Категория» и только в режиме «содержит» (ограничение метода `expressions/query`); группировка и фильтры по остальным колонкам отключены.

Список под текущий фильтр приходит через `POST /api/admin/smart/expressions/testcases/expressions/query` (метод `queryExpressionsForCompile` в `admin-data-http.service`), запуск — через `POST /api/admin/smart/expressions/testcases/run`. Параметр запроса `forceRecompileEsql` форсирует перекомпиляцию даже для уже скомпилированных выражений. Прогресс выводится компонентом `SmartCompileResultComponent`: счётчики «Обработано / Скомпилировано / Ошибок / Пропущено» (`total / compiled / failed / skipped`). Прерывание показывает уведомление `compileCancelledNotice` и сохраняет накопленный результат.

Бэкенд шлёт прогресс прогона живьём по SignalR: сигналы на старте, в процессе (с троттлингом — не более ~100 за прогон) и по завершении. DTO `SmartExpressionsTestProgressDto` несёт текущий прогресс, агрегаты (`compiled` / `failed` / `skipped`), флаг завершения и `RunId` для корреляции ответа на фронте.

> ⚠️ Перекомпилируются только выражения, у которых заполнен исходный `Content` (XML / `TSqlContent` / `PgSqlContent`). Пустые выражения попадают в `skipped`.

#### Переход из грида пакетов на связанные правила (с 2.268.396)

Грид смарт-пакетов (`/administration/smart-packs`) отображает для каждой строки номера смарт-правил (`packs-on-events`), связанных с пакетом, в колонке «Правила». Номер правила — ссылка: она открывает в новой вкладке браузера вкладку «Правила» грида смартов категории этого правила (`smartGridTab=smartPacksOnEvents`, `ruleId` — номер правила), а для правила без категории — той категории, в настройках которой открыт грид; в общих смартах у правила без категории номер выводится текстом. На открытой вкладке номер уже стоит в фильтре колонки «ID» и в списке остаётся только это правило; отбор по состоянию над гридом при таком открытии снят, поэтому отключённое правило тоже находится. Если правила с таким номером в категории нет, список пуст, а фильтр по «ID» остаётся; стерев значение из фильтра, администратор видит все правила категории. Клик по самой строке открывает форму пакета прямо на странице смартов, адрес страницы не меняется; категория для формы берётся из строки пакета, а если у пакета её нет — категория страницы. Ctrl/Cmd + клик или клик средней кнопкой открывает пакет отдельным экраном `/administration/smart-pack-action` в новой вкладке браузера. Строки вкладок «Выражения», «Фильтры» и «Скрипты» открывают редактор в текущей вкладке браузера, а с Ctrl/Cmd + клик или средней кнопкой — в новой. Для ссылки на правило в контракте ответа есть короткий DTO:

- `PackRuleShortDto { Id: int, SubcatId: int? }` — короткая ссылка на правило (`EventsActions.Id` и категория правила, по которым строится ссылка на вкладку «Правила» грида смартов); используется вместо возврата полного правила.
- `PackShortDto` расширен полем `SubcatName` (имя категории пакета для колонки грида) и коллекцией `Rules: List<PackRuleShortDto>` — все правила, ссылающиеся на данный `ActionsPack`.
- `SmartExpInfoDto` / `SmartExpressionItemDto` (карточка смарт-выражения / правила в списке) получили `SubcatName`; для глобальных правил без `SubcatId` подставляется признак `SmartGlobal` (метка глобального уровня в UI).
- `EventsActionsEntityService.GetByActionsPackIds(IEnumerable<int> packIds)` — batch-запрос по списку `ActionsPackID` вместо построчного вызова `GetByPackId` в цикле по гриду; убирает N+1 при построении списка правил.

Права и фильтры грида не изменились — приходит только та часть правил, к которым у администратора есть доступ (`CheckGodOrSubcatAdmin`).

### Пакеты действий (ActionsPacks)

**Где настраивается:** AdminSPA → настройки категории → вкладка «Смарт» / Общие смарты (`/administration/smart-packs`) / Admin API (`/api/admin/smart/packs`)
**Таблицы БД:** `dbo.ActionsPacks`, `dbo.PacksActions`, `dbo.PacksActionsParameters`

Пакеты настраиваются через Admin API (визуальный редактор в SPA). В автоадминке формы для ActionsPacks **нет**.

![Редактор пакета действий: переключатель цикличности, кнопка «+ Действие» и список действий пакета](https://help.1forma.ru/help-images/smart-actions/pack_editor.png)

**Структура:**

- `ActionsPacks` — метаданные пакета (имя, GUID, категория)
- `PacksActions` — действия внутри пакета (тип, порядок)
- `PacksActionsParameters` — параметры каждого действия

```mermaid
flowchart TD
    A["Событие-триггер: EventID + ParameterValue"] --> B{"Условие правила выполнено? (дерево смарт-выражения)"}
    B -- "нет" --> X["Пакет действий не запускается"]
    B -- "да" --> C["Пакет действий (ActionsPacks)"]
    C --> D["Действия выполняются по порядку (PacksActions)"]
    D --> E["Каждое действие — в контекстной задаче или по ID, от имени пользователя или робота 1Ф"]
    E --> F["Результат действия → переменная для последующих действий пакета"]
    C --> G["Циклический пакет повторяется для каждого объекта выборки"]
```

**Типы действий:** 70+ типов — полный каталог в [Справочнике действий](https://help.1forma.ru/domains/smart-actions/actions-reference.md)

**Состав списка действий зависит от загруженных сборок.** В списке доступны стандартные действия платформы и пользовательские (кастомные) действия из подключённых сборок. Если сборка с пользовательскими действиями недоступна на сервере приложения, в списке остаются стандартные действия, а в журнале ошибок платформы появляется запись о сбое загрузки пользовательских действий. Проверяйте журнал ошибок прежде, чем править настройки пакета: состав списка в этом случае не причина, а следствие.

**Ключевые группы действий:**

- Задачи: создание, изменение статуса, полей, исполнителей
- Комментарии: добавление, изменение
- Уведомления: email, push, SMS
- Файлы: генерация документов, копирование
- Интеграции: HTTP-запросы, SQL, внешние системы
- Управление: остановка/возобновление таймеров, JS-скрипты

**Область видимости списков.** Вкладка «Пакеты действий» в настройках категории показывает только пакеты этой категории — глобальные пакеты площадки в список и счётчик вкладки не попадают. Глобальные пакеты настраиваются в разделе «Общие смарты» и остаются доступны для выбора при настройке смарт-правил в любой категории.

Вкладка «Скрипты» в настройках категории тоже показывает только скрипты этой категории, без глобальных; в Общих смартах та же вкладка выводит все скрипты площадки, а у категорийных в колонке «Категории» стоит ссылка, открывающая в новой вкладке браузера вкладку «Скрипты» их категории.

#### Создание пакета

Пакет можно создать двумя способами:

- **С привязкой к событию** — создать смарт-правило (событие → пакет) и нажать «Создать» рядом с полем пакета.
- **Без привязки** — в разделе «Пакеты действий» нажать «Создать пакет». Такой пакет потом можно привязать к событию, расписанию или вызвать вручную.

Пакеты из Общих смартов не поддерживают действия, требующие контекста категории (например, «Принудительно изменить статус»).

**Ограничения именования:** апострофы (`'`) в названии пакета делают его неработоспособным — использовать только кавычки (`"`).

**Привязка к событию через скрипт:** если пакет использует смарт-скрипты, привязанные к событию A, он недоступен для выбора в контексте события B.

#### Добавление действий

Кнопка «+Действие» открывает список всех действий, сгруппированных по смысловым блокам. Поддерживается поиск по слову в названии.

![Окно «Создание действия в пакете»: выбор действия из сгруппированного списка с поиском](https://help.1forma.ru/help-images/smart-actions/add_action.png)

После выбора действия открываются его параметры. Для каждого параметра выбирается **формат значения**:

| Формат | Описание |
|--------|----------|
| **Значение** | Фиксированное значение нужного типа. Для параметров типа «Файл» открывается выбор из файлового хранилища категории: один файл или несколько — в зависимости от того, принимает параметр один файл или список |
| **Smart / TSQL** | Смарт-выражение. Должно возвращать тип, соответствующий параметру; список выражений отфильтрован по типу результата этого параметра. Список значений — в формате `{А, В, С}`. Тестовая выборка ограничена 100 записями. |
| **Lua-скрипт** | Lua-скрипт из репозитория. Требования к типам аналогичны Smart/TSQL, список скриптов также отфильтрован по типу результата параметра. |
| **Оставить пустым** | Для необязательных параметров — значение не присваивается |
| **Текущая задача** | Только для параметров типа «Задача» — выполнить в задаче текущего события |

Параметры, обязательные для заполнения, отмечены `*`.

**Поле «Задача»:** по умолчанию действие выполняется в контекстной задаче. Чтобы выполнить в другой — указать ID через выражение. Если выражение вернёт несколько ID, действие выполнится для каждой задачи.

**Порядок действий** изменяется перетаскиванием строк.


**Выбор смарт-выражения по типу результата.** Для основного параметра действия — в форматах «Smart / TSQL» и «Lua-скрипт» — список помечает несовместимые с ожидаемым типом выражения как недоступные для выбора. Тип берётся из схемы параметра действия. Несовместимые выражения из списка не исчезают: уже сохранённое значение остаётся видимым при редактировании.

В строках блока «Доп параметры» фильтр по типу результата не применяется. Схема задаёт для этого блока тип всего набора дополнительных параметров, а не тип отдельной строки, поэтому список в такой строке доступен без ограничения по типу результата. На фильтрацию основного параметра действия это не влияет.

Если для параметра тип результата не задан, фильтр не применяется и список показывается целиком, как раньше. Уже сохранённое выражение неподходящего типа при этом не теряется: оно остаётся выбранным в параметре, даже если в отфильтрованном списке его нет.

#### Выполнение от имени пользователя

Если действие выполняется от имени конкретного пользователя, у него должны быть соответствующие права. Рекомендуется использовать **системного робота** (настраивается в Общих настройках приложения → «Диспетчер задач») — он обладает максимальными правами.

> ⚠️ **Системный робот снимает проверки прав.** Действие, выполняемое от имени системного робота, обходит проверки доступа, в том числе к файлам. Используйте робота только там, где операция действительно системная и инициатора-пользователя нет. Если действие запущено пользователем — в том числе из публикации или смарт-скрипта, — выполняйте его от имени этого пользователя, чтобы его права проверялись.

#### «Тихий» комментарий

При добавлении комментария через смарт-действие можно сделать его «тихим»: без адресатов, без увеличения счётчика непрочитанных, без push-уведомлений — независимо от настроек подписки.

#### Передача переменных между действиями

Некоторые действия **возвращают результат** (отмечены `возвращает XXX` в описании). Результат записывается в переменную и доступен в последующих действиях пакета через узел **«Переменные»** в дереве смарт-выражения.

#### Циклические пакеты

Если пакет должен выполняться многократно для разных объектов — включить флаг циклического выполнения. Дополнительные настройки:

| Настройка | Описание |
|-----------|----------|
| **Список итерируемых объектов** | Смарт-выражение, возвращающее ID через запятую |
| **Условие прерывания цикла** | Смарт-выражение: когда истинно — цикл прерывается |

Индекс итерации начинается с `0` и доступен в смарт-выражениях пакета вместе с текущим значением итератора. Переменные цикла доступны в редакторе выражений только если заданы **оба** поля (список и условие).

> ⚠️ Проверка глубины рекурсии смарт-пакетов к циклическим пакетам **не применяется**.

#### Меню быстрых действий с пакетом

Через контекстное меню пакета доступны действия:

| Действие | Описание |
|----------|----------|
| Редактировать название | Изменить имя пакета |
| Редактировать описание | Произвольный текст (отображается при наведении на кнопку информации) |
| Редактировать модуль | Привязать пакет к модулю; без выбора — глобальный модуль |
| Дублировать пакет | Создать копию в текущей категории |
| Удалить пакет | Удалить пакет |

**Автогенерация описания действия:** при настройке параметров можно пометить ключевые из них как «Показ превью» — система сгенерирует краткое описание действия в списке пакета.

**Что происходит при выполнении:**

- действия выполняются последовательно в порядке `ActionOrder`;
- асинхронные правила уходят в очередь.

### Смарт-правила (событие → пакет)

**Где настраивается:** Admin API (`/api/admin/smart/packs-on-events`)
**Таблица БД:** `dbo.EventsActions`

Связывает событие с пакетом действий через условие (смарт-выражение).

**Ключевые концепции:**

- Правило = событие + (опциональное) условие + пакет действий
- Правила привязаны к категории (`SubcatID`) или глобальные
- Порядок правил определяет приоритет выполнения (изменение порядка через API)
- Флаг `IsAsync` — асинхронное выполнение через очередь
- Флаг `Cancelable` — возможность отмены пользователем
- Флаг `IsActive` — если отключён, правило сохраняется, но не срабатывает (удобно для отладки)

#### Редактор правила (AdminSPA)

Экран редактирования смарт-правила (`/administration/smart-rule/:id`) разделён на две вкладки.

- **Пакет действий** — список действий пакета. Кнопка «Добавить действие» добавляет новое действие; клик по строке действия открывает его параметры в отдельном окне; удалить действие можно через контекстное меню (правый клик по строке). Порядок действий меняется перетаскиванием строк. Если действие требует скрипта, по клику открывается смарт-редактор в модальном окне.
- **Настройки правила** — событие-триггер, смарт-фильтр (условие срабатывания), флаги «Активна» и «Асинхронно».

Для событий «Перед переходом» и «После перехода» в настройках правила доступен параметр **«Переход»**: можно выбрать конкретный переход категории — тогда пакет сработает только на нём — или значение **«для всех»**, при котором параметр остаётся пустым и пакет выполняется при любом переходе категории.

**К какому элементу маршрута относится правило.** Правило относится к тому элементу, который задан в параметре события: событие перехода (`BeforeTaskStep`, `AfterTaskStep`) привязывает правило к переходу, событие статуса (`BeforeStateChangedForsibly`, `AfterTaskStateChange`) — к статусу. Если параметр не заполнен («для всех») или указанный элемент в граф маршрута не попал, правило остаётся правилом категории. В ответе графа маршрута правило перехода приходит на переходе, правило статуса — на статусе; на чужие элементы оно не дублируется.

Если в окне параметров действия есть несохранённые изменения, закрытие крестиком или клавишей Esc открывает окно «Сохранить изменения» с предупреждением «Изменения будут утеряны, если их не сохранить.». Кнопка «Сохранить» записывает действие и закрывает окно параметров только после успешной записи; если в форме есть ошибки, поля с ними подсвечиваются и окно параметров остаётся открытым. «Не сохранять» закрывает окно параметров без записи, крестик окна предупреждения возвращает к редактированию. Без несохранённых изменений окно параметров закрывается сразу. Уход со страницы параметров действия с несохранёнными изменениями тоже требует подтверждения.

**Несохранённые настройки правила.** Если на вкладке «Настройки правила» есть несохранённые изменения, переход на вкладку «Пакет действий», по хлебным крошкам или в другой раздел открывает окно «Сохранить изменения». «Сохранить» записывает настройки правила и выполняет переход только после успешной записи; при ошибке администратор остаётся на вкладке. «Не сохранять» выполняет переход без записи и возвращает настройки к сохранённым значениям, крестик окна предупреждения возвращает к редактированию. Без несохранённых изменений переход выполняется сразу.

**Несохранённая правка действия пакета.** Если открыты параметры действия и в них есть несохранённые изменения, уход с вкладки «Пакет действий» — на «Настройки правила», по хлебным крошкам, в другой раздел или закрытие экрана — открывает окно «Сохранить изменения». «Сохранить» записывает действие и выполняет переход только после успешной записи, при ошибке администратор остаётся с правкой; «Не сохранять» выполняет переход без записи и отменяет внесённые правки действия. Окно спрашивает только про ту вкладку, с которой уходят, поэтому правки второй вкладки остаются на месте. При уходе с экрана, если не сохранены обе вкладки, «Сохранить» пишет настройки и действие по очереди и останавливается на первой ошибке. Без несохранённых изменений переход выполняется сразу.

Правило можно открыть в новой вкладке браузера: пункт «Открыть в новой вкладке» в контекстном меню строки правила, а также Ctrl/Cmd + клик или клик средней кнопкой мыши по строке. Обычный клик открывает правило в текущей вкладке. На вкладке «Правила» грида смартов клик по значению в колонке «Пакет действий» открывает то же правило сразу на вкладке «Пакет действий» — в адресе стоит `tab=pack`, и настройки пакета видны без промежуточного переключения. Клик по любой другой ячейке строки (ID, «Smart-фильтр», «Параметр») и по свободной части строки открывает «Настройки правила» (`tab=settings`). Поведение одинаково для категорийного списка и для списка общих правил: отдельной ветки для режима без категории нет. Значение пакета в колонке оформлено как ссылка — цвет ссылки, подчёркивание при наведении, курсор-указатель; Ctrl/Cmd + клик и клик средней кнопкой открывают вкладку «Пакет действий» в новой вкладке браузера.

##### Noframe-навигация в редакторе смартов

Редактор смарт-правил и грид пакетов поддерживают режим `noframe` — открытие страниц без шапки 1Ф и без боковой панели навигации (для встраивания в модальные окна и внешние обвязки). Компоненты `admin-smart-grid` (`/administration/smart-packs`) и `admin-smart-rule-edit-page` (`/administration/smart-rule/:id`) инжектят сервис `IsNoFrameModeService`; при внутренних переходах — открытие правила из грида, возврат на грид, переход между вкладками пакета — URL префиксуется префиксом `/noframe/`, если `isNoFrame.value === true`. Так режим редактора без шапки сохраняется на все дочерние экраны — раньше первый же переход из грида «выпадал» на обычный layout с шапкой и терял контекст встраивания.

Внешние ссылки на смарт-редактор (не из этих компонентов) режим не наследуют — префикс проставляется только на клиентских переходах внутри вкладок.

#### Асинхронность правил

Флаг `IsAsync` влияет на порядок выполнения нескольких правил для **одного и того же события**:

- Синхронное правило — следующие по списку правила ждут его завершения.
- Асинхронное правило — следующие правила запускаются немедленно, не ожидая результата.

⚠️ Включать асинхронность стоит только если все правила события независимы друг от друга и порядок выполнения не важен.

#### Порядок событий при смене исполнителя / ответственного

Нелинейная последовательность событий при делегировании и удалении важна для правильной настройки смарт-правил:

| Действие | Условие | Последовательность событий |
|----------|---------|---------------------------|
| Назначение ответственного | Назначается единственный исполнитель | Перед назначением исполнителя → После назначения исполнителя → Перед сменой ответственного → После смены ответственного |
| Назначение ответственного | Новый ответственный уже в списке исполнителей | Перед сменой ответственного → После смены ответственного |
| Удаление исполнителя | Не ответственный | Перед удалением исполнителя → После удаления исполнителя |
| Удаление исполнителя | Ответственный, других нет | Перед сменой ответственного → После смены → Перед удалением → После удаления |
| Удаление исполнителя | Ответственный, есть один другой | То же, что выше |
| Удаление исполнителя | Ответственный, есть 2+ других | Смарт-события **не выполняются** — система требует ручного назначения ответственного |
| Делегирование | Новый ответственный уже в списке, «Удалить всех» = OFF | Перед сменой ответственного → После смены → Перед удалением прежнего → После удаления |
| Делегирование | Новый ответственный не в списке, «Удалить всех» = OFF | Перед назначением → После назначения → Перед сменой → После смены → Перед удалением → После удаления |
| Делегирование | «Удалить всех» = ON, несколько исполнителей | Перед сменой → После смены → Перед удалением × N → После удаления × N |

#### Отображение параметра в списке правил

В AdminSPA на странице настройки смарт-правил (привязка событий к пакетам действий) в колонке «Параметр» таблицы отображается расширенная информация о значении параметра события в зависимости от его типа:

- **Переход:** название перехода, ID, исходный и целевой статусы (например, «Завершить (172060, Выполняется → Завершена)»). Название перехода — ссылка на страницу администрирования маршрута.
- **Дополнительный параметр:** название параметра, ID, тип (например, «Приоритет (172, Число)»). Название параметра — ссылка на настройки дополнительных параметров категории.
- **Статус:** название статуса, ID (например, «В работе (27)»).

**Состав и видимость колонок.** Список правил показывает колонки «ID», «Тип фильтра», «Smart-фильтр», «Пакет действий» и «Параметр» — в этом порядке. Группа событий и событие вынесены в группировку строк и отдельными колонками не показываются. Колонка «Параметр» видна сразу при открытии вкладки, включать её вручную не требуется; длинное значение обрезается и целиком показывается во всплывающей подсказке. Набор столбцов и их ширины можно менять средствами таблицы, но выбор не сохраняется: при следующем открытии вкладки «Правила» набор столбцов снова стандартный.

**Правила перехода на схеме маршрута.** Правила, привязанные к переходам, видны не только в списке правил, но и на самой схеме маршрута в Дизайнере БП: у перехода, к которому привязано хотя бы одно правило автоматизации, на схеме отображается смарт-иконка. Клик по иконке открывает панель с расширенной информацией о правилах этого перехода и не открывает редактор перехода; клик по самому ребру мимо иконки, как и раньше, открывает редактор перехода. Отключённые правила на схеме не показываются, а иконка появляется и у перехода, автоматизация которого состоит только из принудительной смены статуса. Устройство панели — в разделе «Панель правил автоматизации на карте маршрута» в [«Категории» — администрирование](https://help.1forma.ru/domains/categories/admin.md#панель-правил-автоматизации-на-карте-маршрута).

**Ключевые правила, важные для настройки правил:**

1. **Отменяемые события (префикс «Перед …»).** Если событие начинается со слова «Перед …» (`BeforeTaskCreate`, `BeforeStateChange`, `BeforeExtParamChange` и т. п.), пакет действий может **отменить** событие через действие `Отменить`. Используется для запретов («нельзя понизить приоритет, если заказчик — гендиректор»). После «После …» отмена невозможна. При отмене смены значения ДП «Таблица» (`BeforeExtParamChange`) в модальном окне выводится только текст, заданный в действии `Отменить`, без системного префикса — единообразно с ДП-лукапами. Отмена работает и для события «Перед написанием комментария»: комментарий не публикуется, а пользователь видит окно с заголовком и текстом причины. Окно с текстом причины и без кнопки «Создать задачу в техподдержку» показывается также при отмене удаления комментария (событие «Перед удалением комментария») — на всех поверхностях, где комментарий удаляют, — и при отмене сохранения ДП «Таблица» в режиме «динамический набор полей» (события «Перед сменой ДП» и «Перед сохранением значений в строке ДП Таблица»). При включённом автосохранении «набора полей» окна нет.

2. **Глобализация категорийных событий.** Событие, помеченное флагом `CanBeGlobal`, можно привязать к пакету в Общих SMART **без `SubcatID`** — пакет сработает в любой задаче системы. Сначала отрабатывают категорийные пакеты, затем глобальные. Первое событие с флагом — `AfterPostComment` (29). Список глобализируемых событий расширяется в новых версиях (такие события помечены ⚑ в каталоге). Например, флаг получили смарт-события AI-подсказок в чатах — `WhenStartReply` (196) и `WhenWritingComment` (201).

3. **Изначально глобальные группы.** Группы `Глобальные`, `Exchange`, `Пользователи` работают **только** в `Общих SMART` без привязки к категории — это историческое поведение, не путать с глобализированными.

4. **События ресурсного планирования — два разных «Исполнителя».** В контекстных параметрах присутствуют два различающихся параметра:

   | Параметр | Что передаётся | Когда использовать |
   |----------|----------------|--------------------|
   | **Исполнитель (пользователь)** | `UserID` пользователя | Для внесения затрат **по сотруднику** |
   | **Исполнитель (задача)** | `TaskID` задачи из Справочника ресурсов (раздел «Системный») | Для внесения затрат **по ресурсу** |

   Не взаимозаменяемы — путаница приводит к ошибочной аналитике.

5. **`@eventParam*` для специальных событий.** Часть событий передаёт массив (например, в ресурсном планировании параметр «Дата» — массив; читается через `OPENJSON` в SQL). Для `EventID=64` («Во время открытия задачи») контекст интерфейса приходит в `@eventParam1`. Для `Exchange`-событий (новое письмо, изменено событие в календаре) подробности встречи/письма не приходят в параметры — нужно дополнительно вызвать смарт-действие «Получить встречу» / «Получить письмо».

6. **Нумерация enum нелинейная.** Числовые `EventID` не совпадают с порядком объявления в C# (`AfterSignatureSigned=70` объявлен после `BeforeSubscriberAdded=58`). При SQL-диагностике используйте точные числовые значения.

### Смарт-доступ (SmartAccess)

**Где настраивается:** автоадминка → форма `smart-access` / Admin API (`/api/admin/smart/access`)
**Таблица БД:** `dbo.SmartAccess`

Динамические права доступа к задачам на основе смарт-выражений.

![Окно «Смарт доступ»: наименование правила, смарт-выражение для пользователей и тип доступа](https://help.1forma.ru/help-images/smart-actions/smart_access.png)

| Поле | Что контролирует |
|------|------------------|
| `Name` | Название правила смарт-доступа |
| `UsersSmartExpressionID` | Выражение, определяющее набор пользователей |
| `ActionID` | Действие/право (чтение, запись, и т.д.) |
| `GUID` | Уникальный идентификатор для миграции |

**Связанные формы:**

- `smart-access-subcategories` — к каким категориям применяется правило (`dbo.SmartAccessSubcategories`)
- `smart-access-trigger-extparams` — при изменении каких ДП пересчитывать доступ (`dbo.SmartAccessTriggerExtParams`)
- `smart-access-trigger-groups` — при изменении членства в каких группах пересчитывать (`dbo.SmartAccessTriggerGroups`)

**Что происходит при выполнении:**

- при изменении триггерного ДП или группы — автоматический пересчёт прав;
- результат кешируется и применяется при проверке доступа к задаче;
- операция тяжёлая — слишком широкие триггеры вызывают массовые пересчёты.

#### Типы доступа

Смарт-доступ может управлять следующими правами:

| Область | Тип доступа | Описание |
|---------|-------------|----------|
| Задача | Просмотр | Видимость задачи в списках и карточке |
| Задача | Исполнение | Право выполнять задачу |
| Задача | Редактирование заказчика | Изменение поля «Заказчик» |
| Задача | Редактирование исполнителя | Изменение состава исполнителей |
| Задача | Редактирование акцептантов | Изменение состава акцептантов подписи |
| Задача | Редактирование срока | Изменение дедлайна |
| Задача | Добавление в исполнители | Право стать исполнителем |
| Задача | Добавление в подписчики | Право подписаться на задачу |
| ДП | Просмотр | Видимость ДП в карточке |
| ДП | Редактирование | Изменение значения ДП |

> ⚠️ Правила смарт-доступа действуют **только в уже созданных задачах** и не применяются при создании.

#### Смарт-выражение для пользователей

Выражение в поле `UsersSmartExpressionID` должно возвращать список ID пользователей, которым предоставляется доступ.

| Результат выражения | Поведение |
|---------------------|-----------|
| Список ID | Доступ предоставлен указанным пользователям |
| Пустая строка `""` | Доступ не предоставлен никому (нормальная ситуация) |
| `null` | **Пересчёт прерывается** — ошибка конфигурации |
| Ошибка выполнения | **Пересчёт прерывается** |

> ⚠️ Возвращайте пустую строку `""`, а не `null`, если список пользователей пуст. Иначе пересчёт прерывается и предыдущее состояние доступа «замораживается».

#### Триггеры пересчёта

Пересчёт смарт-доступа инициируется при изменении ДП, указанных в форме `smart-access-trigger-extparams`, или при изменении членства в группах (`smart-access-trigger-groups`).

> ⚠️ Пересчёт выполняется **только** при изменении указанных ДП — даже если смарт-выражение ссылается на другие параметры (заказчик, исполнитель и т.п.). Для подписки на изменение не-DP параметров: создайте вспомогательные ДП, копируйте в них значения основных параметров (через отдельное смарт-правило), и настройте триггер на эти вспомогательные ДП.

При наличии настроенных триггеров по ДП, смарт-доступ пересчитывается также **после создания задачи** (если ДП уже заполнен при постановке — повторный пересчёт не производится).

#### Видимость в дереве категорий

Если на категорию настроен смарт-доступ, она отображается в дереве категорий **всегда** — даже если у пользователя нет доступа ни к одной задаче в этой категории.

#### Действия (меню)

Через меню правила смарт-доступа доступны действия:

| Действие | Описание |
|----------|----------|
| **Синк активных задач** | Принудительный пересчёт для всех активных задач привязанных категорий |
| **Синк всех задач** | Пересчёт для всех задач (включая завершённые) привязанных категорий |
| **Удалить привязки доступа к задачам** | Очистка таблицы `SmartAccessViewTasks` для данного правила. Выполнить **перед удалением** самого правила |
| Пересчет при смене ДП | Настройка списка ДП-триггеров |
| Пересчет при добавлении в группу | Настройка групп-триггеров |

> ⚠️ Ручной синк («Синк активных задач» / «Синк всех задач») необходим при первичном подключении правила — в этом случае автоматического пересчёта ещё не было. Также синк можно привязать к расписанию через смарт-действие «Пересчитать смарт-доступ в задачах».

#### AdminSPA — сохранение правил ДП

В AdminSPA правила смарт-доступа к ДП сохраняются **независимо** от основной формы настроек ДП. Каждое правило фиксируется сразу при нажатии кнопки «Добавить параметр» в модальном окне. После сохранения форма автоматически очищается для добавления следующего правила. Это предотвращает потерю данных при добавлении множественных правил.

### Очереди событий (Queues + CustomQueueActions)

**Где настраивается:** автоадминка → формы `queues`, `custom-queue-actions`
**Таблицы БД:** `dbo.Queues`, `dbo.CustomQueueActions`

Очереди — механизм асинхронной обработки событий.

**dbo.Queues** — определение очереди:

| Поле | Что контролирует |
|------|------------------|
| `Alias` | Уникальный псевдоним очереди |
| `Description` | Описание |
| `Origin` | Происхождение (системная / пользовательская) |
| `IsReadOnly` | Запрет редактирования |

**dbo.CustomQueueActions** — привязка действий к очереди:

| Поле | Что контролирует |
|------|------------------|
| `QueueId` | Ссылка на очередь |
| `OrderID` | Порядок обработки |
| `Alias` | Псевдоним действия |
| `SmartFilterID` | Условие (смарт-фильтр) |
| `ActionPackID` | Пакет действий для выполнения |
| `Origin` | Происхождение |
| `IsReadOnly` | Запрет редактирования |

#### Назначение и типы событий

Очереди используются преимущественно при **интеграции с внешними системами**.

- **Исходящее событие** — инициируется внутри 1Формы: смарт-действие «Добавить новое событие в очередь» кладёт запись в нужный поток.
- **Входящее событие** — инициируется внешней системой: настраивается привязка пакета к системному или пользовательскому событию.

На странице «Привязки действий к событиям» пакеты внутри одного события можно переупорядочить **перетаскиванием**.

Обработка событий запускается заданием `QueueEventsJob`.

#### Системные события (из коробки)

Готовые системные события очередей:

| Событие | Описание |
|---------|----------|
| `ChangeEdocumentStatus` | Изменился статус электронного документа в Диадоке |
| `ReceiveNewEdocument` | Получен новый электронный документ через Диадок |

**Что происходит при выполнении:**

- при поступлении события в очередь выполняются привязанные действия в порядке `OrderID`;
- фильтр (`SmartFilterID`) определяет, подходит ли событие для данного действия;
- обработка очередей идёт в фоновых сервисах.

### Произвольные события (CustomEvents)

**Где настраивается:**

- Настройки категории → Смарты → вкладка «Произвольные события»
- Общие смарты (`/administration/smart-packs`) → вкладка «Произвольные события» (глобальные)
- Автоадминка → форма `custom-events`

**Таблица БД:** `dbo.CustomEvents`

| Поле | Что контролирует |
|------|------------------|
| `SubcatId` | Категория, к которой привязано событие |
| `Code` | Уникальный код события. Только буквы латиницы и цифры, без пробелов. Уникален в рамках категории. |
| `Description` | Название события (отображается в интерфейсе) |

#### Назначение

Произвольные события позволяют запустить пакет действий для **множества задач** категории по условию. Типичные сценарии:

- Массовый пересчёт данных во всех задачах категории (по кнопке или расписанию).
- Инициирование обработки задач из **другой** категории из контекста текущей.

В отличие от смарт-правил, которые реагируют на событие одной задачи, произвольное событие адресуется нескольким задачам сразу через смарт-отбор.

#### Как настроить

Настройка произвольного события состоит из трёх шагов:

1. **Создать событие** — в настройках категории добавить запись с уникальным `Code` и названием.
2. **Привязать обработчик** — создать смарт-правило на системное событие **«При произвольном событии»**, указав конкретный `Code`.
3. **Вызвать событие** — через смарт-действие **«Инициировать произвольное событие»** из любой категории. В действии указывается:
   - Целевое событие (категория + код)
   - Сфера действия: текущая задача / конкретный ID / ID из смарт-выражения / список ID / смарт-отбор коллекции

**Как выглядит выбор события.** В выпадающем списке действия «Инициировать произвольное событие» каждый пункт показывается в две строки: сверху — код события, снизу серым подзаголовком — ID и категория в формате «ID:<идентификатор> • Категория <название>». Код события уникален только в пределах категории, поэтому при совпадении кодов ориентируйтесь на категорию и ID в подзаголовке — это особенно важно, когда пакет действий запускается из другой категории. Поиск в списке работает по обеим строкам: пункт находится и по коду, и по категории, и по ID.

### Потоки сообщений (MessageFlows)

**Где настраивается:** автоадминка → форма `message-flows` (страница «Потоки» в разделе очередей)
**Таблица БД:** `dbo.MessageFlows`

Потоки определяют стратегию обработки событий в очереди. Рекомендуется создавать отдельные потоки для событий из разных внешних систем — это позволяет изолировать ошибки одной интеграции от другой. Обработка запускается заданием `QueueEventsJob`.

| Поле | Что контролирует |
|------|------------------|
| `FlowName` | Имя потока |
| `IsStoppable` | Можно ли остановить поток при ошибках |
| `HasErrors` | Флаг наличия ошибок (красный индикатор в списке) |
| `FailedAttemptsCounter` | Накопленный счётчик неудачных попыток |
| `ActionOnError` | Стратегия при ошибке: `skip` (пропустить) / `stop` (остановить) |

#### Стратегии при ошибке

Поведение потока при ошибке обработки события:

| Стратегия | Поведение | Счётчик попыток |
|-----------|-----------|-----------------|
| **Пропускать** | Ошибочное событие пропускается, остальные продолжают обрабатываться | Не увеличивается |
| **Останавливать** | После ошибки обработка всего потока прекращается | Увеличивается |

При стратегии «Останавливать»: администратор должен удалить ошибочное событие из очереди или устранить причину ошибки, после чего запустить повторную обработку вручную.

### Смарт-скрипты

**Где настраивается:** вкладка «Скрипты» в Общих смартах и в разделе «Смарты» настроек категории / Admin API (`/api/admin/smart/scripts/editor`)
**Нет формы автоадминки** — настраиваются только через API/SPA
**Базовый путь:** `/api/admin/smart/scripts/...`

Смарт-скрипты — произвольный код на 5 языках (Lua/JavaScript/Python/OneScript/C#), выполняемый как действие в пакете или по расписанию.


#### Получение списка скриптов

Список смарт-скриптов возвращает запрос:

```http
POST /api/admin/smart/scripts/list
```

Возвращает страницу скриптов без кода (`Scripts`) и общее число скриптов под фильтр (`Total`). Этим запросом работает вкладка «Скрипты» в общих смартах и в настройках категории: грид передаёт `SubcatId` с пагинацией, без `SubcatIndependent`, догружает список страницами до `Total`, счётчик вкладки берёт из `Total`, а название события сервер заполняет в ответе сам — поле `EventName` рядом с `EventId`.

Параметры запроса (`SmartScriptsQueryRequestDto`):

| Параметр | Тип | Назначение |
|---|---|---|
| `IsContextual` | `bool?` | Фильтр по признаку контекстности: `true` — только контекстные скрипты, `false` — только неконтекстные. Игнорируется, если задан `ContextType` |
| `ContextType` | enum | Фильтр по конкретному типу контекста скрипта; имеет приоритет над `IsContextual` |
| `SubcatId` | `int?` | Категория |
| `EventId` | `int?` | Событие |
| `IsLibrary` | `bool?` | Фильтр библиотечных скриптов |
| `DescriptionFilter`, `ScriptFilter` | `string?` | Поиск по названию и по коду |
| `Skip`, `Take`, `OrderBy`, `Order` | — | Пагинация и сортировка |

Облегчённый вариант — `GET api/admin/smart/scripts/list/{subcatId?}`, грид AdminSPA его не вызывает: возвращает DTO `SmartScriptItemDto` с уже резолвнутыми именами события (`EventName`) и категории (`SubcatName`) и признаком публикации (`IsLibrary`), без загрузки кода скриптов и без N+1. Без `subcatId` возвращаются глобальные скрипты, с ним — категорийные и глобальные. Сервис проверяет `CheckGodOrSubcatAdmin`, но у метода нет `[AllowNonAdmin]`, поэтому общий гейт `[RoleAdmin]` пропускает к нему только глобального администратора.

#### Создание нового скрипта

В AdminSPA новый скрипт создаётся кнопкой «Создать» на вкладке «Скрипты»: окно «Создание автоматизации» спрашивает название и режим редактирования (Lua, JavaScript, Python, OneScript, CSharp), затем в новой вкладке браузера открывается редактор нового скрипта. Через API новый скрипт создаётся отправкой объекта редактора:

```python
editor_dto = {
    "script": {
        "id": 0,                    # 0 = новый (сервер назначит ID)
        "description": "Название",
        "scriptCode": js_code,
        "language": "JavaScript",   # "Lua" | "JavaScript" | "Python" | "OneScript" | "CSharp"
        "contextType": "None",      # СТРОКА, не null, не ""
        "isLibrary": False,
        "isDisabled": False
    },
    "context": None                 # null при программном создании
}
# POST /api/admin/smart/scripts/editor
```

#### Проверка компиляции скрипта в редакторе

Проверить компиляцию кода без выполнения:

```http
POST /api/admin/smart/scripts/editor/compile
```

Компилирует код из редактора **без выполнения** и возвращает ошибки компиляции. Применяется редактором для проверки C#-скриптов (Roslyn) до сохранения или запуска — в отличие от `editor/execute`, который компилирует и сразу выполняет скрипт.

**Значения `language`** (enum `ScriptLanguage` / `LanguageId` в БД):

| Строка | `LanguageId` | Движок | Модель | Таймаут | Возврат результата | Доступ к API платформы |
|--------|--------------|---------|--------|---------|-------------------|----------------------|
| `"Lua"` | `0` | NLua | Внутренний | 5 мин | `RESULT = value` | SQL, SMART, HTTP, CACHE, REGISTRY, FILES, UTILS |
| `"JavaScript"` | `1` | Jint | Внутренний | 5 мин | `RESULT = value` | SQL, SMART, HTTP, CACHE, REGISTRY, FILES, UTILS |
| `"Python"` | `2` | Python Executor (HTTP) | Внешний сервис (Docker) | 30 сек | `return value` из `execute(ctx)` | **Нет** — только данные из `ctx` |
| `"OneScript"` | `3` | OneScript | Внутренний | 5 мин | `RESULT = value` | SQL, SMART, HTTP, CACHE, REGISTRY, FILES, UTILS |
| `"CSharp"` | `4` | Roslyn | Внутренний | 5 мин | Последнее выражение | прямой доступ к API платформы |

> **Различия движков:** Lua, JS, OneScript и C# выполняются внутри сервера. Python — внешний сервис (Docker): получает только сериализуемые данные через `ctx`. Адрес и ключ доступа сервиса Python Executor настраиваются в интеграциях — см. [integrations/admin.md](https://help.1forma.ru/domains/integrations/admin.md), раздел «Сервис Python Executor».

#### Контекстные переменные

Основные переменные (`CONTEXT`, `EVENTPARAMS`, `SESSION_USER`) — см. [academy-patterns.md](https://help.1forma.ru/domains/smart-actions/academy-patterns.md). Дополнительные:

| Переменная | Доступна в | Описание |
|-----------|-----------|----------|
| `DB_TYPE` | Lua, JS, OneScript | Тип СУБД: `"MSSQL"` или `"PG"`. Используется для условного ветвления SQL-кода |
| `SYSTEM_INFO` | Lua, JS, OneScript | Объект с полями: `version` — версия приложения, `app` — имя приложения (`TC` или `Uniform`) |
| `RESULT` | Все языки | Выходной параметр скрипта. В Lua/JS/OneScript: `RESULT = value`. В Python: `return value` из `execute(ctx)`. В C#: последнее вычисленное выражение |

#### Репозиторий и include()

Скрипты-библиотеки подключаются функцией `include`:

- `include(id)` — по ID (рекомендуется)
- `include("Description")` — по названию (не рекомендуется)
- `include(id, subcatId)` — по ID из указанной категории (снимает ограничение «только из репозитория»)

> ⚠️ Максимальная глубина рекурсии `include` — **5 уровней**. В подключаемых скриптах не должно быть вычислений, только определения функций и глобальных переменных.

#### Интерфейс редактора скриптов

Элементы интерфейса редактора скриптов:

- **⏱** — история версий скрипта (каждый POST в `/editor` создаёт новую версию)
- **···** — меню: «Из репозитория/библиотеки» (пометка библиотечного скрипта), переход в «Репозиторий»
- **Выполнить** — тестовый запуск скрипта. Результаты отображаются в нижней панели
- **Параметры выполнения** (раскрывающаяся панель) — поля «Id контекста» и параметры события для тестирования

**Нижняя панель результатов** после тестового запуска состоит из двух вкладок:

- **«Результат»** — значение, присвоенное переменной `RESULT` в теле скрипта.
- **«Сообщения»** — логовый вывод скрипта: всё, что выведено через `print`, `var_dump()` и другие функции вывода.

> `var_dump()` выводит отладочную информацию в «Сообщения», а не задаёт возвращаемое значение скрипта. Чтобы значение появилось на вкладке «Результат», присвойте его переменной `RESULT`.

#### Обновление существующего

Чтобы обновить скрипт, изменяйте только код, остальные поля сохраняйте как есть:

```python
# 1. GET текущий DTO
resp = GET(f"/api/admin/smart/scripts/{script_id}/editor")
dto = resp["data"]

# 2. Обновить ТОЛЬКО код — остальные поля НЕ ТРОГАТЬ
dto["script"]["scriptCode"] = new_code

# 3. POST обратно
POST("/api/admin/smart/scripts/editor", {"script": dto["script"], "context": None})
```

**Критически важно:** при деплое менять только `scriptCode`. Все остальные поля (`eventId`, `language`, `contextType`, `subcatId`) — из GET, как есть. Ручная установка `eventId` — только при создании нового SS (`id=0`).

**Тип контекста скрипта.** Тип контекста (`contextType`) определяет, какой объект `CONTEXT` получает скрипт при исполнении: `None`, `Task`, `User`, `Mail`, `EdocumentLink`, `MessageQueue`, `EdocumentSbisLink`. Тип ставится при создании скрипта и дальше меняется только явным действием: у существующего скрипта `POST /api/admin/smart/scripts/editor` тип не меняет — в базе остаётся прежнее значение, что бы ни пришло в теле запроса, а параметр `contextType` в адресе `GET /{id}/editor` игнорируется и в ответе приходит тип из базы. Поэтому при обновлении существующего скрипта поле `contextType` можно не передавать вовсе; для нового скрипта его задают строкой (`"None"`, `"Task"`, `"User"`).

Смена типа — отдельный запрос `POST /api/admin/smart/scripts/{scriptId}/context-type` с телом `{"contextType": "Task"}`. Меняется только значение типа: код, имя, модуль, версия и описание остаются прежними, новая версия не создаётся, а изменение записывается в журнал действий в виде «было → стало». Недопустимое значение — ошибка валидации, несуществующий скрипт — 404. В редакторе AdminSPA тот же вызов делает пункт «Тип контекста» в меню «три точки» сохранённого скрипта; при создании скрипта на вкладке «Скрипты» тип выводится из экрана, с которого создают скрипт.

#### Версии и откат

`GET /api/admin/smart/scripts/{scriptId}/versions` — возвращает не более 10 последних версий скрипта, отсортированных по убыванию номера версии. Версия создаётся автоматически при каждом POST в `/editor`.

**Откат:** получить `scriptCode` нужной версии → POST в `/editor` с тем же `id`. Это создаст новую версию (не перезапишет).

**Удаление версий:** `DELETE /{scriptId}/versions/{version}` (одна) или `DELETE /{scriptId}/versions/` (все). Текущая активная версия не удаляется.

#### Таблица SmartScripts — колонки БД

Реальные имена колонок таблицы `SmartScripts` отличаются от ожидаемых:

| Ожидаемое | Реальное | Ловушка |
|-----------|----------|---------|
| `SmartScriptId` | **`Id`** | `Invalid column name 'SmartScriptId'` |
| `Language` | **`LanguageId`** | Enum int, не строка в БД |
| `Name` | **`Description`** | Нет колонки Name |

#### Инициатор фонового запуска

Скрипт, запущенный в фоне (`run_script_background` / `RunScriptBackground`), выполняется под инициатором из своей карточки — поле `InitiatorUserId` в таблице `SmartScripts`, в DTO редактора `initiatorUserId`.

- Инициатор не задан — фоновый запуск отклоняется с сообщением о том, что инициатор не задан. Субъект выполнения не выбирается автоматически: ни автор вызова, ни системный робот не подставляются.
- Карточку сценария фоновая очередь читает под тем, кто вызвал запуск, а сам скрипт выполняет в отдельном скоупе под инициатором из карточки.
- Отдельного элемента управления в форме редактора нет. Значение задаётся полем `initiatorUserId` внутри объекта `script`: `GET /api/admin/smart/scripts/{id}/editor` отдаёт текущее, POST в `/api/admin/smart/scripts/editor` сохраняет изменённое.
- При сохранении через редактор пустое значение (`null`) не затирает то, что уже записано в базе; ноль сохраняется как «не задан». Указанный пользователь проверяется на существование.
- Фоновым сценариям Анфисы инициатора проставляет кодовая миграция: список названий (`Description`) в ней фиксирован и сверяется на равенство целиком (не по вхождению) — по коллации БД, то есть без учёта регистра и хвостовых пробелов на MS SQL и с учётом регистра на PostgreSQL, — идентификатор берётся из настройки `anfisa_user_id`. Записывает только там, где инициатор пуст или равен системному роботу, — другого назначенного администратором пользователя (не робота) не перезаписывает. Настройка пуста или указанного пользователя нет — миграция не выполняется и повторяется при следующем старте главной ноды, пока включён `CodeMigrationsIsEnabled`.
- Явный пользователь в файловых методах скриптового API проверяется по сессии, а не по переданному значению — см. справочники языков скриптов.

#### URL — частая ошибка

**`smart-scripts` (дефис) → 404.** Правильно: **`smart/scripts`** (слеш).

- `GET /api/admin/smart/scripts/{id}/editor` — читать
- `POST /api/admin/smart/scripts/editor` — сохранить

#### Типичные ошибки SmartScript API

Частые ошибки при работе со смарт-скриптами через API:

| Проблема | Причина | Решение |
|----------|---------|---------|
| Отказ «Не переданы данные скрипта» при POST | Тело запроса без обёртки `{script, context}` | Всегда `{script: {...}, context: null}`. Пустое тело отклоняется отдельным сообщением «Не передано тело запроса»; так же проверяются `editor/compile` и `editor/execute` |
| `EVENTPARAMS` не определён при выполнении | `eventId` сброшен в null после GET→POST | Проверить что GET вернул правильный `eventId`. Если null — восстановить оригинальное значение |
| `contextType` ошибка | Передали null или пустую строку в DTO нового скрипта | Строка `"None"` (или `"Task"`, `"User"`). У существующего скрипта поле не проверяется — тип берётся из базы |
| «Фоновый запуск SmartScript id=N запрещён: не задан инициатор (InitiatorUserId в карточке сценария)» | У сценария не задан инициатор фонового запуска | Поля инициатора в форме редактора нет: взять DTO через `GET /{id}/editor`, проставить в `script` поле `initiatorUserId` и отправить POST в `/editor` |
| «Пользователь инициатора N не найден» | Указанный инициатор не существует | Передать идентификатор существующего пользователя |
| `GET /editor` vs `GET /{id}/editor` | Первый = новый пустой объект, второй = существующий | Для чтения: `/{id}/editor` |
| `include(503)` → crash | SS 503 занят другим скриптом | Проверить ID скрипта перед include() |
| Jint StatementsCountOverflow | Лимит инструкций при выполнении | `MaxStatements(100_000_000)` по умолчанию. Для больших выборок — разбивать на части |
| `languageId` вместо `language` | API молча игнорирует `languageId`, ставит Lua=0 | Поле называется `language`. Значения: `"JavaScript"` (строка) или `1` (int). Enum: Lua=0, JavaScript=1, Python=2, OneScript=3, CSharp=4 |
| `RESULT is not defined` | Jint ReferenceError при `if (!RESULT)` | Использовать `typeof RESULT === "undefined"` или `var RESULT;` в начале |
| `include(N)` → 500 без диагностики | Ошибка при загрузке include — до try/catch | Отладка: `POST /api/admin/smart/scripts/editor/execute`. Тестировать include по одному |
| Публикация → 500 без деталей | Publication endpoint глотает ошибки SS | Для отладки: `editor/execute` возвращает `{success, error, output, result}` |
| Тип контекста скрипта не меняется | Ждали, что тип сменится при сохранении из редактора | Тип существующего скрипта меняет только `POST {id}/context-type`; `POST editor` его не трогает |

#### HTTP из SmartScript

Особенности HTTP-запросов из смарт-скриптов:

| Проблема | Причина | Решение |
|----------|---------|---------|
| `HTTP.send_http_request` + multipart → FormatException | `StringContent(body, enc, contentType)` ломается на boundary | Не подходит для multipart. Использовать `HTTP.post_multipart` |
| `HTTP.post_multipart` без filename | `Value` (без `FileId`) → `StringContent` без `filename=` | Для загрузки файлов: только через `FileId` (из хранилища 1Формы) |
| Большой SS-код через shell → crash | `jq --argjson` / переменные > 64KB | Python для отправки больших SS: `urllib.request` + `json.dumps()` |

#### HTTP-опции: изоляция cookie (`DisableCookieContainer`)

По умолчанию «простые» HTTP-запросы из скриптов переиспользуют общий клиент с общим хранилищем cookie (~2 минуты). Если внешний сервис выдаёт сессионную cookie, при повторном запросе на тот же домен `Set-Cookie` повторно не приходит — подставляется ранее сохранённая.

Опция **`DisableCookieContainer`** отправляет запрос через отдельный одноразовый клиент без общего хранилища cookie: каждый вызов изолирован, внешний сервис всегда отдаёт `Set-Cookie` заново. По умолчанию выключена. Полезна для ротации сессий с лимитом одновременных соединений (например, внешний «Спарк»: нужно явно закрывать каждую сессию и каждый раз получать новый идентификатор).

Имя ключа зависит от языка скрипта (внимание на регистр):

| Язык | Ключ |
|---|---|
| **Lua** | `disable_cookie_container` (snake_case) |
| **JS-Jint** | `DisableCookieContainer` (PascalCase) |
| **C# (Roslyn)** | `DisableCookieContainer` |

Lua:
```lua
-- Прямой таблицей:
local opts = { disable_cookie_container = true }
local resp = HTTP:send_http_request('POST', url, {}, headers, body, opts)

-- Или через json_decode (ключи в JSON тоже snake_case):
local opts = UTILS:json_decode('{"disable_cookie_container": true}')
```

JS-Jint:
```javascript
var resp = HTTP.send_http_request('POST', url, null, headers, rawBody,
    { DisableCookieContainer: true });
```

C# (Roslyn SS):
```csharp
var resp = await HTTP.send_http_request_async("POST", url,
    @params: null, headers: headers, rawBody: rawBody,
    options: new ScriptHttpRequestOptions { DisableCookieContainer = true });
```

**Когда использовать.** Включайте флаг для запросов, где сервер выдаёт сессионную cookie и она должна доходить до скрипта при каждом вызове (закрытие/ротация сессий, аутентификация по короткоживущим токенам). Для обычных интеграций оставляйте выключенной — переиспользование хранилища cookie экономит соединения. Флаг действует только на текущий запрос; другие запросы того же скрипта без флага продолжают использовать общий клиент.

#### Таблицы логов SmartScript

**`AutomationScriptsLog`:** `Id, Type, ObjectKey, ObjectParams, AdditionalInfo, TaskId, UserID, ThreadId, Duration, Date, ObjectName, PlanExecution, AppPool`

- **НЕ существует:** `Tag`, `Message`, `ScriptId`, `EventName`, `CreatedDate`
- API `/api/admin/smart/scripts/logs` ненадёжен — может вернуть невалидный JSON. Использовать SQL.

##### Data-source view для журнала (`vw_ds_LogAutomationScripts`, с 2.268.396)

Data-source view `vw_ds_LogAutomationScripts` — источник данных для страницы журнала автоматизации в AdminSPA (грид, фильтры, отчёты). В версии 2.268.396 набор колонок view расширен и приведён к составу базовой таблицы `AutomationScriptsLog`, чтобы фильтры и колонки на клиенте отражали реальный лог без пост-обработки:

| Колонка | Что содержит |
|---------|--------------|
| `PlanExecution` | План выполнения SQL-запроса (PG), пишется когда `Duration > LogPlanExecution`. См. § «План запроса (только PostgreSQL)» выше |
| `AppPool` | Идентификатор пула приложений (worker-процесса), где сработал скрипт — для диагностики многопуловых установок |
| `CommentId` | ID комментария-инициатора (для событий, где триггером стал комментарий: `AfterPostComment` и т. п.) |
| `SessionId` | ID пользовательской сессии, из которой стартовал вызов — соединяет запись журнала с сессией в `dbo.Sessions` |

Ранее эти колонки существовали в базовой таблице, но не пробрасывались через view — фильтровать/группировать по ним из грида было нельзя.

#### Lua SmartScript — особенности

Особенности и подводные камни Lua-скриптов:

| Проблема | Симптом | Решение |
|----------|--------|---------|
| `SQL:query(sql)` без второго аргумента | Запросы молча возвращают nil | Всегда `SQL:query(sql, {})` — второй аргумент обязателен, даже если пустой |
| PG: кавычки вокруг идентификаторов | `"Tasks"` → `relation "Tasks" does not exist` | На dev-pg таблицы/колонки lowercase без кавычек (схема `dbo`) |
| PG: `CASE` ветки с разными типами | `42804: CASE types text and timestamp cannot be matched` | Обе ветки CASE должны возвращать одинаковый тип |
| PG: `WITH RECURSIVE` | `syntax error at or near "UNION"` | PG требует `WITH RECURSIVE`. MSSQL не принимает. Решение: `WITH {PG}RECURSIVE{/PG}` |

### Журнал автоматизации

Журнал фиксирует выполнение смарт-автоматизаций: смарт-запросов, действий, расписаний, пакетов, смарт-доступа и смарт-скриптов. Запись SQL-запросов — через NLog.

#### Пороги логирования (Общие настройки приложения)

Для каждого типа смарт-операции задаётся отдельный порог в миллисекундах. Логика общая:

- **поле пустое** → логирование выключено;
- **0** → логируются все вызовы;
- **>0** → логируются только вызовы дольше порога.

⚠️ Минимально допустимое значение — **100 мс** (форма не позволит сохранить меньше). Рекомендуемое значение для большинства типов — **200 мс**.

| Настройка | Тип | Рекомендация |
|-----------|-----|--------------|
| Логировать время выполнения **smart-запросов** дольше, мс | smart expressions | 200 |
| Логировать время выполнения **smart-действий** дольше, мс | actions | 200 |
| Логировать выполнение **smart-пакетов** дольше, мс | packs (с 2.264 «Кассиопея» — также смарт-расписания) | 200 |
| Логировать выполнение **smart-доступа** дольше, мс | smart access | 200 |
| Логировать **SQL-запросы** | NLog SQL | 200 (по умолчанию) |
| Логировать обращение в **WS из БД** дольше, мс | внешние HTTP-вызовы из SQL | 5000 |
| Логировать обращения к **опубликованным объектам** дольше, мс | publications | по необходимости |
| Ограничение длины **трейса ошибок**, кб | exception traces | 2 кб (по умолчанию) |

Связанные тонкие настройки:

- **Логировать тело запроса к WS из БД** — флаг. Если включён, тело запроса пишется в «Входящие параметры», тело ответа — в «Доп. информация» лога автоматизации.
- **Запускать SMART-запросы и фильтры с использованием ReadUncommitted транзакций** — *устаревшая* (не используется). Для MS SQL Server включала отдельную транзакцию с уровнем изоляции `READ UNCOMMITTED` для смарт-выражений; на PostgreSQL не применяется (требовала бы prepared transactions). На PG разрешено подавление ambient-транзакции (`SuppressTransaction`) для смарт-выражений с отдельной строкой подключения (`SmartConnectionString`).

#### Глубина рекурсии смарт-пакетов

Параметр **«Глубина рекурсии смарт-пакетов»** (значение по умолчанию — `10`) ограничивает количество рекурсивных вызовов смарт-действия в рамках одного потока (поток определяется `TaskID` + `UserID` + текущим контекстом). При превышении смарт-действие не выполняется, ошибка фиксируется в журнале ошибок.

⚠️ Проверка **не применяется** к пакетам, выполняющимся в рамках расписаний, и к циклическим пакетам.

#### Уровень детализации скриптов

Настройка `SmartScriptLogLevel` управляет объёмом записей от скриптов.

| Значение | Записываются | Макс. сообщений за выполнение |
|----------|-------------|-------------------------------|
| `Debug` | Все: debug + info + error | debug ≤ 10, info ≤ 30, error ≤ 50 |
| `Info` | Информационные + ошибки | info ≤ 30, error ≤ 50 |
| `Error` *(по умолчанию)* | Только ошибки | error ≤ 50 |

Методы в скриптах: `SS.logDebug()`, `SS.logInfo()`, `SS.logError()` (Lua/JS/OneScript); `console.log()`, `console.warn()`, `console.error()` (JS). Сообщения сверх лимита отбрасываются.

> ⚠️ **`SS.logError()` и `console.error()` дублируются в журнал исключений (`ExceptionsLog`) независимо от `SmartScriptLogLevel`.** Ошибки скриптов всегда видны в журнале «Ошибки» как тип `SmartScript`.

> ⚠️ При ручном запуске скрипта из интерфейса администрирования в журнал автоматически записывается текст исполняемого скрипта и пользователь-инициатор. Запись создаётся и для несохранённого скрипта — сохранять его в репозитории ради аудита не нужно. Текст длиннее 4096 символов в журнале усекается: остаются первые 4096 символов и суффикс `...[truncated]`.

Идентификация в стек-трейсах и `ExceptionsLog`: формат `SmartScript {ID} ("{Название}")`.

Реализация и затронутые файлы — см. [known-issues.md](https://help.1forma.ru/domains/smart-actions/known-issues.md) § 6 «SmartScript Execution Logger».

#### План запроса (только PostgreSQL)

Колонка «План запроса» отображается при условиях:

1. СУБД — PostgreSQL
2. Включено логирование SQL-запросов в общих настройках приложения
3. Задана настройка `LogPlanExecution` (порог в миллисекундах)
4. Фактическое время выполнения превысило порог

#### CustomSettings — таймауты и логирование скриптов

Ключи пользовательских настроек, влияющие на выполнение и логирование скриптов:

| Ключ | Тип / По умолчанию | Назначение |
|------|---------------|------------|
| `SmartScriptJSEngineTimeoutMinutes` | int (мин) | Таймаут выполнения JavaScript SmartScript в движке Jint. Применяется как к таймауту скрипта, так и к `PromiseTimeout`. Изменение вступает в силу **без перезапуска** сервера |
| `LogPlanExecution` | int (мс) | Порог для записи плана выполнения SQL-запроса в журнал автоматизации (только PG, см. выше) |

### Расписание (SmartRecurrence)

**Где настраивается:**

- Настройки категории → Смарты → Расписания
- Общие смарты (`/administration/smart-packs`) — для расписаний без привязки к конкретной категории
- Admin API (`/api/admin/smart/recurrences`, GET/POST/PUT) — программное создание расписаний

Механизм периодического запуска пакетов действий по таймеру. В отличие от смарт-правил, не привязан к событию — запускается заданием `SmartRecurrenceJob`.

#### Список расписаний

Расписания с ошибками выделены **красным**. Колонка **«Из лимита»** — накопленное число неудачных попыток; **«Лимит»** — максимально допустимое (соответствует настройке «Попыток выполнения»).

#### Поля формы

Поля формы расписания:

| Настройка | Описание |
|-----------|----------|
| **Активно** | Включает/выключает расписание. Отключённое расписание сохраняется, но не запускается. |
| **Интервал** | Частота запуска: Ежеминутно / Ежедневно / Еженедельно / Ежемесячно / Ежегодно. Синтаксис отличается от cron — настраивается через форму, не строкой. |
| **Начало повторения** | Дата и время первого запуска. |
| **Прекратить после** | Дата и время, после которых расписание не запускается, даже если интервал наступает. |
| **Повторять до** | Конечная дата действия расписания; после достижения выполнение прекращается. |
| **Вложенные файлы** | Режим работы с файлами задач: «Не копировать» / «Использовать ссылки на оригинал» / «Создать новые копии». |
| **Попыток выполнения** | Максимальное число повторных попыток при ошибке. По умолчанию: 5, минимум: 1. При превышении расписание становится красным и не запускается до сброса счётчика. |
| **Асинхронно** | Пакет выполняется в отдельном потоке. По умолчанию все расписания используют один поток — «тяжёлый» пакет может задержать остальные. Флаг устраняет это: пакет запускается параллельно. |
| **Режим выполнения** | «Вне контекста задачи» — пакет выполняется независимо от конкретной задачи (доступен ограниченный набор действий). «Для каждой задачи [смарт-фильтр]» — пакет выполняется для каждой задачи категории, удовлетворяющей условию фильтра. |
| **Пакет действий** | Пакет, который запускается в назначенное время. |
| **Игнорировать ошибки в процессе выполнения** | Если включено — при ошибке на одной задаче выполнение продолжается для следующих. По умолчанию выключено: ошибка прерывает весь запуск. |

#### Операции со строкой расписания

Действия над строкой расписания:

| Действие | Как вызвать |
|----------|-------------|
| Создать | Кнопка «Создать смарт-расписание» над таблицей |
| Редактировать | Клик по строке или контекстное меню → «Редактировать» |
| Принудительно выполнить | Контекстное меню → «Выполнить» — запускает пакет вне графика |
| Сбросить счётчик ошибок | Контекстное меню → «Сбросить счётчик неудачных попыток» — расписание перестаёт быть красным и снова доступно |
| Удалить | Контекстное меню → «Удалить» |

**Несохранённые изменения в окне расписания.** Если в окне создания или редактирования расписания есть несохранённые изменения, закрытие крестиком, клавишей Esc или кликом по фону открывает окно «Сохранить изменения». Кнопка «Сохранить» записывает расписание и закрывает окно только после успешной записи; если в форме есть ошибки, поля с ними подсвечиваются и окно расписания остаётся открытым. «Не сохранять» закрывает окно без записи, крестик окна предупреждения возвращает к редактированию. Без несохранённых изменений окно закрывается сразу.

#### Ограничения

Ограничения расписаний:

- Расписание в настройках категории работает **только в рамках этой категории** и только при наличии хотя бы одной задачи. Для задач из другой категории — используй расписания в Общих смартах.
- Пакеты с действием «Отменить» недоступны для расписаний.
- Проверка глубины рекурсии смарт-пакетов (см. выше) к расписаниям **не применяется**.

#### Смарт-фильтр в режиме «для каждой задачи»

Если фильтр был создан как смарт-выражение, а затем преобразован в TSQL, он по умолчанию содержит условие привязки к контексту задачи:

```sql
WHERE ([Extent1].[TaskID] = @ContextID) AND [...]
```

При использовании в расписании это условие нужно убрать: у расписания нет контекста одиночной задачи.

#### Логирование

С версии 2.264 «Кассиопея» выполнение расписаний логируется через настройку **«Логировать выполнение smart-пакетов дольше, мс»** (см. раздел «Журнал автоматизации» выше).

## Типичные ошибки настройки

Частые проблемы настройки смарт-действий и где их искать:

| Симптом | Причина | Где проверить | SQL-диагностика |
|---------|---------|---------------|-----------------|
| Правило не срабатывает | Неверный EventID или ParameterValue | Форма `general-smart` → EventID, ParameterValue | `select ID, Name, EventID, ParameterValue, IsFilter, SubcatID from dbo.SmartExpressions where SubcatID = {id} and IsFilter = 0` |
| Правило срабатывает дважды | Дубль в EventsActions + правило на глобальном уровне | привязки событий к пакетам | `select * from dbo.EventsActions where SubcatID = {id} and EventID = {eventId}` |
| Смарт-доступ не пересчитывается | Не настроены триггеры (ExtParams/Groups) | Формы `smart-access-trigger-*` | `select sa.Name, count(te.Id) as TriggerEP, count(tg.Id) as TriggerGrp from dbo.SmartAccess sa left join dbo.SmartAccessTriggerExtParams te on sa.ID = te.SmartAccessId left join dbo.SmartAccessTriggerGroups tg on sa.ID = tg.SmartAccessId group by sa.Name` |
| Массовые тормоза при изменении ДП | Слишком широкий триггер смарт-доступа (все ДП категории) | Форма `smart-access-trigger-extparams` | `select sa.Name, count(*) as TriggerCount from dbo.SmartAccessTriggerExtParams te inner join dbo.SmartAccess sa on te.SmartAccessId = sa.ID group by sa.Name order by count(*) desc` |
| Действие в пакете не выполняется | Асинхронное правило, очередь заполнена / остановлена | Форма `message-flows` → HasErrors | `select FlowName, HasErrors, FailedAttemptsCounter from dbo.MessageFlows where HasErrors = 1` |
| Произвольное событие не вызывается | Неверный Code или SubcatId | Форма `custom-events` | `select * from dbo.CustomEvents where SubcatId = {id}` |

## JSON-синтаксис модификации ДП

> **Для ДП «Таблица» есть путь без строки-команды.** Формат с префиксами `+`, `=` и `-` нужен для построчной правки значения; массовые операции — заполнение по выборке, синхронизацию, удаление строк по условию — выполняют смарт-действия «Добавить», «Изменить» и «Удалить строки ДП Таблица», см. [`actions-reference-files-data.md`](https://help.1forma.ru/domains/smart-actions/actions-reference-files-data.md), раздел «ДП — модификация значений».


Форматы значений для смарт-действий «Изменить значение ДП», «Копировать значение ДП» и аналогичных. Для сложных типов ДП платформа ожидает JSON-строку.

### SelectUsers — Выбор пользователей

Формат значения для ДП «Выбор пользователей»:

```json
{"Users":{"Added":[ID1,ID2],"Deleted":[ID3]},"Groups":{"Added":[ID4]},"OrgUnits":{"Deleted":[ID5]}}
```

- Ключи верхнего уровня: `Users`, `Groups`, `OrgUnits`
- Операции внутри: `Added` (добавить), `Deleted` (удалить)
- Значения — ID (int), преобразованные в строку
- Три способа указания значений: напрямую (ID), через другое ДП, через SQL-запрос

> При изменении через смарт-действие: если ДП допускает несколько элементов, новые значения **добавляются** к существующим, а не замещают их.

### Email — Адресаты

Формат значения для ДП «Адресаты»:

```json
{"ValsToAdd":[{"UserId":1821},{"Email":"user@example.com"}],"ValsToDelete":[{"UserId":14}]}
```

- `ValsToAdd` — добавить адресатов
- `ValsToDelete` — удалить адресатов
- Каждый адресат: `{"UserId": ID}` или `{"Email": "address"}`

### Таблица — строки и ячейки

Ячейка: `{ID_колонки:{"First":"значение","Second":"доп.значение"}}`

- `First` — основное значение (тип должен соответствовать типу колонки)
- `Second` — только для выпадающих списков (колонка значения). Для остальных типов — пропускается или `null`

| Операция | Префикс | Синтаксис | Пример |
|----------|---------|-----------|--------|
| Добавить строку | `+` | `'+[{111:{"First":"текст"},222:{"First":"123"}}]'` | ID строки не указывается |
| Изменить строку | `=` | `'={"First":"ID_строки","Second":{111:{"First":"новое"}}}'` | ID строки в кавычках |
| Удалить строку | `-` | `'-ID_строки'` | `'-111'` |
| Массовое обновление | `=` | `'=[{"First":"1","Second":{...}},{"First":"2","Second":{...}}]'` | Массив объектов |
| Заменить все (full replace) | `#` | `'#{"1":{11:{"First":"x"}},"2":{11:{"First":"y"}}}'` | Удаляет отсутствующие строки, изменяет существующие, добавляет новые |
| Пересоздать все | `\|` | `'|{"1":{...},"2":{...}}'` | Удаляет **все** строки, затем создаёт из переданных данных |

> ⚠️ Если ID существующих строк неизвестны — используйте оператор `\|`. При `#` строки с отсутствующими ID будут удалены.

> ⚠️ Если в таблице есть скрытые колонки — они **обязаны** быть заполнены, иначе возникнет ошибка.

Несколько операций в одном выражении объединяются через `#`:

```
'-111#222|||текст_комментария'
```

Массовое обновление нескольких строк — через массив объектов (префикс `=`).

### Выбор нескольких задач

Операции с ДП «Выбор нескольких задач»:

| Операция | Синтаксис | Пример |
|----------|-----------|--------|
| Добавить задачу | `ВСтроку(ID)` | `"123456"` |
| Добавить несколько | `ID + "]\|\|[" + ID` | `"111111]\|\|[222222]\|\|[333333"` |
| Удалить задачу | `"-" + ВСтроку(ID)` | `"-123456"` |
| Обновить комментарий | `"=" + ID + "\|\|" + "текст"` | `"=123456\|\|комментарий"` |
| Полная замена | `"$" + ID + "]\|\|[" + ID` | `"$123]\|\|[456]\|\|[789"` — заменяет все значения |
| Несколько операций | Через `#` | `"-111111#222222\|\|коммент"` |

### Файл

Режимы и форматы значений для ДП «Файл»:

| Режим | Описание |
|-------|----------|
| Единичный | Без признака «Мультифайл» — обращение как к одному объекту |
| Мультифайл | С признаком «Мультифайл» — обращение как к коллекции |
| Добавить к существующим | Префикс `+`: `'+id1,id2,id3'` |
| Версия файла | Формат `FileID.VersionID` |
| Из другой задачи | Через SQL-запрос к `FileStorageFiles` + `FileStorageFileToTaskLinks` |

### Экранирование в значениях

Правила экранирования спецсимволов в значениях:

| Символ | Экранирование | Пример |
|--------|---------------|--------|
| `"` | `\"` | `"начало \"ABC\" продолжение"` |
| `\` | `\\` | `"начало \\\\ продолжение"` |
| `'` | `''` | `"начало ''ABC'' продолжение"` |
| `\n`, `\r` | Не экранировать | Работает только для колонок «Большой текст» |

## Связанные документы

Смежные разделы:

- [Каталог действий](https://help.1forma.ru/domains/smart-actions/actions-reference.md) — 70+ типов смарт-действий с параметрами
- [Паттерны и примеры](https://help.1forma.ru/domains/smart-actions/academy-patterns.md)
- [Известные проблемы](https://help.1forma.ru/domains/smart-actions/known-issues.md)
