Полный цикл в digital

API для смарт процессов в CRM Битрикс24

Вся кастомизация в новом API CRM построена на паттерне Service Locator (Сервис-локатор). В основу нового api заложены такие паттерны как: Factory Method (Фабричный Метод).

Container, Factory, Operation и Action: основные понятия

В новом API появилось много не совсем очевидных слов поэтому давайте разберемся в терминологии:

Container (Контейнер)

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

Задача: обеспечить точку входа.

Factory Method (Фабричный Метод)

Мы уже знаем что точкой входа в новое API является контейнер, основной рабочей лошадкой является фабрика. Именно она выполняет функцию унификации в коде и отвечает за единообразие всех методов универсального API.

Каждая фабрика является наследником абстрактного класса \Bitrix\Crm\Service\Factory который содержит универсальные методы, работающие на любом наследнике.

Паттерн: реализация паттерна Фабричный Метод (Factory Method).

Задача: фабрика создает объекты Operation (операции) для работы с элементами CRM.

Как работает: DinamicFactory наследуется от базового \Bitrix\Crm\Service\Factory.

Нас интересует три главных метода фабрики, которые возвращают объекты операций:

  1. getAddOperation()Operation\Add (создание)
  2. getUpdateOperation()Operation\Update (обновление)
  3. getDeleteOperation()Operation\Delete (удаление)
Operation (Операция)

Термин Operation (Операция) наиболее емко описывает необходимый набор действий для достижений конкретной цели.

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

Задача: инкапсулирует всю логику выполнения действия над элементом (добавление, изменение, удаление).

Как работает: каждая операция содержит массив Action, отдельные шаги с бизнес-логикой, операция последовательно выполняет эти шаги.

Action (Действие)

Аналогом обработчиков событий в старом подходе Битрикс, являются Action (Действие), специальный класс реализующий Bitrix\Crm\Service\Operation\Action, который добавляется к операции.

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

Как работает: вас должен интересовать один главный метод process(). Он принимает объект Bitrix\Crm\Item (элемент, над которым проводится операция) и возвращает Bitrix\Main\Result (успех или ошибку).

С какими сущностями работает новое API

Новый API полностью поддерживается для:

  1. Смарт-процессов
  2. Счетов
  3. Коммерческих предложений
  4. Документов (подсистема подписи)

Опциональная поддержка, в зависимости от настроек:

  1. Лиды
  2. Сделки
  3. Контакты
  4. Компании

Как узнать включено или нет новое API

Проверить, включено ли новое API, можно только через php-код. Для проверки можно использовать специальные классы настроек:

use \Bitrix\Crm\Settings;
use \Bitrix\Main\Loader;

Loader::requireModule('crm');

var_dump([
    'deal' => Settings\DealSettings::getCurrent()->isFactoryEnabled(),
    'lead' => Settings\LeadSettings::getCurrent()->isFactoryEnabled(),
    'contact' => Settings\ContactSettings::getCurrent()->isFactoryEnabled(),
    'company => Settings\CompanySettings::getCurrent()->isFactoryEnabled(),
]);

А также можно использовать объекты сущностей:

use \Bitrix\Main\Loader;

Loader::requireModule('crm');

var_dump([
    'deal' => ( new \CCrmDeal() )->isUseOperation(),
    'lead' => ( new \CCrmLead() )->isUseOperation(),
    'contact' => ( new \CCrmContact() )->isUseOperation(),
    'company' => ( new \CCrmCompany() )->isUseOperation(),
]);

Пример результата когда использование выключено:

array(4) {
  ["deal"] => bool(false)
  ["lead"] => bool(false)
  ["contact"] => bool(false)
  ["company"] => bool(false)
}

Как включить/отключить новое API

Включить механизм можно двумя способами:

  1. Через код
  2. Через браузер

Включить через код

Для включения через код, необходимо задействовать механизм настроек, переводим флаг в положение true:

\Bitrix\Main\Loader::requireModule('crm');

\Bitrix\Crm\Settings\DealSettings::getCurrent()->setFactoryEnabled(true);

Аналогично можно сделать для других сущностей, достаточно вместо DealSettings использовать LeadSettings, ContactSettings и CompanySettings соответственно.

Включить через браузер

Для включения через браузер, достаточно перейти в раздел CRM и добавить параметр ?enableFactory=Y. Включить новую настройку может любой пользователь с правами на доступ в CRM.

Отключение

Отключение механизма производится аналогично подключению, с единственной разницей, необходимо передавать false в php-коде и N в адресной строке.

Service Locator

Вся кастомизация в новом API построена на паттерне Service Locator (Сервис-локатор).

Регистрация сервисов

Битрикс заранее регистрирует в системе все классы-фабрики для работы с сущностями CRM. Это происходит в конфигурационных файлах, например в bitrix/modules/crm/.settings.php:

'crm.service.factory.contact' => [
    'className' => '\\Bitrix\\Crm\\Service\\Factory\\Contact',
],

Получение сервиса

В коде любой модуль или компонент может получить стандартную фабрику для работы с контактами, получаем стандартную фабрику контактов через Service Locator:

$factory = \Bitrix\Main\DI\ServiceLocator::getInstance()->get('crm.service.factory.contact');

Подмена сервиса с целью кастомизации

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

// наш кастомный класс фабрики
class ContactFactory extends \Bitrix\Crm\Service\Factory\Contact
{
    // здесь мы переопределим методы, например, для добавления обработчиков
}

Затем мы подменяем стандартный сервис в локаторе на наш:

// подменяем стандартную фабрику своей
\Bitrix\Main\DI\ServiceLocator::getInstance()->addInstance('crm.service.factory.contact', new ContactFactory());

Результат подмены

Теперь любой код в системе, который запрашивает фабрику контактов, будет получать наш экземпляр ContactFactory. Таким образом, наша логика будет выполняться везде, где используется фабрика:

$factory = \Bitrix\Main\DI\ServiceLocator::getInstance()->get('crm.service.factory.contact');

Упрощенное получение через Container

Для удобства, разработчики Битрикс24 добавили класс-обертку Container. Стандартный способ получить фабрику через Container, после подмены он тоже вернет наш кастомный класс:

$factory = \Bitrix\Crm\Service\Container::getInstance()->getFactory(\CCrmOwnerType::Contact);

Как добавить свой обработчик

Вся магия происходит внутри методов фабрики. Вы переопределяете нужный метод, например для обновления getUpdateOperation, получаете стандартную операцию и добавляете в нее свой Action:

// переопределяем метод получения операции обновления
public function getUpdateOperation(Item $item, Context $context = null): Operation\Update
{
    // 1. получаем стандартную операцию обновления
    $operation = parent::getUpdateOperation($item, $context);

    // 2. добавляем свой Action на этап "ПЕРЕД сохранением"
    $operation->addAction(
        Operation::ACTION_BEFORE_SAVE,
        new ExampleAction()
    );

    return $operation;
}

Метод addAction() принимает два ключевых параметра:

  • Operation::ACTION_BEFORE_SAVE до сохранения элемента в БД
  • Operation::ACTION_AFTER_SAVE после успешного сохранения элемента в БД

Чтобы добавить свою логику, вам нужно:

  1. В фабрике переопределить метод в зависимости от этапа на котором нужно поменять логику:
    1. getAddOperation для кастомизации при создании
    2. getUpdateOperation для кастомизации при обновлении
    3. getDeleteOperation для кастомизации при удалении
  2. Добавить свой Action в нужную операцию с помощью addAction(), указав этап выполнения ACTION_BEFORE_SAVE или ACTION_AFTER_SAVE

Как добавить свой обработчик в смарт-процесс

Разберем на примере смарт-процесса под названием Рекламации с идентификатором 1036:

Создаем кастомную фабрику

Создаем класс-фабрику, которая будет возвращать операцию с вашим обработчиком:

/local/modules/hmarketing.rest/lib/Crm/OrderFactory.php<?php

namespace Hmarketing\Rest\Crm;

use Bitrix\Crm\Service\Factory\Dynamic;
use Bitrix\Crm\Service\Context;
use Bitrix\Crm\Service\Operation;
use Bitrix\Crm\Model\Dynamic\TypeTable;
use Bitrix\Crm\Item;

class OrderFactory extends Dynamic
{
    // идентификатор типа смарт-процесса
    protected int $entityTypeId = 1036;

    public function __construct()
    {
        $type = TypeTable::getByEntityTypeId($this->entityTypeId)->fetchObject();

        if (!is_null($type))
        {
            parent::__construct($type);
        }
        else
        {
            // важно, обработайте случай, если тип не найден
            throw new \Exception("Smart Process с идентификатором ID={$this->entityTypeId} не найден.");
        }
    }

    // переопределяем метод получения операции обновления
    public function getUpdateOperation(Item $item, Context $context = null): Operation\Update
    {
        // 1. получаем стандартную операцию обновления
        $operation = parent::getUpdateOperation($item, $context);

        // 2. добавляем свой Action на этап "ПЕРЕД сохранением"
        $operation->addAction(
            Operation::ACTION_BEFORE_SAVE,
            new \Hmarketing\Rest\Action\OrderAction()
        );

        return $operation;
    }
}
Подменяем фабрику в сервис-локаторе

Регистрируем нашу фабрику вместо стандартной. Это нужно сделать в точке входа, например в init.php. Ключ сервиса формируется по шаблону crm.service.factory.dynamic.{ID_СУЩНОСТИ}:

/local/php_interface/init.php<?php

