Главная/Статьи/Битрикс24: запуск бизнес-процессов через API

Битрикс24: запуск бизнес-процессов через API

Программный запуск бизнес-процессов для сделок, лидов, контактов и других сущностей CRM. Передача параметров и обработка ошибок.

ДМ
Дмитрий Мещеряков
📅 20 октября 2022 г.📖 7 мин чтения

Программный запуск бизнес-процессов — та часть Битрикс24, где документация заканчивается на сигнатуре метода, а всё остальное выясняется опытным путём. Разберём CBPDocument::StartWorkflow() по аргументам, и отдельно — три вещи, которые определяют, будет ли это работать в продакшене: контекст прав, асинхронность и защита от повторных запусков.

Введение

Бизнес-процессы в Битрикс24 можно запускать не только вручную, но и программно через API. Это позволяет:

  • Автоматизировать запуск процессов при определённых событиях
  • Передавать параметры в процесс динамически
  • Интегрировать БП с внешними системами

Базовый запуск процесса

php
<?php
use Bitrix\Main\Loader;

Loader::includeModule('bizproc');
Loader::includeModule('crm');

$workflowTemplateId = 423;  // ID шаблона бизнес-процесса
$dealId = 962558;           // ID сделки

$errors = [];

$workflowId = \CBPDocument::StartWorkflow(
    $workflowTemplateId,
    ['crm', 'CCrmDocumentDeal', 'DEAL_' . $dealId],
    [
        // Параметры для процесса
        'phone' => '+79991234567',
    ],
    $errors
);

if (!empty($errors)) {
    foreach ($errors as $error) {
        echo "[{$error['code']}] {$error['message']}\n";
    }
} else {
    echo "Запущен процесс: {$workflowId}\n";
}
💡 Совет

Три элемента во втором аргументе — это адрес документа, и путать их местами получается легко. ['crm', 'CCrmDocumentDeal', 'DEAL_962558']: модуль, класс документа, идентификатор сущности с типовым префиксом. Префикс обязателен — '962558' вместо 'DEAL_962558' даёт ошибку «документ не найден», а не подсказку.

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

Типы документов CRM

СущностьКласс документаФормат ID
СделкаCCrmDocumentDealDEAL_123
ЛидCCrmDocumentLeadLEAD_123
КонтактCCrmDocumentContactCONTACT_123
КомпанияCCrmDocumentCompanyCOMPANY_123
СчётCCrmDocumentInvoiceINVOICE_123
Смарт-процессCCrmDocumentSmartInvoiceSMART_INVOICE_123

Класс для работы с бизнес-процессами

php
<?php

namespace Local\Bizproc;

use Bitrix\Main\Loader;

class WorkflowLauncher
{
    /**
     * Типы документов CRM
     */
    private const DOCUMENT_TYPES = [
        'deal' => ['crm', 'CCrmDocumentDeal', 'DEAL_'],
        'lead' => ['crm', 'CCrmDocumentLead', 'LEAD_'],
        'contact' => ['crm', 'CCrmDocumentContact', 'CONTACT_'],
        'company' => ['crm', 'CCrmDocumentCompany', 'COMPANY_'],
        'invoice' => ['crm', 'CCrmDocumentInvoice', 'INVOICE_'],
    ];

    public function __construct()
    {
        if (!Loader::includeModule('bizproc')) {
            throw new \RuntimeException('Модуль bizproc не установлен');
        }
        Loader::includeModule('crm');
    }

    /**
     * Запуск бизнес-процесса для сделки
     */
    public function startForDeal(int $templateId, int $dealId, array $params = []): WorkflowResult
    {
        return $this->start($templateId, 'deal', $dealId, $params);
    }

    /**
     * Запуск бизнес-процесса для лида
     */
    public function startForLead(int $templateId, int $leadId, array $params = []): WorkflowResult
    {
        return $this->start($templateId, 'lead', $leadId, $params);
    }

    /**
     * Запуск бизнес-процесса для контакта
     */
    public function startForContact(int $templateId, int $contactId, array $params = []): WorkflowResult
    {
        return $this->start($templateId, 'contact', $contactId, $params);
    }

    /**
     * Запуск бизнес-процесса для компании
     */
    public function startForCompany(int $templateId, int $companyId, array $params = []): WorkflowResult
    {
        return $this->start($templateId, 'company', $companyId, $params);
    }

    /**
     * Универсальный запуск процесса
     */
    public function start(int $templateId, string $entityType, int $entityId, array $params = []): WorkflowResult
    {
        if (!isset(self::DOCUMENT_TYPES[$entityType])) {
            throw new \InvalidArgumentException("Неизвестный тип сущности: {$entityType}");
        }

        $docType = self::DOCUMENT_TYPES[$entityType];
        $documentId = [$docType[0], $docType[1], $docType[2] . $entityId];

        $errors = [];
        
        $workflowId = \CBPDocument::StartWorkflow(
            $templateId,
            $documentId,
            $params,
            $errors
        );

        return new WorkflowResult($workflowId, $errors);
    }

    /**
     * Получение списка шаблонов БП для типа сущности
     */
    public function getTemplates(string $entityType): array
    {
        if (!isset(self::DOCUMENT_TYPES[$entityType])) {
            return [];
        }

        $docType = self::DOCUMENT_TYPES[$entityType];

        return \CBPWorkflowTemplateLoader::GetList(
            ['ID' => 'DESC'],
            [
                'DOCUMENT_TYPE' => $docType,
                'ACTIVE' => 'Y',
            ],
            false,
            false,
            ['ID', 'NAME', 'DESCRIPTION', 'MODIFIED']
        );
    }

