Права доступа и ограничения

Права доступа модуля sale определяют, может ли пользователь просматривать и изменять заказ, выполнять отдельные операции и переводить заказ или отгрузку между статусами. Проверяйте права до загрузки данных заказа, недоступных пользователю, и до изменения объектов.

Объектный API заказа не применяет права административного интерфейса автоматически. Метод Bitrix\Sale\Order::load(), методы изменения полей и метод Order::save() работают с данными, но не подтверждают полномочия пользователя. Если серверный сценарий выполняется по пользовательскому запросу, код должен отдельно проверить доступ.

Как устроены права интернет-магазина

Решение о доступе к заказу складывается из нескольких уровней.

Уровень

Что ограничивает

Где учитывается

Уровень доступа к модулю sale

Общий доступ группы к интернет-магазину

В настройках прав групп на модуль

Доступ группы к сайту

Заказы каких сайтов может обрабатывать группа

В связях групп и сайтов магазина

Операции в статусе

Просмотр, изменение, удаление и отдельные действия над заказом в текущем статусе

В настройках прав статуса

Переход между статусами

Возможность выйти из текущего статуса и перейти в целевой

В правах исходного и целевого статусов

Принадлежность заказа

Доступ владельца к собственному заказу в отдельных классических проверках

По полю USER_ID заказа

Бизнес-правило проекта

Дополнительное условие конкретного процесса

В коде проекта

Для каждого статуса группе назначают задачу доступа. Такая задача объединяет операции, которые группа может выполнять в этом статусе. Связи групп, задач и статусов доступны через ORM-класс Bitrix\Sale\Internals\StatusGroupTaskTable. Проверяйте права через Bitrix\Sale\OrderStatus для заказов и Bitrix\Sale\DeliveryStatus для отгрузок. Справочник статусов и его поля описаны в разделе Поля статуса.

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

Выбрать метод проверки

Выберите API по объекту и задаче проверки: методы CSaleOrder работают с конкретным заказом, а классы Bitrix\Sale\OrderStatus и Bitrix\Sale\DeliveryStatus — с матрицей разрешенных операций и переходов.

Проверки конкретного заказа

Методы классического API CSaleOrder проверяют права на конкретный заказ. Порядок проверки зависит от роли пользователя.

  • Сотрудник магазина. Учитываются уровень доступа к модулю sale, доступ к сайту заказа и разрешение операции в текущем статусе.

  • Владелец. Методы просмотра, отмены, отметки и удаления дополнительно сравнивают переданный $userId с полем USER_ID заказа.

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

Задача

Метод класса CSaleOrder

Результат

Просмотреть заказ

CanUserViewOrder()

true, если просмотр разрешен

Изменить заказ

CanUserUpdateOrder()

true, если изменение разрешено

Отменить заказ

CanUserCancelOrder()

true, если отмена разрешена

Отметить проблему

CanUserMarkOrder()

true, если изменение отметки разрешено

Изменить оплату, разрешение доставки или отгрузку

CanUserChangeOrderFlag()

true, если выбранная операция разрешена

Изменить статус заказа

CanUserChangeOrderStatus()

true, если разрешены заказ и переход

Удалить заказ

CanUserDeleteOrder()

true, если удаление разрешено

Проверки матрицы прав статусов

Для работы только с матрицей прав статусов используйте методы классов OrderStatus или DeliveryStatus.

Задача

Метод

Особенность

Получить доступные целевые статусы

getAllowedUserStatuses()

Проверяет право выхода из текущего статуса и право входа в целевой

Получить статусы с разрешенной операцией

getStatusesUserCanDoOperations()

Возвращает коды статусов, где пользователю доступна указанная операция

Проверить операции для групп

canGroupDoOperations()

Возвращает true, только если группе доступны все переданные операции

Получить статусы для групп

getStatusesGroupCanDoOperations()

Работает с идентификаторами групп без поиска пользователя

