# Комментарии — Администрирование

Настройка поведения комментариев в задачах: видимость, адресация, быстрые ответы, системные комментарии. Основная часть настроек живёт в категории (смежный домен categories). Для администраторов и специалистов поддержки.

## Обзор

Администрирование комментариев опирается на два механизма:

- **Редактор сущностей** — схема `quickReply` (шаблоны быстрых ответов)
- **Настройки категории** — поля в `dbo.Subcategories`, управляющие поведением комментариев

Собственных форм автоадминистрирования у домена нет: банк стикеров ведётся на отдельном экране админки, остальное настраивается редактором сущностей и настройками категории.

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

**Редактор сущностей**

| Схема JSON | Таблица | Назначение |
|-----------|---------|------------|
| `quickReply` | dbo.QuickReplyTemplates | Шаблоны быстрых ответов для комментариев |

**Настройки через смежный домен (categories)**

Ключевые флаги поведения комментариев задаются в настройках категории (`subcategories`):

| Поле | Таблица | Что контролирует |
|------|---------|------------------|
| `EnableComments` | dbo.Subcategories | Глобальное включение/выключение комментариев |
| `HideSystemComments` | dbo.Subcategories | Скрытие системных комментариев из пользовательской ленты |
| `ForwardCommentsToAllHelpers` | dbo.Subcategories | Автодобавление всех исполнителей в адресаты |
| `OneFMainVisibilityMode` | dbo.Subcategories | Ограничение видимости комментариев |
| `IsDeleteUserCommentsForbidden` | dbo.Subcategories | Запрет удаления пользовательских комментариев |
| `DenyEnterComments` | dbo.Subcategories | Запрет добавления комментариев в задачах категории |
| `ReactionsEnabled` | dbo.Subcategories | Включение реакций на комментарии |
| `EnableCommentsEdit` | dbo.Subcategories | Право авторов редактировать свои комментарии |
| `DenyPostCommentsInClosedTasks` | dbo.Subcategories | Запрет комментариев в закрытых задачах |
| `AutoReadCommentsWhenTaskCloses` | dbo.Subcategories | Автопрочтение комментариев при закрытии задачи |

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

При `DenyEnterComments = true` попытка добавить комментарий отклоняется с ошибкой о запрете. Исключение — служебный комментарий о вложении файла (`TypeID = 10`, «Вложение файла»): он создаётся и в категории с запретом, иначе загрузка файла не оставляла бы следа в ленте задачи.

Исключение узкое и срабатывает только при одновременном выполнении трёх условий: тип комментария — «Вложение файла», к комментарию приложен хотя бы один файл, комментарий создаётся без уведомлений. Применяется на служебном пути прикрепления файлов при создании задачи (в том числе при синхронизации с Exchange). Комментарии, создаваемые пользователем в интерфейсе или через REST API, при `DenyEnterComments = true` отклоняются по-прежнему.

Поэтому появление в ленте записи «Вложение файла» в категории с запретом комментариев — штатное поведение, а не ошибка настройки. Проверка флага: `select DenyEnterComments from dbo.Subcategories where SubcatID = {subcatId}`.

## Банк стикеров

Паки стикеров администратор ведёт на странице **«Стикеры»** в разделе администрирования (адрес `/administration/stickers`). Пункт меню и сам экран доступны только пользователю с правами администратора.

В списке по каждому паку видны название, обложка, число стикеров в паке и статус — включён он или выключен. С экрана доступны:

- создание пака с вводом названия;
- переименование;
- загрузка обложки — PNG или WebP размером ровно 100 на 100 точек и не тяжелее 128 КБ;
- переключатель статуса прямо в строке списка;
- удаление с подтверждением.

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

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

### Состав пака

Клик по паку открывает его состав — экран по адресу `/administration/stickers/{идентификатор пака}`. Файлы загружаются пачкой: их перетаскивают в зону загрузки или выбирают в диалоге. Принимаются PNG и WebP шириной ровно 512 точек, высотой не больше 512 и весом не больше 512 КБ; тип и вес проверяются до отправки, размеры — на сервере.

Если файл не прошёл проверку, причина показывается напротив самого файла, а загрузка остальных в пачке не прерывается.

