Как стать автором
Обновить

Создание REST aсtivity (действий) Битрикс24 с приложением-встройкой для препроцессинга параметров

Время на прочтение9 мин
Количество просмотров10K

Действие или activity в Битрикс24 – это составной элемент бизнес-процесса, которые имеет собственные параметры и выполняет какое-то действие.

В REST API присутствует метод “bizproc.activity.add”, он создает активити и может назначить файл с обработчиком для встраивания фрейма в интерфейс портала, что позволяет размещать «фронт» приложений в настройках вашего действия в качестве препроцессора (автоматизируем сложную логику, валидируем данные) или более удобного интерфейса (делаем списки с выбором из человекопонятных пунктов, вместо поиска id или символьного кода для внесения в поле).

Создадим действие для поиска элементов смарт процесса на определённом статусе с использованием REST, php, js. Также мы будем использовать vue.js, но это не необходимо, главное - реализуйте ajax запросы. И, разумеется, вам потребуется хостинг для размещения обработчиков с доступом по https.


Структура

Нам потребуется реализовать приложение из 3 файлов:

1. index.php – файл для установки и удаления активити;

index.php
<?php
header('Content-Type: text/html; charset=UTF-8');

$protocol = $_SERVER['SERVER_PORT'] == '443' ? 'https' : 'http';
$host = explode(':', $_SERVER['HTTP_HOST']);
$host = $host[0];

define('BP_APP_HANDLER', $protocol.'://'.$host.explode('?', $_SERVER['REQUEST_URI'])[0]);
?>
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <script src="//api.bitrix24.com/api/v1/"></script>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    </head>
    <body>
        <h1 style="text-align: center;margin-bottom: 2rem;width: 100%">Активити оптим</h1>
        <div style="margin-left: 30px;max-width: 26rem;">
            <div class="item">
                <h3 style="text-align: center;">"Выборка элементов СП"</h3>
                <button class="btn btn-primary" style="margin-right: 8px;" onclick="installActivity();"><i class="bi bi-download"></i> Установить действие БП</button>
                <button class="btn btn-primary" onclick="uninstallActivity('getspel');"><i class="bi bi-x-square"></i> Удалить действие</button>
            </div>
        </div>
        <script type="text/javascript">
            function installActivity()
            {
                var params = {
					'CODE': 'getspel', //код, уникальный для портала
					'HANDLER': 'https://example.com/example.app/handler.php',//ваш обработчик
					'AUTH_USER_ID': 1,
					'USE_SUBSCRIPTION': '',
					'NAME': 'Получить элементы СП',
                    'USE_PLACEMENT': 'Y',
                    'PLACEMENT_HANDLER': 'https://example.com/example.app/setting.php',//ваш файл настроек
					'DESCRIPTION': 'Принимает тип СП, категорию и стадию, выдаёт массив id элементов на стадии',
					'PROPERTIES': { //здесь параметры, которые будут задаваться через setting, чтобы не отлавливать символьные коды руками
						'typeSP': {
							'Name': 'Тип СП',
							'Type': 'string',
							'Required': 'Y',
							'Multiple': 'N'
						},
						'categoryID': { 
							'Name': 'Категория',
							'Type': 'string',
							'Required': 'Y',
							'Multiple': 'N'
						},
						'statusID': { 
							'Name': 'Статус',
							'Type': 'string',
							'Required': 'Y',
							'Multiple': 'N'
						},
                        'sTypeSP': {
							'Name': 'Тип СП',
							'Type': 'string',
							'Required': 'Y',
							'Multiple': 'N'
						},
						'sCategoryID': { 
							'Name': 'Категория',
							'Type': 'string',
							'Required': 'Y',
							'Multiple': 'N'
						},
						'sStatusID': { 
							'Name': 'Статус',
							'Type': 'string',
							'Required': 'Y',
							'Multiple': 'N'
						}
					},
                    'RETURN_PROPERTIES': { //вернём массив ID привязанных СП
                        'outputString': {
                            'Name': {
                                'ru': 'IDs',
                                'en': 'IDs'
                            },
                            'Type': 'string',
                            'Multiple': 'Y',
                            'Default': null
                        }
                    }
			    };

                BX24.callMethod(
                    'bizproc.activity.add',
                    params,
                    function(result)
                    {
                        if(result.error())
                            alert("Error: " + result.error());
                        else
                            alert("Успешно: " + result.data());
                    }
                );
            }

            function uninstallActivity(code)
            {
                let params = {
                    'CODE': code
                };

                BX24.callMethod(
                    'bizproc.activity.delete',
                    params,
                    function(result)
                    {
                        if(result.error())
                            alert('Error: ' + result.error());
                        else
                            alert("Успешно: " + result.data());
                    }
                );
            }
        </script>
    </body>
</html>

2. handler.php – обработчик, принимает параметры, возвращает результат;

handler.php
<?php
$protocol = $_SERVER['SERVER_PORT'] == '443' ? 'https' : 'http';
$host = explode(':', $_SERVER['HTTP_HOST']);
$host = $host[0];

define('BP_APP_HANDLER', $protocol.'://'.$host.$_SERVER['REQUEST_URI']);

if (!empty($_REQUEST['workflow_id']))//добавим простые проверки - измените под себя
{
    if (!empty($_REQUEST['properties']['typeSP'])){

        $par = array( //сформируем параметры для выборки элементов нужного СП на выбранном статусе
            'entityTypeId' => $_REQUEST['properties']['typeSP'], 
            'select'       => ['id'],
            'order'        => null, 
            'filter'       => ['categoryId' => $_REQUEST['properties']['categoryID'], 'stageId' => $_REQUEST['properties']['statusID']],
        );

        //используем вебхук с правами на CRM, чтобы не отвлекаться на Crest - настраивайте под задачу
        $result = callB24Method('https://example.bitrix24.ru/rest/1/59i35rrrzqg0np/','crm.item.list', $par); //запрашиваем ID's элементов СП

        $arr = [];
        foreach($result['result']['items'] as $item){ //готовим простой массив
            $arr[] = $item['id'];
        }

        //берем авторизацию из пришедшего БП, добавляем массив, возвращаем в БП
        $params = array(
            "auth" => $_REQUEST['auth']["access_token"],
            "event_token" => $_REQUEST["event_token"],
            "log_message" => "Элементы получены",
            "return_values" => array(
                "outputString" => $arr,
            )
        );
        $r = callB24Method('https://example.bitrix24.ru/rest/','bizproc.event.send', $params);
    }

}


function callB24Method($bitrix, $method, $params){ //напишем функцию для отправки запросов через вебхук
    $c = curl_init($bitrix . $method . '.json');

    curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($c, CURLOPT_POST, true);
    curl_setopt($c, CURLOPT_POSTFIELDS, http_build_query($params));

    $response = curl_exec($c);
    $response = json_decode($response, true);

    return $response;
}

3. setting.php – фронтенд фрейма в настройках активити, наш препроцессор.

setting.php
<?php
header('Content-Type: text/html; charset=UTF-8');

$protocol = $_SERVER['SERVER_PORT'] == '443' ? 'https' : 'http';
$host = explode(':', $_SERVER['HTTP_HOST']);
$host = $host[0];

define('BP_APP_HANDLER', $protocol.'://'.$host.explode('?', $_SERVER['REQUEST_URI'])[0]);

