Как добавить действие для создания смарт-счета на основании лида или сделки
Scope:
bizproc,crmКто может выполнять методы: чтобы пройти сценарий целиком, нужен администратор Битрикс24 с правами на чтение лида или сделки, создание смарт-счета и изменение его товарных позиций
- bizproc.activity.add и bizproc.robot.add — администратор
- crm.item.get — пользователь с правом чтения элементов объекта CRM
- crm.item.add — пользователь с правом добавления элементов объекта CRM
- crm.item.productrow.list — пользователь с правом чтения объекта CRM, товарные позиции которого выбираются
- crm.item.productrow.set — пользователь с правом изменения объекта CRM, товарные позиции которого устанавливаются
Выберите инструмент для разработки с AI-агентом:
- используйте Битрикс24 Вайбкод, чтобы создать приложение для Битрикс24 по описанию задачи без знания языков программирования. Агент напишет код и разместит приложение на сервере без ручной настройки хостинга
- используйте MCP-сервер, чтобы разрабатывать интеграцию через REST API в своем проекте. Агент будет обращаться к официальной REST-документации
Сценарий показывает, как приложение добавляет действие бизнес-процесса, которое получает лид или сделку из контекста запуска и создает смарт-счет по данным CRM-объекта. Из лида или сделки в счет передаются клиент и товарные позиции. Для сделки счет дополнительно связывается с исходной сделкой через поле parentId2.
Действие можно использовать в дизайнере бизнес-процессов. Для CRM-автоматизации используйте робота приложения: набор параметров и обработчик останутся такими же, изменится только метод регистрации.
Сценарий состоит из четырех шагов.
- Зарегистрируйте действие методом bizproc.activity.add
- Получите в обработчике
document_idи определите, что запустило действие: лид или сделка - Получите данные CRM-объекта методом crm.item.get и товарные позиции методом crm.item.productrow.list
- Создайте смарт-счет методом crm.item.add и перенесите товары методом crm.item.productrow.set
Подготовьте приложение
Методы bizproc.activity.add и bizproc.robot.add работают только в контексте приложения. Входящий вебхук не подойдет: метод вернет ошибку ACCESS_DENIED с описанием Application context required.
Перед началом подготовьте:
- установленное приложение со scope
bizprocиcrm - публичный HTTPS-адрес обработчика, например
https://your-domain.example/bp-handler - идентификатор администратора для параметра
AUTH_USER_ID - идентификатор вашей компании для поля смарт-счета
mycompanyId
Авторизацию приложения обработчик получает в запросе от Битрикс24. Значения auth[domain], auth[access_token] и auth[refresh_token] используйте для вызовов CRM-методов из обработчика.
В сценарии используются идентификаторы типов CRM:
|
Объект |
|
|
|
Лид |
|
|
|
Сделка |
|
|
|
Смарт-счет |
|
|
Как использовать примеры в документации
Инициализируйте SDK в обработчике
Обработчик получает авторизацию в запросе от Битрикс24. Используйте auth, чтобы создать клиент SDK для вызовов CRM-методов.
// npm install @bitrix24/b24jssdk
import { B24OAuth } from '@bitrix24/b24jssdk'
const APP = { clientId: 'local.xxxxxxxx.xxxxxxxx', clientSecret: 'yyyyyyyy' }
function makeClient(auth) {
const $b24 = new B24OAuth({
domain: auth.domain,
accessToken: auth.access_token,
refreshToken: auth.refresh_token,
memberId: auth.member_id,
}, APP)
$b24.offClientSideWarning()
return $b24
}
const $b24 = makeClient(req.body.auth)
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Core\Credentials\ApplicationProfile;
use Bitrix24\SDK\Core\Credentials\AuthToken;
use Bitrix24\SDK\Core\Credentials\DefaultOAuthServerUrl;
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
$request = Request::createFromGlobals();
$appProfile = ApplicationProfile::initFromArray([
'BITRIX24_PHP_SDK_APPLICATION_CLIENT_ID' => 'local.xxxxxxxx.xxxxxxxx',
'BITRIX24_PHP_SDK_APPLICATION_CLIENT_SECRET' => 'yyyyyyyy',
'BITRIX24_PHP_SDK_APPLICATION_SCOPE' => 'bizproc,crm',
]);
$authToken = AuthToken::initFromEventRequest($request);
$domain = (string)$request->request->all('auth')['domain'];
$log = new Logger('bizproc');
$log->pushHandler(new StreamHandler('php://stdout'));
$b24 = (new ServiceBuilderFactory(new EventDispatcher(), $log))
->init($appProfile, $authToken, $domain, DefaultOAuthServerUrl::default());
# pip install b24pysdk
from b24pysdk import BitrixApp, BitrixToken, Client
APP = BitrixApp(client_id="local.xxxxxxxx.xxxxxxxx", client_secret="yyyyyyyy")
def make_client(auth: dict) -> tuple[Client, BitrixToken]:
token = BitrixToken(
domain=auth["domain"],
auth_token=auth["access_token"],
refresh_token=auth.get("refresh_token", ""),
bitrix_app=APP,
)
return Client(token), token
auth = request.json["auth"] # словарь auth из тела запроса обработчика
client, token = make_client(auth)
1. Зарегистрируйте действие
Передайте в CODE уникальный код действия в рамках приложения. В HANDLER укажите публичный URL, на который Битрикс24 отправит данные при выполнении действия. В PROPERTIES опишите параметры, которые администратор заполнит в дизайнере бизнес-процесса.
В примере действие получает два параметра:
invoice_title— название смарт-счетаmycompany_id— идентификатор вашей компании
// npm install @bitrix24/b24jssdk
import { initializeB24Frame } from '@bitrix24/b24jssdk'
const $b24 = await initializeB24Frame()
const response = await $b24.actions.v2.call.make({
method: 'bizproc.activity.add',
params: {
CODE: 'create_smart_invoice',
HANDLER: 'https://your-domain.example/bp-handler',
AUTH_USER_ID: 1,
NAME: 'Создать смарт-счет',
DESCRIPTION: 'Создает смарт-счет по данным лида или сделки',
PROPERTIES: {
invoice_title: {
Name: 'Название счета',
Type: 'string',
Required: 'Y',
Default: 'Счет по документу CRM',
},
mycompany_id: {
Name: 'Идентификатор вашей компании',
Type: 'int',
Required: 'Y',
Default: '1',
},
},
FILTER: {
INCLUDE: [
['crm', 'CCrmDocumentDeal'],
['crm', 'CCrmDocumentLead'],
],
},
},
requestId: 'bizproc-activity-add',
})
if (!response.isSuccess) {
throw new Error(response.getErrorMessages().join('; '))
}
console.info(response.getData().result) // true
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
// $b24 построен на токене установленного приложения.
// Типизированный метод activity()->add принимает расширенные DTO,
// поэтому для короткого примера используем прямой вызов через ядро SDK.
$response = $b24->core->call('bizproc.activity.add', [
'CODE' => 'create_smart_invoice',
'HANDLER' => 'https://your-domain.example/bp-handler',
'AUTH_USER_ID' => 1,
'NAME' => 'Создать смарт-счет',
'DESCRIPTION' => 'Создает смарт-счет по данным лида или сделки',
'PROPERTIES' => [
'invoice_title' => [
'Name' => 'Название счета',
'Type' => 'string',
'Required' => 'Y',
'Default' => 'Счет по документу CRM',
],
'mycompany_id' => [
'Name' => 'Идентификатор вашей компании',
'Type' => 'int',
'Required' => 'Y',
'Default' => '1',
],
],
'FILTER' => [
'INCLUDE' => [
['crm', 'CCrmDocumentDeal'],
['crm', 'CCrmDocumentLead'],
],
],
]);
print_r($response->getResponseData()->getResult()); // true
# pip install b24pysdk
# client построен на токене установленного приложения.
result = client.bizproc.activity.add(
code="create_smart_invoice",
handler="https://your-domain.example/bp-handler",
auth_user_id=1,
name="Создать смарт-счет",
description="Создает смарт-счет по данным лида или сделки",
properties={
"invoice_title": {
"Name": "Название счета",
"Type": "string",
"Required": "Y",
"Default": "Счет по документу CRM",
},
"mycompany_id": {
"Name": "Идентификатор вашей компании",
"Type": "int",
"Required": "Y",
"Default": "1",
},
},
filter={
"INCLUDE": [
["crm", "CCrmDocumentDeal"],
["crm", "CCrmDocumentLead"],
],
},
).response.result
print(result) # True
Если нужно добавить робота для CRM-автоматизации, замените метод bizproc.activity.add на bizproc.robot.add. Параметры CODE, HANDLER, AUTH_USER_ID, NAME, DESCRIPTION, PROPERTIES и FILTER используются так же.
Пример успешного ответа:
{
"result": true
}
После регистрации действие появится в дизайнере бизнес-процессов для лидов и сделок. Когда бизнес-процесс дойдет до этого действия, Битрикс24 вызовет HANDLER.
2. Разберите данные обработчика
В обработчик Битрикс24 передает параметры действия в properties и идентификаторы документа в document_id. Для лида значение содержит строку вида LEAD_456, для сделки — DEAL_123.
Из document_id сохраните:
entityTypeIdисходного CRM-объекта:1для лида или2для сделкиownerTypeисходного CRM-объекта:Lдля лида илиDдля сделки- числовой идентификатор исходного CRM-объекта
function parseDocumentId(documentId) {
const values = Array.isArray(documentId) ? documentId : [documentId]
const deal = values.find((value) => String(value).startsWith('DEAL_'))
const lead = values.find((value) => String(value).startsWith('LEAD_'))
if (deal) {
return { entityTypeId: 2, ownerType: 'D', id: Number(deal.slice(5)) }
}
if (lead) {
return { entityTypeId: 1, ownerType: 'L', id: Number(lead.slice(5)) }
}
throw new Error('Действие запущено не из лида и не из сделки')
}
const source = parseDocumentId(req.body.document_id)
const properties = req.body.properties || {}
<?php
function parseDocumentId(array $documentId): array
{
foreach ($documentId as $value) {
if (str_starts_with((string)$value, 'DEAL_')) {
return ['entityTypeId' => 2, 'ownerType' => 'D', 'id' => (int)substr((string)$value, 5)];
}
if (str_starts_with((string)$value, 'LEAD_')) {
return ['entityTypeId' => 1, 'ownerType' => 'L', 'id' => (int)substr((string)$value, 5)];
}
}
throw new RuntimeException('Действие запущено не из лида и не из сделки');
}
$source = parseDocumentId((array)($_REQUEST['document_id'] ?? []));
$properties = $_REQUEST['properties'] ?? [];
def parse_document_id(document_id: list[str]) -> dict:
for value in document_id:
if value.startswith("DEAL_"):
return {"entityTypeId": 2, "ownerType": "D", "id": int(value[5:])}
if value.startswith("LEAD_"):
return {"entityTypeId": 1, "ownerType": "L", "id": int(value[5:])}
raise ValueError("Действие запущено не из лида и не из сделки")
payload = request.json
source = parse_document_id(payload.get("document_id", []))
properties = payload.get("properties", {})
3. Получите CRM-объект и товары
Вызовите crm.item.get, чтобы получить поля исходного лида или сделки. В параметр entityTypeId передайте значение, полученное при разборе document_id, в id — числовой идентификатор объекта.
Товарные позиции получите методом crm.item.productrow.list. В фильтр передайте =ownerType и =ownerId исходного CRM-объекта.
async function callMethod($b24, method, params) {
const response = await $b24.actions.v2.call.make({
method,
params,
requestId: method,
})
if (!response.isSuccess) {
throw new Error(response.getErrorMessages().join('; '))
}
return response.getData().result
}
const sourceItemResult = await callMethod($b24, 'crm.item.get', {
entityTypeId: source.entityTypeId,
id: source.id,
})
const sourceItem = sourceItemResult.item
const sourceRowsResult = await callMethod($b24, 'crm.item.productrow.list', {
filter: {
'=ownerType': source.ownerType,
'=ownerId': source.id,
},
})
const sourceRows = sourceRowsResult.productRows
<?php
$sourceItem = $b24->core
->call('crm.item.get', [
'entityTypeId' => $source['entityTypeId'],
'id' => $source['id'],
])
->getResponseData()
->getResult()['item'];
$sourceRows = $b24->core
->call('crm.item.productrow.list', [
'filter' => [
'=ownerType' => $source['ownerType'],
'=ownerId' => $source['id'],
],
])
->getResponseData()
->getResult()['productRows'];
source_item = token.call_method("crm.item.get", {
"entityTypeId": source["entityTypeId"],
"id": source["id"],
})["result"]["item"]
source_rows = token.call_method("crm.item.productrow.list", {
"filter": {
"=ownerType": source["ownerType"],
"=ownerId": source["id"],
},
})["result"]["productRows"]
В ответе crm.item.get сохраните item.companyId, item.contactId или item.contactIds. Эти поля нужны для клиента смарт-счета. В ответе crm.item.productrow.list сохраните массив productRows: его нужно подготовить и передать в смарт-счет.
4. Создайте смарт-счет и перенесите товары
Создайте смарт-счет методом crm.item.add. В параметр entityTypeId передайте 31.
В fields передайте:
title— название смарт-счета из параметра действияinvoice_titlecompanyId— идентификатор компании из исходного CRM-объектаcontactId— идентификатор контакта из исходного CRM-объектаcontactIds— массив идентификаторов контактов из исходного CRM-объекта, если он естьmycompanyId— идентификатор вашей компании из параметра действияmycompany_idparentId2— идентификатор сделки, если действие запущено из сделки
Затем передайте товарные позиции в созданный смарт-счет методом crm.item.productrow.set. В ownerType укажите SI, в ownerId — идентификатор смарт-счета из ответа crm.item.add.
function prepareProductRows(rows) {
return rows.map((row, index) => ({
productId: row.productId,
productName: row.productName,
price: row.price,
quantity: row.quantity,
discountTypeId: row.discountTypeId,
discountRate: row.discountRate,
discountSum: row.discountSum,
taxRate: row.taxRate,
taxIncluded: row.taxIncluded,
measureCode: row.measureCode,
sort: row.sort || (index + 1) * 10,
}))
}
const fields = {
title: properties.invoice_title || 'Счет по документу CRM',
companyId: sourceItem.companyId || 0,
contactId: sourceItem.contactId || 0,
contactIds: sourceItem.contactIds || [],
mycompanyId: Number(properties.mycompany_id),
}
if (source.entityTypeId === 2) {
fields.parentId2 = source.id
}
const invoiceResult = await callMethod($b24, 'crm.item.add', {
entityTypeId: 31,
fields,
})
const invoiceId = invoiceResult.item.id
if (sourceRows.length > 0) {
await callMethod($b24, 'crm.item.productrow.set', {
ownerType: 'SI',
ownerId: invoiceId,
productRows: prepareProductRows(sourceRows),
})
}
console.info(`Создан смарт-счет ${invoiceId}`)
<?php
function prepareProductRows(array $rows): array
{
$preparedRows = [];
foreach (array_values($rows) as $index => $row) {
$preparedRows[] = [
'productId' => $row['productId'] ?? null,
'productName' => $row['productName'] ?? null,
'price' => $row['price'] ?? null,
'quantity' => $row['quantity'] ?? 1,
'discountTypeId' => $row['discountTypeId'] ?? null,
'discountRate' => $row['discountRate'] ?? null,
'discountSum' => $row['discountSum'] ?? null,
'taxRate' => $row['taxRate'] ?? null,
'taxIncluded' => $row['taxIncluded'] ?? null,
'measureCode' => $row['measureCode'] ?? null,
'sort' => $row['sort'] ?? (($index + 1) * 10),
];
}
return $preparedRows;
}
$fields = [
'title' => $properties['invoice_title'] ?? 'Счет по документу CRM',
'companyId' => (int)($sourceItem['companyId'] ?? 0),
'contactId' => (int)($sourceItem['contactId'] ?? 0),
'contactIds' => $sourceItem['contactIds'] ?? [],
'mycompanyId' => (int)($properties['mycompany_id'] ?? 0),
];
if ($source['entityTypeId'] === 2) {
$fields['parentId2'] = $source['id'];
}
$invoice = $b24->core
->call('crm.item.add', [
'entityTypeId' => 31,
'fields' => $fields,
])
->getResponseData()
->getResult()['item'];
if ($sourceRows !== []) {
$b24->core->call('crm.item.productrow.set', [
'ownerType' => 'SI',
'ownerId' => $invoice['id'],
'productRows' => prepareProductRows($sourceRows),
]);
}
echo 'Создан смарт-счет ' . $invoice['id'];
def prepare_product_rows(rows: list[dict]) -> list[dict]:
return [
{
"productId": row.get("productId"),
"productName": row.get("productName"),
"price": row.get("price"),
"quantity": row.get("quantity", 1),
"discountTypeId": row.get("discountTypeId"),
"discountRate": row.get("discountRate"),
"discountSum": row.get("discountSum"),
"taxRate": row.get("taxRate"),
"taxIncluded": row.get("taxIncluded"),
"measureCode": row.get("measureCode"),
"sort": row.get("sort", (index + 1) * 10),
}
for index, row in enumerate(rows)
]
fields = {
"title": properties.get("invoice_title", "Счет по документу CRM"),
"companyId": int(source_item.get("companyId") or 0),
"contactId": int(source_item.get("contactId") or 0),
"contactIds": source_item.get("contactIds") or [],
"mycompanyId": int(properties.get("mycompany_id") or 0),
}
if source["entityTypeId"] == 2:
fields["parentId2"] = source["id"]
invoice = token.call_method("crm.item.add", {
"entityTypeId": 31,
"fields": fields,
})["result"]["item"]
if source_rows:
token.call_method("crm.item.productrow.set", {
"ownerType": "SI",
"ownerId": invoice["id"],
"productRows": prepare_product_rows(source_rows),
})
print(f"Создан смарт-счет {invoice['id']}")
Пример успешного ответа crm.item.add:
{
"result": {
"item": {
"id": 128,
"entityTypeId": 31,
"title": "Счет по документу CRM"
}
}
}
Сохраните result.item.id: это идентификатор созданного смарт-счета. Его нужно передать в ownerId метода crm.item.productrow.set.
Проверим результат
Откройте карточку смарт-счета. В ней должны быть заполнены название, клиент, ваша компания и товары из исходного лида или сделки.
Через REST результат можно проверить методом crm.item.get. Передайте entityTypeId = 31 и id из ответа crm.item.add.
Диагностика ошибок
Если метод вернул ошибку, проверьте данные запроса.
ACCESS_DENIED,Application context required— действие или робот регистрируется не из приложения. Установите приложение и вызовите метод в его контекстеACCESS_DENIED,Access denied!— регистрацию выполняет не администраторERROR_ACTIVITY_VALIDATION_FAILURE,Wrong properties array!— некорректно заполненыPROPERTIESилиRETURN_PROPERTIESERROR_ACTIVITY_VALIDATION_FAILURE,Wrong activity DOCUMENT_TYPE!— некорректно указанDOCUMENT_TYPEили правилоFILTERACCESS_DENIEDпри вызове CRM-методов — у пользователя изAUTH_USER_IDнет прав на чтение исходного CRM-объекта, создание смарт-счета или изменение товарных позиций счетаOWNER_NOT_FOUND— вcrm.item.productrow.setпередан неверныйownerTypeилиownerIdсмарт-счета- пустой
productRows— в исходном лиде или сделке нет товарных позиций, смарт-счет будет создан без товаров
Что важно учитывать
- Для сделки смарт-счет связывается с исходной сделкой через поле
parentId2 - Для лида сценарий копирует клиента и товары в смарт-счет. Отдельная связь смарт-счета с лидом через поле
parentId1в статье по смарт-счетам не описана - Повторный запуск действия создаст новый смарт-счет. Если дубликаты недопустимы, храните связь между CRM-объектом и созданным счетом в поле CRM или во внешней системе
- Значение
AUTH_USER_IDопределяет, чей токен Битрикс24 передаст обработчику. У этого пользователя должны быть права на чтение исходного CRM-объекта, создание смарт-счета и изменение его товарных позиций - Параметр
FILTERограничивает доступность действия в дизайнере, но не заменяет проверку прав пользователя в CRM