API для смарт процессов в CRM Битрикс24
Вся кастомизация в новом API CRM построена на паттерне Service Locator (Сервис-локатор). В основу нового api заложены такие паттерны как: Factory Method (Фабричный Метод).
Container, Factory, Operation и Action: основные понятия
В новом API появилось много не совсем очевидных слов поэтому давайте разберемся в терминологии:
Точкой входа в весь механизм является контейнер, контейнер это singleton-класс который с одной стороны обеспечивает простой доступ к сервисам из локатора сервисов, с другой стороны является абстрактной фабрикой для фабрик по работе с сущностями.
Задача: обеспечить точку входа.
Мы уже знаем что точкой входа в новое API является контейнер, основной рабочей лошадкой является фабрика. Именно она выполняет функцию унификации в коде и отвечает за единообразие всех методов универсального API.
Каждая фабрика является наследником абстрактного класса \Bitrix\Crm\Service\Factory который содержит универсальные методы, работающие на любом наследнике.
Паттерн: реализация паттерна Фабричный Метод (Factory Method).
Задача: фабрика создает объекты Operation (операции) для работы с элементами CRM.
Как работает: DinamicFactory наследуется от базового \Bitrix\Crm\Service\Factory.
Нас интересует три главных метода фабрики, которые возвращают объекты операций:
getAddOperation()→Operation\Add(создание)getUpdateOperation()→Operation\Update(обновление)getDeleteOperation()→Operation\Delete(удаление)
Термин Operation (Операция) наиболее емко описывает необходимый набор действий для достижений конкретной цели.
Представим себе элемент crm, который мы хотим добавить. Добавление, это действие которое мы хотим совершить и запись в базу лишь часть действительно большого процесса — добавления элемента. Здесь мы можем попасть в логическую ловушку и подумать что процесс добавление элемента это и есть фактическое добавление записи в базу данных однако не стоит забывать что в нем так же участвуют обработчики событий, проверка прав, нормализация данных, поисковая индексация и другие важные этапы, в том числе запуск бизнес-процессов запускаемых при добавлении элемента.
Задача: инкапсулирует всю логику выполнения действия над элементом (добавление, изменение, удаление).
Как работает: каждая операция содержит массив Action, отдельные шаги с бизнес-логикой, операция последовательно выполняет эти шаги.
Аналогом обработчиков событий в старом подходе Битрикс, являются Action (Действие), специальный класс реализующий Bitrix\Crm\Service\Operation\Action, который добавляется к операции.
Задача: это отдельный, атомарный кусок бизнес-логики представляющий ваш кастомный код.
Как работает: вас должен интересовать один главный метод process(). Он принимает объект Bitrix\Crm\Item (элемент, над которым проводится операция) и возвращает Bitrix\Main\Result (успех или ошибку).
С какими сущностями работает новое API
Новый API полностью поддерживается для:
- Смарт-процессов
- Счетов
- Коммерческих предложений
- Документов (подсистема подписи)
Опциональная поддержка, в зависимости от настроек:
- Лиды
- Сделки
- Контакты
- Компании
Как узнать включено или нет новое 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
Включить механизм можно двумя способами:
- Через код
- Через браузер
Включить через код
Для включения через код, необходимо задействовать механизм настроек, переводим флаг в положение 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после успешного сохранения элемента в БД
Чтобы добавить свою логику, вам нужно:
- В фабрике переопределить метод в зависимости от этапа на котором нужно поменять логику:
getAddOperationдля кастомизации при созданииgetUpdateOperationдля кастомизации при обновленииgetDeleteOperationдля кастомизации при удалении
- Добавить свой
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()
);
Создаем класс, в котором будет реализована ваша логика обработки:
/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']
];
}
Валидация заложенная разработчиками Битрикс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();
}


