Предупреждение: код работает на версиях модуля sale 16.0 и выше.
В 16 версии магазина Битрикса были переработаны обработчики служб доставки. Обработчиками доставки являются классы-наследники \Bitrix\Sale\Delivery\Services\Base. Встроенные обработчики лежат в папке /bitrix/modules/sale/handlers/delivery/. Там можно ознакомиться с примерами новых обработчиков.
Чтобы подключить свой собственный обработчик, вроде бы нужно положить папку со своим классом в /bitrix/php_interface/include/sale_delivery/ или /local/php_interface/include/sale_delivery/, но на текущей версии(16.0.5) это не сработало. Но есть рабочий альтернативный метод подключения любого класса как обработчика, в init.php добавляем:
$eventManager = \Bitrix\Main\EventManager::getInstance();
$eventManager->addEventHandler('sale', 'onSaleDeliveryHandlersClassNamesBuildList', 'addCustomDeliveryServices');
function addCustomDeliveryServices(\Bitrix\Main\Event $event)
{
$result = new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::SUCCESS,
array(
'\YourNamespace\YetAnotherDeliveryHandler' => '/local/classes/YetAnotherDeliveryHandler.php'
)
);
return $result;
}
Все пути к файлам, пространства имён и классы, относящиеся к службе доставки, приведены для примера. Конечно, нужно размещают свои классы в своих модулях и своих пространствах имён.
По указанному пути создаём класс:
namespace YourNamespace;
class YetAnotherDeliveryHandler extends \Bitrix\Sale\Delivery\Services\Base
{
protected static $isCalculatePriceImmediately = true;
protected static $whetherAdminExtraServicesShow = false;
}
Добавляем несколько методов, смысл которых вполне понятен из названия:
public function __construct(array $initParams)
{
parent::__construct($initParams);
}
public static function getClassTitle()
{
return 'Yet Another Delivery';
}
public static function getClassDescription()
{
return 'My custom handler for Yet Another Delivery Service';
}
public function isCalculatePriceImmediately()
{
return self::$isCalculatePriceImmediately;
}
public static function whetherAdminExtraServicesShow()
{
return self::$whetherAdminExtraServicesShow;
}
Следующий метод отвечает за настройки службы доставки:
protected function getConfigStructure()
{
$result = array(
'MAIN' => array(
'TITLE' => 'Основные',
'DESCRIPTION' => 'Основные настройки',
'ITEMS' => array(
'API_KEY' => array(
'TYPE' => 'STRING',
'NAME' => 'Ключ API',
),
'TEST_MODE' => array(
'TYPE' => 'Y/N',
'NAME' => 'Тестовый режим',
'DEFAULT' => 'N'
),
'PACKAGING_TYPE' => array(
'TYPE' => 'ENUM',
'NAME' => 'Тип упаковки',
'DEFAULT' => 'BOX',
'OPTIONS' => array(
'BOX' => 'Коробка',
'ENV' => 'Конверт',
)
),
)
)
);
return $result;
}
И, наконец, за расчёт стоимости отвечает метод calculateConcrete (аналог Calculate из старых обработчиков доставки):
protected function calculateConcrete(\Bitrix\Sale\Shipment $shipment = null)
{
// Какие-то действия по получению стоимости и срока...
$result = new \Bitrix\Sale\Delivery\CalculationResult();
$result->setDeliveryPrice(
roundEx(
500,
SALE_VALUE_PRECISION
)
);
$result->setPeriodDescription('4-7 days');
return $result;
}
Обязательно кешируйте все данные, полученные от внешних API с ключом, содержащим все нужные параметры для расчёта. Как кешировать данные в D7 читайте в этой статье. Как сделать запрос читайте в статье HTTP-запросы в Битрикс D7.
Аргументом является объект класса \Bitrix\Sale\Shipment, из которого можно получить объект заказа и нужные параметры расчёта:
$weight = $shipment->getWeight(); // вес отгрузки
$order = $shipment->getCollection()->getOrder(); // заказ
$props = $order->getPropertyCollection();
$locationCode = $props->getDeliveryLocation()->getValue(); // местоположение
Примечание: По информации ведущего разработчика Битрикс на данный момент (sale 16.0.5), пока не вышел обновлённый sale.order.ajax, в компоненте для совместимости в качестве $locationCode выступает ID местоположения, поэтому нужен такой временный костыль для получения кода местоположения:
if ($loc = \Bitrix\Sale\Location\LocationTable::getRowById($locationCode)) {
$locationCode = $loc['CODE'];
}
Также доступен массив настроек службы доставки:
$isTestMode = ($this->config['MAIN']['TEST_MODE'] == "Y");
Метод calculateConcrete должен возвращать объект класса \Bitrix\Sale\Delivery\CalculationResult с доступными методами:
/** @param float $price */
public function setDeliveryPrice($price) {}
/** @param float $price */
public function setExtraServicesPrice($price) {}
/** @param string $description */
public function setPeriodDescription($description) {}
/** @param Bitrix\Main\Error $error */
public function addError($error) {}
Если при расчёте возникла ошибка, в CalculationResult можно добавить ошибки расчёта (эта ошибка будет выведена при оформлении заказа):
$result->addError(new Bitrix\Main\Error("Данный сервис недоступен для выбранного местоположения"));
Для проверки совместимости службы доставки и переданных параметров можно перегрузить метод isCompatible, например, самый простой вариант - проверить успешность расчёта, при наличии ошибок служба доставки просто не будет показываться при оформлении заказа:
public function isCompatible(\Bitrix\Sale\Shipment $shipment)
{
$calcResult = self::calculateConcrete($shipment);
return $calcResult->isSuccess();
}
Профили доставки
Для того, чтобы класс сервиса доставки мог поддерживать профили, добавляем в него поле и методы:
protected static $canHasProfiles = true;
public static function canHasProfiles()
{
return self::$canHasProfiles;
}
public static function getChildrenClassNames()
{
return array(
'\YourNamespace\YetAnotherDeliveryProfile'
);
}
public function getProfilesList()
{
return array("Новый профиль");
}
В методе getChildrenClassNames указали, что профили могут быть объектами класса \YourNamespace\YetAnotherDeliveryProfile, создаём файл с классом и указываем его в обработчике, добавленном в начале статьи:
function addCustomDeliveryServices(\Bitrix\Main\Event $event)
{
$result = new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::SUCCESS,
array(
'\YourNamespace\YetAnotherDeliveryHandler' => '/local/classes/YetAnotherDeliveryHandler.php',
'\YourNamespace\YetAnotherDeliveryProfile' => '/local/classes/YetAnotherDeliveryProfile.php',
)
);
return $result;
}
По указанному пути размещаем класс:
namespace YourNamespace;
class YetAnotherDeliveryProfile extends \Bitrix\Sale\Delivery\Services\Base
{
protected static $isProfile = true;
protected static $parent = null;
public function __construct(array $initParams)
{
parent::__construct($initParams);
$this->parent = \Bitrix\Sale\Delivery\Services\Manager::getObjectById($this->parentId);
}
public static function getClassTitle()
{
return 'Yet Another Delivery profile';
}
public static function getClassDescription()
{
return 'My custom handler for Yet Another Delivery Service profile';
}
public function getParentService()
{
return $this->parent;
}
public function isCalculatePriceImmediately()
{
return $this->getParentService()->isCalculatePriceImmediately();
}
public static function isProfile()
{
return self::$isProfile;
}
}
В остальном класс профиля является аналогом класса родительской службы.
Профиль также может иметь свою конфигурацию, например, профилям могут соответствовать разные тарифы доставки:
protected function getConfigStructure()
{
$result = array(
"MAIN" => array(
'TITLE' => 'Основные',
'DESCRIPTION' => 'Основные настройки',
'ITEMS' => array(
'TARIFF_ID' => array(
"TYPE" => 'STRING',
"NAME" => 'ID тарифа службы доставки',
),
)
)
);
return $result;
}
За расчёт стоимости также отвечает метод calculateConcrete:
protected function calculateConcrete(\Bitrix\Sale\Shipment $shipment = null)
{
// Какие-то действия по получению стоимости и срока...
$result = new \Bitrix\Sale\Delivery\CalculationResult();
$result->setDeliveryPrice(
roundEx(
500,
SALE_VALUE_PRECISION
)
);
$result->setPeriodDescription('2-3 days');
return $result;
}
В методах профиля можно получить не только настройки профиля, но и настройки родительской службы:
$tariff = $this->config['MAIN']['TARIFF_ID'];
$login = $this->getParentService()->config['MAIN']['API_KEY'];
И также здесь можно проверять совместимость профилей (аналог метода Compability в старых службах доставки):
public function isCompatible(\Bitrix\Sale\Shipment $shipment)
{
$calcResult = self::calculateConcrete($shipment);
return $calcResult->isSuccess();
}
Во избежание случайных ошибок, при использовании профилей в методе расчёта родительского сервиса стоит выбрасывать исключение:
protected function calculateConcrete(\Bitrix\Sale\Shipment $shipment = null)
{
throw new \Bitrix\Main\SystemException('Only profiles can calculate concrete');
}
События расчёта доставки
После расчёта доставки вызывается событие onSaleDeliveryServiceCalculate модуля sale с параметрами RESULT - результат расчёта (\Bitrix\Sale\Delivery\CalculationResult) и SHIPMENT - отгрузка (\Bitrix\Sale\Shipment). Можно использовать это событие для изменения результатов расчёта без вмешательства в код обработчика и без копирования стандартных обработчиков в свои пространства имён.
\Bitrix\Main\EventManager::getInstance()->addEventHandler('sale', 'onSaleDeliveryServiceCalculate', 'yourHandler');
function yourHandler(\Bitrix\Main\Event $event)
{
$calcResult = $event->getParameter('RESULT');
$shipment = $event->getParameter('SHIPMENT');
// например, прибавим 200 рублей к стоимости доставки
$newPrice = $calcResult->getDeliveryPrice() + 200;
$calcResult->setDeliveryPrice($newPrice);
return new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::SUCCESS,
array(
"RESULT" => $calcResult,
)
);
}
Читайте также: