Разработка модулей доставки для PrestaShop
Разработка модулей доставки для PrestaShop
Базовый функционал Prestashop позволяет создавать способы доставки с расчетом стоимости в зависимости от веса заказа, и зоны, в которой расположен получатель. Все это можно задать в виде таблицы в бэк офисе. Однако иногда возникает необходимость в расчете стоимости доставки через внешние сервисы (EMS, UPS, …), либо исходя из каких-либо собственных условий (количество товара в заказе, объем товара). В этом случае требуется разработка специального модуля доставки.
Модули доставки появились в версии 1.4 и в более ранних работать не будут. Разработка этих модулей требует знания структуры модулей, механизма хуков и основных принципов работы с объектной моделью PrestaShop.
Базовый каркас модуля доставки
Каркас модуля доставки практически идентичен обычному модулю. Основное отличие заключается в том, что он является потомком класса CarrierModule.
<?php if (!defined('_CAN_LOAD_FILES_')) exit; class TutorialCarrier extends CarrierModule { //Конструктор класса. Здесь основное описание модуля. public function __construct() { $this->name = 'TutorialCarrier'; $this->tab = 'front_office_features'; $this->version = '0.1'; $this->author = 'PrestaLab'; parent::__construct(); $this->displayName = $this->l('Доставка TutorialCarrier'); $this->description = $this->l('Описание модуля TutorialCarrier.'); } }
Установка и удаление
Функции установки и удаления аналогичны функциям в стандартных модулях: устанавливаем, то что нужно и удаляем весь хлам за собой.
Установка
Установка модуля заключается в создании способа доставки, привязанного к модулю и добавлению хука updateCarrier.
//Метод вызывается при установке модуля public function install() { //Конфигурация способа доставки. //Имеет такой вид для удобства добавления. //Далее будет представлена функция для добавления. $carrierConfig=array( //Название способа доставки 'name' => 'TutorialCarrier', //Налоговая группа: без налога 'id_tax_rules_group' => 0, //Способ доставки активен 'active' => true, //Не удален. При удалении способа доставки он не удаляется из //базы данных, а только помечается как удаленный 'deleted' => 0, //Доставка и обработка: нет 'shipping_handling' => false, //Исключения: применить наибольшую цену доставки 'range_behavior' => 0, //Время доставки. В виде массива для разных языков. // Последний элемент применяется для непредусмотренных языков. 'delay' => array('ru' => 'Описание TutorialCarrier', 'default' => 'TutorialCarrier description'), //Зона. 7 - Europe (out E.U), т.е Россия, Украина, ... 'id_zone' => 7, //Признак того, что способ доставки принадлежит модулю 'is_module' => true, //Расчет производится из внешнего источника 'shipping_external' => true, //Имя модуля, которому принадлежит способ доставки 'external_module_name' => $this->name, //Требуется или использование стандартного способа //расчета стоимости (зависимость от зоны, веса или цены) 'need_range' => true ); //Добавляем способ доставки. //Функция добавления будет описана далее if($id_carrier = self::installExternalCarrier($carrierConfig)){ //Сохраняем идентифкатор способа доставки //Он нам понадобится если кто-то вздумает изменить //Параметры способа доставки Configuration::updateValue('TUTORIALCARRIER_ID', (int)$id_carrier); //Устанавливаем модуль и регистрируем хук if (!parent::install() || !$this->registerHook('updateCarrier')) return false; return true; } return false; }
Программное создание способа доставки
Добавление способа доставки не представляет сложности, для того кто знаком с объектной моделью престы.
private static function installExternalCarrier($config) { //Создаем объект Carrier $carrier = new Carrier(); //И устанавливаем его свойства из конфигурации. $carrier->name = $config['name']; $carrier->id_tax_rules_group = $config['id_tax_rules_group']; $carrier->id_zone = $config['id_zone']; $carrier->active = $config['active']; $carrier->deleted = $config['deleted']; $carrier->delay = $config['delay']; $carrier->shipping_handling = $config['shipping_handling']; $carrier->range_behavior = $config['range_behavior']; $carrier->is_module = $config['is_module']; $carrier->shipping_external = $config['shipping_external']; $carrier->external_module_name = $config['external_module_name']; $carrier->need_range = $config['need_range']; //Получаем список языков магазина $languages = Language::getLanguages(true); foreach ($languages as $language) { //Проверяем есть ли текущий язык в списке предусмотренных языков if (!isset($config['delay'][$language['iso_code']])) //Если нет, то ставим фразу по умолчанию $carrier->delay[$language['id_lang']] = $config['delay']['default']; else //Иначе берем фразу из конфига $carrier->delay[(int)$language['id_lang']] = $config['delay'][$language['iso_code']]; } //Добавляем способ доставки if ($carrier->add()) { //Делаем способ доставки доступным для всех групп $groups = Group::getGroups(true); foreach ($groups as $group) Db::getInstance()->autoExecute(_DB_PREFIX_.'carrier_group', array('id_carrier' => (int)($carrier->id), 'id_group' => (int)($group['id_group'])), 'INSERT'); //Устанавливаем диапазоны цен $rangePrice = new RangePrice(); $rangePrice->id_carrier = $carrier->id; $rangePrice->delimiter1 = '0'; $rangePrice->delimiter2 = '1000000'; $rangePrice->add(); //И веса $rangeWeight = new RangeWeight(); $rangeWeight->id_carrier = $carrier->id; $rangeWeight->delimiter1 = '0'; $rangeWeight->delimiter2 = '1000000'; $rangeWeight->add(); //Устанавливаем нулевую цену для доставки в каждую зону для цены и веса $zones = Zone::getZones(true); foreach ($zones as $zone) { Db::getInstance()->autoExecute(_DB_PREFIX_.'carrier_zone', array('id_carrier' => (int)($carrier->id), 'id_zone' => (int)($zone['id_zone'])), 'INSERT'); Db::getInstance()->autoExecuteWithNullValues(_DB_PREFIX_.'delivery', array('id_carrier' => (int)($carrier->id), 'id_range_price' => (int)($rangePrice->id), 'id_range_weight' => NULL, 'id_zone' => (int)($zone['id_zone']), 'price' => '0'), 'INSERT'); Db::getInstance()->autoExecuteWithNullValues(_DB_PREFIX_.'delivery', array('id_carrier' => (int)($carrier->id), 'id_range_price' => NULL, 'id_range_weight' => (int)($rangeWeight->id), 'id_zone' => (int)($zone['id_zone']), 'price' => '0'), 'INSERT'); } // Копируем логотип способа доставки if (!copy(dirname(__FILE__).'/carrier.jpg', _PS_SHIP_IMG_DIR_.'/'.(int)$carrier->id.'.jpg')) return false; // Возвращаем идентификатор способа доставки return (int)($carrier->id); } return false; }
Удаление
Удаляем способ доставки, и если он по умолчанию, то ставим что-нибудь другое.
public function uninstall() { global $cookie; //Вызываем родительскую функцию удаления. //Она самостоятельно удаляет хуки за модулем if (!parent::uninstall()) return false; //Получаем ид способа доставки $carrier = new Carrier((int)(Configuration::get('TUTORIALCARRIER_ID'))); //Если наш способ доставки установлен по умолчанию //сделаем по умолчанию что-нибудь другое if (Configuration::get('PS_CARRIER_DEFAULT') == (int)$carrier->id) { $carriersD = Carrier::getCarriers($cookie->id_lang, true, false, false, NULL, PS_CARRIERS_AND_CARRIER_MODULES_NEED_RANGE); foreach($carriersD as $carrierD) if ($carrierD['active'] AND !$carrierD['deleted'] AND ($carrierD['name'] != $carrier->name)) Configuration::updateValue('PS_CARRIER_DEFAULT', $carrierD['id_carrier']); } //Ставим отметку удаления $carrier->deleted = 1; if (!$carrier->update()) return false; return true; }
Обновление способа доставки
Столь непонятный хук сделан из-за того, что при ручном изменения информации о доставке происходит удаление редактируемого способа доставки и создание нового с уже отредактированными параметрами. Это нужно для того, чтобы изменения в способе доставки не коснулись уже произведенных заказов.
Тут просто сохраняем новый идентификатор в конфиг.
public function hookupdateCarrier($params) { if ((int)($params['id_carrier']) == (int)(Configuration::get('TUTORIALCARRIER_ID'))) Configuration::updateValue('TUTORIALCARRIER_ID', (int)($params['carrier']->id)); }
Расчет стоимости доставки
В модуле может быть два способа расчета стоимости доставки. Первый getOrderShippingCost вызывается если в конфиге параметр need_range поставлен как true. В остальных случаях используется getOrderShippingCostExternal.
Параметр $params содержит в виде массива cart, customer и address. $shipping_cost - стоимость доставки расчитанная стандартным методом (через таблицы зависимости от региона и диапазонов)
public function getOrderShippingCost($params, $shipping_cost) { //В этом примере к стоимости, расчитанной по таблице на вкладке доставка //добавляется некоторая фиксированная сумма. //У вас это могут быть самые извращенные расчеты //или запросы к внешнему сервису (не забывайте использовать при этом кэширование). if ($this->id_carrier == (int)(Configuration::get('TUTORIALCARRIER_ID'))) return $shipping_cost+123; // If the carrier is not known, you can return false, the carrier won't appear in the order process return false; } public function getOrderShippingCostExternal($params) { //Собствено этот метод нам не нужен, на для совместимости //можно вызвать предидущий метод return $this->getOrderShippingCost($params, 0); }