> ## Documentation Index
> Fetch the complete documentation index at: https://omniflow-vpn.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Служба поддержки

> Работа с очередью обращений, внутренние заметки, недоступные клиентам, и отчёт по времени ответа, определения в котором идут вместе с цифрами

Служба поддержки живёт по адресу `/admin/support`, и для её открытия нужно право
`support.read`. В ней две вкладки: **Queue** — рабочее пространство обращений, и
**Report** — показатели нагрузки и времени ответа.

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

У клиента может быть несколько разговоров одновременно. Вопрос по оплате и
проблема с подключением — это два разговора, и попытка свести их в одну ветку
делает оба труднее для ответа и невозможными для раздельной маршрутизации. Бот
по-прежнему продолжает самое свежее живое обращение клиента — открытое, ожидающее
или решённое, активное в последние 72 часа, — если тот просто пишет сообщение,
потому что клиент имеет в виду именно это; веб-панель всегда открывает новое
обращение из `/account/support/new`.

## Очередь несёт обещание

Очередь — это именованная корзина с целевым временем первого ответа и целевым
временем решения, оба в секундах. Цель живёт на очереди, а не на приоритете,
потому что они отвечают на разные вопросы: приоритет — это то, как данное
обращение соотносится с работой перед ним, а цель — это то, что оператор пообещал
клиенту. Очередь по оплате может обещать четыре часа для всего, что в ней есть,
тогда как общая очередь обещает двое суток.

Ровно одна очередь является очередью по умолчанию, и это обеспечивается частичным
уникальным индексом, так что новому обращению всегда есть куда попасть. Без такой
очереди создание обращения отклоняется с 503 `support_unavailable`, и вопрос
клиента не попал бы никуда — API записывает это как неисправность установки, а не
как ошибку клиента.

В каждой установке заводится очередь `general` с названием «General» на английском
и «Общие вопросы» на русском, назначенная очередью по умолчанию, с порядком
сортировки ноль.

**Нулевая цель означает, что очередь ничего не обещает и никогда не нарушает
срок.** Предикат нарушения требует положительной цели, и это законная конфигурация
для очереди, существующей только ради маршрутизации.

### Как её определить

Формы нет. Отправьте `PUT /v1/panel/support/queues` с телом:

```json theme={null}
{
  "code": "billing",
  "nameEn": "Billing",
  "nameRu": "Оплата",
  "firstResponseTargetSeconds": 14400,
  "resolutionTargetSeconds": 86400,
  "isDefault": false,
  "sortOrder": 10
}
```

`code` приводится к нижнему регистру и должен соответствовать
`^[a-z][a-z0-9_-]{1,63}$`. Upsert выполняется по коду. Установка `isDefault: true`
снимает признак с предыдущей очереди по умолчанию в той же транзакции, поэтому
никогда не бывает момента с двумя значениями по умолчанию или без них. Отсутствие
названия на одном из языков либо отрицательная цель — это 422.

Этому маршруту нужно право **`settings.write`**, а не `support.write`. Очереди,
метки и шаблоны ответов — это конфигурация, а не повседневная работа поддержки:
оператор, отвечающий на обращения, не должен иметь возможность переопределять
обещания, которые даёт его очередь.

> [!NOTE]
>
> **Архивация очереди недостижима.** Защищённый запрос существует — он отклоняет
> очередь, которая является очередью по умолчанию или всё ещё держит открытые либо
> ожидающие обращения, — но ни один маршрут и ни один метод сервиса его не
> вызывает. Повторное сохранение архивированного кода снимает отметку об архиве,
> так что очередь можно вернуть, но нельзя убрать через API.

## Работа с очередью

Каждая очередь отображается карточкой с английским названием, числом открытых
обращений, янтарным значком «unassigned», красным значком «breached» и целевым
временем первого ответа в виде длительности. Эти три счётчика — то, что оператор
читает перед выбором работы: сколько здесь всего, сколько ничьего и сколько уже
вышло за обещанное очередью. Клик по карточке фильтрует список по этой очереди.
Карточки обновляются каждые 30 секунд, а список обращений — каждые 15.

Фильтр статуса предлагает все, `open` (по умолчанию), `pending`, `resolved` и
`closed`. Обращения упорядочены по давности активности, от самых старых, на
keyset-курсоре по `(last_message_at, id)` — собственный ящик клиента сортируется в
обратную сторону, от новых, потому что клиент отвечает на другой вопрос.

