Обработчик для Битрикс24: Последний комментарий не от ответственного

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

<?php
AddEventHandler('crm', 'OnAfterCrmTimelineCommentAdd', 'handleNewComment');

function handleNewComment($commentId) {
    // 1. Настройки
    $settings = [
        'log_dir' => $_SERVER['DOCUMENT_ROOT'].'/upload/logs/', // Папка логов
        'log_file' => 'comments_'.date('Y-m-d').'.log', // Название лог файла
        'custom_field' => 'UF_CRM_3_1746033726221', // Код вашего поля
        'error_color' => '[COLOR=#800080]' // Цвет комментария не от ответственного
    ];

    // 2. Инициализация логов
    try {
        // Создаем директорию логов если нет
        if (!file_exists($settings['log_dir'])) {
            mkdir($settings['log_dir'], 0755, true);
        }
        
        $logPath = $settings['log_dir'].$settings['log_file'];
        
        // Функция логирования
        $log = function($message) use ($logPath) {
            $logMessage = date('[Y-m-d H:i:s] ').$message.PHP_EOL;
            file_put_contents($logPath, $logMessage, FILE_APPEND | LOCK_EX);
        };

        $log("=== Обработка нового комментария ===");
        $log("ID комментария: ".$commentId);

        // 3. Проверяем модуль CRM
        if (!CModule::IncludeModule('crm')) {
            $log("Ошибка: Модуль CRM не доступен");
            return;
        }

        // 4. Получаем данные комментария через CommentEntry
        $commentEntry = \Bitrix\Crm\Timeline\CommentEntry::getByID($commentId);
        if (!$commentEntry) {
            $log("Ошибка: Не найдена запись комментария");
            return;
        }

        $log("Данные комментария: ".print_r($commentEntry, true));

        // 5. Получаем привязку к сущности
        $entityTypeId = (int)$commentEntry['ENTITY_TYPE_ID'];
        $entityId = (int)$commentEntry['ENTITY_ID'];
        
        if ($entityTypeId === 0 || $entityId === 0) {
            // Альтернативный способ получить привязку через TimelineBinding
            $binding = \Bitrix\Crm\Timeline\Entity\TimelineBindingTable::getList([
                'filter' => ['OWNER_ID' => $commentId]
            ])->fetch();
            
            if ($binding) {
                $entityTypeId = (int)$binding['ENTITY_TYPE_ID'];
                $entityId = (int)$binding['ENTITY_ID'];
                $log("Найдена привязка через TimelineBinding: тип=$entityTypeId, ID=$entityId");
            }
        }
        
        if ($entityTypeId === 0 || $entityId === 0) {
            $log("Пропуск: Нет привязки к сущности");
            return;
        }

        $log("Привязка: тип=$entityTypeId, ID=$entityId");

        // 6. Получаем фабрику элемента
        $factory = Bitrix\Crm\Service\Container::getInstance()
            ->getFactory($entityTypeId);
            
        if (!$factory) {
            $log("Ошибка: Не найдена фабрика для типа $entityTypeId");
            return;
        }

        // 7. Получаем элемент
        $item = $factory->getItem($entityId);
        if (!$item) {
            $log("Ошибка: Элемент $entityId не найден");
            return;
        }

        // 8. Получаем текст комментария
        $commentText = $commentEntry['COMMENT'] ?? '';
        if (empty($commentText)) {
            $log("Пропуск: Пустой комментарий");
            return;
        }

        // 9. Получаем ID автора и ответственного
        $commentAuthorId = (int)$commentEntry['AUTHOR_ID'];
        $responsibleId = (int)$item->get('ASSIGNED_BY_ID');
        
        $log("ID автора: $commentAuthorId, ID ответственного: $responsibleId");

        // 10. Проверяем, что автор не является ответственным
        if ($commentAuthorId != $responsibleId) {
            $log("Автор не является ответственным - выполняем действия");
            
            // 10.1. Обновляем кастомное поле
            if ($factory->isFieldExists($settings['custom_field'])) {
                $item->set($settings['custom_field'], $commentText);
                
                $operation = $factory->getUpdateOperation($item);
                $updateResult = $operation->launch();
                
                if ($updateResult->isSuccess()) {
                    $log("Успех: Поле обновлено");
                } else {
                    $errors = implode(', ', $updateResult->getErrorMessages());
                    $log("Ошибка обновления: $errors");
                }
            } else {
                $log("Ошибка: Поле {$settings['custom_field']} не существует в сущности");
            }

            // 10.2. Изменяем цвет комментария
            $newCommentText = $settings['error_color'].$commentText."[/COLOR]";
            $settingsData = $commentEntry['SETTINGS'] ?? [];
            $settingsData['COMMENT'] = $newCommentText;
            $settingsData['COLOR'] = '#800080';
            $settingsData['HAS_FILES'] = 'N';
            
            // Обновляем через CommentEntry
            $updateResult = \Bitrix\Crm\Timeline\CommentEntry::update($commentId, [
                'COMMENT' => $newCommentText,
                'SETTINGS' => $settingsData
            ]);
            
            if ($updateResult->isSuccess()) {
                $log("Успех: Цвет комментария изменен");
            } else {
                $errors = implode(', ', $updateResult->getErrorMessages());
                $log("Ошибка изменения цвета: $errors");
            }
        } else {
            $log("Пропуск: Автор комментария является ответственным - действия не требуются");
        }

        $log("=== Обработка завершена ===");

    } catch (Exception $e) {
        // Аварийное логирование
        $errorMessage = "Критическая ошибка: ".$e->getMessage()."\n".$e->getTraceAsString();
        file_put_contents(
            $settings['log_dir'].'comments_errors.log',
            date('[Y-m-d H:i:s] ').$errorMessage.PHP_EOL,
            FILE_APPEND
        );
    }
}

// Тестовый вызов для проверки
if ($_GET['test_comment_handler'] == 'Y') {
    handleNewComment(1); // Укажите реальный ID комментария
    die('Тест завершен. Проверьте логи в /upload/logs/');
}

Давайте посмотрим, как это выглядит….