РАЗРАБОТКА НА БИТРИКС

Установка и настройка модуля "Расчет стоимости доставки по зонам с подсказками от DaData"

Подписывайтесь на канал для bitrix-разработчиков в Telegram!
В данном кейсе рассмотрена настройка и использование модуля "Расчет стоимости доставки по зонам с подсказками от DaData", и его расширение с добавлением еще 2-х параметров, влияющих на стоимость доставки.

Описание задачи

Необходимо было реализовать расчет стоимости доставки таким образом, чтобы в зависимости от того, в какой точке Москвы или Московской области находится клиент, рассчитывалась стоимость доставки. Кроме этого, необходимо было добавить выбор типа доставки и время, в которое нужно доставить покупку. В зависимости от типа доставки и выбранного времени изменяется цена, рассчитанная исходя из местоположения адресата.

Решение

Установка модуля
Установка модуля стандартная и не требует нестандартных действий.
Настройка модуля
Сразу после установки модуль готов к использованию.
В модуле имеется собственный инструмент редактирования зон доставки, на котором необходимо обрисовать зоны на Яндекс.карте. В исходном состоянии отрисованы МКАД и диаметры расстояния от МКАДа.
Модуль расчета стоимости доставки по зонам с подсказками от DaData - KISLOROD
Если эти зоны клиенту не подходят, то нужно отрисовать свои, что достаточно кропотливая и медитативная задача.
Для отрисовки этих 7 зон понадобилось 4 часа и подручные средства в виде плагина для Хрома PerfectPixel, который устанавливает подложку под веб-страницу из картинки, например, макета для верстки пиксель в пиксель. Здесь его можно использовать для отрисовки более-менее ровных кругов с заданным центром.
Отрисовка зон в модуле расчета стоимости доставки от DaData - KISLOROD
Далее на заданные зоны задается стоимость доставки.
Настройка стоимости расчета доставки в 1С-Битрикс - KISLOROD
Доработка модуля
В модуле для расчета стоимости реализовано поле для ввода адреса. К нему были добавлены чекбоксы типа доставки — "Обычная доставка", "К точному времени" и 4 селекта даты и времени доставки.
Расчет стоимости доставки в чекауте 1С-Битрикс - KISLOROD
Для этого в модуле доставки в файле local/modules/corsik.yadelivery/sale_delivery/yandexDelivery/profile.php была модифицирована функция protected function calculateConcrete() Эта функция возвращает $result, в методе которого $result->setDescription($description); возвращается $description, то есть описание доставки. В html описания была реализована форма выбора адреса. В этот же html были добавлены новые селекты.
Для вывода первоначальных значений селектов в классе YandexDeliveryProfile были дописаны функции, которые формируют оптионсы в зависимости от уже заданных значений даты и времени.
$this->getSimpleDateOptions($propertyDayInfo)
Код функции
public function getSimpleDateOptions($day){

        $curHours= date("H");
        $arDate = [];
        $strOptions = '';
        if (!strlen($day)){}
        $diff1Day = new \DateInterval('P1D');
        $date = new \DateTime();

        if($curHours < 19){
            $arDate[] = 'today';
            if (!strlen($day)){ $day = 'today';}
        }else{
           $date->add($diff1Day);
  //
           if (!strlen($day)){ $day = $date->format("j").'.'.$date->format("n");}
        }

        for($i = 1; $i < 7; $i++){
            $date->add($diff1Day);
            $arDate[] = $date->format("j").'.'.$date->format("n");
        }

        foreach ($arDate as $item) {
            $selected = '';
            if ($day == $item) {$selected = 'selected = ""';}
            if ($item == 'today'){
                $strOptions = $strOptions.'<option '.$selected.' value="'.$item.'">'. Loc::getMessage("CORSIK_DELIVERY_TODAY").'</option>'.PHP_EOL;
            }else{
            $strOptions = $strOptions.'<option '.$selected.' value="'.$item.'">'.$item.'</option>'.PHP_EOL;}
        };
        return $strOptions;
    }
$this->getSimpleHoursOptions($propertyDayInfo, $propertyTimeInfo)
Код функции
function getSimpleHoursOptions($inputDay, $inputHours){
        // текущая дата
    $curHours= date("H");
    $arDate = [];
    $strOptions = '';
    $date = new \DateTime();
    $inputHours = explode(':',$inputHours)[0];
    if ($curHours > 18 || $inputDay != 'today' && $inputDay != ''){$hours = 0;}else{$hours = $curHours + 3;}
    $diff1Hours = new \DateInterval('PT1H');
    $date->setTime($hours,0,0);
    while($hours < 22) {
        $arDate[] = $date->format("G");
        $date->add($diff1Hours);
        $hours = $hours + 1;
    }

    foreach ($arDate as $item) {
        $selected = '';
        if ($inputHours == $item) {$selected = 'selected = ""';}
        $strOptions = $strOptions . '<option ' . $selected . ' value="' . $item . ':00 - ' . ((int)$item + 3) . ':00" >' . $item . ':00 - ' . ((int)$item + 3) . ':00' . '</option>'.PHP_EOL;
       };
    return $strOptions;
    }