$obj = json_decode($_POST['PLACEMENT_OPTIONS']); //объект с параметрами, отрисуем сохранённые параметры
$sp       = $obj->current_values->sTypeSP;
$category = $obj->current_values->sCategoryID;
$status   = $obj->current_values->sStatusID;
?>
<!DOCTYPE html>
<html lang="en" dir="ltr">
    <head>
        <meta charset="utf-8">
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
        <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
        <script src="//api.bitrix24.com/api/v1/"></script>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
        <style>
            .select {
                min-width: 25%;
                margin-bottom: 20px;
                padding-bottom: 3px;
                padding-top: 3px;
            }
            .label {
                font-size: 0.9rem;
                margin-top: 1rem;
                margin-left: 20rem;
            }
        </style>
    </head>
    <body>
        <!-- вёрстка может быть любой, главное - вызвать BX24.placement.call и сохранить новые параметры -->
        <div id="app" style="background-color:#f5f9f9; height: 250px;width: 853px;" >
            <div style="width: 635px;padding-right: 42px; float: right; display: inline-block;margin-top: 15px;">
                <div>
                    <select class="form-select select" v-model="typeSearch">
                        <option value="" disabled selected><?=$sp ?></option>
                        <option v-for="type in types" v-bind:value="{ id: type.entityTypeId, name: type.title }"> {{ type.title }}</option>
                    </select>
                </div>
                <div>
                    <select class="form-select select" v-model="categorySearch">
                        <option value="" disabled selected><?=$category ?></option>
                        <option v-for="category in categories" v-bind:value="{ id: category.id, name: category.name }"> {{ category.name }}</option>
                    </select>
                </div>
                <div >
                    <select class="form-select select" v-model="statusSearch">
                        <option value="" disabled selected><?=$status ?></option>
                        <option v-for="status in statuses" v-bind:value="{ id: status.STATUS_ID, name: status.NAME }"> {{ status.NAME }}</option>
                    </select>
                </div>
            </div>
            <div style="float: left; display: inline-block;margin-top: 15px;margin-left: 5rem;">
                <div> 
                    <p style="font-size: 0.9rem;text-align: end;padding-top: 4px;">Выберите СП:</p>
                </div>
                <div>
                    <p style="font-size: 0.9rem;text-align: end;margin-top: 29px;">Выберите воронку:</p>
                </div>
                <div>
                    <p style="font-size: 0.9rem;text-align: end;margin-top: 29px;">Выберите статус:</p>
                </div>
            </div>
        </div>
        <script>
            let app = new Vue({
                el: '#app',
                data: {
                    types: [],
                    categories: [],
                    statuses: [],
                    typeSearch: '',
                    categorySearch: '',
                    statusSearch: ''
                },
                created: function() { //получаем список СП после отрисовки фрейма
                    BX24.resizeWindow(853, 250);
                    BX24.callMethod(
                        'crm.type.list',
                        '',
                        function(result)
                        {
                            if(result.error())
                                alert("Error: " + result.error());
                            else

                                app.types = result.data().types;
                        }
                    );
                },
                watch: {
                    typeSearch: function() { //выбрали СП - подгружаем его воронки
                        BX24.callMethod(
                            'crm.category.list',
                            {"entityTypeId": app.typeSearch.id},
                            function(result)
                            {
                                if(result.error())
                                    alert("Error");
                                else

                                app.categories = result.data().categories;
                            }
                        );
                    },
                    categorySearch: function() {//выбрали воронку - подгружаем статусы
                        BX24.callMethod(
                            'crm.status.list',
                            {'filter': { "ENTITY_ID": 'DYNAMIC_' + app.typeSearch.id + '_STAGE_' + app.categorySearch.id}},
                            function(result)
                            {
                                if(result.error())
                                    alert("Error");
                                else

                                app.statuses = result.data();
                            }
                        );
                    },
                    statusSearch: function() {
                        BX24.placement.call( //обновим параметры после заполнения каждого пункта
                            'setPropertyValue',
                            {'typeSP': app.typeSearch.id, 'categoryID': app.categorySearch.id, 'statusID': app.statusSearch.id, 'sTypeSP': app.typeSearch.name, 'sCategoryID': app.categorySearch.name, 'sStatusID': app.statusSearch.name}
                        )
                    }
                }
            })
        </script>
    </body>
</html>

Реализация на примере

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

Рекомендуем скачать весь код, развернуть на сервере и следовать статье - мы осветим все ключевые моменты, требующие кастомизации примера (ссылки в index.php и handler.php)

  1. Создадим index.php, вызываемый Б24. Файл в нашем случае содержит php проверку, html вёрстку (заголовок-кнопки) и js скрипт на кнопках установить/удалить. Ниже представлен объект с параметрами для bizproc.activity.add, обратите внимание на USE_PLACEMENT:

var params = {
			'CODE': 'getspel', //код, уникальный для портала
			'HANDLER': 'https:///example.com/example.app/handler.php',//ваш обработчик
			'AUTH_USER_ID': 1,
			'USE_SUBSCRIPTION': '',
			'NAME': 'Получить элементы СП',
            'USE_PLACEMENT': 'Y',
            'PLACEMENT_HANDLER': 'example.com/example.app/setting.php',//ваш файл настроек
			'DESCRIPTION': 'Принимает тип СП, категорию и стадию, выдаёт массив id элементов на стадии',
			'PROPERTIES': { //здесь параметры, которые будут задаваться через setting, чтобы не отлавливать символьные коды руками
    			'typeSP': {
            		'Name': 'Тип СП',
    				'Type': 'string',
    				'Required': 'Y',
    				'Multiple': 'N'
            	},
                  ....
			},
            'RETURN_PROPERTIES': { //вернём массив ID привязанных СП
                  'outputString': {
                      'Name': {
                          'ru': 'IDs',
                          'en': 'IDs'
                      },
                      'Type': 'string',
                      'Multiple': 'Y',
                      'Default': null
                    }
            }
};
  1. Добавим локальное приложение на портал, укажем ссылку на index.php, настроим права:

Нам требуется CRM для доступа к Смарт процессам, БП для создания активити и права на встраивание приложений в карточку настройки активити.
Нам требуется CRM для доступа к Смарт процессам, БП для создания активити и права на встраивание приложений в карточку настройки активити.
  1. Перейдем в приложение, попробуем установить (в фрейме приложения используются права пользователя, так что устанавливайте и настраивайте активити из под админа), в случае чего отработаем ошибки:

    Успешно установлено!
    Успешно установлено!

    Посмотрим результат:

    Активити доступна, но в настройках будет ошибка
    Активити доступна, но в настройках будет ошибка

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

<?php
//объект с параметрами, отрисуем в вёрстке
$obj = json_decode($_POST['PLACEMENT_OPTIONS']); 
$sp       = $obj->current_values->sTypeSP;
$category = $obj->current_values->sCategoryID;
$status   = $obj->current_values->sStatusID;

...Здесь вёрстка...

BX24.placement.call( //обновим параметры после заполнения полей
                            'setPropertyValue',
                            {'typeSP': app.typeSearch.id, 'categoryID': app.categorySearch.id, 'statusID': app.statusSearch.id, 'sTypeSP': app.typeSearch.name, 'sCategoryID': app.categorySearch.name, 'sStatusID': app.statusSearch.name}
                        )
  1. Смотрим карточку настроек:

    Заполняем параметры, сохраняем. 
Результат кладём в строковое множественное поле для демонстрации
    Заполняем параметры, сохраняем. Результат кладём в строковое множественное поле для демонстрации
  2. Теперь переходим в handler.php. Здесь потребуется заменить авторизацию или поместить свой вебхук с правами на CRM:

<?php
//добавим простые проверки - измените под себя
if (!empty($_REQUEST['workflow_id']))
{
  //сформируем параметры для выборки элементов нужного СП на выбранном статусе
  $par = array(
      'entityTypeId' => $_REQUEST['properties']['typeSP'], 
      'select'       => ['id'],
      'order'        => null, 
      'filter'       => ['categoryId' => $_REQUEST['properties']['categoryID'],'stageId' => $_REQUEST['properties']['statusID']],
  );

  //используем вебхук с правами на CRM, чтобы не отвлекаться на Crest - 
  //настраивайте под задачу
  $result = callB24Method('https://example.bitrix24.ru/rest/1/59itd45y6rzqg0np/',                    
                          'crm.item.list', $par); //запрашиваем ID's элементов СП

  $arr = [];
  foreach($result['result']['items'] as $item){ //готовим простой массив
          $arr[] = $item['id'];
  }

  //берем авторизацию из пришедшего БП, добавляем массив, возвращаем в БП
  $params = array(
      "auth" => $_REQUEST['auth']["access_token"],
      "event_token" => $_REQUEST["event_token"],
      "log_message" => "Элементы получены",
      "return_values" => array("outputString" => $arr)
  );
  $r = callB24Method('https://example.bitrix24.ru/rest/','bizproc.event.send',$params);
}
  1. Вызываем бизнес-процесс, смотрим результат:

На этом всё, благодарим за внимание! Будем признательны обратной связи в комментариях и постараемся ответить на вопросы.

Теги:
Хабы:
+4
Комментарии6

Публикации

Изменить настройки темы

Истории

Работа

Ближайшие события

Weekend Offer в AliExpress
Дата20 – 21 апреля
Время10:00 – 20:00
Место
Онлайн
Конференция «Я.Железо»
Дата18 мая
Время14:00 – 23:59
Место
МоскваОнлайн