Каждая строка показывает тему, значок непрочитанного, значок приоритета, значок
«overdue», когда цель первого ответа нарушена, исполнителя или «nobody», дату
последнего сообщения и чипы меток. Признак нарушения вычисляется в SQL, поэтому
«overdue» означает одно и то же в списке, в счётчиках очереди и в отчёте.

> [!NOTE]
>
> API дополнительно фильтрует по `priority`, `assigneeId`, `unassigned=true`,
> `customerId` и `tag`, а также принимает `pageSize` до 200 (по умолчанию 25;
> панель запрашивает 50). Элементы управления в панели есть только у фильтров по
> очереди и статусу — остальные доступны напрямую через
> `GET /v1/panel/support/tickets`.

### Назначение исполнителя

Кнопка **Take it** в шапке разговора назначает обращение на вошедшего оператора;
та же кнопка становится **Release**, когда обращение назначено. Освобождение — это
полноценное действие, а не состояние ошибки: оператор, который не может что-то
довести до конца, должен иметь возможность это отложить.

Маршрут — `POST /v1/panel/support/tickets/{ticketID}/assign` с
`{"assigneeId": "<uuid>"}`; пустая строка освобождает обращение. Он проставляет
`assigned_at` и обнуляет его при освобождении, а в журнал аудита пишет
`panel.support_ticket.assigned` или `panel.support_ticket.released`.

### Приоритет, очередь и метки

**Приоритет** — это `low`, `normal`, `high` или `urgent`, по умолчанию `normal`,
задаётся селектом в шапке. Он намеренно не меняет цель SLA, которая принадлежит
очереди. Что угодно за пределами этих четырёх значений — 422. Клиенты видят
приоритет обращения, но не могут его задать.

**Очередь** — это селект, переносящий обращение
(`POST /v1/panel/support/tickets/{ticketID}/queue`).

**Метки** — это двуязычные ярлыки, по которым можно фильтровать очередь; это
категоризация, с которой оператор может работать, не изобретая второй словарь
статусов. Селект меток в шапке перечисляет каждую метку с префиксом `+` для
привязки или `−` для отвязки, и панель отправляет соответственно `PUT` или
`DELETE` на `/v1/panel/support/tickets/{ticketID}/tags/{tag}`. Параметр пути — это
**код** метки, а не её идентификатор; неизвестный код даёт 404, а повторная
привязка ничего не меняет. Создание метки доступно только через API:
`PUT /v1/panel/support/tags` с `{code, nameEn, nameRu}`, за правом
`settings.write`.

Все перечисленные выше элементы управления полностью скрываются, когда статус
обращения — `merged`, и каждый нижележащий запрос несёт `AND status <> 'merged'`,
так что прямой вызов API отвечает 404, а не изменяет поглощённое обращение.

## Статус, переоткрытие и отметки времени SLA

| Статус | Кем устанавливается | Значение |
| - | - | - |
| `open` | Любой стороной | Ждёт оператора |
| `pending` | Оператором | Ждёт клиента |
| `resolved` | Оператором | Отвечено; ответ всё ещё возможен |
| `closed` | Оператором или клиентом | Завершено |
| `merged` | Только действием объединения | Влито в другое обращение |

`merged` нельзя выставить через маршрут смены статуса — именно поэтому объединение
является отдельным действием.

Отметки времени записываются как факты, а не вычисляются, поэтому ответ на вопрос
«когда на это ответили впервые» переживает последующее изменение цели очереди:

* `first_response_at` пишется первым ответом оператора и **никогда не сдвигается**,
  так что разговор, идущий туда-сюда неделю, сохраняет исходное измерение.
* Установка `resolved` или `closed` проставляет `resolved_at`, если он ещё не был
  установлен.
* Возврат в `open` или `pending`, когда `resolved_at` был установлен, увеличивает
  `reopened_count` — сигнал о том, что ответ на самом деле не ответил на вопрос.

Ответ клиента в решённом обращении автоматически переоткрывает его и увеличивает
`reopened_count`; ответ клиента в ожидающем обращении возвращает его в `open`,
потому что ожидание закончилось. Те же правила, слово в слово, действуют в боте и
в веб-панели.

### Клиенту сообщают