Передавайте в getStatusesUserCanDoOperations() одну операцию за вызов. При нескольких операциях метод возвращает статусы, в которых найдена хотя бы одна из них. Для проверки всех операций используйте canGroupDoOperations().

Имена операций

Методы классов статусов принимают имя операции и проверяют, разрешено ли соответствующее действие в статусе. Доступные имена операций:

Имя

Что разрешает

view

Просматривать заказ или отгрузку в статусе

update

Изменять данные

delete

Удалять

cancel

Менять признак отмены заказа

mark

Менять отметку о проблеме в заказе

payment

Менять состояние оплаты

delivery

Менять разрешение доставки

deduction

Менять состояние отгрузки

from

Выходить из статуса

to

Переходить в статус

Методы классов статусов преобразуют эти имена во внутренние операции sale_status_*. Не передавайте внутренний префикс самостоятельно.

Подготовить проверку прав

Перед проверкой конкретного заказа подготовьте данные пользователя и настройте права его групп.

Подготовить контекст пользователя

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

if (!\Bitrix\Main\Loader::includeModule('sale'))
{
    throw new \RuntimeException('Модуль sale не установлен');
}

global $USER;

$userId = (int) $USER->GetID();
$userGroups = $USER->GetUserGroupArray();

Классические методы принимают массив групп явно. Методы OrderStatus и DeliveryStatus принимают идентификатор пользователя и сами получают его группы.

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

Методы классов статусов могут получить группы пользователя по его идентификатору. При этом даты начала и окончания участия в группе не проверяются. Для фоновой операции сначала получите актуальные группы пользователя, затем передайте их в canGroupDoOperations() или getStatusesGroupCanDoOperations().

Настроить права групп

Задайте общий уровень доступа группы к модулю sale на странице Настройки > Настройки продукта > Настройки модулей > Интернет-магазин.

Уровень

Назначение

D

Доступ закрыт

P

Разрешена привязка к компании

U

Разрешена обработка заказов в пределах доступных сайтов и операций статуса

W

Предоставлен полный доступ

Для сотрудника, который работает с заказами, уровня U недостаточно. Дополнительно:

  1. Разрешите группе обрабатывать заказы нужных сайтов в настройках модуля.

  2. Откройте настройки каждого статуса заказа и отгрузки.

  3. Назначьте группе задачу с нужными операциями.

  4. Для переходов разрешите операцию выхода в исходном статусе и операцию входа в целевом.

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

Проверить права на заказы и отгрузки

Проверку прав нужно выполнить до загрузки или изменения заказа.

Проверить просмотр заказа

Проверяйте право на просмотр заказа до Order::load(), если ответ не должен раскрывать данные, недоступные пользователю. Сначала запросите только идентификатор владельца. Продолжайте проверку, если пользователь владеет заказом или имеет уровень U либо W для модуля sale. Затем вызовите CanUserViewOrder() с идентификатором заказа, группами и идентификатором пользователя.

// Получаем только данные, необходимые для предварительной проверки
$orderAccess = \Bitrix\Sale\Order::getList([
    'select' => ['ID', 'USER_ID'],
    'filter' => ['=ID' => $orderId],
    'limit' => 1,
])->fetch();

if (!$orderAccess)
{
    throw new \RuntimeException('Недостаточно прав для просмотра заказа');
}

// Разрешаем продолжить владельцу или сотруднику магазина
$isOwner = (
    $userId > 0
    && (int) $orderAccess['USER_ID'] === $userId
);
$moduleRight = \CMain::GetUserRight('sale', $userGroups, 'Y', 'Y');
$isEmployee = ($moduleRight >= 'U');

if (!$isOwner && !$isEmployee)
{
    throw new \RuntimeException('Недостаточно прав для просмотра заказа');
}

// Проверяем право на просмотр с учетом условий заказа
$canView = \CSaleOrder::CanUserViewOrder(
    $orderId,
    $userGroups,
    $userId
);

