Добавить сделку и компанию с реквизитами
Scope:
crmКто может выполнять методы: чтобы пройти сценарий целиком, нужны сразу два права — «Добавление|Импорт» компаний и «добавление» сделок
- crm.address.fields — любой пользователь
- crm.requisite.preset.list — пользователь с правом на чтение контактов и компаний
- crm.company.add — пользователь с правом «Добавление|Импорт» компаний
- crm.requisite.add и crm.address.add — пользователь с правом на добавление компании, которая владеет реквизитом
- crm.deal.add — пользователь с правом «добавления» сделок
Выберите инструмент для разработки с AI-агентом:
- используйте Битрикс24 Вайбкод, чтобы создать приложение для Битрикс24 по описанию задачи без знания языков программирования. Агент напишет код и разместит приложение на сервере без ручной настройки хостинга
- используйте MCP-сервер, чтобы разрабатывать интеграцию через REST API в своем проекте. Агент будет обращаться к официальной REST-документации
С помощью веб-формы можно автоматически добавлять новые сделки и компании с реквизитами в Битрикс24. Когда клиент заполняет форму, данные попадают в обработчик. Скрипт-обработчик создает объекты в CRM через API.
В результате сценария в CRM появятся четыре связанных объекта: компания, ее реквизит, адрес реквизита и сделка, привязанная к компании.
Настройка состоит из двух этапов.
-
Подготавливаем поля и размещаем веб-форму на странице. Состав полей формы берем из методов crm.address.fields и crm.requisite.preset.list
-
Создаем файл-обработчик, который вызывает последовательно методы crm.company.add, crm.requisite.add, crm.address.add и crm.deal.add
Порядок вызовов задан связями объектов: реквизит создается для уже существующей компании, адрес — для уже существующего реквизита, а сделка привязывается к компании.
Что нужно до начала
-
в Битрикс24 настроен хотя бы один шаблон реквизитов. Если шаблонов нет, метод crm.requisite.preset.list вернет пустой список и форму собрать не из чего
-
вебхук создан от имени пользователя, у которого есть права на добавление компаний и сделок
-
есть сервер, который отдает страницу с формой и принимает данные формы методом
POST. В примерах это Express для JS, PHP-скрипт и Flask для Python -
путь вебхука хранится в окружении, а не в коде страницы. Страница с формой публичная, и попадать в нее секрет не должен
1. Создаем веб-форму
Для формирования полей используем два метода:
-
crm.address.fields — получаем список полей адреса. Результат сохраняем в массив
$arAddressFields -
crm.requisite.preset.list — получаем список шаблонов реквизитов по полям
IDиNAME. Результат сохраняем в массив$arPresets
Как использовать примеры в документации
const arAddressFields = (await $b24.actions.v2.call.make({
method: 'crm.address.fields', params: {}, requestId: 'address-fields'
})).getData().result
const arPresets = (await $b24.actions.v2.call.make({
method: 'crm.requisite.preset.list', params: { select: ['ID', 'NAME'] }, requestId: 'preset-list'
})).getData().result
ar_address_fields = client.crm.address.fields().result
ar_presets = client.crm.requisite.preset.list(select=["ID", "NAME"]).result
$arAddressFields = $sb->getCRMScope()->address()->fields()->getFieldsDescription();
$arPresets = $sb->getCRMScope()->requisitePreset()->list(
order: [], filter: [], select: ["ID", "NAME"]
)->getRequisitePresets();
Метод crm.requisite.preset.list возвращает массив объектов, а не пары «идентификатор — название». Для выпадающего списка перебирайте этот массив и берите из каждого объекта ID и NAME.
{
"result": [
{ "ID": "1", "NAME": "Организация" },
{ "ID": "3", "NAME": "ИП" },
{ "ID": "5", "NAME": "Физ. лицо" }
]
}
Метод crm.address.fields возвращает объект, где ключ — код поля, а значение — его описание с признаком обязательности isRequired и названием title.
{
"result": {
"TYPE_ID": {
"type": "integer",
"isRequired": true,
"isReadOnly": false,
"isImmutable": true,
"isMultiple": false,
"isDynamic": false,
"title": "TYPE_ID"
},
"ADDRESS_1": {
"type": "string",
"isRequired": false,
"isReadOnly": false,
"isImmutable": false,
"isMultiple": false,
"isDynamic": false,
"title": "Улица, дом, корпус, строение"
},
"CITY": {
"type": "string",
"isRequired": false,
"isReadOnly": false,
"isImmutable": false,
"isMultiple": false,
"isDynamic": false,
"title": "Город"
}
}
}
Из массива $arAddressFields удаляем ненужные поля адреса, чтобы они не отображались в форме. Три из них — TYPE_ID, ENTITY_TYPE_ID и ENTITY_ID — обязательные системные, клиент их не заполняет, обработчик подставит их сам.
for (const f of ['TYPE_ID', 'ENTITY_TYPE_ID', 'ENTITY_ID', 'COUNTRY_CODE', 'ANCHOR_TYPE_ID', 'ANCHOR_ID']) {
delete arAddressFields[f]
}
for f in ("TYPE_ID", "ENTITY_TYPE_ID", "ENTITY_ID", "COUNTRY_CODE", "ANCHOR_TYPE_ID", "ANCHOR_ID"):
ar_address_fields.pop(f, None)
foreach (['TYPE_ID', 'ENTITY_TYPE_ID', 'ENTITY_ID', 'COUNTRY_CODE', 'ANCHOR_TYPE_ID', 'ANCHOR_ID'] as $field) {
unset($arAddressFields[$field]);
}
Создаем HTML-форму с полями:
-
REQ_TYPE— выпадающий список с шаблонами реквизитов из массива$arPresets. Обязательное поле -
TITLE— название компании. Обязательное поле -
INN— ИНН компании -
PHONE— номер телефона -
ADDRESS— поля для адреса создаются динамически из$arAddressFields. Если поле обязательное, добавляется атрибутrequired
Форма собирает данные и отправляет их методом POST в обработчик. Разметка формы — ниже (выпадающий список реквизитов и поля адреса подставляются из полученных данных).
// строку с формой собираем из полученных данных и вставляем в ответ сервера
const options = arPresets.map(p => `<option value="${p.ID}">${p.NAME}</option>`).join('')
const addressInputs = Object.entries(arAddressFields).map(([key, field]) =>
`<input type="text" name="ADDRESS[${key}]" placeholder="${field.title}" ${field.isRequired ? 'required' : ''}>`
).join('')
const formHtml = `
<form id="form_to_crm">
<select name="REQ_TYPE" required>
<option value="" disabled selected>Select</option>
${options}
</select>
<input type="text" name="TITLE" placeholder="Org name" required>
<input type="text" name="INN" placeholder="INN">
<input type="text" name="PHONE" placeholder="Phone">
${addressInputs}
<input type="submit" value="Submit">
</form>`
# строку с формой собираем из полученных данных и вставляем в ответ сервера
from markupsafe import escape
options = "".join(
f'<option value="{escape(preset["ID"])}">{escape(preset["NAME"])}</option>'
for preset in ar_presets
)
address_inputs = "".join(
f'<input type="text" name="ADDRESS[{escape(key)}]" '
f'placeholder="{escape(field["title"])}" '
f'{"required" if field["isRequired"] else ""}>'
for key, field in ar_address_fields.items()
)
form_html = f"""
<form id="form_to_crm">
<select name="REQ_TYPE" required>
<option value="" disabled selected>Select</option>
{options}
</select>
<input type="text" name="TITLE" placeholder="Org name" required>
<input type="text" name="INN" placeholder="INN">
<input type="text" name="PHONE" placeholder="Phone">
{address_inputs}
<input type="submit" value="Submit">
</form>"""
<form id="form_to_crm">
<select name="REQ_TYPE" required>
<option value="" disabled selected>Select</option>
<?php foreach($arPresets as $preset):?>
<option value="<?=$preset->ID?>"><?=$preset->NAME?></option>
<?php endforeach;?>
</select>
<input type="text" name="TITLE" placeholder="Org name" required>
<input type="text" name="INN" placeholder="INN">
<input type="text" name="PHONE" placeholder="Phone">
<?php if(is_array($arAddressFields)):?>
<?php foreach($arAddressFields as $key=>$arField):?>
<input type="text" name="ADDRESS[<?=$key?>]" placeholder="<?=$arField['title']?>" <?=($arField['isRequired'])?'required':'';?>>
<?php endforeach;?>
<?php endif;?>
<input type="submit" value="Submit">
</form>
Полный пример кода
import express from 'express'
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
// B24_HOOK = 'https://your-domain.bitrix24.ru/rest/USER_ID/TOKEN/'
const app = express()
// Страница с формой: получаем данные из Битрикс24 и рендерим HTML
app.get('/', async (req, res) => {
const arAddressFields = (await $b24.actions.v2.call.make({
method: 'crm.address.fields', params: {}, requestId: 'address-fields'
})).getData().result
const arPresets = (await $b24.actions.v2.call.make({
method: 'crm.requisite.preset.list', params: { select: ['ID', 'NAME'] }, requestId: 'preset-list'
})).getData().result
if (!arPresets.length) {
res.send('No requisite types.')
return
}
// Удаляем системные и неиспользуемые поля адреса
for (const f of ['TYPE_ID', 'ENTITY_TYPE_ID', 'ENTITY_ID', 'COUNTRY_CODE', 'ANCHOR_TYPE_ID', 'ANCHOR_ID']) {
delete arAddressFields[f]
}
const options = arPresets.map(p => `<option value="${p.ID}">${p.NAME}</option>`).join('')
const addressInputs = Object.entries(arAddressFields).map(([key, field]) =>
`<input type="text" name="ADDRESS[${key}]" placeholder="${field.title}" ${field.isRequired ? 'required' : ''}>`
).join('')
res.send(`
<form id="form_to_crm">
<select name="REQ_TYPE" required>
<option value="" disabled selected>Select</option>
${options}
</select>
<input type="text" name="TITLE" placeholder="Org name" required>
<input type="text" name="INN" placeholder="INN">
<input type="text" name="PHONE" placeholder="Phone">
${addressInputs}
<input type="submit" value="Submit">
</form>
<script>
document.getElementById('form_to_crm').addEventListener('submit', async (el) => {
el.preventDefault()
const formData = Object.fromEntries(new FormData(el.currentTarget).entries())
const response = await fetch('/form', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData),
})
alert((await response.json()).message)
})
<\/script>
`)
})
app.listen(3000)
# pip install b24pysdk flask
from flask import Flask
from markupsafe import escape
from b24pysdk import BitrixWebhook, Client
app = Flask(__name__)
client = Client(BitrixWebhook(
domain="your-domain.bitrix24.ru",
webhook_token="USER_ID/TOKEN", # только user_id/token, без https://
))
# обычная строка без подстановок: фигурные скобки JS не нужно экранировать
SCRIPT = """
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$('#form_to_crm').on('submit', function(el) {
el.preventDefault();
$.ajax({
'method': 'POST', 'dataType': 'json', 'url': '/form',
'data': $(this).serialize(),
success: function(data){ alert(data.message); }
});
});
});
</script>
"""
@app.route("/")
def form_page():
ar_address_fields = client.crm.address.fields().result
ar_presets = client.crm.requisite.preset.list(select=["ID", "NAME"]).result
if not ar_presets:
return "No requisite types."
# unset system + uninteresting address fields
for f in ("TYPE_ID", "ENTITY_TYPE_ID", "ENTITY_ID", "COUNTRY_CODE", "ANCHOR_TYPE_ID", "ANCHOR_ID"):
ar_address_fields.pop(f, None)
# строку с формой собираем из полученных данных
options = "".join(
f'<option value="{escape(preset["ID"])}">{escape(preset["NAME"])}</option>'
for preset in ar_presets
)
address_inputs = "".join(
f'<input type="text" name="ADDRESS[{escape(key)}]" '
f'placeholder="{escape(field["title"])}" '
f'{"required" if field["isRequired"] else ""}>'
for key, field in ar_address_fields.items()
)
return f"""
<form id="form_to_crm">
<select name="REQ_TYPE" required>
<option value="" disabled selected>Select</option>
{options}
</select>
<input type="text" name="TITLE" placeholder="Org name" required>
<input type="text" name="INN" placeholder="INN">
<input type="text" name="PHONE" placeholder="Phone">
{address_inputs}
<input type="submit" value="Submit">
</form>""" + SCRIPT
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Psr\Log\NullLogger;
$sb = (new ServiceBuilderFactory(new EventDispatcher(), new NullLogger()))
->initFromWebhook('https://your-domain.bitrix24.ru/rest/USER_ID/TOKEN/');
$arAddressFields = $sb->getCRMScope()->address()->fields()->getFieldsDescription();
$arPresets = $sb->getCRMScope()->requisitePreset()->list(
order: [], filter: [], select: ["ID", "NAME"]
)->getRequisitePresets();
if(!empty($arPresets)):
//unset system address fields
unset($arAddressFields['TYPE_ID']);
unset($arAddressFields['ENTITY_TYPE_ID']);
unset($arAddressFields['ENTITY_ID']);
//unset uninteresting address fields
unset($arAddressFields['COUNTRY_CODE']);
unset($arAddressFields['ANCHOR_TYPE_ID']);
unset($arAddressFields['ANCHOR_ID']);
?>
<form id="form_to_crm">
<select name="REQ_TYPE" required>
<option value="" disabled selected>Select</option>
<?php foreach($arPresets as $preset):?>
<option value="<?=$preset->ID?>"><?=$preset->NAME?></option>
<?php endforeach;?>
</select>
<input type="text" name="TITLE" placeholder="Org name" required>
<input type="text" name="INN" placeholder="INN">
<input type="text" name="PHONE" placeholder="Phone">
<?php if(is_array($arAddressFields)):?>
<?php foreach($arAddressFields as $key=>$arField):?>
<input type="text" name="ADDRESS[<?=$key?>]" placeholder="<?=$arField['title']?>" <?=($arField['isRequired'])?'required':'';?>>
<?php endforeach;?>
<?php endif;?>
<input type="submit" value="Submit">
</form>
<?php else:?>
No requisite types.
<?php endif;?>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
$('#form_to_crm').on( 'submit', function(el) {//event submit form
el.preventDefault();//the default action of the event will not be triggered
var formData = $(this).serialize();
$.ajax({
'method': 'POST',
'dataType': 'json',
'url': 'form.php', // файл для сохранения заполненных форм
'data': formData,
success: function(data){//success callback
alert(data.message);
}
});
});
});
</script>
2. Создаем обработчик формы
Создаем файл, который будет обрабатывать данные и сохранять их в CRM.
Получаем данные
Получаем и обрабатываем данные из формы.
const iRequisitePresetID = parseInt(req.body.REQ_TYPE, 10)
const sTitle = String(req.body.TITLE ?? '')
const sINN = String(req.body.INN ?? '')
const sPhone = String(req.body.PHONE ?? '')
const arAddress = {}
for (const [key, val] of Object.entries(req.body.ADDRESS ?? {})) {
arAddress[key] = String(val)
}
i_requisite_preset_id = int(request.form.get("REQ_TYPE", 0))
s_title = request.form.get("TITLE", "")
s_inn = request.form.get("INN", "")
s_phone = request.form.get("PHONE", "")
ar_address = {k[len("ADDRESS["):-1]: v for k, v in request.form.to_dict().items()
if k.startswith("ADDRESS[")}
$iRequisitePresetID = intval($_POST["REQ_TYPE"]);
$sTitle = htmlspecialchars($_POST["TITLE"]);
$sINN = htmlspecialchars($_POST["INN"]);
$sPhone = htmlspecialchars($_POST["PHONE"]);
$arAddress = [];
foreach ($_POST["ADDRESS"] as $key => $val) {
$arAddress[$key] = htmlspecialchars($val);
}
-
$iRequisitePresetID— преобразуем идентификатор шаблона реквизитовREQ_TYPEв целое число -
$sTitle,$sINN,$sPhone— безопасно обрабатываем данные изTITLE,INN,PHONE, чтобы избежать XSS-атак -
$arAddress— сохраняем данные из массива с адресными полямиADDRESS
Подготавливаем данные
Добавляем в массив $arAddress два обязательных системных поля.
-
TYPE_ID— тип адреса. Укажем1— фактический адрес. Список типов адресов можно получить с помощью метода crm.enum.addresstype -
ENTITY_TYPE_ID— идентификатор типа объекта CRM. Передаем8— реквизиты. Полный список типов объектов можно получить с помощью метода crm.enum.ownertype
Третье обязательное поле ENTITY_ID подставим позже: это идентификатор реквизита, а его еще нет.
arAddress.TYPE_ID = 1
arAddress.ENTITY_TYPE_ID = 8
ar_address["TYPE_ID"] = 1
ar_address["ENTITY_TYPE_ID"] = 8
$arAddress['TYPE_ID'] = 1;
$arAddress['ENTITY_TYPE_ID'] = 8;
Система хранит телефон как массив объектов crm_multifield, поэтому значение $sPhone нужно привести к формату массива:
-
в первый элемент
VALUEзаписываем$sPhone -
во второй элемент
VALUE_TYPEпередаем, например,WORK
Если в переменной $sPhone нет значения, то указываем пустой массив.
const arPhone = sPhone ? [{ VALUE: sPhone, VALUE_TYPE: 'WORK' }] : []
ar_phone = [{"VALUE": s_phone, "VALUE_TYPE": "WORK"}] if s_phone else []
$arPhone = (!empty($sPhone)) ? array(array('VALUE' => $sPhone, 'VALUE_TYPE' => 'WORK')) : array();
Добавляем компанию
Чтобы добавить компанию, используем метод crm.company.add. В него нужно передать следующие данные:
-
TITLE— название компании. Передаем$sTitle, который получили из формы -
COMPANY_TYPE— тип компании. УкажемCUSTOMER— клиент. Список типов можно получить с помощью метода crm.status.list с фильтром'filter'=>['ENTITY_ID'=>'COMPANY_TYPE'] -
PHONE— массив с телефоном$arPhone, который получили из формы
Проверьте, какие обязательные поля настроены для компаний в вашем Битрикс24. Все обязательные поля нужно передать в метод crm.company.add.
const companyResponse = await $b24.actions.v2.call.make({
method: 'crm.company.add',
params: { fields: { TITLE: sTitle, COMPANY_TYPE: 'CUSTOMER', PHONE: arPhone } },
requestId: 'company-add'
})
const iCompanyID = companyResponse.getData()?.result
i_company_id = client.crm.company.add(fields={
"TITLE": s_title,
"COMPANY_TYPE": "CUSTOMER",
"PHONE": ar_phone,
}).result
$iCompanyID = $sb->getCRMScope()->company()->add([
'TITLE' => $sTitle,
'COMPANY_TYPE' => 'CUSTOMER',
'PHONE' => $arPhone,
])->getId();
Если компания успешно создана, метод вернет ее идентификатор в $iCompanyID. Сохраните значение: оно понадобится и реквизиту, и сделке.
{
"result": 2999
}
Добавляем реквизиты
Чтобы добавить реквизиты, используем метод crm.requisite.add. В него нужно передать следующие данные:
-
ENTITY_TYPE_ID— идентификатор типа объекта CRM. Передаем4— компания. Полный список типов объектов можно получить с помощью метода crm.enum.ownertype -
ENTITY_ID— идентификатор компании. Передаем$iCompanyID, который получили при создании компании -
PRESET_ID— идентификатор шаблона реквизитов. Указываем$iRequisitePresetID, который получили из формы -
NAME— название реквизита. Передаем$sTitle, который получили из формы -
RQ_INN— ИНН компании. Передаем$sINN, который получили из формы -
ACTIVE— флаг активности, укажемY
const requisiteResponse = await $b24.actions.v2.call.make({
method: 'crm.requisite.add',
params: {
fields: {
ENTITY_TYPE_ID: 4,
ENTITY_ID: iCompanyID,
PRESET_ID: iRequisitePresetID,
ACTIVE: 'Y',
NAME: sTitle,
RQ_INN: sINN,
}
},
requestId: 'requisite-add'
})
const iRequisiteID = requisiteResponse.getData()?.result
i_requisite_id = client.crm.requisite.add(fields={
"ENTITY_TYPE_ID": 4,
"ENTITY_ID": i_company_id,
"PRESET_ID": i_requisite_preset_id,
"ACTIVE": "Y",
"NAME": s_title,
"RQ_INN": s_inn,
}).result
$iRequisiteID = $sb->getCRMScope()->requisite()->add(
entityId: $iCompanyID,
entityTypeId: 4,
requisitePresetId: $iRequisitePresetID,
requisiteName: $sTitle,
fields: ['ACTIVE' => 'Y', 'RQ_INN' => $sINN]
)->getId();
Если реквизиты успешно добавлены, метод вернет идентификатор записи в $iRequisiteID.
{
"result": 409
}
Метод не проверяет, существует ли шаблон с переданным PRESET_ID. С несуществующим идентификатором реквизит создастся, но останется без полей шаблона. Берите PRESET_ID из ответа crm.requisite.preset.list, а не подставляйте произвольное число.
Добавляем адрес к реквизитам
-
Добавляем в массив
$arAddressполеENTITY_ID— идентификатор реквизита. Передаем$iRequisiteID, который получили при создании реквизитаJSPythonPHParAddress.ENTITY_ID = iRequisiteIDar_address["ENTITY_ID"] = i_requisite_id$arAddress['ENTITY_ID'] = $iRequisiteID; -
Используем метод crm.address.add. В него нужно передать массив
$arAddressJSPythonPHPconst bAddressAdded = (await $b24.actions.v2.call.make({ method: 'crm.address.add', params: { fields: arAddress }, requestId: 'address-add' })).getData().resultb_address_added = client.crm.address.add(fields=ar_address).result$bAddressAdded = $sb->getCRMScope()->address()->add($arAddress)->isSuccess();
Метод возвращает в переменной $bAddressAdded одно из значений:
-
true— адрес добавлен -
false— адрес не добавлен
{
"result": true
}
Добавляем сделку
Создаем массив $arDealFields с данными для сделки.
-
TITLE— название сделки. Укажем название компании$sTitle, которое получено из формы -
COMPANY_ID— идентификатор компании, которая привязана к сделке. Передаем$iCompanyID, который получили при создании компании
const arDealFields = { TITLE: sTitle, COMPANY_ID: iCompanyID }
ar_deal_fields = {"TITLE": s_title, "COMPANY_ID": i_company_id}
$arDealFields = [
'TITLE' => $sTitle,
'COMPANY_ID' => $iCompanyID
];
Реквизит в сделку отдельно передавать не нужно. Сделка получает реквизит от привязанной компании: Битрикс24 подставляет реквизит клиента автоматически при создании сделки с COMPANY_ID.
У сделки нет поля REQUISITE_ID — его нет и в ответе метода crm.deal.fields. Если передать REQUISITE_ID в crm.deal.add, метод не вернет ошибку, но значение будет проигнорировано.
Чтобы добавить сделку, используем метод crm.deal.add. В него передаем массив $arDealFields.
const dealResponse = await $b24.actions.v2.call.make({
method: 'crm.deal.add', params: { fields: arDealFields }, requestId: 'deal-add'
})
const iDealID = dealResponse.getData()?.result
i_deal_id = client.crm.deal.add(fields=ar_deal_fields).result
$iDealID = $sb->getCRMScope()->deal()->add($arDealFields)->getId();
Если сделка создана успешно, метод вернет ее идентификатор.
{
"result": 1789
}
Проверим результат
Откройте созданную сделку в Битрикс24. В карточке заполнено поле «Компания», а в компании на вкладке «Реквизиты» отображается реквизит с ИНН и адресом из формы.
Через REST связь сделки с реквизитом проверяет метод crm.requisite.link.get с параметрами:
-
entityTypeId—2, сделка -
entityId— идентификатор созданной сделки
const linkResponse = await $b24.actions.v2.call.make({
method: 'crm.requisite.link.get',
params: { entityTypeId: 2, entityId: iDealID },
requestId: 'requisite-link-get'
})
console.dir(linkResponse.getData().result)
link = client.crm.requisite.link.get(
entity_type_id=2,
entity_id=i_deal_id,
).result
// у crm.requisite.link.get нет обёртки в SDK — вызываем метод напрямую
$link = $sb->core->call(
'crm.requisite.link.get',
[
'entityTypeId' => 2,
'entityId' => $iDealID,
]
)->getResponseData()->getResult();
Сценарий выполнен, если REQUISITE_ID в ответе совпадает с идентификатором реквизита из шага «Добавляем реквизиты».
{
"result": {
"ENTITY_TYPE_ID": 2,
"ENTITY_ID": 1789,
"REQUISITE_ID": "409",
"BANK_DETAIL_ID": "0",
"MC_REQUISITE_ID": "0",
"MC_BANK_DETAIL_ID": "0"
}
}
Ошибки и диагностика
Если метод вернул ошибку, проверьте данные запроса. Методы реквизитов и адресов возвращают ошибки с пустым кодом, поэтому ориентируйтесь на текст в error_description.
|
Текст ошибки |
Причина и действие |
|
|
В crm.requisite.add передан |
|
|
Не передан или неверен тип владельца. Для реквизита компании нужно |
|
|
Не передан идентификатор владельца. В crm.address.add это идентификатор реквизита, а не компании |
|
|
В crm.address.add не передан тип адреса. Список значений возвращает метод crm.enum.addresstype |
|
|
У реквизита уже есть адрес такого типа. Один реквизит хранит по одному адресу каждого типа — измените существующий методом crm.address.update или передайте другой |
|
|
У пользователя нет прав на добавление компании или сделки. Проверьте, от имени какого пользователя создан вебхук |
Сценарий создает четыре объекта подряд, и ошибка на любом шаге оставляет предыдущие объекты в CRM. Повторяйте не весь обработчик, а тот шаг, который упал:
-
ошибка в crm.company.add — в CRM ничего не создано, можно повторить обработчик целиком
-
ошибка в crm.requisite.add — компания уже создана. Повторный запуск обработчика создаст ее дубль, поэтому передайте существующий
ENTITY_ID -
ошибка в crm.address.add — компания и реквизит уже созданы, сделки еще нет. Добавьте адрес отдельным вызовом и создайте сделку
Если сделка создалась, но реквизит к ней не привязался, у компании больше одного реквизита. Битрикс24 подставляет только один, и это не обязательно тот, который создал обработчик.
Что важно учитывать
-
чтобы привязать к сделке конкретный реквизит, а не тот, который Битрикс24 подставил сам, вызовите метод crm.requisite.link.register с
ENTITY_TYPE_ID:2. Метод требует все четыре идентификатора связи —REQUISITE_ID,BANK_DETAIL_ID,MC_REQUISITE_IDиMC_BANK_DETAIL_ID. Ненужные передавайте нулями, иначе метод вернет ошибку видаMC_REQUISITE_ID is not defined or invalid -
один реквизит хранит по одному адресу каждого типа. Второй адрес того же типа метод crm.address.add не создаст
-
метод crm.address.add возвращает
trueилиfalse, а не идентификатор. Отдельного идентификатора у адреса нет: он опознается паройENTITY_TYPE_IDиENTITY_IDплюсTYPE_ID -
набор полей реквизита зависит от шаблона. У шаблона физического лица поля
RQ_INNнет: значение сохранится и вернется в crm.requisite.get, но в карточке реквизита не отобразится. Состав полей шаблона возвращает метод crm.requisite.preset.field.list -
повторная отправка формы с теми же данными создает новую компанию, новый реквизит и новую сделку. Дубликаты не отсеиваются
Полный пример кода обработчика
import { B24Hook } from '@bitrix24/b24jssdk'
const $b24 = B24Hook.fromWebhookUrl(process.env.B24_HOOK)
// B24_HOOK = 'https://your-domain.bitrix24.ru/rest/USER_ID/TOKEN/'
export async function handler(req, res) {
const iRequisitePresetID = parseInt(req.body.REQ_TYPE, 10)
const sTitle = String(req.body.TITLE ?? '')
const sINN = String(req.body.INN ?? '')
const sPhone = String(req.body.PHONE ?? '')
const arAddress = {}
for (const [key, val] of Object.entries(req.body.ADDRESS ?? {})) {
arAddress[key] = String(val)
}
arAddress.TYPE_ID = 1 // 1 — фактический адрес (crm.enum.addresstype)
arAddress.ENTITY_TYPE_ID = 8 // 8 — реквизит (crm.enum.ownertype)
const arPhone = sPhone ? [{ VALUE: sPhone, VALUE_TYPE: 'WORK' }] : []
try {
const companyResponse = await $b24.actions.v2.call.make({
method: 'crm.company.add',
params: { fields: { TITLE: sTitle, COMPANY_TYPE: 'CUSTOMER', PHONE: arPhone } },
requestId: 'company-add'
})
const iCompanyID = companyResponse.getData()?.result
if (!iCompanyID) {
res.json({ message: 'not added: ' + companyResponse.getErrorMessages().join('; ') })
return
}
const requisiteResponse = await $b24.actions.v2.call.make({
method: 'crm.requisite.add',
params: {
fields: {
ENTITY_TYPE_ID: 4, // 4 — компания (crm.enum.ownertype)
ENTITY_ID: iCompanyID,
PRESET_ID: iRequisitePresetID,
ACTIVE: 'Y',
NAME: sTitle,
RQ_INN: sINN,
}
},
requestId: 'requisite-add'
})
const iRequisiteID = requisiteResponse.getData()?.result
if (iRequisiteID) {
arAddress.ENTITY_ID = iRequisiteID
await $b24.actions.v2.call.make({
method: 'crm.address.add', params: { fields: arAddress }, requestId: 'address-add'
})
}
// Реквизит в сделку не передаем: у сделки нет поля REQUISITE_ID,
// Битрикс24 сам подставит реквизит привязанной компании
await $b24.actions.v2.call.make({
method: 'crm.deal.add',
params: { fields: { TITLE: sTitle, COMPANY_ID: iCompanyID } },
requestId: 'deal-add'
})
res.json({ message: 'add' })
} catch (e) {
res.json({ message: 'not added: ' + e.message })
}
}
# pip install b24pysdk flask
from flask import Flask, request, jsonify
from b24pysdk import BitrixWebhook, Client
app = Flask(__name__)
client = Client(BitrixWebhook(
domain="your-domain.bitrix24.ru",
webhook_token="USER_ID/TOKEN", # только user_id/token, без https://
))
@app.route("/form", methods=["POST"])
def handle_form():
i_requisite_preset_id = int(request.form.get("REQ_TYPE", 0))
s_title = request.form.get("TITLE", "")
s_inn = request.form.get("INN", "")
s_phone = request.form.get("PHONE", "")
ar_address = {k[len("ADDRESS["):-1]: v for k, v in request.form.to_dict().items()
if k.startswith("ADDRESS[")}
ar_address["TYPE_ID"] = 1 # 1 — фактический адрес (crm.enum.addresstype)
ar_address["ENTITY_TYPE_ID"] = 8 # 8 — реквизит (crm.enum.ownertype)
ar_phone = [{"VALUE": s_phone, "VALUE_TYPE": "WORK"}] if s_phone else []
try:
i_company_id = client.crm.company.add(fields={
"TITLE": s_title,
"COMPANY_TYPE": "CUSTOMER", # клиент (crm.status.list ENTITY_ID=COMPANY_TYPE)
"PHONE": ar_phone,
}).result
i_requisite_id = client.crm.requisite.add(fields={
"ENTITY_TYPE_ID": 4, # 4 — компания (crm.enum.ownertype)
"ENTITY_ID": i_company_id,
"PRESET_ID": i_requisite_preset_id,
"ACTIVE": "Y",
"NAME": s_title,
"RQ_INN": s_inn,
}).result
if i_requisite_id:
ar_address["ENTITY_ID"] = i_requisite_id
client.crm.address.add(fields=ar_address)
# Реквизит в сделку не передаем: у сделки нет поля REQUISITE_ID,
# Битрикс24 сам подставит реквизит привязанной компании
client.crm.deal.add(fields={
"TITLE": s_title,
"COMPANY_ID": i_company_id,
})
return jsonify({"message": "add"})
except Exception as e:
return jsonify({"message": f"not added: {e}"})
<?php
// composer require bitrix24/b24phpsdk:"^3.0"
require_once 'vendor/autoload.php';
use Bitrix24\SDK\Services\ServiceBuilderFactory;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Psr\Log\NullLogger;
$sb = (new ServiceBuilderFactory(new EventDispatcher(), new NullLogger()))
->initFromWebhook('https://your-domain.bitrix24.ru/rest/USER_ID/TOKEN/');
$crm = $sb->getCRMScope();
$iRequisitePresetID = intVal($_POST["REQ_TYPE"]);
$sTitle = htmlspecialchars($_POST["TITLE"]);
$sINN = htmlspecialchars($_POST["INN"]);
$sPhone = htmlspecialchars($_POST["PHONE"]);
$arAddress = [];
foreach($_POST["ADDRESS"] as $key=>$val){
$arAddress[$key] = htmlspecialchars($val);
}
$arAddress['TYPE_ID'] = 1; // 1 — фактический адрес (crm.enum.addresstype)
$arAddress['ENTITY_TYPE_ID'] = 8; // 8 — реквизит (crm.enum.ownertype)
$arPhone = (!empty($sPhone)) ? array(array('VALUE' => $sPhone, 'VALUE_TYPE' => 'WORK')) : array();
try {
$iCompanyID = $crm->company()->add([
'TITLE' => $sTitle,
'COMPANY_TYPE' => 'CUSTOMER', // клиент (crm.status.list ENTITY_ID=COMPANY_TYPE)
'PHONE' => $arPhone,
])->getId();
$iRequisiteID = $crm->requisite()->add(
entityId: $iCompanyID,
entityTypeId: 4, // 4 — компания (crm.enum.ownertype)
requisitePresetId: $iRequisitePresetID,
requisiteName: $sTitle,
fields: ['ACTIVE' => 'Y', 'RQ_INN' => $sINN]
)->getId();
if (!empty($iRequisiteID)) {
$arAddress['ENTITY_ID'] = $iRequisiteID;
$crm->address()->add($arAddress);
}
// Реквизит в сделку не передаем: у сделки нет поля REQUISITE_ID,
// Битрикс24 сам подставит реквизит привязанной компании
$crm->deal()->add([
'TITLE' => $sTitle,
'COMPANY_ID' => $iCompanyID
]);
echo json_encode(['message' => 'add']);
} catch (\Throwable $e) {
echo json_encode(['message' => 'not added: ' . $e->getMessage()]);
}
Продолжите изучение
- Создать новую компанию crm.company.add
- Добавить реквизит crm.requisite.add
- Добавить адрес crm.address.add
- Создать новую сделку crm.deal.add
- Получить описание полей адреса crm.address.fields
- Получить список шаблонов реквизитов по фильтру crm.requisite.preset.list
- Зарегистрировать связь реквизитов с объектом crm.requisite.link.register
- Получить связь реквизита с объектом crm.requisite.link.get
- Получить типы адресов crm.enum.addresstype
- Получить типы объектов CRM crm.enum.ownertype