Усиливаем
IT-команды
с 2016 года
Все статьи
Забудьте про автоматизацию на списках: смарт-процессы в Битрикс24 решают все
Bitrix24
Юлиана
PHP-разработчик

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

Компании увидели, как красиво, наглядно, легко все автоматизируется в сделках CRM и захотели тоже самое, но не для продаж, а просто для обработки заявок в других областях.


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

И как только появились смарт-процессы, сразу стало понятно, что они закрывают большинство потребностей: 

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


Например, продумаем кейс - обработка заявок в отдел кадров.

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

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

Конечно, валидацию дат для отпуска придется доработать и тут нет, например, правил показа полей, как в срм-формах, но для каждого смарт-процесса можно дорабатывать карточку по своему. Валидацию и показ/скрытие полей, например, можно сделать на javascript, ловя события редактора, а также предварительную проверку можно организовать в событии перед сохранением в кастомизированной операции добавления. 

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


Рассмотрим необходимый для работы код API.

1) Создание элемента смарт-процесса
2) Изменение элемента смарт-процесса
3) Добавление клиента - контакта или компании в элементе смарт-процесса
4) Получение списка элементов
5) Получение связанных элементов
6) Событие добавления, изменения обрабатываются не так, как для стандартных сущностей, и вообще сначала лучше рассмотреть вариант роботов и бп для этих событий, то есть можно обойтись и без программиста. Но в крайнем случае можно расширить операции через замену контейнера фабрики
7) Запрос на получение элемента, списка элементов, изменение элемента через аякс в javascript
8) Замена компонента карточки или списка конкретного смарт-процесса своим компонентом через роутер

Для работы со смарт-процессом используется новое АПИ. 

Из документации узнаем, что использован паттерн проектирования "Абстрактная фабрика", где для каждого типа сущности есть своя фабрика. Получение  фабрики для каждого типа возвращает набор классов, специфичных именно для этого типа. Сервис фабрики является входной точкой всегда, когда необходимо работать с данными, специфичными для конкретного типа сущности. Инстанс фабрики получается из контейнера сервисов.  

use Bitrix\Crm\Service\Container;
$fabrika = Container::getInstance()->getFactory($entityTypeId);
//$entityTypeId - идентификатор типа смарт-процесса.

Получение стадий смарт процесса по его идентификатору типа и ID смарт-процесса:

Идентификатор типа смарт-процесса можно найти в списке смарт-процессов в настройках СРМ. 


Каждый смарт-процесс получает ID, который можно увидеть при создании дополнительных полей в смарт-процессе или найти в списке смарт-процессов в настройках СРМ. 


Соответственно объектом для пользовательских полей будет строка “CRM_ид смарт-процесса”, стадии смарт-процесса можно получить по ид типа и ид смарт-процесса с помощью кода:

use Bitrix\Crm\Service\Container;
$fabrika = Container::getInstance()->getFactory($entityTypeId);
if ($fabrika && $fabrika->isStagesSupported()) {
    $stages = $fabrika->getStages()->getAll();
    foreach( $stages as $stage) {
        $arStages = $stage->getStatusId();
    }
}

Будет возвращен массив типа:

DT174_6:NEW,DT174_6:PREPARATION,DT174_6:CLIENT,DT174_6:SUCCESS,DT174_6:FAIL,

где 174 - идентификатор типа смарт-процесса, а 6 - ид смарт-процесса.

Создание элемента смарт-процесса

use Bitrix\Crm\Service\Container;
$fabrika = Container::getInstance()->getFactory($entityTypeId);
$data = [
    'TITLE' => 'Название элемента',
    'ASSIGNED_BY_ID'=>$userId,
    'UF_CRM_1_DATE' => new Bitrix\Main\Type\DateTime(),
    'PARENT_ID_2' => $dealId,
    "STAGE_ID" => "DT110_1:NEW",
];
$item = $fabrika ->createItem($data);
$item->save();

Для поддержки кастомизации операции добавления:

use Bitrix\Crm\Service\Container;
$fabrika = Container::getInstance()->getFactory($entityTypeId);
/*если добавление происходит в агенте например, то надо установить юзера*/
$context = new \Bitrix\Crm\Service\Context();
$context -> setUserId($userId);
$data = [
    'TITLE' => 'Название элемента',
    'ASSIGNED_BY_ID'=>$userId,
    'UF_CRM_1_DATE' => new Bitrix\Main\Type\DateTime(),
    'PARENT_ID_2' => $dealId,
    "STAGE_ID" => "DT110_1:NEW",
];
$item = $fabrika ->createItem($data);
$saveOperation = $fabrika ->getAddOperation($item, $context);
$operationResult = $saveOperation->launch();

Изменение элемента смарт-процесса

Для одного поля:

use Bitrix\Crm\Service\Container;
$fabrika = Container::getInstance()->getFactory($entityTypeId);
$item = $fabrika->getItem($entityId);
$item->set('UF_CRM_1_POLE', $value);
$item->save();

Для нескольких полей:

use Bitrix\Crm\Service\Container;
$fabrika = Container::getInstance()->getFactory($entityTypeId);
$class = $fabrika -> getDataClass();
$saveOperation = $fabrika ->getUpdateOperation($item, $context);
$item->save();

С поддержкой кастомизации операции изменения:

use Bitrix\Crm\Service\Container;
$fabrika = Container::getInstance()->getFactory($entityTypeId);
$context = new \Bitrix\Crm\Service\Context();
$context -> setUserId($userId);
$item = $fabrika->getItem($entityId);
$saveOperation = $fabrika ->getUpdateOperation($item, $context);
$operationResult = $saveOperation->launch();

Добавление, изменение контакта(компании) в элементе смарт-процесса

Если просто обновить поле CONTACT_ID в таблице через update, то поле клиент в карточке не будет показывать этот контакт.

use Bitrix\Crm\Service\Container;
$fabrika = Container::getInstance()->getFactory($entityTypeId);
$item=$fabrika ->getItem($entityId);
// добавить связь с контактом с ид = 4
$item->bindContacts(
    \Bitrix\Crm\Binding\EntityBinding::prepareEntityBindings(
        \CCrmOwnerType::Contact, [4]
    )
);
// удалить связь с контактом с ид = 3
$item->unBindContacts(
    \Bitrix\Crm\Binding\EntityBinding::prepareEntityBindings(
        \CCrmOwnerType::Contact, [3]
    )
);

Удаление элемента смарт-процесса

use Bitrix\Crm\Service\Container;
$fabrika = Container::getInstance()->getFactory($entityTypeId);
$item->$item->getItem($entityId);
$saveOperation = $fabrika->getDeleteOperation($item, $context);
$operationResult = $saveOperation->launch();

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

use Bitrix\Crm\Service\Container;
$fabrika = Container::getInstance()->getFactory($entityTypeId);
$items = $fabrika->getItems([
    'filter' => ['@ID' => $ids,'=PARENT_ID_1'=>$otherSmartElementId],
    'select' => ['ID', 'UF_CRM_2_POLE', 'TITLE'],
    'order' => ['UF_CRM_2_POLE' => 'ASC'],
    'limit'=>1000,
    'offset' =>0
]);

Либо через getList например с вычисляемыми полями другого связанного смарт-процесса и ответственного пользователя через runtime: 