$this->getFastHoursOptions($propertyDayInfo, $propertyTimeInfo)
Код функции
function getFastHoursOptions($inputDay, $inputHours){
        // текущая дата
        $curHours= date("H");
        $arDate = [];
        $strOptions = '';
        $date = new \DateTime();
        $inputMins = explode(':',$inputHours)[2];
        $inputHours = explode(':',$inputHours)[0];
        if ($curHours > 18 || $inputDay != 'today' && $inputDay != ''){$hours = 0;}else{$hours = $curHours + 3;}
        $diff1Hours = new \DateInterval('PT1H');
        $date->setTime($hours,0,0);
        while($hours < 24) {
            $arDate[] = $date->format("G");
            $date->add($diff1Hours);
            $hours = $hours + 1;
        }

        foreach ($arDate as $item) {
            if ($inputHours == $item && $inputMins = '00') {$selected = 'selected = ""';}else{$selected = '';}
            $strOptions = $strOptions . '<option ' . $selected . ' value="' . $item . ':00 - ' . $item  . ':15" >' . $item . ':00 - ' . $item  . ':15' . '</option>'.PHP_EOL;

            if ($inputHours == $item && $inputMins = '15') {$selected = 'selected = ""';}else{$selected = '';}
            $strOptions = $strOptions . '<option ' . $selected . ' value="' . $item . ':15 - ' . $item  . ':30" >' . $item . ':15 - ' . $item  . ':30' . '</option>'.PHP_EOL;


            if ($inputHours == $item && $inputMins = '30') {$selected = 'selected = ""';}else{$selected = '';}
            $strOptions = $strOptions . '<option ' . $selected . ' value="' . $item . ':30 - ' . $item  . ':45" >' . $item . ':30 - ' . $item  . ':45' . '</option>'.PHP_EOL;


            if ($inputHours == $item && $inputMins = '45') {$selected = 'selected = ""';}else{$selected = '';}

            $strOptions = $strOptions . '<option ' . $selected . ' value="' . $item . ':45 - ' . ((int)$item + 1)  . ':00" >' . $item . ':45 - ' . ((int)$item + 1)  . ':00' . '</option>'.PHP_EOL;
        };

        return $strOptions;
    }
Также на изменение чекбоксов и селектов были подвешены функции в JS.
Код JS функций

// текущая дата
let date = new Date();


function changeDeliveryChbox(){
    let chbox1 = document.querySelector('#DELIVERY_TYPE_1')
    let chbox2 = document.querySelector('#DELIVERY_TYPE_2')
    let inputDate = document.querySelector('#delivery_date')
    let inputHours = document.querySelector('#delivery_time')
    let inputFastDate = document.querySelector('#delivery_date_fast')
    let inputFastHours = document.querySelector('#delivery_time_fast')
    let deliveryBoxSimple = document.querySelector('.delivery-box-simple')
    let deliveryBoxFast = document.querySelector('.delivery-box-fast')
    let calculateButton = document.querySelector('#calculate-button')
    if (chbox1.checked) {
        chbox1.setAttribute('value', 'simple')
        deliveryBoxSimple.setAttribute('style', 'display: block')
        deliveryBoxFast.setAttribute('style', 'display: none')
        calculateButton.setAttribute('style', 'display: block')
        chbox2.checked = false;

        chbox1.setAttribute('name', 'ORDER_PROP_33')
        chbox2.setAttribute('name', '')

        inputDate.setAttribute('name', 'ORDER_PROP_32')
        inputFastDate.setAttribute('name', '')

        inputHours.setAttribute('name', 'ORDER_PROP_25')
        inputFastHours.setAttribute('name', '')

        inputDate.innerHTML = getSimpleDateOptions();
        inputHours.innerHTML = getSimpleHoursOptions();
    }
    else {
        chbox1.checked = true;
    }

    if (chbox2.checked) {
        chbox2.setAttribute('value', 'fast')
        deliveryBoxSimple.setAttribute('style', 'display: none')
        deliveryBoxFast.setAttribute('style', 'display: block')
        chbox1.checked = false;
    }
    else {
        chbox2.setAttribute('value', '')
        deliveryBoxFast.setAttribute('style', 'display: none')
    }

}