Установка `resolved` или `closed` пишет в переписку сообщение от `system` —
«Поддержка отметила обращение как решённое. Если вопрос не решён, просто
ответьте — обращение откроется заново.» или «Поддержка закрыла обращение. Вы
можете открыть его заново или создать новое.» — на языке клиента, который
определяется в момент записи по настройке бота, а затем по языку аккаунта. Оно
считается активностью обращения, веб-кабинет показывает его как системную
реплику, бот — как `⚙️ Система`, а цикл доставки ответов в боте отправляет его в
Telegram ровно так же, как ответ оператора, с заголовком `⚙️ Обращение
обновлено`. Повторная установка того же статуса — не переход и ничего не пишет.
Переход в `pending` не объявляется: это внутренний учёт стола, а ответ
оператора, который обычно его сопровождает, сам говорит, что нужно.

Ответ оператора — тоже активность: он обновляет `last_message_at` и `updated_at`
в момент записи, поэтому отвеченное обращение клиента, работающего только через
веб, поднимается наверх его списка, не дожидаясь push-а, который для него никогда
не придёт. У клиентов есть
и явные элементы управления **Close** и **Reopen**
(`POST /v1/account/support/tickets/{ticketID}/close` и `/reopen`). Закрытие уже
закрытого обращения выполняется успешно и ничего не меняет — клиент просил
состояние, а не переход. Закрытие или переоткрытие объединённого обращения — это
422 с пояснением, что оно было влито в другой разговор.

## Внутренние заметки — это не сообщения

Заметка — это отдельная таблица, а не флаг `visibility` в `support_messages`.

Это сделано намеренно, и в этом состоит всё свойство безопасности. Каждый путь,
доставляющий сообщение клиенту, читает `support_messages`; флаг в этой таблице
означал бы, что каждый такой путь обязан помнить о проверке, и первый же
забывший отправит приватную заметку оператора тому клиенту, о котором она
написана. Заметку невозможно доставить, потому что её нет в таблице, которую
читает путь доставки. Ни один запрос в клиентском пакете вообще не упоминает
таблицу заметок.

На экране разговора два поля с двумя кнопками: текстовая область ответа с кнопкой
**Send** и пунктирная текстовая область заметки с янтарной рамкой и кнопкой
**Add note**. Это намеренно не одно поле с переключателем, потому что переключатель
— это состояние, в котором можно ошибиться, а ошибка здесь означает отправку
клиенту заметки о нём самом. Заметки отображаются прямо в ленте на пунктирной
предупреждающей подложке с подписью «internal note», а ответ с деталями обращения
возвращает сообщения и заметки **двумя отдельными массивами**, так что ни один
клиент не может принять одно за другое. Каждый массив ограничен 200 записями.

`POST /v1/panel/support/tickets/{ticketID}/notes` принимает `{"body": "..."}`
длиной от 1 до 4000 символов и требует автора, который является реальным
администратором. В журнал аудита это пишется как `panel.support_ticket.noted`
**вообще без метаданных** — текст заметки никогда не копируется в журнал аудита.

## Ответы пишутся здесь, а доставляет их бот

Панель пишет ответ; доставляет его нотификатор бота. Так согласие, тихие часы,
здоровье доставки в Telegram и обработка заблокированных аккаунтов остаются в
одном месте, а не дублируются в панель.

**1. При необходимости вставьте шаблон ответа**

Селект **Insert canned reply** заполняет поле ответа текстом шаблона, и его
использование увеличивает счётчик применений.

**2. Напишите и отправьте**

`POST /v1/panel/support/tickets/{ticketID}/reply` с `{"body": "…"}` и
**обязательным** заголовком `Idempotency-Key`. Запрос без него — это 422
`idempotency_key_required`.

**3. Проследите переход из queued в delivered**

Сообщение появляется сразу с пометкой `queued`. Цикл доставки ответов
поддержки в боте выполняется каждые 30 секунд — отдельно и независимо от
15-минутного прохода жизненного цикла, — забирает по порядку до 200
недоставленных сообщений оператора и системы, отправляет их и только после
этого проставляет `delivered_at`: в этот момент панель показывает
`delivered`, а счётчик непрочитанного у клиента увеличивается ровно один раз.

### Что означает метка доставки

Каждая отправка оставляет строку в `notification_deliveries` с видом `support`
и ключом `support-message:<id>`, и стол поддержки читает эту строку рядом с
сообщением. История уведомлений самого клиента показывает ту же строку, так что
на вопрос «вы это отправляли?» есть один ответ с обеих сторон.

