Перейти к содержанию

Права на сделку зависят от сотрудника и действия, которое он выполняет. Один сотрудник может читать запись, а другой не увидит ее даже по известному ID. Если обработчик загружает сделку без учета прав, он рискует передать ее данные постороннему пользователю. Фоновое задание может изменить запись от имени пользователя окружения, если код не задаст нужного сотрудника. В PHP API CRM чтение ограничивают выборкой с учетом прав, а запись выполняют через операцию с контекстом пользователя.

Проверить доступ к типу и элементу

Права зависят от действия. Разрешение прочитать сделку не означает разрешения изменить или удалить ее. Для нового элемента еще нет ID, поэтому право на добавление проверяют для типа и выбранного направления — воронки CRM. У существующего элемента учитывают конкретную запись, а для типов со стадиями и направлениями — ее положение в процессе.

В примерах $userId — положительный ID сотрудника, чьи права нужно применить. Для пользовательского запроса возьмите его из авторизованного контекста, для фонового задания задайте в настройках задания. Не берите ID сотрудника из запроса посетителя, даже если он передал его вместе с ID сделки или другого элемента CRM.

После подключения модуля crm получите права сотрудника через Container::getUserPermissions($userId).

if (!\Bitrix\Main\Loader::includeModule('crm'))
{
    throw new \RuntimeException('Не удалось подключить модуль CRM');
}

if ($userId <= 0)
{
    throw new \RuntimeException('Не задан пользователь CRM');
}

$container = \Bitrix\Crm\Service\Container::getInstance();
$permissions = $container->getUserPermissions($userId);

Выберите проверку по действию, которое собираетесь выполнить.

|

|| Действие | Проверка | Когда применять || || Прочитать элемент | item()->canRead($entityTypeId, $itemId) | Предварительно проверить доступ к известной записи || || Добавить элемент | entityType()->canAddItemsInCategory($entityTypeId, $categoryId) | Перед созданием в выбранном направлении || || Изменить элемент | item()->canUpdateItem($item) | После загрузки элемента, перед операцией изменения || || Удалить элемент | item()->canDeleteItem($item) | После загрузки элемента, перед операцией удаления || |#

Методы возвращают bool. Для существующей записи можно проверить изменение и удаление также по паре «тип — ID» методами canUpdate($entityTypeId, $itemId) и canDelete($entityTypeId, $itemId). Указывайте оба значения, потому что одинаковый числовой ID у сделки и контакта обозначает разные записи.

Сотрудник может добавлять сделки в одном направлении и не иметь такого права в другом. Перед проверкой canAddItemsInCategory() получите ID выбранного направления из настроек типа. Направление и начальную стадию выберите по примеру создания элемента.

Метод canRead() проверяет право прочитать конкретную запись в момент вызова. По его результату можно решить, показывать ли ссылку на карточку известного элемента. Метод не возвращает поля записи. Когда нужны данные, используйте выборку с учетом прав.

Предварительная проверка помогает вовремя остановить действие или скрыть недоступную кнопку. Она не закрепляет право за пользователем. Настройки доступа и состояние элемента могут измениться до записи, поэтому операция проверит доступ еще раз при запуске.

При смене стадии или направления учитывайте права и ограничения перехода. Для стадии используйте пример проверки перехода, для направления — пример переноса элемента.

Читать данные с учетом прав

Для списка в интерфейсе или ответа на пользовательский запрос используйте getItemsFilteredByPermissions($parameters, $userId). Метод применяет условия выборки и возвращает только доступные для чтения элементы. Обычные getItem() и getItems() не добавляют фильтр прав. Они подходят для доверенного серверного кода, который уже определил доступ к данным.

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

Право читать сделку не распространяется на связанный контакт или компанию. Если показываете данные клиента рядом со сделкой, выберите контакт или компанию с учетом прав того же сотрудника. Порядок чтения клиента — в примере работы со связями.

Если ID сделки пришел из запроса, отберите ее через getItemsFilteredByPermissions() с фильтром по ID. Так код не загрузит данные сделки до проверки доступа.

Пример. Прочитайте сделку с ID 123 для пользователя $userId. Переменные $container и $userId подготовлены выше. Сделка из этого примера нужна для операции изменения в следующем разделе.

