← Назад к кейсам

Кейсы

Добавление упомянутых сотрудников в наблюдатели задачи

Как обработчик сообщения в чате задачи автоматически добавляет упомянутых сотрудников в наблюдатели.

Если сотрудника упомянуть в чате задачи через [USER=ID], он увидит сообщение, но не всегда станет участником самой задачи. Для этого был сделан обработчик, который добавляет упомянутых сотрудников в наблюдатели.

Задача

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

Упомянутого сотрудника нужно было подключить к задаче

В задачах Bitrix24 обсуждение часто идёт прямо в чате задачи. Там можно упомянуть сотрудника, но само упоминание не всегда означает, что этот сотрудник становится участником задачи.

Нужно было сделать обработчик, который:

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

Обсуждение в чате должно менять состав задачи

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

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

Проблема

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

Упоминание само по себе не даёт доступа к задаче

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

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

В наблюдатели должны попадать только нужные люди

Обработчик не должен просто брать все ID из сообщения и без проверки записывать их в задачу. Иначе можно получить дубли, ошибки прав или попытки добавить неактивных пользователей.

Перед изменением задачи нужно проверить:

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

Решение

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

Обработчик новых сообщений в чате задачи

Обработчик подключается к событию добавления сообщения. На входе есть ID сообщения и поля сообщения, по которым можно получить автора, чат и текст.

<?php

use Bitrix\Main\EventManager;

EventManager::getInstance()->addEventHandlerCompatible(
    'im',
    'OnAfterMessagesAdd',
    ['TaskChatMentionAuditorsHandler', 'handleMessageAdd']
);

Внутри обработчика сначала отсекаются лишние случаи: системные сообщения, сообщения без автора, сообщения без CHAT_ID и чаты, которые не относятся к задачам.

Чат задачи определяется по типу сущности. В обработчике использовался тип TASKS_TASK, а ID задачи брался из данных чата.

Разбор [USER] из текста сообщения

В сообщениях Bitrix24 упоминание пользователя может быть записано в формате BBCode:

[USER=123]Имя сотрудника[/USER]

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

/**
 * Получает ID пользователей из BBCode-упоминаний.
 */
private static function extractMentionedUserIds(string $message_text): array
{
    if ($message_text === '') {
        return [];
    }

    preg_match_all('~\[USER=(\d+)\].*?\[/USER\]~isu', $message_text, $matches);

    if (empty($matches[1])) {
        return [];
    }

    return self::normalizeUserIds($matches[1]);
}

После поиска упоминаний дополнительно проверяется активность пользователей через UserTable. Неактивных сотрудников обработчик не добавляет.

Проверка, кто уже есть в задаче

Перед добавлением нужно собрать текущих участников задачи. Упомянутый сотрудник не должен добавляться повторно, если он уже участвует в задаче.

Проверяются основные роли:

  • постановщик;
  • исполнитель;
  • соисполнители;
  • наблюдатели.
/**
 * Получает всех текущих участников задачи.
 */
private static function getTaskParticipantIds(array $task_data): array
{
    $participant_ids = [];

    $participant_ids[] = (int)($task_data['CREATED_BY'] ?? 0);
    $participant_ids[] = (int)($task_data['RESPONSIBLE_ID'] ?? 0);

    $accomplice_ids = self::normalizeUserIds($task_data['ACCOMPLICES'] ?? []);
    $auditor_ids = self::normalizeUserIds($task_data['AUDITORS'] ?? []);

    $participant_ids = array_merge(
        $participant_ids,
        $accomplice_ids,
        $auditor_ids
    );

    return self::normalizeUserIds($participant_ids);
}

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

Автоматическое добавление в наблюдатели

После проверок остаются только новые пользователи. Они объединяются с текущими наблюдателями, и задача обновляется через API задач.

/**
 * Добавляет пользователей в наблюдатели задачи, если они ещё не участвуют в задаче.
 */
private static function addTaskAuditors(int $task_id, array $mentioned_user_ids): void
{
    $task = CTaskItem::getInstance($task_id, TASK_UPDATE_USER_ID);
    $task_data = $task->getData(false);

    $current_auditor_ids = self::normalizeUserIds($task_data['AUDITORS'] ?? []);
    $task_participant_ids = self::getTaskParticipantIds($task_data);
    $mentioned_user_ids = self::normalizeUserIds($mentioned_user_ids);

    $new_user_ids = array_values(array_diff($mentioned_user_ids, $task_participant_ids));

    if (empty($new_user_ids)) {
        return;
    }

    $auditor_ids = array_values(array_unique(array_merge($current_auditor_ids, $new_user_ids)));

    $task->update([
        'AUDITORS' => $auditor_ids,
    ]);
}

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

Результат

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

Упоминания стали работать как подключение к задаче

Теперь если в чате задачи написать сообщение с упоминанием сотрудника, обработчик проверит это сообщение и добавит сотрудника в наблюдатели, если он ещё не участвует в задаче.

В итоге:

  • не нужно вручную добавлять наблюдателей после упоминания;
  • упомянутый сотрудник остаётся в контексте задачи;
  • задача не обновляется лишний раз, если сотрудник уже участвует в ней;
  • неактивные пользователи не добавляются;
  • системные сообщения и чужие чаты не обрабатываются.

Доработка получилась точечной: она не меняет интерфейс задач, а только дополняет стандартное поведение чата задачи.