В списке стикеров пака можно перетаскиванием менять их порядок — он и определяет порядок показа в панели выбора, — задать каждому стикеру эмодзи и удалить стикер с подтверждением.

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

**Шаблоны быстрых ответов (QuickReplyTemplates)**

**Где настраивается:** редактор сущностей → схема `quickReply`
**Таблица БД:** `dbo.QuickReplyTemplates`

Шаблоны быстрых ответов, доступные пользователям при написании комментария. Администратор создаёт набор стандартных фраз/ответов.

**Как применяется:** шаблоны доступны в интерфейсе комментирования через кнопку быстрого ответа.

**Поведение комментариев в категории**

**Где настраивается:** настройки категории (`subcategories`) → секция комментариев
**Таблица БД:** `dbo.Subcategories`

| Группа полей | Что контролирует |
|-------------|------------------|
| Включение комментариев | Доступность функции комментирования в задачах |
| Системные комментарии | Отображение автоматических комментариев (смена статуса, исполнителя и т.д.) |
| Адресация | Автоматическое добавление исполнителей в адресаты |
| Ограничения | Запрет удаления, режимы видимости |

**Как применяется:** учитывается при создании, отображении и удалении комментариев.

**Видимость комментария**

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

Видимость комментария определяется не фактом записи в `Comments`, а наличием записи в `CommentRecipients`. Системные комментарии при `HideSystemComments = true` пишутся в БД, но не попадают в пользовательскую ленту.

**Подписки на типы комментариев**

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

Пользователь фильтрует типы комментариев в своей ленте через настройки подписки.

## CustomSettings — глобальные ключи комментариев

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

| Ключ | Тип | Назначение |
|------|-----|------------|
| `FirstCommentIdWithNoRecipients` | int | ID комментария, начиная с которого адресаты больше не хранятся в теле комментария (с v2.240). Создан для совместимости с фоновой очисткой адресатов (`ClearCommentRecipientsArchiveJob`). Рекомендуется компаниям, работающим в веб-интерфейсе |
| `PostMarkAsAnsweredWithUserType` | bool / `false` | Если `true` — комментарий «Как отвеченный» отмечается как **пользовательский**, а не системный. По умолчанию (`false`) — системный (как раньше) |

**Отложенные комментарии (с 2.268.398)**

| Ключ | Таблица | Тип | Default | Назначение |
|------|---------|-----|---------|------------|
| `ScheduledCommentsEnabled` | `dbo.Settings` | bit | `1` | Глобальное включение/выключение функции отложенных сообщений. При `0` контроллер `api/scheduled-comments` отдаёт 403, а фоновая джоба `SendScheduledCommentsJob` не публикует накопленные черновики. |

**appsettings.json — кодировка комментариев**

| Ключ | Тип | Назначение |
|------|-----|------------|
| `UseClassicEncodeInComments` | bool | Использовать классическую кодировку в комментариях. Включается при выявлении проблем с отображением спецсимволов в устаревших окружениях |

## Парсинг внешних ссылок: пределы загрузки

**Пределы загрузки при разборе внешних ссылок.** Читаются из `Configuration` (`Valhalla.Configuration.Configuration`); значение по умолчанию применяется, если ключ отсутствует или неразбираем/неположителен.

| Ключ | Дефолт | Что ограничивает |
|------|--------|------------------|
| `LinkSnippetMaxTextDownloadBytes` | `5 МБ` | распакованный размер одного текстового ответа (страница, `manifest.json`) |
| `LinkSnippetMaxTotalTextDownloadBytes` | `10 МБ` | суммарный бюджет всех текстовых загрузок одного запроса со списком адресов |
| `LinkSnippetMaxTextDecompressionRatio` | `100` | максимальное отношение распакованных байт к сжатым |

Превышение любого порога — этот URL не даёт сниппет, память процесса не раздувается; остальные адреса запроса обрабатываются.

Также действует глобальный выключатель исходящих HTTP `OutboundHttpAllowed` (при `false` разбор ссылок не выполняется) и SSRF-блок приватных/link-local адресов для прямых соединений (настраивается кодом клиентов, не ключом).

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

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

