Yandex Metrika
sanches.free 1 просмотр

События OnBeforeSale*SetField в Bitrix: заказ, корзина, отгрузка, оплата

Зачем это нужно

В интернет-магазине на D7 сущности заказа, корзины, отгрузки и оплаты — это объекты с набором полей. Ядро при каждом присвоении значения через штатные сеттеры порождает события до фиксации в хранилище и после неё. Так можно перехватить смену статуса, флагов отгрузки, оплаты или пользовательских свойств и либо скорректировать поведение, либо вернуть понятную ошибку вместо «тихого» сохранения.

Общая схема

Модуль первым аргументом — строка sale. Имена событий строятся по шаблону OnBefore…SetField и On…SetField, где вместо многоточия подставляется имя класса сущности без префикса Sale, например Order, BasketItem, Shipment, ShipmentItem, Payment или PropertyValue (значение свойства заказа).

В обработчик передаётся \Bitrix\Main\Event. Типичные параметры: ENTITY — изменяемый объект, NAME — символьный код поля, VALUE — новое значение. Перед своей логикой полезно просматривать уже существующие EventResult предыдущих обработчиков: если кто-то вернул неуспех, дальше лучше не продолжать.

Пример: запрет финального статуса заказа

Ниже обработчик срабатывает, когда поле STATUS_ID пытаются сменить на «финальный» вариант F. Пока заказ ещё не сохранён в базе, у него нет числового идентификатора — в таком состоянии часто разумно выйти и не выполнять проверки, завязанные на реквизиты заказа в БД.

namespace Acme\Sale;

use Bitrix\Main\Error;
use Bitrix\Main\Event;
use Bitrix\Main\EventManager;
use Bitrix\Main\EventResult;
use Bitrix\Sale\ResultError;

final class StatusSentinel
{
    public static function guardTerminalStatus(Event $event)
    {
        foreach ($event->getResults() as $prior) {
            if ($prior->getType() !== EventResult::SUCCESS) {
                return;
            }
        }

        /** @var \Bitrix\Sale\Order $order */
        $order = $event->getParameter('ENTITY');
        $fieldName = (string) $event->getParameter('NAME');
        $newValue = $event->getParameter('VALUE');

        if (!$order || !$order->getId()) {
            return;
        }

        $current = (string) $order->getField($fieldName);
        $replacement = (string) $newValue;

        if (
            $fieldName === 'STATUS_ID'
            && $current !== 'F'
            && $replacement === 'F'
            /* && здесь ваше бизнес-условие */
        ) {
            return new EventResult(
                EventResult::ERROR,
                ResultError::create(
                    new Error('Нельзя перевести заказ в финальный статус', 'ACME_ORDER_STATUS')
                )
            );
        }

        return;
    }
}

// local/php_interface/init.php (после автозагрузки класса)
EventManager::getInstance()->addEventHandler(
    'sale',
    'OnBeforeSaleOrderSetField',
    [StatusSentinel::class, 'guardTerminalStatus']
);

Пример: отгрузка — блокировка разрешения доставки или факта списания

Здесь важно брать идентификатор у переменной отгрузки, а не заказа: в оригинальном сниппете по ошибке фигурировала несуществующая в методе переменная $order.

namespace Acme\Sale;

use Bitrix\Main\Error;
use Bitrix\Main\Event;
use Bitrix\Main\EventManager;
use Bitrix\Main\EventResult;
use Bitrix\Sale\ResultError;

final class ShipmentSentinel
{
    public static function guardShipmentFlags(Event $event)
    {
        foreach ($event->getResults() as $prior) {
            if ($prior->getType() !== EventResult::SUCCESS) {
                return;
            }
        }

        /** @var \Bitrix\Sale\Shipment $shipment */
        $shipment = $event->getParameter('ENTITY');
        $fieldName = (string) $event->getParameter('NAME');
        $newValue = $event->getParameter('VALUE');

        if (!$shipment || !$shipment->getId()) {
            return;
        }

        $current = (string) $shipment->getField($fieldName);
        $replacement = (string) $newValue;

        if (
            ($fieldName === 'ALLOW_DELIVERY' || $fieldName === 'DEDUCTED')
            && $current === 'N'
            && $replacement === 'Y'
            /* && ваше условие */
        ) {
            return new EventResult(
                EventResult::ERROR,
                ResultError::create(
                    new Error('Нельзя завершить отгрузку', 'ACME_SHIPMENT_BLOCK')
                )
            );
        }

        return;
    }
}

EventManager::getInstance()->addEventHandler(
    'sale',
    'OnBeforeSaleShipmentSetField',
    [ShipmentSentinel::class, 'guardShipmentFlags']
);

Регистрацию обработчиков оформите в init.php или bootstrap вашего модуля; классы разнесите по автозагрузке PSR-4, чтобы не дублировать статические строки событий по проекту.

См. также (источники идеи)

Не хотите копаться сами?

Починю за 1-3 дня. Без предоплаты — оплата по результату.

15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии