понедельник, 22 апреля 2013 г.

Импорт скидок номенклатуры из 1С Предприятия в 1С Битрикс

Начну с цитаты

...если документом "Установка скидок номенклатуры" определены скидки на товары в зависимости от их количества в заказе, то данная информация поступит на сайт и скидка будет применяться и при продаже в Интернет-магазине.
Кроме этого, модуль на стороне 1С выгружает в CommerceML и другие типы скидок, но их автоматический импорт в 1С-Битрикс предлагается реализовать разработчикам интернет-магазина, поскольку их применение очень сильно зависит от особенностей ценообразования для каждого конкретного случая.

Цитата от сюда: https://1c.1c-bitrix.ru/ecommerce/v2.php

Вот об опыте импорта тех самых "других" типов скидок я и хочу рассказать в данной статье. В частности, об импорте скидок, которые применяются к определенному товару вне зависимости от каких-либо условий. (В сезон распродажи или на залежалый товар/размер).

Данную задачу мы (я и мой помощник Вадим Соловьев) решали для того же самого проекта, для которого я незадолго до этого подогнала импорт под готовую структуру инфоблоков (см. статью Как подогнать стандартный импорт из 1С под готовое решение из маркетплейс Битрикс). В этом проекте у нас уже был унаследован класс CIBlockCMLImport, был кастомизирован компонент catalog.import.1c для работы именно с этим классом.

class CIBlockCMLCustomImport extends CIBlockCMLImport{}

и была перегружена функция ImportElement

Этот же перегруженный метод  ImportElement мы и дополнили функционалом для импорта из 1С безусловных скидок на товар.

Информация о скидках приходила к нам из 1С Предприятия в следующем виде:
<СкидкиНаценки>
     <СкидкаНаценка>
      <Условие>Количество одного товара в документе превысило</Условие>
      <Процент>20</Процент>
      <Валюта>руб</Валюта>
      <Получатель>8a21ac94-2ac6-11e0-8e8a-00265abc1d64</Получатель>
     </СкидкаНаценка>
...
</СкидкиНаценки>
И торговые предложения, у которых в секции  СкидкиНаценки присутствовала скидка указанного вида нам нужно было интерпретировать как торговые предложения, на которые действует безусловная процентная скидка.

Думая о том, как организовать хранение этих скидок в Битрикс, я хотела дописывать торговые предложения, на которые действует скидка в условия для существующих в системе процентных скидок (желтая стрелочка на рисунке). Вадим предложил заводить отдельную скидку для каждого торгового предложения (зеленая стрелочка на рисунке). И я отдала предпочтение решению Вадима, так как оно предполагает меньшее количество запросов к БД при импорте ссылок (особенно в случаях когда в 1С удалена скидка на товар, к-я ранее была импортирована на сайт и которую нужно удалить и на сайте в процессе импорта), хотя результат и не так красиво смотрится в админке.

Итак, в нашем перегруженном методе ImportElement мы дописали следующее:

В самом начале метода мы смотрим, есть ли для указанного элемента безусловная процентная скидка. Если есть, пишем ее в переменную $discount_perc. Если нет - эта переменная остается равной 0.

$discount_perc = 0.0;
if(isset($arXMLElement[GetMessage("IBLOCK_XML2_DISCOUNTS")]))
{
    foreach($arXMLElement[GetMessage("IBLOCK_XML2_DISCOUNTS")] as $key=>$discount)
    {
        if(
            isset($discount[GetMessage("IBLOCK_XML2_DISCOUNT_CONDITION")])
            && $discount[GetMessage("IBLOCK_XML2_DISCOUNT_CONDITION")]===GetMessage("IBLOCK_XML2_DISCOUNT_COND_VOLUME")
        )
        {
            $discount_perc = $this->ToFloat($discount[GetMessage("IBLOCK_XML2_DISCOUNT_COND_PERCENT")]);
        }
    }
}
Далее, ниже секции, в которой в стандартном методе ImportElement определяются скидки на товары в зависимости от  их количества в заказе, мы вставили код, создающи/обновляющий/удаляющий наши безусловные процентные скидки. В этом коде мы пошли на небольшую хитрость и ставим каждой новой скидке ID равный ID элемента, для которого данная скидка была создана.

$discount_id = intval($arElement["ID"]);
$arDiscount = array();
$arDiscount = CCatalogDiscount::GetByID($discount_id);
if ($discount_perc == 0.0)
{
    if($arDiscount) CCatalogDiscount::Delete($discount_id);
}
else
{
    if($arDiscount)
    {
        if($arDiscount["VALUE"] != $discount_perc)
        {
            $arDiscount["VALUE"] = $discount_perc;
            CCatalogDiscount::Update($discount_id, $arDiscount);
        }
    }
    else
    {
       
        if ($discount_perc>0){
        $arLogic = array (
            "CLASS_ID" => "CondGroup",
            "DATA" => array (
                "All" => "OR",
                "True" => "True",
                ),
            "CHILDREN" => array (
                "0" => array (
                    "CLASS_ID" => "CondIBElement",
                    "DATA" => array (
                        "logic" => "Equal",
                        "value" => $discount_id,
                        ),
                ),
            ),
        );

        $arDiscount = array (
            "ID" => $discount_id,
            "SITE_ID" => "s1",
            "ACTIVE" => "Y",
            "NAME" => strval($discount_id),
            "MAX_USES" => 0,
            "COUNT_USES" => 0,
            "COUPON" => "",
            "SORT" => 100,
            "MAX_DISCOUNT" => 0.0000,
            "VALUE_TYPE" => "P",
            "VALUE" => $discount_perc,
            "CURRENCY" => "RUB",
            "MIN_ORDER_SUM" => 0.0000,
            "NOTES" => "",
            "RENEWAL" => "N",
            "ACTIVE_FROM" => "",
            "ACTIVE_TO" => "",
            "PRIORITY" => 1,
            "LAST_DISCOUNT" => "Y",
            "CONDITIONS" => serialize($arLogic),
        );
       
        CCatalogDiscount::Add($arDiscount);
       
        }
    }
}
И безусловные скидки на товары были успешно импортированы из 1С Предприятия в Битрикс.
Решение не претендует на идеальность, но оно имеет место быть, успешно внедрено и не перегружает достаточно стандартную виртуальную хостинговую площадку.

суббота, 20 апреля 2013 г.

Как увеличить продажи интернет-магазина на 1С Битрикс

Примерно 80% моих заказчиков занимаются розничными продажами через интернет-магазин, и проблема низкой конверсии неизменно волнует их. (конверсия - отношение покупателей к посетителям, выраженное в процентах)  Чего только ни делают люди, чтобы увеличить продажи: покупают все больше и больше ссылок, покупают рекламу, заказывают продающие статьи и т.д., и т.п. А конверсия часто - не растет.

Одна из причин по которой конверсии нет - в том, что SEO-шники привлекли на сайт не целевых посетителей - выбрали не то семантическое ядро. О выборе семантического ядра я не буду писать в этой статье - это больше по части моей SEO-шницы Ерыгиной Ирины. Я же хочу рассказать о том, как выжать максимум из того, что имеешь. А именно о тех замечательных возможностях, которые дает нам Битрикс и про которые многие забывают.

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

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

Берем инфоблок, в котором контент-менеджеры будут размещать лендинги, берем компонент "новость детально" или комплексный "новости" - кому как больше  нравится. И делаем следующее:

- В инфоблоке добавляем множественное свойство  "Товары", тип для свойства указываем "Привязка к элементам инфоблока", в качестве объекта привязки выбираем инфоблок товаров. Выводим контент-менеждеру это поле в форму добавления статьи.


Теперь добавляя SEO-статью, контент-менеджер сможет выбрать, какие товары мы будем показывать-предлагать пользователю на странице с этой статьей. Зачем это нужно? Поисковик SEO-статью съест, и она долго-долго еще будет появляться в его выдаче по тому запросу, под который была оптимизирована. Страница с этой статьей - будет для пользователя, нашедшего ее через поисковик точкой входа на сайт. И когда он зайдет и прочитает статью, его уже нельзя отпускать - поэтому в конце статьи стоит показать ему несколько товаров из каталога магазина которые соответствуют теме статьи. Статья - формирует желание. А в конце ее будут выводиться товары - конкретное предложение. Ассортимент магазина может меняться, и, изменяя привязанные товары, можно распределять трафик посетителей, зашедших через поисковик.