| Симптом | Причина | Где проверить | SQL-диагностика |
|---------|---------|---------------|-----------------|
| Комментарий отправлен, но его никто не видит | Нет записей в `CommentRecipients` | `dbo.CommentRecipients` | `select * from dbo.CommentRecipients where CommentId = {commentId}` |
| Системные комментарии исчезли из ленты | Включён `HideSystemComments` | Настройки категории | `select HideSystemComments from dbo.Subcategories where Id = {subcatId}` |
| Исполнители не получают комментарии | Выключен `ForwardCommentsToAllHelpers` или неверная адресация | Настройки категории | `select ForwardCommentsToAllHelpers from dbo.Subcategories where Id = {subcatId}` |
| Пользователь не может удалить свой комментарий | Включён `IsDeleteUserCommentsForbidden` | Настройки категории | `select IsDeleteUserCommentsForbidden from dbo.Subcategories where Id = {subcatId}` |
| Тип комментария не виден в ленте | Пользователь отписался от типа | `dbo.UserLentaCommentTypes` | `select * from dbo.UserLentaCommentTypes where UserId = {userId}` |

**Детальная диагностика «Не вижу отправленный комментарий»**

**Причины** (в порядке частоты):

1. Комментарий системный + `HideSystemComments` включено
2. Пользователь не подписчик задачи → нет записи в `CommentRecipients`
3. Тип комментария отключён в настройках подписки пользователя
4. Smart Event заблокировал уведомление (`BeforeSendNotification`)
5. `EnableComments` выключено / `OneFMainVisibilityMode` скрывает

**SQL-диагностика:**

```sql
-- 1. Проверить наличие комментария
select c.CommentID, c.TaskID, c.UserID, c.TypeID, c.IsDeleted, c.Content, c.Date
from Comments c
where c.CommentID = @commentId;

-- 2. Проверить адресатов
select r.UserID, u.FullName, r.IsUnread, r.IsRealRecipient, r.IsCopyRecipient, r.HasToAnswer
from CommentRecipients r
        join Users u on u.UserID = r.UserID
where r.CommentID = @commentId
order by r.UserID;

-- 3. Проверить подписку на задачу
select s.UserID, s.IsDeleted
from MailSubscribersUsers s
where s.TaskID = @taskId and s.UserID = @userId;

-- 4. Проверить настройки категории
select sc.SubcatID, sc.HideSystemComments, sc.ForwardCommentsToAllHelpers, sc.EnableComments
from Subcategories sc
where sc.SubcatID = (select t.SubcatID from Tasks t where t.TaskID = @taskId);
```

**Чеклист:**

1. Комментарий существует в `Comments`? Не удалён (`IsDeleted = 0`)?
2. Есть запись в `CommentRecipients` для данного пользователя?
3. Если нет — проверить `TypeID` (системный?) + `HideSystemComments`
4. Если нет — проверить подписку пользователя на задачу
5. Если нет — проверить Smart Events в категории (`BeforeSendNotification`)
6. Если есть, но `IsUnread = false` — проверить AutoRead настройки
7. Если всё корректно в БД — проверить доставку в реальном времени

**«Комментарий виден заказчику, но не исполнителю»**

**Частая причина:** `ForwardCommentsToAllHelpers` выключено — комментарий адресован конкретному исполнителю, а не всем.

**«Системные комментарии не отображаются»**

**Причина:** `HideSystemComments = true` в настройках категории. Системные комментарии записываются, но адресаты не создаются — никто их не видит в ленте.

## Создание и форматирование комментариев через REST API

**Создание через REST**

```python
# PascalCase обязателен
POST("/api/comments/add", {
    "TaskId": 12345,         # НЕ taskId (lowercase → поле игнорируется)
    "CommentText": "текст"
})  # → {"data": commentId}
```

**Вложение файлов в комментарий**

```bash
# 1. Preupload
curl -s -X POST -H "1F-Pat: $PAT" \
  -F "file=@path/to/file.md" \
  "https://{host}/api/files/upload/ToPreUploadedFiles"
# → {"data": [{"preUploadFileId": 708072}]}

# 2. Комментарий с файлом: ключи предзагруженных файлов передаются в теле запроса
curl -s -X POST -H "1F-Pat: $PAT" \
  -H "Content-Type: application/json" \
  -d '{"TaskId": 123, "CommentText": "Текст", "PreUploadedFileKeys": ["708072"]}' \
  "https://{host}/api/comments/add"
```

