Разработка модулей доставки для 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);
}

Обсуждение

Баюн, 2011/11/10 18:37

Скажите пожалуйста,а для версии 1.3 это не работает?

Или есть какой-то другой вариант написания модуля доставки?

Sir, 2011/11/14 15:24

в версии 1.4 не работает - все делал в точности.

в админке пишет «ошибка парсера»

Ally, 2012/08/04 15:17

Подскажите, пожалуйста, как добавить % от стоимости товара в корзине?

Ally, 2012/08/04 15:38

Вопрос, уточнение. В стоимость доставки нужно добавить % от стоимости товара в корзине (т е это данные из другого модуля). Как прибавить этот % стоимости товара в корзине?

Дмитрий, 2012/12/10 19:29

Подскажите как организовали такое - + процент от стоимости товара???

Ваш комментарий. вики-синтаксис разрешён:
HYMZA
 
moduli-dostavki.txt · Последние изменения: 2015/12/27 16:22 (внешнее изменение)