// подключаем модуль crm
\Bitrix\Main\Loader::includeModule('crm');

// подключаем кастомный модуль где лежат все файлы hmarketing.rest
\Bitrix\Main\Loader::includeModule('hmarketing.rest');


// подменяем фабрику для конкретного смарт-процесса (ID=1036)
\Bitrix\Main\DI\ServiceLocator::getInstance()->addInstance(
    // ключ для динамического типа
    'crm.service.factory.dynamic.1036',
    // подкидываем экземпляр класса созданного на первом шаге
    new Hmarketing\Rest\Crm\OrderFactory()
);
Создаем Action с бизнес-логикой

Создаем класс, в котором будет реализована ваша логика обработки:

/local/modules/hmarketing.rest/lib/Action/OrderAction.php<?php

namespace Hmarketing\Rest\Action;

use Bitrix\Main\Result;
use Bitrix\Main\Error;
use Bitrix\Crm\Item;
use Bitrix\Crm\Service\Operation\Action;

class OrderAction extends Action
{
    public function process(Item $item): Result
    {
        $result = new Result();

        // для примера, проверим созданное свойство сумма
        if ($item->get('UF_CRM_3_1766692211591') < 0)
        {
            $result->addError(new Error("Сумма заказа не может быть отрицательной!"));
        }

        // возвращаем результат операции
        return $result;
    }
}

Создаем свойство типа число, с ID свойства равное UF_CRM_3_1766692211591:

Если введем отрицательное число, получим ошибку, так как стоит проверка в нашем Action:

Как это работает:

  • Сервис-локатор возвращает кастомную фабрику OrderFactory
  • При обновлении элемента смарт-процесса система запрашивает операции через метод getUpdateOperation у фабрики
  • Фабрика создает операцию обновления и добавляет в нее ваш OrderAction
  • Система выполняет операцию. Перед сохранением ACTION_BEFORE_SAVE вызывая метод process
  • Если process вернул успешный Result — выполнение продолжается. Если вернулись ошибки — операция прерывается, и элемент не сохраняется

Базовая работа со смарт процессами

В базовой работе нам на выручку придет класс обертка \Bitrix\Crm\Service\Container о каторой я писал выше.

Получение элементов смарт процесса

С полученными смарт процессами можно работать на свое усмотрение.

// переменная содержащия массив для выборки
$itemIDs = [];
// смарт процесс с идентификатором 1036
$entityTypeId = 10361;
// получаем фабрику через класс-обертку
$factory = Bitrix\Crm\Service\Container::getInstance()->getFactory($entityTypeId);
// проверка на наличие смарт процесса
if (!$factory) {
    throw new \Exception("Smart Process с идентификатором ID={$entityTypeId} не найден.");
}
// выборка элементов с лимитом
$items = $factory->getItems([
    // лимит записей за один запрос, критично для производительности
    'limit' => 500
]);
// перебираем полученные данные
foreach ($items as $item) {
    $itemIDs[] = [
        'ID' => $item['ID'],
        'TITLE' => $item['TITLE'],
        'UF_SUMMA' => $item['UF_CRM_3_1766692211591']
    ];
}
Получение элементов с фильтрацией по пользовательскому полю

Операторы сравнения позволяют задавать условия фильтрации:

  • = равно (работает и с массивами)
  • % подстрока
  • > больше
  • < меньше
  • @ IN (EXPR) в качестве значения передается массив или объект
  • !@ NOT IN (EXPR)в качестве значения передается массив или объект
  • != не равно
  • !% не подстрока
  • >< между, в качестве значения передается массив array (MIN, MAX)
  • >= больше или равно
  • <= меньше или равно
  • =% ищет строки, которые начинаются с указанного значения. Аналог оператора LIKE в SQL
  • %= ищет строки, которые заканчиваются указанным значением. Аналог оператора LIKE в SQL

Префиксы %= и =% эквивалентны и используются для поиска подстрок:

  • '%=NAME' => 'тест' аналог LIKE, не подстрока
  • '%=NAME' => 'тест%' содержит «тест» в начале
  • '%=NAME' => '%тест' содержит «тест» в конце
  • '%=NAME' => '%тест%' содержит подстроку «тест»

Булево выражение для ExpressionField, например для EXISTS() или NOT EXISTS()

  • == булевое выражение для ExpressionField (например, для EXISTS () или NOT EXISTS ())
  • !>< не между, в качестве значения передается массив array (MIN, MAX)
  • !=% NOT LIKE
  • !%= NOT LIKE
  • '==ID' => null условие, что поле ID равно NULL (в sql-запросе будет преобразовано в ID IS NULL)
  • '!==NAME' => null условие, что поле NAME не равно NULL (в sql-запросе будет преобразовано в NAME IS NOT NULL)
// переменная содержащия массив для выборки
$itemIDs = [];
// смарт процесс с идентификатором 1036
$entityTypeId = 1036;
// получаем фабрику через класс-обертку
$factory = Bitrix\Crm\Service\Container::getInstance()->getFactory($entityTypeId);
// проверка на наличие смарт процесса
if (!$factory) {
    throw new \Exception("Smart Process с идентификатором ID={$entityTypeId} не найден.");
}
// выборка элементов с лимитом по пользовательскому полю
$items = $factory->getItems([
    // фильтр по указанному полю
    'filter' => ['=UF_CRM_3_1766692211591' => 10.0],
    // выбираем только нужные поля
    'select' => ['ID', 'TITLE', 'UF_CRM_3_1766692211591'],
    // лимит записей за один запрос, критично для производительности
    'limit' => 500
]);
// перебираем полученные данные
foreach ($items as $item) {
    $itemIDs[] = [
        'ID' => $item['ID'],
        'TITLE' => $item['TITLE'],
        'UF_SUMMA' => $item['UF_CRM_3_1766692211591']
    ];
}
Создать новую запись cмарт процесса

Валидация заложенная разработчиками Битрикс24:

Действие Включение Выключение Проверка
Проверка прав доступа к элементу enableCheckAccess disableCheckAccess isCheckAccessEnabled
Проверка запущенных БП enableCheckWorkflows disableCheckWorkflows isCheckWorkflowsEnabled
Обработка бизнес-логик полей (до и после) enableFieldProcession disableFieldProcession isFieldProcessionEnabled
Проверка заполненных полей (на предмет обязательности) enableCheckFields disableCheckFields isCheckFieldsEnabled
Валидация польз.полей enableCheckRequiredUserFields disableCheckRequiredUserFields isCheckRequiredUserFields
Если элемент изменен, запуск Действий до сохранения enableBeforeSaveActions disableBeforeSaveActions isBeforeSaveActionsEnabled
Сохранение записи в историю и timeline enableSaveToHistory disableSaveToHistory isSaveToHistoryEnabled
Запуск Действий после сохранения enableAfterSaveActions disableAfterSaveActions isAfterSaveActionsEnabled
Запуск бизнес-процессов enableBizProc disableBizProc isBizProcEnabled
Запуск автоматизации enableAutomation disableAutomation isAutomationEnabled
// смарт процесс с идентификатором 1036
$entityTypeId = 1036;
// получаем фабрику через класс-обертку
$factory = Bitrix\Crm\Service\Container::getInstance()->getFactory($entityTypeId);
// проверка на наличие смарт процесса
if (!$factory) {
    throw new \Exception("Smart Process с идентификатором ID={$entityTypeId} не найден.");
}
// описываем массив полей смарт процесса, включая кастомные
$item = $factory->createItem([
    'TITLE' => 'Новый смарт процесс2',
    'UF_CRM_3_1766692211591' => 10.0
]);
// метод добавления смарт процесса
$operation = $factory->getAddOperation($item);
// отключаем все проверки
$operation->disableAllChecks();
// сохраняем новый смарт процесс
$addResult = $operation->launch();
// проверяем на ощибки
$errorMessages = $addResult->getErrorMessages();
// если ощибок нет, получаем ID новой записи СП
if ($addResult->isSuccess()) {
    $newId = $item->getId();
} else {
    throw new \Exception("Элемент Smart Processa не создан.");
}
Удаление записей смарт процессов

Переменная $addResult будет содержать данные об удаленных смарт процессах:

// смарт процесс с идентификатором 1036
$entityTypeId = 1036;
// получаем фабрику через класс-обертку
$factory = Bitrix\Crm\Service\Container::getInstance()->getFactory($entityTypeId);
// проверка на наличие смарт процесса
if (!$factory) {
    throw new \Exception("Smart Process с идентификатором ID={$entityTypeId} не найден.");
}
// получаем смарт процессы по фильтру
$items = $factory->getItems([
    'filter' => [
        'TITLE' => 'Новый смарт процесс1',
    ]
]);
// перебираем полученные данные
foreach($items as $item) {
    // метод удаления смарт процесса
    $operation = $factory->getDeleteOperation($item);
    // отключаем проверки
    $operation
        ->disableCheckFields()
        ->disableBizProc()
        ->disableCheckAccess()
    ;
    // сохраняем удаленные смарт процессы
    $addResult = $operation->launch();
}

Заполните форму уже сегодня!
Для начала сотрудничества необходимо заполнить заявку или заказать обратный звонок. В ответ получите коммерческое предложение, которое будет содержать индивидуальную стратегию с учетом требований и поставленных задач
Работаем по будням с 9:00 до 18:00. Заявки, отправленные в выходные, обрабатываем в первый рабочий день до 12:00.
Спасибо, ваш запрос принят и будет обработан!