$factory = $container->getFactory(\CCrmOwnerType::Deal);
if ($factory === null)
{
    throw new \RuntimeException('Фабрика сделок недоступна');
}

$dealId = 123;
$deals = $factory->getItemsFilteredByPermissions([
    'filter' => ['=ID' => $dealId],
], $userId);

$deal = $deals[0] ?? null;
if ($deal === null)
{
    throw new \RuntimeException('Сделка не найдена или недоступна');
}

Запустить операцию от нужного пользователя

По умолчанию операция фабрики проверяет права при launch(). Чтобы она использовала права сотрудника $userId, выполните три шага.

  1. Создайте объект Bitrix\Crm\Service\Context.

  2. Задайте в нем ID сотрудника методом setUserId().

  3. Передайте контекст в getAddOperation(), getUpdateOperation() или getDeleteOperation() и запустите операцию.

Объект $permissions нужен для предварительной проверки. Сам по себе он не задает пользователя операции.

Пример. Измените название сделки от имени сотрудника. Перед запуском подготовьте:

  • $userId и $permissions — ID сотрудника и его права из первого раздела,

  • $factory — фабрику сделок из примера чтения одной записи,

  • $deal — сделку, найденную по ID выборкой с учетом прав.

// Проверьте право до изменения элемента. Операция проверит его повторно при запуске
if (!$permissions->item()->canUpdateItem($deal))
{
    throw new \RuntimeException('Нет права изменить сделку');
}

$newTitle = 'Новое название';
$deal->set(\Bitrix\Crm\Item::FIELD_NAME_TITLE, $newTitle);

// Контекст задает сотрудника, от имени которого операция проверит права
$context = (new \Bitrix\Crm\Service\Context())->setUserId($userId);
$result = $factory->getUpdateOperation($deal, $context)->launch();

// Запишите коды ошибок в журнал, а в исключение передайте общее сообщение
if (!$result->isSuccess())
{
    $errorCodes = [];
    foreach ($result->getErrors() as $error)
    {
        $errorCodes[] = (string)$error->getCode();
    }

    error_log(sprintf(
        'CRM: не удалось изменить сделку ID %d, коды ошибок: %s',
        $deal->getId(),
        implode(', ', $errorCodes)
    ));
    throw new \RuntimeException('Не удалось изменить сделку');
}

// Прочитайте сделку заново с учетом прав и проверьте сохраненное название
$updatedDeals = $factory->getItemsFilteredByPermissions([
    'filter' => ['=ID' => $deal->getId()],
], $userId);
$updatedDeal = $updatedDeals[0] ?? null;
if ($updatedDeal === null)
{
    throw new \RuntimeException('Сделка больше недоступна для чтения');
}
if ($updatedDeal->get(\Bitrix\Crm\Item::FIELD_NAME_TITLE) !== $newTitle)
{
    throw new \RuntimeException('Название сделки отличается от ожидаемого');
}

Проверяйте результат операции через isSuccess(). Если метод вернул false, getErrors() даст ошибки, а getCode() — их коды. Пример записывает в журнал ID сделки и коды, но не показывает техническое сообщение пользователю. Если для диагностики нужны сообщения из getErrorMessages(), проверьте, нет ли в них данных клиента, прежде чем сохранять их в закрытом журнале приложения.

Повторная выборка в примере подтверждает сохраненное название и право сотрудника увидеть сделку после изменения. При ошибке проверьте состояние записи перед повтором. Успех операции не подтверждает завершение роботов и бизнес-процессов; границы результата приведены в статье Схема работы CRM и основные объекты API.

При добавлении элемента CRM создайте его через фабрику и передайте контекст в getAddOperation(). При удалении загрузите существующий элемент и передайте контекст в getDeleteOperation(). Порядок действий смотрите в примерах создания и удаления элемента.

Проверка прав операцией относится к действиям с элементом через фабрику. Методы записи реквизитов add(), update() и delete() не проверяют права. RelationManager::bindItems() сам по себе не гарантирует проверку прав. Перед такими вызовами проверьте доступ по правилам соответствующего объекта. Для реквизитов используйте правила проверки прав. Для связей — пример с RelationManager.

Если после операции сделка больше не попала в выборку с учетом прав, не возвращайте пользователю данные из ранее загруженного $deal. Сотрудник мог потерять доступ после изменения. Для ответа используйте только результат повторной выборки.