# Автоматическое распределение тикетов: правила автоназначения и ребалансировщик
Swarmica умеет распределять тикеты по агентам и группам без диспетчера. Это делают два механизма, и оба используют один и тот же упорядоченный список **правил автоназначения (AutoAssign)**:

| Механизм                               | Когда запускается                     | Какие тикеты обрабатывает                                                                                                                  |
| -------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| **Автоназначение при создании тикета** | Один раз, сразу после создания тикета | Новый тикет, созданный **без исполнителя и без группы**                                                                                    |
| **Ребалансировщик (Rebalancer)**       | Периодически, по расписанию CRON      | Тикеты в статусе **Новый** или **Открыт**, у которых **нет исполнителя** или исполнитель в статусе **Офлайн**, а тикет **не заблокирован** |

Каждое изменение записывается в историю тикета событием *«Применено правило автоназначения»*, автор события — системный пользователь **Swarmica**.

***

## 1. Правила автоназначения

Правила проверяются **сверху вниз, в заданном порядке**. Побеждает первое правило, которое дало результат; остальные для этого тикета уже не проверяются.

### 1.1 Опции правила

| Опция (API)           | Значения                              | Что делает                                                                                                                                                                                                                                                    |
| --------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Название (`name`), описание (`description`) | локализованный текст                  | Только для администраторов, на распределение не влияет.                                                                                                                                                                                                       |
| Включено (`enabled`) / Порядок (`order`)   | —                                     | В работе участвуют только включённые правила. Эти параметры настраиваются через визуальный редактор в разделе Настройка > Автоматизация > Автоназначение заявок. |
| Условия заявки (`ticket_conditions`)   | фильтр тикетов (Query Language)       | К каким тикетам применяется правило. Те же условия, что в фильтрах тикетов (статус, приоритет, канал, продукт, навыки, кастомные поля, SLA и т. д.). Пусто — любой тикет.  Настраивается через визуальный редактор.                                                                                   |
| Условия пользователя (`user_conditions`)     | фильтр пользователей (Query Language) | Каких агентов может выбрать правило (например, по роли, навыкам, сегменту, кастомным полям). Пусто — любой агент. Настраивается через визуальный редактор.                                                                                                                                            |
| Группа (`group`)               | группа или пусто                      | **Задана:** агент выбирается среди участников этой группы, а тикет **переводится в эту группу**. **Пусто:** агент выбирается среди всех агентов, группа тикета **не меняется**.                                                                               |
| Искать исполнителя внутри текущей группы заявки (`within_ticket_group`) | да / нет                              | Агент выбирается среди участников **текущей группы тикета** (любой агент, если у тикета нет группы). Группа тикета никогда не меняется. Нельзя сочетать с `group` и с `do_not_assign`.                                                                        |
| Назначение по навыкам (`skill_match`)         | пусто / `ANY` / `FULL`                | Как навыки тикета ограничивают пул агентов. **Пусто:** навыки не учитываются. **ANY:** у агента должен быть хотя бы один из навыков тикета. **FULL:** у агента должны быть все навыки тикета. Тикет без навыков не ограничивает пул.                          |
| Не назначать никого (`do_not_assign`)       | да / нет                              | Правило никогда не назначает агента. Вместе с `group` оно **только переводит тикет в группу** (маршрутизация в очередь). Без группы правило ничего не делает, и проверка переходит к следующему правилу.                                                      |
| Не обрабатывать другие правила (`skip_other_rules`)    | да / нет                              | **Стоп-правило:** если тикет под него подходит, тикет остаётся **как есть** (без агента и без смены группы), и следующие правила не проверяются. Используйте, чтобы исключить тикеты из автоматического распределения (VIP, определённый канал и т. п.).      |

Удаление правила архивирует его (оно пропадает из списка и выключается). При удалении навыка он убирается из условий всех правил; если в условии не осталось навыков, условие удаляется.

### 1.2 Как выбирается агент

Из агентов, попавших в пул правила (`user_conditions`, группа, навыки), рассматриваются только **доступные**:

*   статус агента — **Онлайн** или **В сессии** (агенты в статусах Занят, Завершение работы, Отошёл и Офлайн не выбираются никогда);
*   агент **не достиг личного лимита назначений** (`autoassign_limit` в профиле агента; `0` — без лимита; значение по умолчанию для новых агентов берётся из переменной окружения `DEFAULT_AUTOASSIGN_LIMIT`). Нагрузка — это число тикетов агента в статусах **Новый** и **Открыт**.

Затем доступные агенты ранжируются, и выбирается первый:

1.  **Статус:** Онлайн раньше, чем В сессии.
2.  **Нагрузка:** сначала те, у кого меньше тикетов Новый/Открыт.
3.  **Время ожидания:** первым идёт агент, который дольше всех не получал тикет (из любого источника — вручную, правилом, триггером); агент, которому тикеты ещё ни разу не назначались, идёт самым первым.
4.  При равенстве — по ID агента.

Нагрузка пересчитывается для каждого тикета, поэтому за один запуск ребалансировщика тикеты распределяются по кругу между наименее загруженными агентами.

### 1.3 Сочетания опций

| Поместить тикет в группу  | Искать исполнителя в текущей группе | Не назначать никого | Не обрабатывать другие правила | Результат для подходящего тикета                                                                                                               |
| -------- | --------------------- | --------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| —        | —                     | —               | ✔                  | Тикет не меняется; следующие правила не проверяются. *(Остальные опции стоп-правила игнорируются.)*                                            |
| пусто    | нет                   | нет             | нет                | Агент из **всех** агентов; группа тикета не меняется. Если доступного агента нет — следующее правило.                                          |
| Группа A | нет                   | нет             | нет                | Агент из **группы A**; тикет **переводится в A** и назначается. Если в A нет доступного агента — следующее правило (тикет **не** переводится). |
| Группа A | нет                   | ✔               | нет                | Тикет **переводится в A без агента**. Срабатывает всегда — доступность агентов не важна.                                                       |
| пусто    | нет                   | ✔               | нет                | Не действует: правило пропускается, проверяется следующее. *(Такой настройки лучше избегать.)*                                                 |
| пусто    | ✔                     | нет             | нет                | Агент из **собственной группы тикета** (любой агент, если у тикета нет группы); группа не меняется. Если агента нет — следующее правило.       |
| Группа A | ✔                     | любое           | —                  | **Запрещено** (ошибка валидации).                                                                                                              |
| пусто    | ✔                     | ✔               | —                  | **Запрещено** (ошибка валидации).                                                                                                              |

`skill_match` и `user_conditions` можно добавить к любой назначающей строке — они только сужают пул агентов.

**Типовые настройки**

*   *Сначала маршрутизация в очередь, потом назначение:* правило «Тикеты по биллингу → группа Биллинг, `do_not_assign`» стоит выше назначающего правила с `within_ticket_group`. Первое правило маршрутизирует, а ребалансировщик (в режиме GROUP) затем назначает агента внутри группы.
*   *Цепочка с запасными вариантами:* «Агенты L2 с навыками (`skill_match = FULL`)» → «Любой агент L2» → «Любой агент L1». Если в первом пуле никто не доступен, пробуется следующее правило.
*   *Исключение:* правило со `skip_other_rules` в самом верху для тикетов, которые нужно распределять вручную.

***

## 2. Автоназначение при создании тикета

Настройка: `enable_autoassign` в настройках ребалансировщика (по умолчанию включена).

*   Запускается один раз, в фоне, сразу после создания тикета.
*   Запускается **только** если тикет создан **без исполнителя и без группы** (тикет, созданный сразу в группе — например, каналом или триггером, — остаётся ребалансировщику).
*   **Не зависит** от режима ребалансировщика — работает и при выключенном ребалансировщике.
*   Проверяются все включённые правила (ограничение режима GROUP не действует — у тикета всё равно нет группы).
*   Если ни одно правило не дало результата, тикет остаётся неназначенным; позже его может подхватить ребалансировщик.

```mermaid
flowchart TD
    A([Тикет создан]) --> B{Есть исполнитель<br/>или группа?}
    B -- да --> Z1([Пропуск: оставить как создан])
    B -- нет --> C{enable_autoassign<br/>включён?}
    C -- нет --> Z2([Пропуск])
    C -- да --> D[Фоновая задача:<br/>включённые правила по порядку]
    D --> E{Следующее правило?}
    E -- правил не осталось --> Z3([Без изменений:<br/>тикет не назначен])
    E -- правило --> F{Тикет подходит под<br/>ticket_conditions?}
    F -- нет --> E
    F -- да --> G{skip_other_rules?}
    G -- да --> Z4([Стоп: тикет не меняется])
    G -- нет --> H{do_not_assign?}
    H -- да --> I{У правила есть группа?}
    I -- нет --> E
    I -- да --> J[Перевести тикет в группу<br/>правила, без агента]
    H -- нет --> K[Собрать пул агентов:<br/>user_conditions + skill_match<br/>+ группа правила / группа тикета / все]
    K --> L[Оставить агентов Онлайн / В сессии<br/>ниже их autoassign_limit]
    L --> M{Есть агент?}
    M -- нет --> E
    M -- да --> N[Выбрать лучшего агента:<br/>статус → нагрузка → дольше ждёт → id]
    N --> O[Назначить агента;<br/>перевести в группу правила, если задана<br/>и нет within_ticket_group]
    J --> P([Сохранить тикет + событие<br/>«Применено правило автоназначения»<br/>от Swarmica])
    O --> P
```

***

## 3. Ребалансировщик

Ребалансировщик — одна глобальная настройка.

### 3.1 Опции ребалансировщика

| Опция (API)         | Значения                                    | Что делает                                                                                                                                                                                                         |
| ------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `mode`              | `NONE` / `GROUP` / `ALL`                    | **NONE:** периодическая ребалансировка выключена. **GROUP:** тикеты остаются в своей группе (см. ниже). **ALL:** каждое включённое правило применяется к каждому тикету, тикеты могут переводиться между группами. |
| `cron_schedule`     | выражение CRON, по умолчанию `*/30 * * * *` | Как часто запускается ребалансировщик (по умолчанию каждые 30 минут).                                                                                                                                              |
| `enable_autoassign` | да / нет, по умолчанию да                   | Включает или выключает автоназначение при создании тикета (раздел 2). Не зависит от `mode`.                                                                                                                        |

Переменная окружения: `REBALANCER_LOCK_TIMEOUT` (в секундах, по умолчанию 1800) — максимальное время, на которое один запуск удерживает блокировку.

### 3.2 Какие тикеты ребалансируются

Тикет ребалансируется, если выполнены **все** условия:

*   статус **Новый** или **Открыт** (тикеты в статусах Ожидание, Решён, Закрыт и т. д. не трогаются никогда);
*   у тикета **нет исполнителя**, **или** его исполнитель в статусе **Офлайн**, а тикет **не заблокирован** (заблокированный тикет остаётся за офлайн-исполнителем).

Тикеты обрабатываются **начиная с самых срочных** — по индексу нарушения SLA, по убыванию; тикеты без SLA идут последними.

### 3.3 Режимы GROUP и ALL

Режим определяет, какие правила может использовать тикет:

| Тикет      | Режим **ALL**                                                       | Режим **GROUP**                                                                                                                |
| ---------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| Без группы | все включённые правила                                              | все включённые правила                                                                                                         |
| В группе X | все включённые правила (тикет может быть переведён в другую группу) | только правила с `group = X` и правила с `within_ticket_group`; правила с другой группой **и правила без группы** пропускаются |

Таким образом, в режиме GROUP тикет, уже находящийся в группе, никогда её не покидает и назначается только участникам этой группы. Тикет в группе, для которой нет подходящих правил, не меняется.

### 3.4 Алгоритм запуска

```mermaid
flowchart TD
    S([Срабатывание CRON]) --> L{Блокировка свободна?<br/>другой запуск не идёт}
    L -- нет --> X0([Пропустить запуск])
    L -- да --> M{mode = NONE?}
    M -- да --> X1([Стоп])
    M -- нет --> R{Есть включённое<br/>правило?}
    R -- нет --> X2([Стоп])
    R -- да --> T[Выбрать тикеты: статус Новый/Открыт<br/>и нет исполнителя, или исполнитель Офлайн<br/>и тикет не заблокирован]
    T --> T2{Есть тикеты?}
    T2 -- нет --> X3([Стоп])
    T2 -- да --> A{Есть агент Онлайн / В сессии<br/>ниже лимита ИЛИ правило do_not_assign<br/>с группой?}
    A -- нет --> X4([Стоп])
    A -- да --> O[Отсортировать тикеты по индексу<br/>нарушения SLA, самые срочные первыми]
    O --> NT{Следующий тикет?}
    NT -- не осталось --> DONE([Снять блокировку, завершить])
    NT -- тикет --> P[Подобрать правило и агента<br/>тем же алгоритмом, что при создании,<br/>с фильтром правил режима GROUP]
    P --> Q{Правило дало агента<br/>или группу?}
    Q -- нет --> NT
    Q -- да --> C{Изменится исполнитель<br/>или группа?}
    C -- нет --> NT
    C -- да --> RE[Перечитать тикет]
    RE --> V{Всё ещё Новый/Открыт, правило применимо,<br/>группа не менялась для правила within_ticket_group,<br/>изменение всё ещё есть?}
    V -- нет --> NT
    V -- да --> W[Сохранить исполнителя / группу<br/>+ событие «Применено правило автоназначения»<br/>от Swarmica]
    W --> NT
```