Можно так же добавить в инфоблок еще одно свойство типа "Файл" для добавления верхнего баннера, к-й маркетологи любят размещать в верхней части лендинга. Если поле для ввода баннера добавили, то логично добавить и поле для ссылки - перехода по клику на этот баннер.

После этого в настройках компонента новостей нужно включить вывод данных свойств. В свойстве "Товары" у нас будут содержаться только ID товаров, поэтому нужно кастомизировать компонент новостей либо на уровне самого компонента, либо на уровне файла result_modifier.php шаблона. (Я предпочитаю на уровне компонента). А затем кастомизировать шаблон детальной страницы новости, чтобы внизу (а можно вверху и внизу) выводились картиночки товаров со ссылками на их детальные страницы (или даже с кнопками купить). 

К примеру, на сайте моих клиентов http://sandalshoes.ru мы с ними сделали вот так:
На верх лендинга контент-менеджер может добавлять веселенький баннер
А внизу - сразу под статьей - выводятся привязанные к этой статье товары:

Согласитесь - это более эффективное использование SEO-статьи - превращение ее из механизма поискового продвижения в механизм маркетинга.

среда, 10 апреля 2013 г.

О лишних запросах

Совершенство достигается не тогда, когда уже нечего прибавить, но когда уже ничего нельзя отнять
Антуан де Сент-Экзюпери

Оптимизация процесса интеграции 1С Предприятия и 1С Битрикс – это неисчерпаемая тема.  Чего только не увидишь порой в обработчиках событий добавления и обновления элемента инфоблока.
Больше всего меня убивает, когда я вижу, что разработчики тянут из базы то, что у них и так уже есть. Пример из жизни (нет, серьезно – это реальный пример):

AddEventHandler("iblock", "OnAfterIBlockElementAdd", "BXMUpdateElement_FIELDS");
function BXMUpdateElement_FIELDS(&$arFields)
{
   
     $ibe = new CIBlockElement();
    
    $dbr = $ibe->GetList(array(), array('ID'=>$arFields['ID']) );
    while($oe = $dbr->GetNextElement())
    {
        $arP = $oe->GetProperty('CML2_TRAITS');
   
     
}


       
}

А то, что нужно – уже передано в обработчик в $arFields['PROPERTY_VALUES'][92]
Зачем так неэкономно разбрасываться запросами к базе? Одна строка – и 20 000 лишних обращений к базе при импорте 20 000 элементов.

О событиях, которые возникают при импорте каталога из 1С в Битрикс

В контексте интеграции 1С Предприятия и Битрикс не утихают споры по поводу того, какие события при этом отрабатывают, а какие не отрабатывают. А спорить, в общем-то, не о чем – благо, Битрикс поставляется нам в исходных кодах.
Класс импорта CIBlockCMLImport, который я так люблю наследовать, описан в файле 
\bitrix\modules\iblock\classes\general\cml2
Открыв этот файл, мы ясно видим, что элементы инфоблока товаров, как и элементы инфоблока предложений добавляются функцией CIBlockElement::Add
, а изменяются функцией CIBlockElement::Update

Исходные коды этих функций мы можем посмотреть в файлах (в одном описан класс CAllIBlockElement в другом – его наследник CIBlockElement)
 \bitrix\modules\iblock\classes\general\iblockelement.php
\bitrix\modules\iblock\classes\mysql\iblockelement.php

Что же мы видим там?
В функции Add имеется вот такая конструкция – перед самым сбросом управляемого кеша:

$events = GetModuleEvents("iblock", "OnAfterIBlockElementAdd");
while ($arEvent = $events->Fetch())
ExecuteModuleEventEx($arEvent, array(&$arFields));