function changeDeliveryChboxFast(){
    let chbox1 = document.querySelector('#DELIVERY_TYPE_1')
    let chbox2 = document.querySelector('#DELIVERY_TYPE_2')
    let inputDate = document.querySelector('#delivery_date')
    let inputHours = document.querySelector('#delivery_time')
    let inputFastDate = document.querySelector('#delivery_date_fast')
    let inputFastHours = document.querySelector('#delivery_time_fast')
    let deliveryBoxSimple = document.querySelector('.delivery-box-simple')
    let deliveryBoxFast = document.querySelector('.delivery-box-fast')
    let calculateButton = document.querySelector('#calculate-button')
    if (chbox2.checked) {
        chbox2.setAttribute('value', 'fast')
        deliveryBoxSimple.setAttribute('style', 'display: none')
        deliveryBoxFast.setAttribute('style', 'display: block')
        calculateButton.setAttribute('style', 'display: block')
        chbox1.checked = false;

        chbox2.setAttribute('name', 'ORDER_PROP_33')
        chbox1.setAttribute('name', '')

        inputFastDate.setAttribute('name', 'ORDER_PROP_32')
        inputDate.setAttribute('name', '')

        inputFastHours.setAttribute('name', 'ORDER_PROP_25')
        inputHours.setAttribute('name', '')

        inputFastDate.innerHTML = getFastDateOptions();
        inputFastHours.innerHTML = getFastHoursOptions();
    }
    else {
        chbox2.checked = true;

    }
}

function getSimpleDateOptions(){
    // текущая дата
    let date = new Date();
    let arDate = [];
    let strOptions = ''
    let inputDate = document.querySelector('#delivery_date')
    let valueDate = inputDate.getAttribute('data-value')
    let inputLang = inputDate.getAttribute('lang')
    if(date.getHours() < 19){ arDate.push('today')}
    for(let i = 0; i < 7; i++){
        date.setDate(date.getDate()+1);
        arDate.push(date.getDate() + '.' + (date.getMonth()+1))
    }
    arDate.forEach(function(item, i, arr) {
        selected = ''
        if (valueDate == item) {selected = 'selected = ""'}
        if (inputLang = 'ru'){
            strOptions = strOptions + '<option ' + selected + ' value="' + item + '">' + 'Сегодня' + '</option>\n'
        }else{
            strOptions = strOptions + '<option ' + selected + ' value="' + item + '">' + 'Today' + '</option>\n'}
    });
    return strOptions
}

function getSimpleHoursOptions($fullday){
    // текущая дата
    let date = new Date()
    let arDate = []
    let strOptions = ''
    let hours
    let inputDate = document.querySelector('#delivery_time')
    inputDate = inputDate.getAttribute('data-value')
    if ($fullday == true){hours = 0}else{hours = date.getHours() + 3}
    while(hours < 22) {
        arDate.push(hours)
        hours++;
    }

    arDate.forEach(function(item, i, arr) {
        selected = ''
        if (inputDate == (item + ':00 - ' + (item + 3) + ':00')) {selected = 'selected = ""'}
        strOptions = strOptions + '<option ' + selected + ' value="' + item + ':00 - ' + (item + 3) + ':00" >' + item + ':00 - ' + (item + 3) + ':00' + '</option>\n'
    });
    return strOptions
}

function getFastDateOptions(){
    // текущая дата
    let date = new Date();
    let arDate = [];
    let strOptions = ''
    let inputDate = document.querySelector('#delivery_date_fast')
    let valueDate = inputDate.getAttribute('data-value')
    let inputLang = inputDate.getAttribute('lang')
    if(date.getHours() < 19){ arDate.push('today')}
    for(let i = 0; i < 7; i++){
        date.setDate(date.getDate()+1);
        arDate.push(date.getDate() + '.' + (date.getMonth()+1))
    }
    arDate.forEach(function(item, i, arr) {
        selected = ''
        if (valueDate == item) {selected = 'selected = ""'}
        if (inputLang = 'ru'){
            strOptions = strOptions + '<option ' + selected + ' value="' + item + '">' + 'Сегодня' + '</option>\n'
        }else{
            strOptions = strOptions + '<option ' + selected + ' value="' + item + '">' + 'Today' + '</option>\n'}
    });
    return strOptions
}

function getFastHoursOptions($fullday){
    // текущая дата
    let date = new Date()
    let arDate = []
    let strOptions = ''
    let hours
    let inputDate = document.querySelector('#delivery_time_fast')
    inputDate = inputDate.getAttribute('data-value')
    if ($fullday == true){hours = 0}else{hours = date.getHours() + 3}
    while(hours < 24) {
        arDate.push(hours)
        hours++;
    }

    arDate.forEach(function(item, i, arr) {
        selected = ''

        if (inputDate == (item + ':00 - ' + item + ':15')) {selected = 'selected = ""'}
        strOptions = strOptions + '<option ' + selected + ' value="' + item + ':00 - ' + item  + ':15" >' + item + ':00 - ' + item + ':15' + '</option>\n'

        if (inputDate == (item + ':15 - ' + item + ':30')) {selected = 'selected = ""'}
        strOptions = strOptions + '<option ' + selected + ' value="' + item + ':15 - ' + item + ':30" >' + item + ':15 - ' + item + ':30' + '</option>\n'

        if (inputDate == (item + ':30 - ' + item + ':45')) {selected = 'selected = ""'}
        strOptions = strOptions + '<option ' + selected + ' value="' + item + ':30 - ' + item + ':45" >' + item + ':30 - ' + item + ':45' + '</option>\n'

        if (inputDate == (item + ':45 - ' + (item + 1) +':00')) {selected = 'selected = ""'}
        strOptions = strOptions + '<option ' + selected + ' value="' + item + ':45 - ' + (item + 1) +':00" >' + item + ':45 - ' + (item + 1) + ':00' + '</option>\n'
    });
    return strOptions
}

function delivery_day_change_date(input) {
    input.setAttribute('data-value', input.value)
    let inputHours = document.querySelector('#delivery_time')
    let calculateButton = document.querySelector('#calculate-button')
    calculateButton.setAttribute('style', 'display: block')
    if (input.value == 'today'){
        inputHours.innerHTML = getSimpleHoursOptions()
    }
    else{
    inputHours.innerHTML = getSimpleHoursOptions(true)
    }
}

function delivery_day_change_date_fast(input) {
    input.setAttribute('data-value', input.value)
    let inputHours = document.querySelector('#delivery_time_fast')
    let calculateButton = document.querySelector('#calculate-button')
    calculateButton.setAttribute('style', 'display: block')
    if (input.value == 'today'){
        inputHours.innerHTML = getFastHoursOptions()
    }
    else{
        inputHours.innerHTML = getFastHoursOptions(true)
    }
}



function delivery_time_change_date(input) {
    let calculateButton = document.querySelector('#calculate-button')
    calculateButton.setAttribute('style', 'display: block')
    input.setAttribute('data-value', input.value)
}
После того как благодаря модулю получена стоимость доставки, и на добавленных чекбоксах и селектах выставлена дата и время, в расчет стоимости доставки включается дописанная функция $this->getSelectedPrice($price, $properTypeInfo, $propertyTimeInfo);
Код функции
public function getSelectedPrice($price, $type, $time){
        $inputHours = explode(':',$time)[0];

        $simple = [
            500 => [500, 1000, 2000],
            700 => [700, 1200, 2200],
            1300 => [1300, 1800, 2800],
            1800 => [1800, 2800, 3800],
            2400 => [2400, 3400, 4400],
            3600 => [3600, 4600, 5600],
            4400 => [4400, 5400,6400]
        ];

        $fast = [
            500 => [800, 1500, 3000],
            700 => [1050, 1800, 3300],
            1300 => [1300, 1800, 2800]
        ];

        if ($type == 'simple'){$arPrice = $simple;} elseif ($type == 'fast') {$arPrice = $fast;}

        if ($inputHours > 8 && $inputHours < 22){
            $period = 0;
        } else if (($inputHours > 21 && $inputHours < 24) || ($inputHours > 6 && $inputHours < 9)){
            $period = 1;
        } else if ($inputHours > 0 && $inputHours < 7){
            $period = 2;
        }

        $outPrice = $arPrice[$price][$period];
        return $outPrice;
    }
В ней реализована логика расчёта цены согласно таблицам, предоставленных заказчиком, в зависимости от заданного региона (цены рассчитанной модулем), выбранного типа доставки и времени доставки.
Далее через метод $result->setDeliveryPrice($price); задается новая стоимость доставки и функция calculateConcrete возвращает $result.

Результат

На основе готового модуля доставки, рассчитывающего цену исходя из принадлежности адреса к заданной зоне, на карте был реализована служба доставки, которая в зависимости от адреса, типа доставки по выбранному временному интервалу и времени доставки рассчитывает стоимость.

Выводы

Готовый модуль расчета доставки в зависимости от адреса решает задачу работы с API Яндекс.карт и однозначно оправдывает свою относительно невысокую цену.
В рамках задачи расчет стоимости происходил по заданной таблице, значения которой не планируют изменять долгое время, то есть не было необходимости дорабатывать интерфейс модуля в административной части для того, чтобы менять значения, влияющие на стоимость доставки. К тому же перечень переменных, влияющих на расчет стоимости для редактирования в административной части, выглядел бы слишком громоздко и не универсально. Поэтому при изменении логики или значений, влияющих на расчет стоимости, необходимо будет прибегнуть к помощи программиста и модифицировать функцию getSelectedPrice.
Подписывайтесь на канал для bitrix-разработчиков в Telegram!

Рекомендованные статьи