if (!$canView)
{
    throw new \RuntimeException('Недостаточно прав для просмотра заказа');
}

// Загружаем заказ только после всех проверок доступа
$order = \Bitrix\Sale\Order::load($orderId);
if (!$order)
{
    throw new \RuntimeException('Заказ не найден');
}

После проверки учтите три условия:

  • Параметр $userId используется для проверки владельца. Если он совпадает с USER_ID заказа, метод учитывает права владельца отдельно от прав сотрудника магазина.

  • Сравнение $moduleRight >= 'U' разрешает уровни U и W и отклоняет D и P. Если внутренний сценарий предназначен только для сотрудника, права владельца недостаточно.

  • На публичной точке входа сохраняйте порядок проверок из примера. Одинаковая ошибка для отсутствующего и недоступного заказа не раскрывает факт существования заказа.

Проверить изменение заказа

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

if (!\CSaleOrder::CanUserUpdateOrder($orderId, $userGroups))
{
    throw new \RuntimeException('Недостаточно прав для изменения заказа');
}

$order = \Bitrix\Sale\Order::load($orderId);
if (!$order)
{
    throw new \RuntimeException('Заказ не найден');
}

$comment = trim($comment);

$setResult = $order->setField('USER_DESCRIPTION', $comment);
if (!$setResult->isSuccess())
{
    throw new \RuntimeException(implode('; ', $setResult->getErrorMessages()));
}

$saveResult = $order->save();
if (!$saveResult->isSuccess())
{
    throw new \RuntimeException(implode('; ', $saveResult->getErrorMessages()));
}

Ошибки setField() и save() относятся к данным и правилам объекта. Они не заменяют проверку прав.

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

Для еще не созданного заказа передайте 0 вместо идентификатора и укажите сайт третьим аргументом. Метод проверит, может ли группа обрабатывать заказы этого сайта.

if (!\CSaleOrder::CanUserUpdateOrder(0, $userGroups, $siteId))
{
    throw new \RuntimeException('Недостаточно прав для создания заказа на сайте');
}

Создание заказа и заполнение связанных объектов описаны в статье Создание заказа.

Проверить переход статуса заказа

Право на переход состоит из двух разрешений.

  1. Операция from разрешает выйти из текущего статуса.

  2. Операция to разрешает перейти в целевой статус.

Для конкретного заказа используйте CSaleOrder::CanUserChangeOrderStatus(). Метод дополнительно проверяет уровень доступа группы к модулю sale и доступ к сайту заказа.

if (!\CSaleOrder::CanUserChangeOrderStatus(
    $orderId,
    $targetStatusId,
    $userGroups
))
{
    throw new \RuntimeException('Переход в выбранный статус недоступен');
}

$order = \Bitrix\Sale\Order::load($orderId);
if (!$order)
{
    throw new \RuntimeException('Заказ не найден');
}

$setResult = $order->setField('STATUS_ID', $targetStatusId);
if (!$setResult->isSuccess())
{
    throw new \RuntimeException(implode('; ', $setResult->getErrorMessages()));
}

$saveResult = $order->save();
if (!$saveResult->isSuccess())
{
    throw new \RuntimeException(implode('; ', $saveResult->getErrorMessages()));
}

Если нужен список вариантов для формы, вызовите OrderStatus::getAllowedUserStatuses(). Метод возвращает массив: ключ — код доступного статуса, значение — его локализованное название.

$currentStatusId = (string) $order->getField('STATUS_ID');

$allowedStatuses = \Bitrix\Sale\OrderStatus::getAllowedUserStatuses(
    $userId,
    $currentStatusId
);

if (!array_key_exists($targetStatusId, $allowedStatuses))
{
    throw new \RuntimeException('Целевой статус недоступен пользователю');
}

Метод getAllowedUserStatuses() проверяет матрицу переходов, но не доступ к конкретному заказу и его сайту. Не используйте этот метод как единственную проверку перед изменением заказа по пользовательскому запросу.

