+7 495 008 8452 пн.-пт. 10:00 – 17:00
Загрузка...
Если у вас возникли какие либо вопросы которые вы не смогли решить по нашим публикациям самостоятельно,
то ждем ваше обращение в нашей службе тех поддержки.


Зависимость цены от свойств товара не прибегая к торговым предложениям


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

Также эта статья будет полезна тем, кто не может разобраться с CSaleBasket::Add и ajax добавлением товара в корзину.

Итак, передо мной стояла следующая задача:

У товара цена зависела от размеров (от длины и ширины) и еще нескольких свойств (эти свойства в данной статье опустим, но вы без проблем, дописав код, сможете реализовать ваши конкретные зависимости сами).

Мы имеем порядка десяти различных ширин и длин (для многих товаров разные). Итого получается около 100 вариантов  цен для каждой единицы товара. Товаров самих тоже очень много. Расчет цены не подчиняется никакому закону, все цены вбиваются вручную. Также должна быть возможность задать процент скидки для всех цен товарной карточки.

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

Задача была реализована следующим образом:  


  1. Создаем у элемента инфоблока 2 свойства: «Размеры и цены» типа «html/текст», куда будет вбиваться таблица с размерами и ценами, и свойство «Скидка» типа число.
  2. Далее таблица разбивается скриптом, на детальную страницу выводятся 2 поля ширина и длина типа select
  3. При выборе подставляется нужная цена с использованием js
  4. При клике «добавить в корзину» ajax’ом отправляются параметры в файл, где отрабатывает скрипт добавления в корзину методом CSaleBasket::Add

Недостаток метода: нет поддержки актуальности корзины (в моем случае это и не надо).

Теперь подробнее:

1. Создаем 2 свойства элемента инфоблока (к которому относится товарная карточка) Размеры и цены» типа «html/текст» с  кодом PRICE_TABLE  и и свойство «Скидка» типа число с кодом DISCOUNT

2. Заполняем первое свойство обычной таблицей вида:

<table>
<tbody>
<tr>
<td></td>
<td>190</td>
<td>200</td>
</tr>
<tr>
<td>80</td>
<td>13440</td>
<td>14740</td>
</tr>
<tr>
<td>90 см.</td>
<td>15250</td>
<td>16250</td>
</tr>
<tr>
<td>120</td>
<td>18826</td>
<td>19826</td>
</tr>
<tr>
<td>140</td>
<td>21425</td>
<td>22841</td>
</tr>
<tr>
<td>160</td>
<td>23101</td>
<td>23804</td>
</tr>
<tr>
<td>180</td>
<td>22105</td>
<td>26105</td>
</tr>
<tr>
<td>190</td>
<td>24566</td>
<td>28726</td>
</tr>
<tr>
<td>200</td>
<td>23445</td>
<td>28726</td>
</tr>
</tbody>
</table>


Столбцы – возможные длины, строки – ширины, на пересечениях – цены.
В свойство скидка вписываем (опционно) скидку, допустим 10 (означает 10%)

3. Далее идем в шаблон элемента каталога,  практически все действия мы будем производить в нем (/bitrix/templates/.default/components/bitrix/catalog/main/bitrix/catalog.element/.default/template.php  в моем случае)
Пишем код:

<?
//функция очисткитаблицы
function parseTable($text){
    // Удаляем переносы строк
    $text = str_replace(array("\r", "\n", "\r\n", " "), '', $text);
    // Удаляем лишние теги
    $text = strip_tags($text, '<table><tr><td>');
    // Очищаем все аттрибуты тегов
    $text = preg_replace('@\<(table|tr|td)[^\>]+\>@', '<\1>', $text);
    // Удаляем начало и конец
    $text = str_replace(array('<table><tr><td>', '</td></tr></table>'), '', $text);
    $text = explode('</td></tr><tr><td>', $text);
    foreach ($text as $tKey => $tVal){
        $text[$tKey] = explode('</td><td>', $tVal);
    }
    return $text;
}
//функция отделения трех последних разрядов цены (12 211 руб.)
function eCount($price){ 
   return preg_replace( '@(\d{3})(\.|$)@u', ' \0', sprintf('%.0f', (intval($price)) ));
}
?>



Это служебные функции, тут все понятно.