Шаг подбора правила (`P`) — тот же, что в разделе 2, с одной дополнительной проверкой в начале каждого правила: в режиме GROUP правило, не подходящее к группе тикета (раздел 3.3), пропускается.

```mermaid
flowchart TD
    E{Следующее правило?} -- не осталось --> N0([Правила нет: тикет не меняется])
    E -- правило --> G{Режим GROUP и тикет в группе X:<br/>группа правила = X или within_ticket_group?}
    G -- нет --> E
    G -- да / режим ALL / тикет без группы --> F{Тикет подходит под<br/>ticket_conditions?}
    F -- нет --> E
    F -- да --> SK{skip_other_rules?}
    SK -- да --> N1([Стоп: тикет не меняется])
    SK -- нет --> DN{do_not_assign?}
    DN -- да, с группой --> RG([Перевести в группу правила, без агента])
    DN -- да, без группы --> E
    DN -- нет --> AG{Есть доступный агент в пуле?}
    AG -- нет --> E
    AG -- да --> OK([Назначить лучшего агента;<br/>перевести в группу правила, если задана])
```

### 3.5 Важные детали

*   **Один запуск за раз.** Если запуск ещё идёт, когда наступает следующее срабатывание CRON, новый запуск пропускается. Запуск, простоявший в очереди дольше кратчайшего интервала расписания, тоже отбрасывается — следующий уже в очереди.
*   **Ранние выходы.** Запуск ничего не делает (и почти ничего не стоит), если режим NONE, нет включённых правил, нет тикетов для ребалансировки, или никто не доступен и нет правила, которое маршрутизирует без агента.
*   **Изменения, сделанные во время запуска, важнее.** Перед записью ребалансировщик перечитывает тикет. Если агент за это время перевёл его в Ожидание, назначил или перенёс в другую группу, тикет пропускается.
*   **Агент, вышедший в онлайн во время запуска,** будет учтён в следующем запуске.
*   **Офлайн-исполнитель + правило `do_not_assign`.** Такое правило меняет только группу: тикет переводится в группу правила, но остаётся за офлайн-исполнителем.
*   Переназначение тикета офлайн-агента не затрагивает другие настройки агента; чтобы тикет остался за офлайн-владельцем, **заблокируйте** тикет.

***

## 4. Сочетание автоназначения при создании и ребалансировщика

| `enable_autoassign` | `mode`      | Поведение                                                                                                                                                                                           |
| ------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| вкл                 | NONE        | Новые тикеты (без группы и исполнителя) назначаются один раз при создании. Если доступного агента не было, повторной попытки не будет, а тикеты офлайн-агентов остаются за ними.                    |
| вкл                 | GROUP       | Назначение при создании плюс периодическая доводка: неназначенные тикеты и тикеты офлайн-агентов переназначаются **внутри своей группы**. Рекомендуется для команд, организованных по группам.      |
| вкл                 | ALL         | Назначение при создании плюс периодическая доводка по всему хелпдеску; правила могут переводить тикеты между группами.                                                                              |
| выкл                | GROUP / ALL | При создании ничего не происходит; тикеты распределяются только по расписанию ребалансировщика (задержка — до одного интервала CRON). Удобно, когда группу сначала проставляют каналы или триггеры. |
| выкл                | NONE        | Автоматическое распределение полностью выключено.                                                                                                                                                   |

Примечания:

*   Тикеты, созданные **с группой** (каналом, формой, триггером или агентом), при создании никогда не обрабатываются — назначить их может только ребалансировщик. Для этого используйте режим GROUP с правилом `within_ticket_group`.
*   Оба механизма используют **одни и те же правила в одном и том же порядке**, поэтому правило ведёт себя в них одинаково, за исключением фильтра режима GROUP, который применяет только ребалансировщик.