use Bitrix\Crm\Service\Container;
$fabrika = Container::getInstance()->getFactory($entityTypeId);
$customTable =$fabrika->getDataClass();
$otherLinkTable = Container::getInstance()->getFactory( OTHER_SMART_PROCESS_ID)->getDataClass();
$arItem = $customTable::getList([
    "filter" => ["ID" => $itemID],
    "select" => [
        "ID",
        "UF_CRM_1_POLE",
        "LINK_ELEMENT_ID" => "OTHER_SMART.SRC_ENTITY_ID",
        "LINK_ELEMENT_TITLE" => "OTHER_SMART_FIELD.TITLE",
        "ASSIGNED_LAST_NAME" => "USER.LAST_NAME",
        "ASSIGNED_NAME" => "USER.NAME",
    ],
    "runtime" => [
        new ReferenceField(
            "OTHER_SMART",
            Bitrix\Crm\Relation\EntityRelationTable::class,
            Bitrix\Main\ORM\Query\Join::on("this.ID", "ref.DST_ENTITY_ID")
                ->where("ref.DST_ENTITY_TYPE_ID", "=", SMART_PROCESS_ID)
                ->where("ref.SRC_ENTITY_TYPE_ID", "=", OTHER_SMART_PROCESS_ID)
        ),
        new ReferenceField(
            "OTHER_SMART_FIELD",
            $otherLinkTable,
            Bitrix\Main\Entity\Query\Join::on("this.OTHER_SMART.SRC_ENTITY_ID", "ref.ID")
        ),
        new ReferenceField(
            "USER",
            Bitrix\Main\UserTable::class,
            Bitrix\Main\Entity\Query\Join::on("this.ASSIGNED_BY_ID", "ref.ID")
        ),
    ],
])->fetch();

Получение связанных элементов других смарт-процессов или других сущностей СРМ через relationManager 

use Bitrix\Crm\Item;
use Bitrix\Crm\ItemIdentifier;
use Bitrix\Crm\Service\Container;
$relationManager = Container::getInstance()->getRelationManager();
$itemIdentifier = new ItemIdentifier($entityTypeId, $entityId);
$bindedElements =  $relationManager->getElements($itemIdentifier);
$childElements = $relationManager->getChildElements($itemIdentifier);
$parentElements = $relationManager->getParentElements($itemIdentifier);

События добавления, изменения, удаления элемента смарт-процесса.

Сначала в файле init.php размещаем такой код

define('CRM_USE_CUSTOM_SERVICES', true);
if (defined('CRM_USE_CUSTOM_SERVICES') && CRM_USE_CUSTOM_SERVICES === true) {
    $fileName = __DIR__ . '/include/crm_services.php';
    if (file_exists($fileName)) {
        require_once ($fileName);
    }
}

Далее в crm_services.php производим подмену сервиса.

use Bitrix\Crm\Item,
    Bitrix\Crm\Service,
    Bitrix\Crm\Service\Operation,
    Bitrix\Main\DI,
    Bitrix\Main\Loader,
    Bitrix\Main\Result,
    Bitrix\Main\Error;
Loader::includeModule("crm");
$container = new class extends Service\Container {
    public function getFactory(int $entityTypeId): ? Service\Factory
    {
        if ($entityTypeId === SMART_PROCESS_ID) {
            $type = $this->getTypeByEntityTypeId($entityTypeId);
            $factory = new class($type) extends Service\Factory\Dynamic {
                public function getAddOperation(Item $item, Service\Context $context = null): Operation\Add
                {
                    $operation = parent::getAddOperation($item, $context);
                    $operation->addAction(
                        Operation::ACTION_BEFORE_SAVE,
                        new class extends Operation\Action {
                            public function process(Item $item): Result
                            {
                                /*ваша функция*/
                                return new Result();
                            }
                        }
                    );
                    return $operation->addAction(
                        Operation::ACTION_AFTER_SAVE,
                        new class extends Operation\Action {
                            public function process(Item $item): Result
                            {
                             /*ваша функция*/
                                return new Result();
                            }
                        }
                    );
                }
                public function getUpdateOperation(Item $item, Service\Context $context = null): Operation\Update
                {
                    $operation = parent::getUpdateOperation($item, $context);
                    return $operation->addAction(
                        Operation::ACTION_AFTER_SAVE,
                        new class extends Operation\Action {
                            public function process(Item $item): Result
                            {
                                 /*ваша функция*/
                                return new Result();
                            }
                        }
                    );
                }
                public function getDeleteOperation(Item $item, \Bitrix\Crm\Service\Context $context = null): Operation\Delete
                {
                    $operation = parent::getDeleteOperation($item, $context);
                    $operation->addAction(
                        Operation::ACTION_BEFORE_SAVE,
                        new class extends Operation\Action {
                            public function process(Item $item): Result
                            {
                                /*ваша функция*/
                                return new Result();
                            }
                        }
                    );
                    return $operation;
                }
            };
            return $factory;
        }
        return parent::getFactory($entityTypeId);
    }
};
DI\ServiceLocator::getInstance()->addInstance('crm.service.container', $container);
?>