**Форматирование**

Подробности: [Форматирование текста](https://help.1forma.ru/domains/comments/text-formatting.md).

1. **Пишите обычный markdown** (CommonMark/GFM). Особый синтаксис 1Формы не нужен — поддерживается стандарт.
2. **1Форма-специфика:**
   - `__текст__` это курсив, не жирный — для жирного используй `**текст**`
   - `((текст))` — подчёркнутый (опционально)
   - `#TaskId` — автоссылка на задачу, `@user{UserId}` — упоминание пользователя
3. **Переносы строк** — настоящий перевод строки (LF). Не `\n` как двусимвольный escape.
4. **Списки `-`/`1.`** в веб-интерфейсе (лента, чат) отображаются как маркированный/нумерованный список; на iOS/Android — как обычный текст.
5. **Заголовки, блоки кода, таблицы** в веб-интерфейсе работают (с мая 2026), на iOS/Android — как обычный текст. Если комментарий важен для мобильных — используйте строчную разметку (`**жирный**`, `` `код` ``) вместо блочной.

**HTML-теги запрещены.** Защита от XSS вырезает угловые скобки `<TKey, TValue>` — экранируйте их как `&lt;TKey, TValue&gt;` или используйте обратные кавычки.

## Ссылки и типичные ошибки REST API

**Ссылки на сущности — URL-паттерны**

В комментариях URL автоматически кликабельны (веб-интерфейс, iOS, Android).

### Shorthand (парсится системой)

Короткие формы ссылок, которые система распознаёт автоматически:

| Синтаксис | Результат | Пример |
|-----------|-----------|--------|
| `#TaskId` | Ссылка на задачу | `#12345` |
| `@user{UserId}` | Упоминание пользователя | `@user42` |

#### Полные URL

**Задачи и гриды:**

| Сущность | URL |
|----------|-----|
| Задача | `https://<host>/spa/tasks/{taskId}` |
| Грид категории | `https://<host>/spa/tasks/subcat/{subcatId}/grid` |
| Канбан категории | `https://<host>/spa/tasks/subcat/{subcatId}/kanban` |
| Портал категории | `https://<host>/spa/tasks/subcat/{subcatId}/portal` |

**Администрирование:**

| Сущность | URL |
|----------|-----|
| Настройки категории | `https://<host>/spa/administration/subcategory/{subcatId}` |
| ДП категории (список) | `https://<host>/spa/administration/ext-params-settings/subcat/{subcatId}` |
| Конкретный ДП | `https://<host>/spa/administration/ext-params-settings/{subcatId}/{extParamId}` |
| SmartScripts | `https://<host>/spa/administration/smart-scripts/{scriptId}` |
| Пользователь (профиль) | `https://<host>/spa/users/{userId}/info` |

**Правила:**

- Задачи — ВСЕГДА короткая форма `#TaskId`, не полный URL. Система автоматически формирует кликабельную ссылку.
- Для остальных сущностей — полный URL. Подставляй реальные ID из контекста.
- `<host>` — адрес вашего стенда 1Формы (на клиентских площадках домен свой, пути те же).

**Типичные ошибки REST API**

| Проблема | Причина | Решение |
|----------|---------|---------|
| `POST /api/comments` (без `/add`) | Это GET-маршрут — возвращает 200 + 229KB task DTO | `/api/comments/add` |
| `POST /api/comments/post` → 404 | Нет такого маршрута | `/api/comments/add` |
| `taskId` (camelCase) | PascalCase обязателен | `TaskId` — иначе поле игнорируется |
| Комментарий в закрытую задачу | Система отклоняет операцию с ошибкой | Проверять `IsClosed` перед отправкой |

## Реакции (emoji)

Включение и выключение реакций на комментарии задаётся флагом `ReactionsEnabled` в настройках категории. Его отдаёт и принимает контракт секции «Комментарии»: `GET` и `POST /api/admin/subcategories/{subcatId}/settings/comments`. Права — администратор системы или администратор категории.

Состав, порядок и локализация самих эмодзи настраиваются отдельно — см. «Реакции на комментарии: административная настройка» в [notifications/admin.md](https://help.1forma.ru/domains/notifications/admin.md). REST-маршруты `/api/comments/{id}/reactions/add`, `remove` и `GET` настройку не меняют — это пользовательские операции с реакциями конкретного комментария.


## Типы комментариев (TypeID)

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

**Пользовательские типы**

| TypeID | Описание | Создаётся |
|--------|----------|-----------|
| 3 | Пользовательский комментарий | Вручную пользователем |
| 6 | Комментарий из подзадачи | Автоматически при публикации в подзадаче |
| 10 | Вложение файла (без текста) | Пользователем через drag&drop файла |
| 20 | Внесение трудозатрат | Пользователем через форму трудозатрат |

**Системные типы**

| TypeID | Описание | Событие |
|--------|----------|---------|
| 1 | Переход по маршруту | Смена шага/статуса задачи |
| 2 | Подпись (резолюция) | Согласование/подписание |
| 5 | Служебный | Различные системные действия |
| 7 | Изменение ДП или файлов | Изменение допараметра или файла |
| 8 | Создание задачи | Создание новой задачи |
| 9 | Изменение текста задачи | Редактирование описания |
| 11 | Смена категории | Перемещение задачи |
| 12 | Смена срока | Изменение дедлайна |
| 13 | Добавлен исполнитель | Назначение исполнителя |
| 15 | Добавлен подписчик | Добавление подписчика |
| 16 | Задача просрочена | Автоматически при просрочке |
| 18 | Смена заказчика | Изменение заказчика задачи |
| 19 | Удалён файл | Удаление файла |

## Типы адресатов (CommentRecipients)

Поля, определяющие роль адресата комментария:

| Поле | Значение |
|------|----------|
| `IsRealRecipient = true` | Явный адресат — выбран пользователем при отправке |
| `IsRealRecipient = false` | Неявный адресат / подписчик задачи без прямой адресации |
| `IsCopyRecipient = true` | В копии — получает сообщение, но без «непрочитанного» |
| `HasToAnswer = true` per-recipient | Получатель должен ответить (вопрос) |

### Подписка при создании коммента (`needSubscribe`) → доступ к задаче → право на вложение

`POST /api/comments/add` принимает два флага подписки, которые интерфейс по умолчанию передаёт как `true`:

| Поле запроса | Что делает |
|---|---|
| `needSubscribe` | подписывает **автора** коммента на задачу |
| `needSubscriberOthers` | подписывает **адресатов** (`RecipientIds`) на задачу |

Подписка → доступ к задаче. Это важно **за пределами видимости ленты**: без доступа к задаче пользователь **не может приложить файл к своему комментарию** — система отклоняет вложение с ошибкой «Нет прав на вложение файлов или установлен запрет на создание вложений». **Это сообщение вводит в заблуждение** — реального запрета нет (право на вложение у задачи разрешено); не хватает именно подписки и доступа.

**Важно при программном создании комментариев.** Если комментарий создаётся в обход интерфейса (через API) без флагов `needSubscribe`/`needSubscriberOthers`, адресат, не являющийся участником задачи, становится получателем, но не подписывается — а значит, не получает доступ к задаче. На свежесозданных задачах это приводит к тому, что его вложение к ответу отклоняется (на задачах, где он уже участник, вложение работает — отсюда «на одних задачах работает, на других нет»). Чтобы этого избежать, при программном создании комментария передавайте оба флага подписки.

**Автопрочтение: технические условия**

Комментарий автоматически помечается как прочитанный если выполнено одно из условий:

1. В профиле пользователя включена настройка автопрочтения (`User.AutoReadComments`)
2. Пользователь исключён из адресатов смарт-событием (`BeforeSendNotification`)
3. Для событий календаря — при выключенном уведомлении по событию (`CalendarEnableNotify`)
4. По персональным настройкам уведомлений пользователя (`NotificationSettings`)

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

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

- [Форматирование текста](https://help.1forma.ru/domains/comments/text-formatting.md) — полные правила форматирования
- [Комментарии — бизнес-логика](https://help.1forma.ru/domains/comments/business.md) — адресация, прочитано/непрочитано, треды
- [Категории — администрирование](https://help.1forma.ru/domains/categories/admin.md) — настройка категорий (поля комментариев в Subcategories)