Далее идет код преобразовния таблицы в select'ы и применения скидки. CATALOG_PRICE_1 может отличаться - ID вашей основной цены, а также html вывод

<?
$gDiscount = intval($arResult["PROPERTIES"]["DISCOUNT"]["VALUE"]);
$gPrice = intval($arResult["CATALOG_PRICE_1"]);
if ($gDiscount > 0):?>               
   <div id="priceblock">
      <span class="old" id="originalPrice">
         <?=eCount( ($gPrice) )?> руб.
      </span>
      <span class="pct">-<?=$gDiscount?>%</span>
      <span class="cost" id="discountPrice">
         <?=eCount( ($gPrice) * (100 - $gDiscount) / 100)?> руб.
      </span>
      <a class="button bigGreen addToCart" href="jav * ascript:void(0)">
      <span>Добавить в корзину</span>
      </a>
   </div>
   <p id="pricecomment" style="display: none">В данном размере матрас не исполняется</p>
<?else:?>
<div id="priceblock">
   <span class="cost" id="originalPrice" style="float: right">
      <?=eCount( ($gPrice) )?> руб.
   </span>
   <a class="button bigGreen addToCart" href="jav * ascript:void(0)"><span>Добавить в корзину</span></a></div>
<p id="pricecomment" style="display: none">В данном размере матрас не исполняется</p>
<?endif;?>
<div class="choiceBlock">
<?
$priceTable = array();

// Получаем ценовые параметры
if (preg_match('@^(\<table([^~]*?)\<\/table\>)(\<p\>\&nbsp\;\<\/p\>)?@', $arResult["PROPERTIES"]["PRICE_TABLE"]["~VALUE"]["TEXT"], $subp)){
   $priceTableTemp = parseTable($subp[1]);
   
   if (count($priceTableTemp) > 2 && count($priceTableTemp[0] > 2)){
      $priceTable = array();
      $priceHeights = $priceTableTemp[0];
      $priceWidths = array();
      unset($priceTableTemp[0]); unset($priceHeights[0]);
      foreach ($priceTableTemp as $pRow){
         $curWidth = $pRow[0];
         $priceTable[$curWidth] = array();
         $priceWidths[] = $curWidth;
         unset($pRow[0]);
         foreach ($pRow as $rKey => $curPrice){
            $priceTable[$curWidth][$priceHeights[$rKey]] = intval($curPrice);
         }
      }
      ?><fieldset class="select"><label>Ширина:<select id="curWidth" on change="recountPrice();">
      <?php foreach ($priceWidths as $cWidth){ ?><option value="<?=$cWidth?>"><?=$cWidth?></option><? } ?>
      </select></label>
      <label>Длина:<select id="curHeight" on change="recountPrice();">
      <?php foreach ($priceHeights as $cHeight){ ?><option value="<?=$cHeight?>"><?=$cHeight?></option><? } ?>
      </select></label>
            </fieldset><?
   }
}
?>
</div>




Далее следует js (подразумевается, что у вас подключена библиотека jquery). Есть дополнительные свойства типа "Информация о чехлах". Можете удалить или заменить их своими.

<sc ript type="text/javascript">
    var shop = {};

    // ID товара
    shop.id = <?=$arResult["ID"]?>;

    // Базовая цена товара
    shop.finalPrice = <?=$gPrice?>;
    shop.price = <?=$gPrice?>;
    shop.discountPrice = <?=intval($gPrice * (100 - $gDiscount) / 100)?>;

    // Скидка
    shop.discount = <?=$gDiscount?>;

    // Таблица цен (для калькулятора)
    shop.priceTable = <?=empty($priceTable)?'false':json_encode($priceTable)?>;

    // Информация о чехлах
    shop.goodCovers = <?=json_encode($goodCovers)?>;
    shop.coverPrice = <?=intval($coverPrice)?>;
    shop.coverID = <?=intval($startCover)?>;

    // Информация о несущих системах
    shop.goodHolders = <?=json_encode($goodHolders)?>;
    shop.holderPrice = <?=intval($holderPrice)?>;
    shop.holderID = <?=intval($startHolder)?>;

    function recountPrice(){

        // Получаем основную цену матраса без дополнительных аксессуаров
        var basePrice;

        var curWidth = docu ment.getElementById('curWidth');
        var curHeight = docu ment.getElementById('curHeight');
   
        if (shop.priceTable && curWidth && curHeight){
            curWidth = curWidth.value; curHeight = curHeight.value;

            if ((typeof shop.priceTable[curWidth] == 'undefined') || (typeof shop.priceTable[curWidth][curHeight] == 'undefined')){
                basePrice = parseInt(shop.price);
            }else{
                basePrice = parseInt(shop.priceTable[curWidth][curHeight]);
            }

           

        
        }else{
            basePrice = parseInt(shop.price);

            // Проставляем цены для чехлов
            $each(shop.goodCovers, f unction(elm, id){
                var coverPrice = elm.basePrice;
                $('coverPrice' + id).set('text', (coverPrice == 0)?'': (' (+'+eCount(coverPrice)+')') );
                if (id == shop.coverID) shop.coverPrice = coverPrice;
            });

            // Проставляем цены для основ
            $each(shop.goodHolders, f unction(elm, id){
                var holderPrice = elm.basePrice;
                $('holderPrice' + id).set('text', (holderPrice == 0)?'': (' (+'+eCount(holderPrice)+')') );
                if (id == shop.holderID) shop.holderPrice = holderPrice;
            });

        }

        // Дополнительные аксессуары
        var accPrice = shop.coverPrice + shop.holderPrice;

        // Проставляем цену
        if (basePrice != 0){
         document.getElementById('priceblock').style.display = 'block';
         document.getElementById('pricecomment').style.display = 'none';
            shop.finalPrice = basePrice + accPrice;
            $('#originalPrice').text(formatPrice(shop.finalPrice));
            if (shop.discount && (shop.discount > 0)){
                shop.discountPrice = parseInt(shop.finalPrice * (100 - shop.discount) / 100);
                $('#discountPrice').text(formatPrice(shop.discountPrice));
            }else{
                shop.discountPrice = shop.finalPrice;
            }
        }else{
            // В данном размере матрас не исполняется
            $('priceblock').setStyle('display', 'none');
            $('pricecomment').setStyle('display', 'block');
            shop.finalPrice = 0;
            shop.discountPrice = 0;
        }
    }
   
   
    function formatPrice(price){
        return price.toString().r eplace(/(\d)(?=(\d\d\d)+([^\d]|$))/g, '$1 ') + ' руб.';
    }

    function eCount(price){ return formatPrice(price); }

    function selectBase(id){
        if (!(curHolder = shop.goodHolders[id])) return false;
        if (shop.priceTable && (curWidth = $('curWidth')) && (curHeight = $('curHeight'))){
            curWidth = curWidth.get('value'); curHeight = curHeight.get('value');
            shop.holderPrice = ($defined(curHolder.prices[curWidth] && $defined(curHolder.prices[curWidth][curHeight]))) ? curHolder.prices[curWidth][curHeight] : curHolder.basePrice;
        }else{
            shop.holderPrice = parseInt(curHolder.basePrice);
        }
        shop.holderID = id;
        recountPrice();
    }

    function selectCover(id){
        if (!(curCover = shop.goodCovers[id])) return false;
        $('coverImage').set('html', (curCover.image == '')?'':('<img src="'+curCover.image+'" />'));
        if (shop.priceTable && (curWidth = $('curWidth')) && (curHeight = $('curHeight'))){
            curWidth = curWidth.get('value'); curHeight = curHeight.get('value');
            shop.coverPrice = ($defined(curCover.prices[curWidth] && $defined(curCover.prices[curWidth][curHeight]))) ? curCover.prices[curWidth][curHeight] : curCover.basePrice;
        }else{
            shop.coverPrice = parseInt(curCover.basePrice);
        }
        shop.coverID = id;
        recountPrice();
    }

    $(f unction(){               
      
        // Добавление товаров в корзину
        var elm = docu ment.getElementsByClassName('.addToCart');
      $(".addToCart").click(f unction (e) {
       

            // Базовые данные
            var sendData = {
                id: shop.id,
                price: shop.finalPrice,
                discount: shop.discount,
                discountPrice: shop.discountPrice,
                value: 1
            };

            // Получаем размеры, если они есть
            var curWidth = docu ment.getElementById('curWidth');
            var curHeight = docu ment.getElementById('curHeight');
            if (shop.priceTable && curWidth && curHeight){
                sendData['width'] = curWidth.value;
                sendData['height'] = curHeight.value;
            }

            // Покрытие и несущая система
            sendData.coverID = shop.coverID;
            sendData.holderID = shop.holderID; 
         
                this.blur();
                var link = $(this).attr('href').match(/\?.*$/);
                $.ajax({
                   url: "/bitrix/templates/main/addToCart.php",
                  global: false,
                  type: "POST",
                  dat a: ({sendData : sendData}),
                  type: 'POST',
                  success: f unction(data){
                     a lert('Товар добавлен в корзину');
                  }
                });


                return false;
              });
            // Добавляем основной товар

        });      
              
</sc ript>




Тут остановимся подробнее.

До функции  $.ajax идет подстановка цены товара и формирование массива sendData, где хранится цена, и выбранные свойства товара.

Нас интересует именно функция  $.ajax

url: "/bitrix/templates/main/addToCart.php",

Здесь мы прописываем путь до скрипта добавления в корзину методом CSaleBasket::Add

В
success: f unction(data){}
указываются действия при успешном добавлении товара. У меня, например это ajax обновление малой корзины, обернутой в <div id="bid"></div> и полет картинки c <img id="fly" /> в малую корзину.

success: f unction(data){
   $('#bid').load('/bitrix/templates/main/basketsmallajax.php');
   $("#fly")  
     .clone()  
      .css({'position' : 'absolute', 'z-index' : '9999', 'width' : '200px', 'height' : '200px', 'right' : '500px', 'top': '100px'})  //Начальные значения для клона, ширину/высоту задаём для хрома, right/top -начальное положение картинки
      .prependTo(".fleft")  //место появления клона картинки, можете изменить на эту же картинку
      .animate({opacity: 0.5, top: -230, right: 70,  width: 0,  height: 0}, 800, f unction() {  //top/right место корзины, 800 - скорость
         $(this).remove();
     });  
}


где в файле  /bitrix/templates/main/basketsmallajax.php  находится код вызова компонента малой корзины.

Переходим к главному.
Содержимое файла  addToCart.php:

<?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");
if (CModule::IncludeModule("sale"))
{
if(CModule::IncludeModule("iblock")){
$arSelect = Array("", "NAME", "DETAIL_PAGE_URL");
$arFilter = Array("IBLOCK_ID"=>"5", "ID"=>$_POST["sendData"]["id"]);
$res = CIBlockElement::GetList(Array(), $arFilter, false, false, $arSelect);
while($ob = $res->GetNextElement())
{
  $element = $ob->GetFields();
}
}
  $arFields = array(
    "PRODUCT_ID" => $_POST["sendData"]["id"],
    "PRICE" => $_POST["sendData"]["discountPrice"],
    "QUANTITY" => 1,
    "LID" => LANG,
    "CAN_BUY" => "Y",
    "CURRENCY" => "RUB",
    "NAME" => $element["NAME"],
    "CALLBACK_FUNC" => "",
    "MODULE" => "",
    "NOTES" => "",
    "ORDER_CALLBACK_FUNC" => "",
    "DETAIL_PAGE_URL" => $element["DETAIL_PAGE_URL"]
  );

  $arProps = array();
  if(isset($_POST["sendData"]["width"])):
  $arProps[] = array(
    "NAME" => "Размер (см)",
    "CODE" => "SIZE",
    "VALUE" => $_POST["sendData"]["width"]."x".$_POST["sendData"]["height"]
  );
  endif;

  $arFields["PROPS"] = $arProps;

  CSaleBasket::Add($arFields);
}
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/epilog_after.php");
?>


Здесь в  "IBLOCK_ID"=>"5" указываете ID вашего инфоблока

в массиве $_POST приходят нужные нам данные с ценой и свойствами. Поле CALLBACK_FUNC оставляем пустым. Т.к. эта функция служит для поддержки актуальности корзины, а нам это не нужно, т.к. мы подменили цену.

Таким образом наши искусственные свойства и новая цена оказываются в корзине. Товары с разными свойствами считаются разными позициями. Что и требовалось. При этом у элемента инфоблока настоящих свойств может не существовать вовсе.

Назад в раздел

Подписаться на новые материалы раздела:
Загрузка...