| Метка | Значение |
| - | - |
| `в очереди` | Записано, попытки ещё не было |
| `повтор` | Telegram был недоступен или попросил паузу; попытка повторится в одном из следующих циклов, до трёх раз |
| `доставлено` | Telegram принял сообщение; проставлен `delivered_at` |
| `не доставить` | Push не состоится никогда: клиент заблокировал бота (`bot_blocked`), удалил аккаунт (`user_deactivated`, `chat_not_found`) или вовсе не имеет Telegram-идентичности (`no_telegram`) |
| `доставка не удалась` | Три транспортных сбоя; попытки прекращены |

Недоставленное сообщение не теряется. Оно остаётся в переписке непрочитанным, и
клиент увидит его, открыв `/account/support`. Метка нужна, чтобы оператор знал
сказать об этом, а не ждал push, которого не будет, — и чтобы один клиент,
заблокировавший бота, не держал голову очереди: сообщение в терминальном
состоянии исключается из следующей выборки, и ответы за ним уходят.

Ключ идемпотентности становится ключом дедупликации сообщения, уникальным в
пределах обращения, поэтому повторно отправленная форма попадает в уже
существующее сообщение, а не отправляет клиенту один и тот же ответ дважды.
Повторный ключ возвращает 200 с пустым объектом сообщения, а не ошибку.

Первый ответ фиксируется в той же транзакции, что и сообщение, так что измерение
SLA и ответ коммитятся вместе — одно не может существовать без другого.

Запись аудита `panel.support_ticket.replied` несёт только идентификатор шаблона
ответа. Текст намеренно отсутствует: само сообщение и есть запись, а копирование
его в журнал аудита продублировало бы клиентский контент во второй режим хранения.

> [!WARNING]
>
> **Клиенту без Telegram-идентичности ответ никогда не отправляется push-ом.**
> Тот, кто вошёл по magic link или через OIDC, увидит ответ, когда откроет
> `/account/support`; цикл записывает сообщение как `не доставить` с причиной
> `no_telegram` и никуда ничего не отправляет. Ответы поддержки по-прежнему
> обходят проверки согласия, тихих часов и лимита частоты — ответ клиент сам
> попросил, — но строку доставки они пишут, и именно в ней живут счётчик
> повторов и причина недоставляемости.
>
> Клиенту говорят, в каком из случаев он находится. `GET /v1/account/support/tickets`
> несёт `telegramLinked` — true, когда у аккаунта есть активная
> Telegram-идентичность или импортированная привязка Remnawave, которую
> принимает и запрос доставки, — и кабинет формулирует страницу поддержки по
> нему: «ответим здесь и в Telegram» для связанного аккаунта, «ответим здесь» с
> подсказкой открыть бота для несвязанного.

### Шаблоны ответов

Шаблон ответа — это переиспользуемый ответ на русском и английском, объявляющий
право доступа, необходимое для его отправки, со счётчиком применений.
Формулировки о возвратах и положения политики пишутся один раз и используются
повторно; оператор, переписывающий их на ходу, — это то, как два клиента получают
разные ответы. Оба языка обязательны, потому что язык клиента — не язык оператора.

Шаблон, которым вошедший оператор не вправе воспользоваться, **отфильтровывается
из списка на сервере**, а не показывается неактивным: список ответов, которые
человек не может отправить, — это список, который приходится каждый раз
пролистывать. Список упорядочен по использованию, самые частые сверху.

Создание доступно только через API: `PUT /v1/panel/support/canned` с
`{code, titleEn, titleRu, bodyEn, bodyRu, requiresPermission}` за правом
`settings.write`. `DELETE /v1/panel/support/canned/{responseID}` архивирует, а не
удаляет, так что запись о том, что было отправлено с его помощью, сохраняется;
архивация уже архивированного шаблона — это 404.

> [!NOTE]
>
> Два шероховатых места, о которых стоит знать. `requiresPermission` по умолчанию
> равно `support.write` и хранится как свободный текст — оно не проверяется по
> реальному списку прав, поэтому опечатка молча скрывает шаблон от всех. И панель
> вставляет **английский** текст независимо от локали клиента, поэтому оператору,
> отвечающему русскоязычному клиенту, придётся вставить русский текст
> самостоятельно.

## Объединение дубликатов

Объединение вливает одно обращение в другое: сообщения поглощённого обращения
переезжают в основное, а строка поглощённого сохраняется со статусом `merged` и
указателем на то, куда оно ушло. Удаление потеряло бы собственные слова клиента и
след, объясняющий, куда они делись, а сохранённая клиентом ссылка продолжает
работать — ответ несёт идентификатор основного обращения, чтобы панель могла
перевести дальше.

Вставьте UUID основного обращения в поле **Merge into** в шапке разговора и
нажмите **Merge** либо вызовите
`POST /v1/panel/support/tickets/{ticketID}/merge` с `{"survivorId": "<uuid>"}`.

Обе строки блокируются до того, как что-либо переезжает. Отказы:

* **Объединение между разными клиентами отклоняется** с 422. Помещение слов
  одного клиента в разговор другого — не та ошибка, о которой стоит
  предупреждать постфактум.
* Объединение обращения с самим собой отклоняется и в Go, и `CHECK` в базе данных.
* Обращение, уже имеющее статус `merged`, нельзя объединить повторно.
* Объединение **в** обращение, которое само было объединено, отклоняется с 409
  `state_conflict`; основное обращение должно быть живым разговором.

Поглощённое обращение получает отметку `resolved_at`, его счётчики
непрочитанного обнуляются, и оно больше не принимает ответы клиента. То, что оно
несло, переходит к основному: оба счётчика непрочитанного прибавляются, из двух
`last_message_at` побеждает более поздний, а основное обращение, которое было
`resolved` или `closed`, пока поглощённое оставалось `open` или `pending`, снова
становится `open` (и это считается переоткрытием), — так живой вопрос нельзя
влить в завершённую ветку и потерять из очереди. Сообщение от `system` в
основном обращении сообщает клиенту, что его другое обращение — названное по
теме — объединено с этим разговором. Ответ в поглощённое обращение со стола
отклоняется с 409 `state_conflict`, потому что его больше никто не читает.
Действие пишется в аудит как `panel.support_ticket.merged` с идентификатором
основного обращения.

## Вложения

Клиент может прикрепить файл с любой из поверхностей, и лимиты у них разные — это
стоит знать, когда в обращении лежит то, что веб-панель отклонила бы.

| | Веб-панель | Telegram-бот |
| - | - | - |
| Файлов на сообщение | 1 | 5 |
| Файлов на обращение | 20 | — |
| Максимальный размер | 5 MiB | 10 MiB |
| Допустимые типы | `image/png`, `image/jpeg`, `image/webp`, `application/pdf`, `text/plain` | Фото и документы |
| Где лежат байты | `APP_SUPPORT_ATTACHMENT_DIR` на диске | В Telegram |
| Скачивание из панели | Да | Нет — 409 `attachment_remote` |

### Что видит оператор

Каждое сообщение в `GET /v1/panel/support/tickets/{ticketID}` несёт массив
`attachments`: идентификатор, вид (`photo` или `document`), имя файла, тип,
размер, `origin` и `downloadable`. `origin` — это `web` для файла, который
хранит эта установка, и `telegram` для ссылки на файл в чате клиента. Экран
переписки показывает каждый файл под его сообщением вместе с размером.

У веб-загрузки есть кнопка **Скачать**, за которой стоит
`GET /v1/panel/support/tickets/{ticketID}/attachments/{attachmentID}` с правом
`support.read` — оператор, которому можно читать слова, может видеть и скриншот,
который пришёл с ними. Файл привязан к своему обращению прямо в запросе, так что
подмена идентификаторов не даст добраться до файла из другой переписки. Он
отдаётся с `Content-Disposition: attachment`, `nosniff` и сохранённым типом —
только пока этот тип всё ещё в списке допустимых; файл, байты которого вычищены,
отвечает 404, а процесс без каталога вложений — 503
`attachment_storage_unavailable`. Панель получает его через API и передаёт байты
браузеру, поэтому отказ показывается сообщением, а не сохраняется на диск как
JSON-файл с именем вложения.

Файл из Telegram помечен как **в Telegram** и кнопки не имеет. Маршрут отвечает
для него 409 `attachment_remote` и ничего не скачивает: Omniflow хранит ссылку,
а не байты, а проксирование файлового API Telegram с токеном бота от имени
оператора поставило бы файл клиента за учётные данные, для которых панель
никогда не предназначалась. Если файл нужен оператору вне чата, попросите
клиента отправить его заново через веб-кабинет.

