← Назад к справке

CRM

Сделки

Работа со сделками CRM через PHP и фабрики: получение, добавление, изменение, удаление, стадии, воронки, контакты и события.

Сделка — основная рабочая сущность CRM. При изменении стадии или важных полей лучше использовать операции фабрики, чтобы сработали проверки, история, таймлайн, роботы и бизнес-процессы.

Основные операции

Базовая работа со сделками через фабрику CRM.

Получить сделку

<?php

use Bitrix\Main\Loader;
use Bitrix\Crm\Service;

const CRM_DEAL_TYPE_ID = \CCrmOwnerType::Deal;

Loader::includeModule('crm');

/**
 * Получает фабрику сделок.
 */
function fetchDealFactory()
{
    return Service\Container::getInstance()->getFactory(CRM_DEAL_TYPE_ID);
}

/**
 * Получает сделку по ID.
 */
function fetchDealItem(int $deal_id)
{
    $factory = fetchDealFactory();

    if ($factory === null) {
        return null;
    }

    return $factory->getItem($deal_id);
}

$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 CRM_DEAL_TYPE_ID = \CCrmOwnerType::Deal;
const DEAL_LIMIT = 100;

Loader::includeModule('crm');

/**
 * Получает фабрику сделок.
 */
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->getItems([
        '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;

const CRM_DEAL_TYPE_ID = \CCrmOwnerType::Deal;

Loader::includeModule('crm');

/**
 * Получает фабрику сделок.
 */
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;

const CRM_DEAL_TYPE_ID = \CCrmOwnerType::Deal;

Loader::includeModule('crm');

/**
 * Получает фабрику сделок.
 */
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->getItem($deal_id);

    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;

const CRM_DEAL_TYPE_ID = \CCrmOwnerType::Deal;

Loader::includeModule('crm');

/**
 * Получает фабрику сделок.
 */
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->getItem($deal_id);

    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;

Loader::includeModule('crm');

/**
 * Получает ID контактов сделки.
 */
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;

const CRM_DEAL_TYPE_ID = \CCrmOwnerType::Deal;

Loader::includeModule('crm');

/**
 * Изменяет компанию сделки.
 */
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->getItem($deal_id);

    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,
            'fields' => $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() и проверьте фактическую структуру на своей версии коробки.

Источники