Введение
При переходе интернет-магазина на платформу Bitrix D7 в модуле доставки реализована система гибких ограничений, позволяющая задавать условия доступности служб доставки на основе:
- Географического местоположения
- Суммарной стоимости заказа
- Веса товаров
- Пользовательских параметров
- и др.
Расширение функциональности ограничений
Механизм подключения пользовательских ограничений
Для интеграции кастомных ограничений используйте системное событие onSaleDeliveryRestrictionsClassNamesBuildList:
$eventManager = \Bitrix\Main\EventManager::getInstance();
$eventManager->addEventHandler(
'sale',
'onSaleDeliveryRestrictionsClassNamesBuildList',
'registerCustomRestrictions'
);
function registerCustomRestrictions() {
return new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::SUCCESS,
[
'\Vendor\Module\DeliveryRestrictions\CustomRestriction' => '/local/modules/vendor.module/lib/restriction.php'
]
);
}
Архитектура классов ограничений
Базовые требования
Класс ограничения должен наследоваться от абстрактного класса Bitrix\Sale\Delivery\Restrictions\Base. Рекомендуется использовать существующие реализации в директории /bitrix/modules/sale/lib/delivery/restrictions/ как базовые.
Минимальная реализация класса:
namespace Vendor\Module\DeliveryRestrictions;
class CustomRestriction extends \Bitrix\Sale\Delivery\Restrictions\Base
{
// Название ограничения
public static function getClassTitle(): string
{
return 'Пользовательское ограничение';
}
// Описание ограничения
public static function getClassDescription(): string
{
return 'Ограничение доставки по кастомным параметрам';
}
protected static function extractParams(\Bitrix\Sale\Shipment $shipment): array
{
return [];
}
public static function getParamsStructure(int $deliveryId = 0): array
{
return [];
}
public static function check($shipmentParams, array $restrictionParams, int $deliveryId = 0): bool
{
return true;
}
}
Ключевые методы
-
extractParams()\ Извлекает параметры отгрузки для последующей проверки:
protected static function extractParams(\Bitrix\Sale\Shipment $shipment): array { $params = []; $order = $shipment->getCollection()->getOrder(); // Получение свойств заказа if ($propCollection = $order->getPropertyCollection()) { $params['LOCATION'] = $propCollection->getDeliveryLocation()->getValue(); } return $params; } -
check()\ Выполняет бизнес-логику проверки ограничения:
public static function check($shipmentParams, array $restrictionParams, int $deliveryId = 0): bool { return $restrictionParams['MAX_WEIGHT'] >= $shipmentParams['TOTAL_WEIGHT']; } -
getParamsStructure()\ Определяет структуру параметров для интерфейса администратора:
public static function getParamsStructure(int $deliveryId = 0): array { return [ 'MAX_WEIGHT' => [ 'TYPE' => 'NUMBER', 'LABEL' => 'Максимальный вес (кг)', 'MIN' => 0, 'DEFAULT' => 50 ] ]; }
Расширенные возможности
Приоритет выполнения ограничений
Используйте статическое свойство $easeSort для управления порядком проверки:
public static int $easeSort = 200;
- 100: Быстрые проверки (рекомендуется по умолчанию)
- 200: Проверки с обращениями к БД
- 300: Ресурсоемкие вычисления
Кастомное хранение параметров
Переопределите методы работы с персистентным слоем:
public static function save(array $fields, int $restrictionId = 0): \Bitrix\Main\Result
{
// Кастомная логика сохранения
return parent::save($fields, $restrictionId);
}
public static function delete(int $restrictionId, int $deliveryId = 0): \Bitrix\Main\Result
{
// Кастомная логика удаления
return parent::delete($restrictionId);
}
Пример реализации: ограничение по исключающим локациям
class ExcludeLocationRestriction extends \Bitrix\Sale\Delivery\Restrictions\Base
{
public static $easeSort = 200;
public static function check($locationCode, array $restrictionParams, int $deliveryId = 0): bool
{
return !\Bitrix\Sale\Delivery\DeliveryLocationTable::checkConnectionExists(
$deliveryId,
$locationCode,
['LOCATION_LINK_TYPE' => 'AUTO']
);
}
protected static function extractParams(\Bitrix\Sale\Shipment $shipment): string
{
return $shipment->getCollection()->getOrder()
->getPropertyCollection()
->getDeliveryLocation()
->getValue() ?? '';
}
}
Еще примеры
Метод extractParams принимает объект отгрузки Bitrix\Sale\Shipment и должен подготовить необходимые данные для проверки ограничения и вернуть их.
Далее эти данные передаются в метод check. В самом простом случае можно вернуть null.
\$shipment - объект отгрузки, для которой идёт подбор подходящих служб доставки. Примеры того, что можно получить из этого объекта:
protected static function extractParams(Bitrix\Sale\Shipment $shipment)
{
$someShipmentParams = array();
// Получаем товары в корзине:
foreach ($shipment->getShipmentItemCollection() as $shipmentItem) {
/** @var \Bitrix\Sale\BasketItem $basketItem - запись в корзине*/
$basketItem = $shipmentItem->getBasketItem();
// ...
}
// Получаем информацию о заказе:
/** @var \Bitrix\Sale\ShipmentCollection $collection - коллекция всех отгрузок в заказе */
$collection = $shipment->getCollection();
/** @var \Bitrix\Sale\Order $order - объект заказа*/
$order = $collection->getOrder();
// Получаем выбранные оплаты:
/** @var \Bitrix\Sale\Payment $payment - объект оплаты */
foreach($order->getPaymentCollection() as $payment) {
/** @var int $paySystemId - ID способа оплаты*/
$paySystemId = $payment->getPaymentSystemId();
// ...
$someShipmentParams["paySystem"] = $paySystemId;
}
//...
return $someShipmentParams;
}
Если вы планируете значения параметров в своей таблице (сомнительная возможность, но может пригодиться), то можно переопределить следующие методы:
// Получение значений параметров
public static function prepareParamsValues(array $paramsValues, $deliveryId = 0)
{
// Достаём откуда-то значения
$paramsValues = array("MY_PARAM" => $value);
return $paramsValues;
}
// Сохранение ограничения
public static function save(array $fields, $restrictionId = 0)
{
$params = $fields["PARAMS"];
$fields["PARAMS"] = array();
$result = parent::save($fields, $restrictionId);
// Сохраняем куда надо
self::saveToYourPlace($fields["SERVICE_ID"], $params); // Здесь может быть ваш метод или код
return $result;
}
// Удаление ограничения
public static function delete($restrictionId, $deliveryId = 0)
{
self::deleteFromYourPlace($fields["SERVICE_ID"]); // Здесь может быть ваш метод или код
return parent::delete($restrictionId);
}
Эти методы переопределены, например, в ограничениях по местоположению. Как известно, ограничение по местоположению раньше было отдельной опцией доставки и хранилось в отдельной таблице b_sale_delivery2location.
В новых ограничениях с целью совместимости данная таблица еще существует и новое ограничение реализовано переопределением prepareParamsValues, save и delete.
Ограничение "Все местоположения, кроме"
Это немного изменённое стандартное ограничение по местоположению:
// /bitrix/php_interface/init.php:
$eventManager = \Bitrix\Main\EventManager::getInstance();
$eventManager->addEventHandler(
'sale',
'onSaleDeliveryRestrictionsClassNamesBuildList',
'myDeliveryRestrictions'
);
function myDeliveryRestrictions()
{
return new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::SUCCESS,
array(
'\ExclLocationsDeliveryRestriction' => '/bitrix/php_interface/include/ExclLocationsDeliveryRestriction.php',
)
);
}
// /bitrix/php_interface/include/ExclLocationsDeliveryRestriction.php:
use Bitrix\Sale\Delivery\DeliveryLocationTable,
Bitrix\Sale\Internals\CollectableEntity,
Bitrix\Sale\Shipment;
class ExclLocationsDeliveryRestriction extends \Bitrix\Sale\Delivery\Restrictions\Base
{
public static $easeSort = 200;
public static function getClassTitle()
{
return 'По местоположению (Все, кроме)';
}
public static function getClassDescription()
{
return 'По местоположению (Все, кроме)';
}
public static function check($locationCode, array $restrictionParams, $deliveryId = 0)
{
if (intval($deliveryId) <= 0) {
return true;
}
if (strlen($locationCode) <= 0) {
return false;
}
try {
return !DeliveryLocationTable::checkConnectionExists(
intval($deliveryId),
$locationCode,
array(
'LOCATION_LINK_TYPE' => 'AUTO'
)
);
}
catch(\Bitrix\Sale\Location\Tree\NodeNotFoundException $e) {
return false;
}
}
protected static function extractParams(CollectableEntity $shipment)
{
$order = $shipment->getCollection()->getOrder();
if (!$props = $order->getPropertyCollection()) {
return '';
}
if (!$locationProp = $props->getDeliveryLocation()) {
return '';
}
if (!$locationCode = $locationProp->getValue()) {
return '';
}
return $locationCode;
}
protected static function prepareParamsForSaving(array $params = array(), $deliveryId = 0)
{
if ($deliveryId > 0) {
$arLocation = array();
if (!!\CSaleLocation::isLocationProEnabled()) {
if (strlen($params["LOCATION"]['L'])) {
$LOCATION1 = explode(':', $params["LOCATION"]['L']);
}
if (strlen($params["LOCATION"]['G'])) {
$LOCATION2 = explode(':', $params["LOCATION"]['G']);
}
}
if (isset($LOCATION1) && is_array($LOCATION1) && count($LOCATION1) > 0) {
$arLocation["L"] = array();
$locationCount = count($LOCATION1);
for ($i = 0; $i < $locationCount; $i++) {
if (strlen($LOCATION1[$i])) {
$arLocation["L"][] = $LOCATION1[$i];
}
}
}
if (isset($LOCATION2) && is_array($LOCATION2) && count($LOCATION2) > 0) {
$arLocation["G"] = array();
$locationCount = count($LOCATION2);
for ($i = 0; $i < $locationCount; $i++) {
if (strlen($LOCATION2[$i])) {
$arLocation["G"][] = $LOCATION2[$i];
}
}
}
DeliveryLocationTable::resetMultipleForOwner($deliveryId, $arLocation);
}
return array();
}
public static function getParamsStructure($deliveryId = 0)
{
$result = array(
"LOCATION" => array(
"TYPE" => "LOCATION_MULTI"
),
);
if ($deliveryId > 0) {
$result["LOCATION"]["DELIVERY_ID"] = $deliveryId;
}
return $result;
}
public static function save(array $fields, $restrictionId = 0)
{
$fields["PARAMS"] = self::prepareParamsForSaving($fields["PARAMS"], $fields["SERVICE_ID"]);
return parent::save($fields, $restrictionId);
}
public static function delete($restrictionId, $deliveryId = 0)
{
DeliveryLocationTable::resetMultipleForOwner($deliveryId);
return parent::delete($restrictionId);
}
}
Решение типовых ошибок
Ошибка совместимости типов
Declaration of CustomRestriction::extractParams(CollectableEntity $shipment)
must be compatible with Base::extractParams(Entity $entity)
Решение: Приведите тип параметра к базовому классу:
protected static function extractParams(\Bitrix\Sale\Internals\Entity $entity): array
{
if (!$entity instanceof \Bitrix\Sale\Shipment) {
return [];
}
// Логика обработки Shipment
}
Заключение
Представленная архитектура позволяет реализовывать сложные сценарии ограничений доставки с полной интеграцией в ядро Bitrix D7. Для оптимальной производительности рекомендуется:
- Минимизировать обращения к БД в методах проверки
- Использовать кеширование повторяющихся вычислений
- Соблюдать принцип инкапсуляции при работе с объектами заказа