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

Продолжение следует…

Разработка модулей PrestaShop: 5 комментариев

  1. Rafael

    Уважаемый!
    Может и не в тему чуть, но не могли бы вы в общих чертах обрисовать, как мне реализовать возможность, что бы покупатели из своего кабинета на фронт-энде могли добавлять новые товары со всеми характеристиками и атрибутами?
    Спасибо!

    1. admin Автор записи

      Установить модуль. Есть несколько модулей, которые позволяют пользователям интернет-магазина добавлять свои тексты, изображения и т.д. Например, Module Prestashop Petites Annonces.
      Если делать модуль самому, то за основу берем модуль комментариев. Так можно упростить работу.

  2. Роман

    Очень интересное начало. Даже как-то все просто, но видимо еще не конец и могут быть подводные камни.
    Вопрос автору, есть ли у него не примете или в разработке модуль доставки с выбором, допустим:
    1. тип доставки ардесная\склад
    2. Номер склада (просто на уровне ввода клиентом строки)
    3. галочки «наложенный платеж»
    И рас пошла такая пьянка не лишним будет ФИО получателя.
    В идеале это модуль для «Новая Почта», буду благодарен за ссылку на похожий или полезные материалы, Готов рассмотреть вариант разработки на заказ, за адекватную цену.
    P.S. Версия prestasop 1.5.4

    1. admin Автор записи

      Готового не встречал. Сделать такой можно. В принципе, тут описан каркас такого модуля. Конечно, подводных камней будет много, но чтобы начать подойдет.
      Могу сам сделать модуль под prestashop для «Новая Почта». Только нужно подробное описание, что от модуля нужно, какие функции в админке показать, по каким параметрам настраиваться должен и т.д. В общем, тех.задание.
      Адекватная цена — это как?

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *