CRM
Сделки
Работа со сделками CRM через PHP и фабрики: получение, добавление, изменение, удаление, стадии, воронки, контакты и события.
Сделка — основная рабочая сущность CRM. При изменении стадии или важных полей лучше использовать операции фабрики, чтобы сработали проверки, история, таймлайн, роботы и бизнес-процессы.
PHP для коробки с подключённым ядром. Примеры чтения через фабрику рассчитаны на авторизованного пользователя и используют getItemsFilteredByPermissions. В CLI нужен отдельно заданный доверенный контекст. Примеры на странице самостоятельны: одинаковые вспомогательные функции не нужно объявлять вместе.
Основные операции
Базовая работа со сделками через фабрику CRM.
Получить сделку
<?php
use Bitrix\Main\Loader;
use Bitrix\Crm\Service;
if (!Loader::includeModule('crm')) {
throw new \RuntimeException('Модуль crm не загружен');
}
global $USER;
if (!$USER || !$USER->IsAuthorized()) {
throw new \RuntimeException('Нужен авторизованный пользователь');
}
const CRM_DEAL_TYPE_ID = \CCrmOwnerType::Deal;
function fetchDealFactory() {
return Service\Container::getInstance()->getFactory(CRM_DEAL_TYPE_ID);
}
function fetchDealItem(int $deal_id) {
$factory = fetchDealFactory();
if ($factory === null) {
return null;
}
return ($factory->getItemsFilteredByPermissions(['filter' => ['=ID' => $deal_id], 'limit' => 1])[0] ?? null);
}
$deal_item = fetchDealItem($deal_id);
if ($deal_item !== null) {
$deal_data = $deal_item->toArray();
$title = $deal_item->getTitle();
$stage_id = $deal_item->getStageId();
print_r($deal_data);
} Получить список сделок
<?php
use Bitrix\Main\Loader;
use Bitrix\Crm\Service;
const DEAL_LIMIT = 100;
if (!Loader::includeModule('crm')) {
throw new \RuntimeException('Модуль crm не загружен');
}
global $USER;
if (!$USER || !$USER->IsAuthorized()) {
throw new \RuntimeException('Нужен авторизованный пользователь');
}
const CRM_DEAL_TYPE_ID = \CCrmOwnerType::Deal;
function fetchDealFactory() {
return Service\Container::getInstance()->getFactory(CRM_DEAL_TYPE_ID);
}
function fetchDealItems(int $category_id, string $stage_id): array {
$factory = fetchDealFactory();
if ($factory === null) {
return [];
}
return $factory->getItemsFilteredByPermissions([
'select' => [
'ID',
'TITLE',
'CATEGORY_ID',
'STAGE_ID',
'COMPANY_ID',
'CONTACT_ID',
'ASSIGNED_BY_ID',
'OPPORTUNITY',
'CURRENCY_ID',
],
'filter' => [
'=CATEGORY_ID' => $category_id,
'=STAGE_ID' => $stage_id,
],
'order' => [
'ID' => 'ASC',
],
'limit' => DEAL_LIMIT,
]);
}
$deal_items = fetchDealItems($category_id, $stage_id);
foreach ($deal_items as $deal_item) {
print_r($deal_item->toArray());
} Добавить сделку
<?php
use Bitrix\Main\Loader;
use Bitrix\Crm\Service;
if (!Loader::includeModule('crm')) {
throw new \RuntimeException('Модуль crm не загружен');
}
global $USER;
if (!$USER || !$USER->IsAuthorized()) {
throw new \RuntimeException('Нужен авторизованный пользователь');
}
const CRM_DEAL_TYPE_ID = \CCrmOwnerType::Deal;
function fetchDealFactory() {
return Service\Container::getInstance()->getFactory(CRM_DEAL_TYPE_ID);
}
function addDeal(array $deal_fields): array {
$factory = fetchDealFactory();
if ($factory === null) {
return [
'is_success' => false,
'deal_id' => 0,
'error_messages' => ['Фабрика сделок не найдена'],
];
}
$deal_item = $factory->createItem($deal_fields);
$operation = $factory->getAddOperation($deal_item);
$operation_result = $operation->launch();
return [
'is_success' => $operation_result->isSuccess(),
'deal_id' => (int) $deal_item->getId(),
'error_messages' => $operation_result->getErrorMessages(),
];
}
$deal_fields = [
'TITLE' => $title,
'CATEGORY_ID' => $category_id,
'STAGE_ID' => $stage_id,
'COMPANY_ID' => $company_id,
'CONTACT_ID' => $contact_id,
'ASSIGNED_BY_ID' => $assigned_by_id,
'OPPORTUNITY' => $opportunity,
'CURRENCY_ID' => 'RUB',
];
$add_result = addDeal($deal_fields);
print_r($add_result); Изменить сделку
<?php
use Bitrix\Main\Loader;
use Bitrix\Crm\Service;
if (!Loader::includeModule('crm')) {
throw new \RuntimeException('Модуль crm не загружен');
}
global $USER;
if (!$USER || !$USER->IsAuthorized()) {
throw new \RuntimeException('Нужен авторизованный пользователь');
}
const CRM_DEAL_TYPE_ID = \CCrmOwnerType::Deal;
function fetchDealFactory() {
return Service\Container::getInstance()->getFactory(CRM_DEAL_TYPE_ID);
}
function moveDealToStage(int $deal_id, string $stage_id): array {
$factory = fetchDealFactory();
if ($factory === null) {
return [
'is_success' => false,
'error_messages' => ['Фабрика сделок не найдена'],
];
}
$deal_item = ($factory->getItemsFilteredByPermissions(['filter' => ['=ID' => $deal_id], 'limit' => 1])[0] ?? null);
if ($deal_item === null) {
return [
'is_success' => false,
'error_messages' => ['Сделка не найдена'],
];
}
$deal_item->setStageId($stage_id);
$operation = $factory->getUpdateOperation($deal_item);
$operation_result = $operation->launch();
return [
'is_success' => $operation_result->isSuccess(),
'error_messages' => $operation_result->getErrorMessages(),
];
}
$update_result = moveDealToStage($deal_id, $stage_id);
print_r($update_result); Удалить сделку
<?php
use Bitrix\Main\Loader;
use Bitrix\Crm\Service;
if (!Loader::includeModule('crm')) {
throw new \RuntimeException('Модуль crm не загружен');
}
global $USER;
if (!$USER || !$USER->IsAuthorized()) {
throw new \RuntimeException('Нужен авторизованный пользователь');
}
const CRM_DEAL_TYPE_ID = \CCrmOwnerType::Deal;
function fetchDealFactory() {
return Service\Container::getInstance()->getFactory(CRM_DEAL_TYPE_ID);
}
function deleteDeal(int $deal_id): array {
$factory = fetchDealFactory();
if ($factory === null) {
return [
'is_success' => false,
'error_messages' => ['Фабрика сделок не найдена'],
];
}
$deal_item = ($factory->getItemsFilteredByPermissions(['filter' => ['=ID' => $deal_id], 'limit' => 1])[0] ?? null);
if ($deal_item === null) {
return [
'is_success' => false,
'error_messages' => ['Сделка не найдена'],
];
}
$operation = $factory->getDeleteOperation($deal_item);
$operation_result = $operation->launch();
return [
'is_success' => $operation_result->isSuccess(),
'error_messages' => $operation_result->getErrorMessages(),
];
}
$delete_result = deleteDeal($deal_id);
print_r($delete_result); Связи
У сделки может быть компания и один или несколько контактов.
Контакты сделки
<?php
use Bitrix\Main\Loader;
use Bitrix\Crm\Binding\DealContactTable;
if (!Loader::includeModule('crm')) {
throw new \RuntimeException('Модуль crm не загружен');
}
global $USER;
if (!$USER || !$USER->IsAuthorized()) {
throw new \RuntimeException('Нужен авторизованный пользователь');
}
function fetchDealContactIds(int $deal_id): array {
return DealContactTable::getDealContactIDs($deal_id);
}
function bindDealContactIds(int $deal_id, array $contact_ids): void {
DealContactTable::bindContactIDs($deal_id, $contact_ids);
}
$contact_ids = fetchDealContactIds($deal_id);
print_r($contact_ids); Компания сделки
Основная компания сделки обычно хранится в поле COMPANY_ID. Для изменения
компании можно получить сделку через фабрику и установить новое значение поля.
<?php
use Bitrix\Main\Loader;
use Bitrix\Crm\Service;
if (!Loader::includeModule('crm')) {
throw new \RuntimeException('Модуль crm не загружен');
}
global $USER;
if (!$USER || !$USER->IsAuthorized()) {
throw new \RuntimeException('Нужен авторизованный пользователь');
}
const CRM_DEAL_TYPE_ID = \CCrmOwnerType::Deal;
function updateDealCompany(int $deal_id, int $company_id): array {
$factory = Service\Container::getInstance()->getFactory(CRM_DEAL_TYPE_ID);
if ($factory === null) {
return [
'is_success' => false,
'error_messages' => ['Фабрика сделок не найдена'],
];
}
$deal_item = ($factory->getItemsFilteredByPermissions(['filter' => ['=ID' => $deal_id], 'limit' => 1])[0] ?? null);
if ($deal_item === null) {
return [
'is_success' => false,
'error_messages' => ['Сделка не найдена'],
];
}
$deal_item->set('COMPANY_ID', $company_id);
$operation = $factory->getUpdateOperation($deal_item);
$operation_result = $operation->launch();
return [
'is_success' => $operation_result->isSuccess(),
'error_messages' => $operation_result->getErrorMessages(),
];
}
$update_result = updateDealCompany($deal_id, $company_id);
print_r($update_result); События
События сделок позволяют выполнить свой PHP-код перед добавлением, после добавления, перед изменением, после изменения, перед удалением и после удаления сделки.
Список событий
Обработчики обычно регистрируют в /local/php_interface/init.php или внутри
своего модуля. Для коробочной разработки это удобный способ реагировать на изменения
сделок без REST-приложения.
| Событие | Когда вызывается | Метод |
|---|---|---|
OnBeforeCrmDealAdd | Перед добавлением сделки. | CCrmDeal::Add |
OnAfterCrmDealAdd | После добавления сделки. | CCrmDeal::Add |
OnBeforeCrmDealUpdate | Перед изменением сделки. | CCrmDeal::Update |
OnAfterCrmDealUpdate | После изменения сделки. | CCrmDeal::Update |
OnBeforeCrmDealDelete | Перед удалением сделки. | CCrmDeal::Delete |
OnAfterCrmDealDelete | После удаления сделки. | CCrmDeal::Delete |
OnAfterExternalCrmDealAdd | После добавления внешней сделки. | CCrmDeal::Add |
OnBeforeCrmDealProductRowsSave | Перед сохранением товарных строк сделки. | CCrmDeal::SaveProductRows |
OnAfterCrmDealProductRowsSave | После сохранения товарных строк сделки. | CCrmDeal::SaveProductRows |
Перед добавлением
В OnBeforeCrmDealAdd можно изменить поля до сохранения или отменить
создание сделки. Например, можно заполнить валюту по умолчанию или запретить создание
сделки без названия.
<?php
use Bitrix\Main\EventManager;
$event_manager = EventManager::getInstance();
function registerBeforeDealAddHandler(EventManager $event_manager): void {
$event_manager->addEventHandlerCompatible(
'crm',
'OnBeforeCrmDealAdd',
'handleBeforeDealAdd'
);
}
function handleBeforeDealAdd(array &$deal_fields): bool {
global $APPLICATION;
if (empty($deal_fields['TITLE'])) {
$APPLICATION->ThrowException('Не заполнено название сделки');
return false;
}
if (empty($deal_fields['CURRENCY_ID'])) {
$deal_fields['CURRENCY_ID'] = 'RUB';
}
return true;
}
registerBeforeDealAddHandler($event_manager);
В событиях до сохранения массив полей передаётся по ссылке. Если изменить значение в
$deal_fields, оно попадёт в сохраняемую сделку.
После изменения
OnAfterCrmDealUpdate можно использовать для логирования, синхронизации со
своими таблицами или запуска вспомогательной логики после изменения сделки.
<?php
use Bitrix\Main\EventManager;
$event_manager = EventManager::getInstance();
function registerAfterDealUpdateHandler(EventManager $event_manager): void {
$event_manager->addEventHandlerCompatible(
'crm',
'OnAfterCrmDealUpdate',
'handleAfterDealUpdate'
);
}
function handleAfterDealUpdate(array &$deal_fields): void {
$deal_id = isset($deal_fields['ID']) ? (int) $deal_fields['ID'] : 0;
if ($deal_id <= 0) {
return;
}
AddMessage2Log(
[
'message' => 'Сделка изменена',
'deal_id' => $deal_id,
'field_names' => array_keys($deal_fields),
],
'deal_events'
);
}
registerAfterDealUpdateHandler($event_manager);
Событие после изменения не всегда содержит полный набор полей сделки. Если нужно
проверить поле, которого нет в $deal_fields, лучше отдельно получить
сделку по ID.
Перед удалением
OnBeforeCrmDealDelete можно использовать, если удаление нужно проверить или
запретить.
<?php
use Bitrix\Main\EventManager;
$event_manager = EventManager::getInstance();
function registerBeforeDealDeleteHandler(EventManager $event_manager): void {
$event_manager->addEventHandlerCompatible(
'crm',
'OnBeforeCrmDealDelete',
'handleBeforeDealDelete'
);
}
function handleBeforeDealDelete(int $deal_id): bool {
global $APPLICATION;
$has_delete_access = true;
if (!$has_delete_access) {
$APPLICATION->ThrowException('Удаление сделки запрещено');
return false;
}
AddMessage2Log(
[
'message' => 'Проверка перед удалением сделки',
'deal_id' => $deal_id,
],
'deal_events'
);
return true;
}
registerBeforeDealDeleteHandler($event_manager); Товарные строки
Для товарных строк сделки есть отдельные события:
OnBeforeCrmDealProductRowsSave и
OnAfterCrmDealProductRowsSave. Они относятся к сохранению товаров сделки,
а не к обычному изменению полей сделки.
<?php
use Bitrix\Main\EventManager;
$event_manager = EventManager::getInstance();
function registerAfterDealProductRowsSaveHandler(EventManager $event_manager): void {
$event_manager->addEventHandlerCompatible(
'crm',
'OnAfterCrmDealProductRowsSave',
'handleAfterDealProductRowsSave'
);
}
function handleAfterDealProductRowsSave(int $deal_id, array $product_rows): void {
AddMessage2Log(
[
'message' => 'Товарные строки сделки сохранены',
'deal_id' => $deal_id,
'product_rows' => $product_rows,
],
'deal_product_rows'
);
}
registerAfterDealProductRowsSaveHandler($event_manager);
Если обработчик не получает товарные строки в ожидаемом виде, сначала выведите все
аргументы обработчика в лог через func_get_args() и проверьте фактическую
структуру на своей версии коробки.