То есть вызывается и отрабатывает событие OnAfterIBlockElementAdd Причем каждый из описанных обработчиков этого события. Как мы можем видеть, вызов этого события происходит всегда – безусловно, то есть каждый раз при отработке функции CIBlockElement::Add и не важно, вызвана ли она в классе импорта или где-то еще.

Кроме этого в этой же функции вызывается другое событие

if(!isset($arFields["WF_PARENT_ELEMENT_ID"]) && $arIBlock["FIELDS"]["LOG_ELEMENT_ADD"]["IS_REQUIRED"] == "Y")
{
    $USER_ID = is_object($USER)? intval($USER->GetID()) : 0;
    $db_events = GetModuleEvents("main", "OnBeforeEventLog");
    $arEvent = $db_events->Fetch();

}
Но не каждый раз, а по условию. Это не что иное, как событие при записи в журнал событий. И оно выполняется, если в настройках инфоблока указано журналировать добавление элемента.

А вот вызова события OnBeforeIBlockElementAdd я в функции Add не нашла (оно там есть, но вызвано не напрямую). Нет его и в классе импорта CIBlockCMLImport 

Посмотрим теперь, что у нас имеется в функции  CIBlockElement::Update

$events = GetModuleEvents("iblock", "OnAfterIBlockElementUpdate");
while ($arEvent = $events->Fetch())
ExecuteModuleEventEx($arEvent, array(&$arFields));

Вызов события OnAfterIBlockElementUpdate – имеется
Вызова события OnBeforeIBlockElementUpdate – не имеется

Зато вызовы событий OnBeforeIBlockElementUpdate и OnBeforeIBlockElementAdd присутствует в методе класса CAllIBlockElement CheckFields, который в свою очередь вызывается и в Add, и в Update Но функция CheckFields вызывается в них уже по условию.

Из вышесказанного я делаю следующие выводы: безусловно, при импорте каталога из 1С Предприятия в Битрикс отрабатывают обработчики событий OnAfterIBlockElementAdd и OnAfterIBlockElementUpdate, они отрабатывают уже после вставки/обновления элемента и, в принципе, могут быть использованы для модификации данных в инфоблоке, но ценой дополнительных запросов к базе. А дополнительные запросы к базе при импорте – это порой убийство импорта.

События OnBeforeIBlockElementUpdate и OnBeforeIBlockElementAdd так же могут отрабатывать при импорте при определенных условиях, однако уже по тому, каким образом они вставлены в исходный код битрикс, видно, что они задуманы не для модификации данных перед вставкой/обновлением, а для какой-то необычной кастомной проверки, возможно, какого-то поля необычного формата.

Поэтому, как я уже писала в одной из предыдущих статей, мне однозначно не нравится кастомизация импорта посредством использования этих событий – я для этих целей предпочитаю кастомизировать компонент импорта и наследовать класс CIBlockCMLImport, добавив наследнику требуемый функционал. Еще лучшим решением было бы конечно, написание, композитного класса, но это очень трудоемко. (Кстати, как видно из исходных кодов, разработчики Битрикс не гнушаются использовать наследование, значит и нам – сам Бог велел).

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

О событии
foreach(GetModuleEvents("catalog", "OnSuccessCatalogImport1C", true) as $arEvent)
ExecuteModuleEventEx($arEvent);
которое Битриксоиды вставили прямо на последний шаг работы своего стандартного компонента импорта из 1С, наверное, знают все. Но если вы используете его для кастомизации импорта … это плохо.

Единственное, что можно безболезненно повесить на это событие – это, к примеру, отправку письма администратору сайта о том, что импорт успешно завершен или что-то иное в этом роде. Но, к примеру, обходить в обработчике этого события весь многотысячный каталог и что-то там перераспределять – пересчитывать….  я думаю, все уже поняли, каково мое мнение на этот счет, так что я воздержусь от нелитературных выражений.

Я думаю, к интеграции 1С Предприятия и Битрикс нельзя подходить по принципу «если работает, то ничего не нужно менять». Нужно оптимизировать, оптимизировать и еще раз оптимизировать.