Статусы, жизненный цикл статусов и события смены описаны в статье Статусы и события.

Проверить операции в статусе

Метод OrderStatus::getStatusesUserCanDoOperations() возвращает коды статусов заказа, в которых пользователю разрешено действие. Результат можно использовать для фильтрации списка заказов.

$currentStatusId = (string) $order->getField('STATUS_ID');

$viewStatusIds = \Bitrix\Sale\OrderStatus::getStatusesUserCanDoOperations(
    $userId,
    ['view']
);

$canViewInCurrentStatus = array_key_exists(
    $currentStatusId,
    $viewStatusIds
);

Результат содержит коды статусов и не подтверждает доступ к конкретному заказу. При построении списка дополнительно ограничьте сайты. Перед получением данных отдельного заказа проверьте владельца или уровень доступа сотрудника, а затем вызовите CanUserViewOrder().

Для проверки одной группы и одного статуса используйте canGroupDoOperations().

$canViewAndUpdate = \Bitrix\Sale\OrderStatus::canGroupDoOperations(
    $userGroups,
    $currentStatusId,
    ['view', 'update']
);

$groupViewStatusIds =
    \Bitrix\Sale\OrderStatus::getStatusesGroupCanDoOperations(
        $userGroups,
        ['view']
    );

$canGroupViewCurrentStatus = array_key_exists(
    $currentStatusId,
    $groupViewStatusIds
);

Метод canGroupDoOperations() вернет true, только если для текущего статуса разрешены обе операции. Метод getStatusesGroupCanDoOperations() возвращает массив, в котором ключи и значения — коды доступных статусов.

Проверить отмену, отметку и удаление

Для отмены, отметки и удаления используйте отдельные методы. До их вызова выполните такую же предварительную проверку владельца или сотрудника, как перед просмотром. Передавайте $userId, когда нужно учесть принадлежность заказа пользователю.

// Получаем владельца заказа для предварительной проверки
$orderAccess = \Bitrix\Sale\Order::getList([
    'select' => ['ID', 'USER_ID'],
    'filter' => ['=ID' => $orderId],
    'limit' => 1,
])->fetch();

if (!$orderAccess)
{
    throw new \RuntimeException('Недостаточно прав для операции с заказом');
}

// Разрешаем продолжить владельцу или сотруднику магазина
$isOwner = (
    $userId > 0
    && (int) $orderAccess['USER_ID'] === $userId
);
$moduleRight = \CMain::GetUserRight('sale', $userGroups, 'Y', 'Y');
$isEmployee = ($moduleRight >= 'U');

if (!$isOwner && !$isEmployee)
{
    throw new \RuntimeException('Недостаточно прав для операции с заказом');
}

// Проверяем каждую операцию отдельным методом CSaleOrder
if (!\CSaleOrder::CanUserCancelOrder(
    $orderId,
    $userGroups,
    $userId
))
{
    throw new \RuntimeException('Недостаточно прав для отмены заказа');
}

if (!\CSaleOrder::CanUserDeleteOrder(
    $orderId,
    $userGroups,
    $userId
))
{
    throw new \RuntimeException('Недостаточно прав для удаления заказа');
}

$canMark = \CSaleOrder::CanUserMarkOrder(
    $orderId,
    $userGroups,
    $userId
);

Метод CanUserMarkOrder() возвращает true, если пользователь может изменить отметку о проблеме в заказе. Саму отметку меняйте через поле MARKED объекта заказа и проверяйте результат setField().

Не объединяйте эти проверки с общим правом update. В матрице статуса отмена и удаление представлены самостоятельными операциями.

Удаление не подходит для фиксации обычного отказа покупателя. Для такого сценария отмените заказ и сохраните причину. Удаляйте заказ только по правилам проекта, когда его история больше не нужна.

Проверить оплату и отгрузку

Метод CanUserChangeOrderFlag() проверяет отдельную операцию по текущему статусу заказа.

Значение второго аргумента

Операция

PERM_PAYMENT

Изменение состояния оплаты

PERM_DELIVERY

Изменение разрешения доставки

PERM_DEDUCTION

Изменение состояния отгрузки

$operations = [
    'payment' => 'PERM_PAYMENT',
    'delivery' => 'PERM_DELIVERY',
    'deduction' => 'PERM_DEDUCTION',
];

$permission = $operations[$requestedOperation] ?? null;
if ($permission === null)
{
    throw new \InvalidArgumentException('Неизвестная операция с заказом');
}

if (!\CSaleOrder::CanUserChangeOrderFlag(
    $orderId,
    $permission,
    $userGroups
))
{
    throw new \RuntimeException('Операция с заказом недоступна');
}

После проверки изменяйте конкретный объект оплаты или отгрузки и сохраняйте заказ. Методы работы с объектами описаны в статьях Оплаты и платежные системы и Доставка и отгрузки.

Проверка права не подтверждает результат самой операции:

  • payment разрешает изменить состояние оплаты, но не подтверждает успешный платеж,

  • deduction разрешает изменить состояние отгрузки, но не подтверждает возможность списать товары со склада.

После изменения проверяйте результат операции и бизнес-условия отдельно.

Проверить статус отгрузки

Для матрицы статусов отгрузки используйте DeliveryStatus. Ее методы и имена операций совпадают с методами OrderStatus, но работают только со статусами типа D.

if (!\CSaleOrder::CanUserUpdateOrder($orderId, $userGroups))
{
    throw new \RuntimeException('Недостаточно прав для изменения заказа');
}

$order = \Bitrix\Sale\Order::load($orderId);
if (!$order)
{
    throw new \RuntimeException('Заказ не найден');
}

$shipment = $order->getShipmentCollection()->getItemById($shipmentId);
if (!$shipment || $shipment->isSystem())
{
    throw new \RuntimeException('Отгрузка не найдена');
}

$currentStatusId = (string) $shipment->getField('STATUS_ID');
$targetStatusId = 'DF';

$allowedStatuses = \Bitrix\Sale\DeliveryStatus::getAllowedUserStatuses(
    $userId,
    $currentStatusId
);

if (!array_key_exists($targetStatusId, $allowedStatuses))
{
    throw new \RuntimeException('Переход отгрузки недоступен');
}

Метод CanUserUpdateOrder() проверяет доступ к заказу, которому принадлежит отгрузка. Метод DeliveryStatus::getAllowedUserStatuses() проверяет только переход между статусами отгрузки.

Для операций с отгрузкой можно получить отдельные наборы статусов:

$updateStatusIds =
    \Bitrix\Sale\DeliveryStatus::getStatusesUserCanDoOperations(
        $userId,
        ['update']
    );

$deductionStatusIds =
    \Bitrix\Sale\DeliveryStatus::getStatusesUserCanDoOperations(
        $userId,
        ['deduction']
    );

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

Учесть ограничения проекта

Проверяйте правила проекта после проверки прав и загрузки заказа, но до изменения состояния. Так код разделяет два разных вопроса:

  • разрешено ли пользователю действие,

  • допустимо ли действие для этого заказа.

Например, проект может запретить отмену заказа из финального статуса, даже если группе разрешена операция cancel.

// Получаем владельца заказа для предварительной проверки
$orderAccess = \Bitrix\Sale\Order::getList([
    'select' => ['ID', 'USER_ID'],
    'filter' => ['=ID' => $orderId],
    'limit' => 1,
])->fetch();

if (!$orderAccess)
{
    throw new \RuntimeException('Недостаточно прав для отмены заказа');
}

// Разрешаем продолжить владельцу или сотруднику магазина
$isOwner = (
    $userId > 0
    && (int) $orderAccess['USER_ID'] === $userId
);
$moduleRight = \CMain::GetUserRight('sale', $userGroups, 'Y', 'Y');
$isEmployee = ($moduleRight >= 'U');