Клиентская панель читает свои лимиты из `GET /v1/account/support/limits` до того,
как форма становится пригодной к использованию, поэтому счётчики символов и
подсказка о размере точно совпадают с тем, на чём отказывает API. **Ни один из
этих лимитов не настраивается** — настраивается только каталог хранения.

Загрузки отклоняются точно, а не оборонительно. Слишком большое тело — это 413
`attachment_too_large`, ограничение срабатывает до разбора, а не после.
Объявленный тип содержимого вне белого списка — либо отсутствующий, либо
неразбираемый — это 415 `attachment_media_type`, без снифинга, потому что догадка
— это ровно то, чего белый список принимать не должен. Пустой файл — 422. Загрузка
в закрытое или объединённое обращение — 409; загрузка в решённое переоткрывает
его, ровно как и ответ, — и, как ответ, отклоняется с 409
`too_many_open_tickets`, если переоткрытие заняло бы открытый слот, которого у
клиента нет. Обращение, в котором уже 20 веб-загрузок
(`maxAttachmentsPerTicket` в `/v1/account/support/limits`), отвечает 409
`too_many_attachments`; подсчёт ведётся под блокировкой строки обращения, так что
две загрузки, столкнувшиеся на границе, не пройдут обе.

Загрузка принимает тот же необязательный заголовок `Idempotency-Key`, что и
ответ. Ключ становится ключом дедупликации сообщения, и повтор находит и
сообщение, и файл, который уже на нём висит, — одно сообщение, один файл,
сколько бы раз ни отправили форму. Панель выводит ключ из имени, размера и
времени изменения файла плюс текста заметки, поэтому двойное нажатие «Отправить»
— одна загрузка, а другой файл — другая.

Три клиентских записи ограничены по частоте на клиента в API, через тот же
лимитер, что и поверхность приватности: 10 новых обращений, 60 сообщений и 20
загрузок в час. После исчерпания бюджета маршрут отвечает 429 `rate_limited`, а
загрузка отклоняется до чтения тела. Сбой лимитера закрывает доступ, как и при
экспорте данных.

Владение проверяется до того, как записан хоть один байт, и ещё раз под блокировкой
строки внутри транзакции, так что посторонний не может потратить диск установки на
обращение, которое он не видит. Байты пишутся раньше строки, поэтому строка
никогда не может указывать на отсутствующий файл.