    /**
     * Проверка статуса запущенного процесса
     */
    public function getWorkflowState(string $workflowId): ?array
    {
        $state = \CBPStateService::GetWorkflowState($workflowId);
        
        if (!$state) {
            return null;
        }

        return [
            'id' => $state['ID'],
            'template_id' => $state['WORKFLOW_TEMPLATE_ID'],
            'document_id' => $state['DOCUMENT_ID'],
            'state' => $state['STATE'],
            'state_title' => $state['STATE_TITLE'],
            'started' => $state['STARTED'],
            'modified' => $state['MODIFIED'],
        ];
    }

    /**
     * Остановка процесса
     */
    public function terminate(string $workflowId): bool
    {
        $errors = [];
        \CBPDocument::TerminateWorkflow($workflowId, [], $errors);
        
        return empty($errors);
    }
}

/**
 * Результат запуска процесса
 */
class WorkflowResult
{
    public readonly ?string $workflowId;
    public readonly array $errors;
    public readonly bool $success;

    public function __construct(?string $workflowId, array $errors)
    {
        $this->workflowId = $workflowId;
        $this->errors = $errors;
        $this->success = !empty($workflowId) && empty($errors);
    }

    public function getErrorMessages(): array
    {
        return array_map(
            fn($e) => "[{$e['code']}] {$e['message']}",
            $this->errors
        );
    }
}

Примеры использования

Запуск при создании сделки

php
<?php
use Bitrix\Main\EventManager;
use Local\Bizproc\WorkflowLauncher;

EventManager::getInstance()->addEventHandler(
    'crm',
    'OnAfterCrmDealAdd',
    function ($fields) {
        // Запускаем БП только для сделок из определённого источника
        if ($fields['SOURCE_ID'] !== 'WEB') {
            return;
        }

        $launcher = new WorkflowLauncher();
        
        $result = $launcher->startForDeal(
            423, // ID шаблона
            $fields['ID'],
            [
                'notify_manager' => true,
                'priority' => 'high',
            ]
        );

        if (!$result->success) {
            AddMessage2Log(
                "Ошибка запуска БП: " . implode(', ', $result->getErrorMessages()),
                'bizproc'
            );
        }
    }
);

Запуск из REST-обработчика

php
<?php
// Обработчик входящего вебхука
$dealId = (int) $_POST['deal_id'];
$action = $_POST['action'] ?? '';

if ($action === 'approve') {
    $launcher = new WorkflowLauncher();
    
    $result = $launcher->startForDeal(
        450, // БП "Согласование"
        $dealId,
        [
            'approved_by' => $_POST['user_id'],
            'comment' => $_POST['comment'] ?? '',
        ]
    );

    echo json_encode([
        'success' => $result->success,
        'workflow_id' => $result->workflowId,
        'errors' => $result->getErrorMessages(),
    ]);
}

Получение списка доступных БП

php
<?php
$launcher = new WorkflowLauncher();
$templates = $launcher->getTemplates('deal');

echo "Доступные бизнес-процессы для сделок:\n";
while ($template = $templates->Fetch()) {
    echo "  [{$template['ID']}] {$template['NAME']}\n";
}

Передача параметров в БП

Параметры передаются третьим аргументом в StartWorkflow(). Они доступны в процессе через переменные:

php
$params = [
    'phone' => '+79991234567',        // Строка
    'amount' => 15000.50,             // Число
    'send_email' => true,             // Да/Нет
    'manager_ids' => [1, 5, 8],       // Множественное значение
    'deadline' => '2026-12-31',       // Дата
];

В редакторе БП создайте параметры с соответствующими кодами и типами.

Три вещи, о которых узнают в продакшене

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

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

Повторный запуск ничем не ограничен. Метод не проверяет, не крутится ли уже такой же процесс на том же документе. Обработчик на событии сделки, отработавший дважды (а он отработает — при повторном сохранении, при импорте, при действии другого процесса), запустит два экземпляра, и клиент получит два одинаковых письма. Проверяйте активные экземпляры перед запуском либо ведите отметку в поле сделки.

Обработка ошибок

Типичные ошибки при запуске:

КодОписаниеРешение
BPWT001Шаблон не найденПроверьте ID шаблона
BPWF001Документ не найденПроверьте ID сущности
ACCESS_DENIEDНет правЗапустите от имени администратора
💡 Совет

Совет: Для отладки включите логирование бизнес-процессов в настройках модуля. Логи помогут понять, на каком этапе возникла ошибка.

⚠️ Важно

Массовый запуск требует отдельного подхода. Экземпляр бизнес-процесса — это записи в нескольких таблицах плюс очередь шагов, которые будут выполняться на хитах или по расписанию. Запуск процесса для тысячи сделок одной командой не «займёт ресурсы», а положит портал: очередь бизнес-процессов забьётся, и остановить это будет сложнее, чем запустить.

Если задача действительно массовая — запускайте порциями с паузой и заранее проверьте, что процесс отрабатывает корректно, на одной-двух сделках.

Итоги

Проверяйте адрес документа первым делом — большинство ошибок «шаблон не найден» и «документ не найден» именно здесь.

Помните про контекст прав. Ручной запуск работает от вашего имени, программный — от того, кто выполняет код.

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

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

💡 Совет

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

🚀

Хотите такое же решение?

Настрою окружение под ваш проект, учту специфику инфраструктуры и обучу команду.

Обсудить проект →
Бесплатная консультация · Ответ в течение дня

Комментарии

Система комментариев скоро будет подключена. А пока вы можете написать мне в Telegram или на email.