if (!$isOwner && !$isEmployee)
{
    throw new \RuntimeException('Недостаточно прав для отмены заказа');
}

// Проверяем право на отмену с учетом условий заказа
if (!\CSaleOrder::CanUserCancelOrder(
    $orderId,
    $userGroups,
    $userId
))
{
    throw new \RuntimeException('Недостаточно прав для отмены заказа');
}

// Загружаем заказ после проверки доступа
$order = \Bitrix\Sale\Order::load($orderId);
if (!$order)
{
    throw new \RuntimeException('Заказ не найден');
}

// Применяем дополнительное правило проекта
if (
    (string) $order->getField('STATUS_ID')
    === \Bitrix\Sale\OrderStatus::getFinalStatus()
)
{
    throw new \DomainException(
        'Нельзя отменить заказ из финального статуса'
    );
}

// Изменяем и сохраняем заказ только после всех проверок
$setResult = $order->setField('CANCELED', 'Y');
if (!$setResult->isSuccess())
{
    throw new \RuntimeException(implode('; ', $setResult->getErrorMessages()));
}

$saveResult = $order->save();
if (!$saveResult->isSuccess())
{
    throw new \RuntimeException(implode('; ', $saveResult->getErrorMessages()));
}

Если ограничение должно работать во всех точках входа, вынесите его в общий сервис проекта. Все контроллеры, агенты, обработчики обмена и фоновые задачи должны вызывать этот сервис вместо прямого изменения заказа.

Для обработчика события предусмотрите защиту от повторного выполнения. События сохранения могут вызываться несколько раз в одном процессе или при повторной обработке задания.

Когда проверка прав не нужна

Доверенный системный процесс может работать без проверки прав конкретного пользователя: например, агент закрывает просроченные заказы или интеграция применяет подтвержденный платеж.

Такое исключение должно быть явным. Для системной операции:

  1. Не подставляйте случайного текущего пользователя.

  2. Ограничьте доступ к точке запуска.

  3. Проверьте бизнес-условия и текущее состояние заказа.

  4. Запишите в журнал инициатора, заказ, действие и результат.

  5. Обеспечьте безопасный повтор операции.

Системный контекст не превращает пользовательский запрос в доверенный. Если контроллер принимает идентификатор заказа от пользователя, проверка доступа обязательна.

Частые ошибки

Ошибки в проверках доступа могут раскрыть данные заказа или разрешить запрещенную операцию. Учитывайте следующие ограничения в пользовательских сценариях.

Ошибка

Почему опасно

Как правильно

Сначала загрузить заказ, потом проверить просмотр

Недоступные поля могут попасть в ответ или журнал

Проверять право на просмотр до Order::load()

Считать Order::save() проверкой доступа

Метод проверяет данные, но не полномочия пользователя

Проверять доступ до изменения и сохранения заказа

Проверять только уровень доступа к модулю

Уровень U не подтверждает доступ к сайту и операции статуса

Дополнительно проверять сайт заказа и нужную операцию

Полагаться только на метод, который учитывает владельца

Для чужого заказа нужно отдельно подтвердить роль сотрудника

Сначала проверить владельца или уровень U либо W сотрудника

Проверять только статус

Матрица статусов не подтверждает доступ к заказу и сайту

Использовать метод CSaleOrder для конкретного заказа

Использовать право update для всех действий

Отмена, оплата, доставка, отгрузка, удаление и переход статуса имеют отдельные операции

Проверять действие соответствующим методом

Смешивать пользователя и его группы

Методы принимают разные типы аргументов

Передавать идентификатор пользователя классам статусов, а массив групп — методам CSaleOrder

Возвращать результат проверки из клиентского кода

Клиентскую проверку можно обойти

Повторять проверку на сервере перед изменением заказа