let item = return BX.ajax.runAction('crm.api.item.get',
            {
                data:
                    {
                        id: entityId,
                        entityTypeId: entityTypeId
                    }
            });

Аякс получение списка элементов смарт-процесса в javascript

let items = BX.ajax.runAction('crm.api.item.list',
            {
                data:
                    {
                        entityTypeId: entityTypeId
                    }
            });

Аякс сохранение элемента смарт-процесса по заполненным полям формы, например, во всплывающем окне в javascript

let form = new FormData(document.getElementById('form'));
let data = {};
let fields ={};
let titles =['pole1','pole2'];
let codes=['ufCrm1_pole1', 'ufCrm1_pole2'];
for(let [name, value] of form) {
    data[name] = value;
}
for(let i=0; i < titles.length; i++){
    let id = titles[i];
    fields[codes[i]] = data[id];
}
fields['ufCrm1_pole3'] = значение;
let result = BX.ajax.runAction(
    'crm.api.item.update',
    {
        data:
            {
                id: groupId,
                entityTypeId: entityTypeId,
                fields
            }
    }
);

Провайдер для динамического выбора элемента смарт процесса для диалогового окна на javascript

Let id ='ваш id поля', title='ваш заголовок поля', value='предустановленное значение';
return new BX.UI.EntitySelector.TagSelector({
            multiple: false,
            dialogOptions: {
                context: id,
                multiple: false,
                dropdownMode: true,
                tabs: [
                    {
                        id: id, title: title, itemOrder: { 'title': 'asc' }
                    }
                ],
                items: [{ id: value, entityId: 'dynamic', title: title,selected:true}],
                entities: [
                    {
                        id: 'dynamic',
                        dynamicSearch: true,
                        dynamicLoad: true,
                    options: {
                                entityTypeId: SMART_PROCESS_ID,
                            }
                    },
                ],
                events: {
                    'Item:onSelect': function(event) {
                        let selectorId = event.getTarget().context;
                        let name ='input[name="'+selectorId+'"]';
                        let value = event.getData().item.id;
                        let input = document.querySelector(name);
                        input.value = value;
                    },
                },
            }
        });

Замена компонента карточки конкретного смарт-процесса своим компонентом

use Bitrix\Crm\Service;
use Bitrix\Main\HttpRequest;
use Bitrix\Crm\Service\Router\ParseResult;
define('SUPER_ENTITY_TYPE_ID', ‘ид типа вашего смарт-процесса’);
$router = new class extends Service\Router {
    public function parseRequest(HttpRequest $httpRequest = null): ParseResult
    {
        $result = parent::parseRequest($httpRequest);
        $parameters = $result->getComponentParameters();
        $entityTypeId = $parameters['ENTITY_TYPE_ID'] ?? $parameters['entityTypeId'] ?? null;
        if ((int)$entityTypeId === SUPER_ENTITY_TYPE_ID) {
            if ($result->getComponentName() === 'bitrix:crm.item.list') {
                $result = new Service\Router\ParseResult(
                    'dev:crm.item.list',
                    $result->getComponentParameters(),
                    $result->getTemplateName()
                );
            }
            if ($result->getComponentName() === 'bitrix:crm.item.details') {
                $result = new ParseResult(
                    'dev:crm.item.details',
                    $parameters,
                    $result->getTemplateName()
                );
            }
        }
        return $result;
    }
};
DI\ServiceLocator::getInstance()->addInstance('crm.service.router', $router);
?>

Другие наши статьи
Создание своей триггерной рассылки битрикс на закрытых событиях
Bitrix: управление сайтом
Вадим
PHP-разработчик
Reflection API. Заглянуть внутрь своего кода или как программа может модифицировать собственную структуру.
Bitrix24
Вадим
PHP-разработчик
Связаться с нами
Оставьте заявку,
чтобы обсудить условия