Вложения хранятся 90 дней; это значение по умолчанию у колонки, и оно не
настраивается. Проход очистки по сроку хранения и каталог оба важны эксплуатационно
— см. [Новости и коммуникация с клиентами](https://omniflow-vpn.mintlify.site/ru/commerce/referrals-communication.md)
про настройку каталога и про то, почему API и воркер должны быть согласованы в ней.

## Отчёт по нагрузке и времени ответа

`/admin/support` → **Report**. Кнопки окна предлагают 7, 30 и 90 дней; по умолчанию
7, а API ограничивает окно 90 днями, так что один запрос не может попросить базу
данных вычислить медиану по всем обращениям за всё время.

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

Раньше они были списком в самом низу страницы под заголовком *What these numbers
mean* — шесть абзацев в максимальном удалении от цифр, которые они объясняли, и
по-английски независимо от выбранного оператором языка, потому что прозу отдавал
API. Теперь этот текст живёт в каталогах сообщений панели, как и любая другая
строка для оператора, а в ответе остались только цифры.

| Показатель | Определение |
| - | - |
| Open tickets | Обращения в состоянии open или pending, в какой бы очереди они ни были |
| Unassigned | Обращения в состоянии open или pending, которые никто не взял |
| Breached | Обращения в состоянии open или pending, вышедшие за цель первого ответа своей очереди и всё ещё без ответа оператора. Очередь с нулевой целью никогда не нарушает срок |
| Resolved in window | Обращения, достигшие статуса resolved или closed внутри окна |
| Median first response | Медианное время от создания обращения до первого ответа оператора, по ответам, сделанным внутри окна. Медиана, а не среднее, чтобы один очень поздний ответ не искажал картину |
| Replies | Сообщения оператора, написанные этим оператором внутри окна, включая ответы на обращения, назначенные на кого-то другого |

Под пятью основными цифрами располагаются ответы по операторам — горизонтальная
столбчатая диаграмма, а под ней полная таблица: отображаемое имя, ответы,
открытые обращения, решённые обращения и медиана первого ответа этого оператора.
Столбики горизонтальные, потому что категории — это имена людей, которые
вертикальная диаграмма обрезала бы или наклоняла; таблица рисуется всегда, а не
прячется под раскрытием, потому что диаграмма не заменяет число, по которому
нужно действовать. Две вещи в таблице сделаны намеренно. Открытые обращения по оператору **не**
ограничены окном — это то, что этот человек держит прямо сейчас, а это вопрос в
настоящем времени. И **ответы** считают сообщения, которые оператор написал,
независимо от того, на кого было назначено обращение, потому что помощь с чужим
обращением — это тоже работа.

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

## Счётчики непрочитанного у оператора

`operator_unread_count` растёт с каждым сообщением клиента на любой из поверхностей
и с каждым переоткрытием клиентом, так что строка очереди может показать, что
пришло с тех пор, как кто-либо смотрел. Повторно проигранная отправка клиента с тем
же ключом идемпотентности его не увеличивает, потому что инкремент выполняется
только тогда, когда строка действительно была вставлена.

> [!NOTE]
>
> **Значок не сбрасывается, когда оператор открывает обращение.**
> `POST /v1/panel/support/tickets/{ticketID}/read` существует и очищает счётчик, но
> панель его никогда не вызывает, так что счётчик уменьшается, только если это
> делает что-то за пределами панели.

## Права доступа

| Действие | Право доступа |
| - | - |
| Чтение очередей, обращений, меток, шаблонов ответов и отчёта | `support.read` |
| Назначение, перенос, приоритизация, ответ, заметка, метка, объединение, смена статуса, отметка о прочтении | `support.write` |
| Определение очередей, меток и шаблонов ответов | `settings.write` |

Чтение обращения и ответ на него разделены, потому что это разные работы:
финансовому оператору, разбирающему возврат, нужно прочитать разговор, который к
нему привёл, и он не должен иметь возможности ответить клиенту от лица поддержки.

Каждое изменение аудируется: `panel.support_ticket.assigned`, `.released`,
`.moved`, `.prioritised`, `.status_changed`, `.replied`, `.noted`, `.merged`, а
также `panel.support_queue.saved`, `panel.support_tag.saved`,
`panel.canned_response.saved` и `.archived`.

## Ограничения для клиента

| Ограничение | Значение |
| - | - |
| Одновременно открытых разговоров | 5, считаются `open` и `pending` |
| Тема | 120 рун; пустая тема заменяется первой строкой сообщения |
| Сообщение | 4000 рун |

Шестое открытое обращение — это 409 `too_many_open_tickets` с числом в поле detail.
Квота существует, чтобы цикл в клиентском приложении не мог заполнять очередь
операторов быстрее, чем люди успевают её разбирать. **Решённое** обращение не
считается открытым и не занимает слот, и на него по-прежнему можно ответить — что
его переоткроет и, следовательно, займёт слот: ответ, загрузка и явное
**Открыть заново** отклоняются с тем же 409, когда у клиента уже максимум. Без
этого квота была правилом только о создании обращений и ни о чём больше.

Подсчёт ведётся в рунах, а не в байтах, поэтому сообщение на русском языке не
штрафуется за свою кодировку. И `open`, и `canReply` вычисляются на сервере, а не
выводятся клиентом из строки статуса, так что правило квоты и правило поля ввода
не могут разойтись.

## Смежные страницы

> **[Клиентская веб-панель](https://omniflow-vpn.mintlify.site/ru/customer/web-panel.md)**
>
> `/account/support` со стороны клиента.

> **[Telegram-бот](https://omniflow-vpn.mintlify.site/ru/customer/telegram-bot.md)**
>
> `/support`, поток составления сообщения и обработка вложений в чате.

> **[Новости и коммуникация с клиентами](https://omniflow-vpn.mintlify.site/ru/commerce/referrals-communication.md)**
>
> Классы доставки, срок хранения вложений и каталог хранения.

> **[Админ-панель](https://omniflow-vpn.mintlify.site/ru/operations/admin-panel.md)**
>
> Роли, сессии и всё остальное в `/admin`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.