Действие или 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)
Создадим 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
}
}
};
Добавим локальное приложение на портал, укажем ссылку на index.php, настроим права:
Перейдем в приложение, попробуем установить (в фрейме приложения используются права пользователя, так что устанавливайте и настраивайте активити из под админа), в случае чего отработаем ошибки:
Посмотрим результат:
Напишем код настроек параметров, ключевой момент - принять параметры и обновить их после сохранения настроек, изменение кода не требуется:
<?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}
)
Смотрим карточку настроек:
Теперь переходим в 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);
}
Вызываем бизнес-процесс, смотрим результат:
На этом всё, благодарим за внимание! Будем признательны обратной связи в комментариях и постараемся ответить на вопросы.