+7 8512 41-41-61

Сортировка товаров по цене с учетом валюты в 1С-Битрикс

Наверное, каждый часто сталкивался с проблемой, что при заполнении цен у товаров в разных валютах сортировка по цене работает неправильно. В компонентах каталога сортировка указывается через параметр catalog_PRICE_* с указанием ID типа цены. В таком случае, как сообщает тех. поддержка Битрикса, сортировка идёт по абсолютному значению без учёта валюты. Т.к. 100 рублей будет равняться 100 долларам.

Для того, чтобы решить эту проблему, потребуется провести некоторые доработки на сайте. В первую очередь, необходимо создать 2 свойства у товара - MINIMUM_PRICE и MAXIMUM_PRICE с типом Число. Символьные коды этих свойств могут быть и другие, но в данном примере мы будем использовать эти.

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

AddEventHandler("iblock", "OnAfterIBlockElementUpdate", "BXIBlockAfterSave");
AddEventHandler("iblock", "OnAfterIBlockElementAdd", "BXIBlockAfterSave");
AddEventHandler("catalog", "OnPriceAdd", "BXIBlockAfterSave");
AddEventHandler("catalog", "OnPriceUpdate", "BXIBlockAfterSave");

function BXIBlockAfterSave($arg1, $arg2 = false) {
    $ELEMENT_ID = false;
    $IBLOCK_ID = false;
    $OFFERS_IBLOCK_ID = false;
    $OFFERS_PROPERTY_ID = false;
    CModule::IncludeModule('sale');
    CModule::IncludeModule('catalog');
    if (!class_exists('CCurrency')) return true;
    $CURRENCY = CCurrency::GetBaseCurrency();

    if(is_array($arg2) && $arg2["PRODUCT_ID"] > 0)
    {
        $rsPriceElement = CIBlockElement::GetList(
            array(),
            array(
                "ID" => $arg2["PRODUCT_ID"],
            ),
            false,
            false,
            array("ID", "IBLOCK_ID")
        );
        if($arPriceElement = $rsPriceElement->Fetch())
        {
            $arCatalog = CCatalog::GetByID($arPriceElement["IBLOCK_ID"]);
            if(is_array($arCatalog))
            {
                if($arCatalog["OFFERS"] == "Y")
                {
                    $rsElement = CIBlockElement::GetProperty(
                        $arPriceElement["IBLOCK_ID"],
                        $arPriceElement["ID"],
                        "sort",
                        "asc",
                        array("ID" => $arCatalog["SKU_PROPERTY_ID"])
                    );
                    $arElement = $rsElement->Fetch();
                    if($arElement && $arElement["VALUE"] > 0)
                    {
                        $ELEMENT_ID = $arElement["VALUE"];
                        $IBLOCK_ID = $arCatalog["PRODUCT_IBLOCK_ID"];
                        $OFFERS_IBLOCK_ID = $arCatalog["IBLOCK_ID"];
                        $OFFERS_PROPERTY_ID = $arCatalog["SKU_PROPERTY_ID"];
                    }
                }
                elseif($arCatalog["OFFERS_IBLOCK_ID"] > 0)
                {
                    $ELEMENT_ID = $arPriceElement["ID"];
                    $IBLOCK_ID = $arPriceElement["IBLOCK_ID"];
                    $OFFERS_IBLOCK_ID = $arCatalog["OFFERS_IBLOCK_ID"];
                    $OFFERS_PROPERTY_ID = $arCatalog["OFFERS_PROPERTY_ID"];
                }
                else
                {
                    $ELEMENT_ID = $arPriceElement["ID"];
                    $IBLOCK_ID = $arPriceElement["IBLOCK_ID"];
                    $OFFERS_IBLOCK_ID = false;
                    $OFFERS_PROPERTY_ID = false;
                }
            }
        }
    }    
    elseif(is_array($arg1) && $arg1["ID"] > 0 && $arg1["IBLOCK_ID"] > 0)
    {
        $arOffers = CIBlockPriceTools::GetOffersIBlock($arg1["IBLOCK_ID"]);
        if(is_array($arOffers))
        {
            $ELEMENT_ID = $arg1["ID"];
            $IBLOCK_ID = $arg1["IBLOCK_ID"];
            $OFFERS_IBLOCK_ID = $arOffers["OFFERS_IBLOCK_ID"];
            $OFFERS_PROPERTY_ID = $arOffers["OFFERS_PROPERTY_ID"];
        }
    }

    if($ELEMENT_ID)
    {
        static $arPropCache = array();
        if(!array_key_exists($IBLOCK_ID, $arPropCache))
        {
            $rsProperty = CIBlockProperty::GetByID("MINIMUM_PRICE", $IBLOCK_ID);
            $arProperty = $rsProperty->Fetch();
            if($arProperty)
                $arPropCache[$IBLOCK_ID] = $arProperty["ID"];
            else
                $arPropCache[$IBLOCK_ID] = false;
        }

        if($arPropCache[$IBLOCK_ID])
        {
            $arProductID = array($ELEMENT_ID);
            if($OFFERS_IBLOCK_ID)
            {
                $rsOffers = CIBlockElement::GetList(
                    array(),
                    array(
                        "IBLOCK_ID" => $OFFERS_IBLOCK_ID,
                        "PROPERTY_".$OFFERS_PROPERTY_ID => $ELEMENT_ID,
                    ),
                    false,
                    false,
                    array("ID")
                );
                while($arOffer = $rsOffers->Fetch())
                    $arProductID[] = $arOffer["ID"];
            }

            $minPrice = false;
            $maxPrice = false;

            $rsPrices = CPrice::GetList(
                array(),
                array(
                    "BASE" => "Y",
                    "PRODUCT_ID" => $arProductID,
                )
            );
            while($arPrice = $rsPrices->Fetch())
            {
                if (!intval($arPrice["PRICE"])) continue;

                $PRICE = CCurrencyRates::ConvertCurrency($arPrice["PRICE"], $arPrice['CURRENCY'], $CURRENCY);

                if($minPrice === false || $minPrice > $PRICE)
                    $minPrice = $PRICE;

                if($maxPrice === false || $maxPrice < $PRICE)
                    $maxPrice = $PRICE;
            }

            if($minPrice !== false)
            {
                CIBlockElement::SetPropertyValuesEx(
                    $ELEMENT_ID,
                    $IBLOCK_ID,
                    array(
                        "MINIMUM_PRICE" => $minPrice,
                        "MAXIMUM_PRICE" => $maxPrice,
                    )
                );
            }
        }
    }
}

Обработчик необходимо разместить в файле init.php в папке /bitrix/php_interface/ . Стоит отметить, что данный обработчик также учитывает торговые предложения у товара. Соответственно, свойства MINIMUM_PRICE и MAXIMUM_PRICE также можно будет использовать и для фильтрации (в Битриксе есть проблема с фильтрацией товаров в разной валюте, но об этом в другой статье).

Часть сделана - при обновлении или добавлении товара у него будет заполняться свойство с ценой в базовой валюте. Но остаётся еще 2 проблемы:

  • Что делать с товарами, которые уже есть на сайте?
  • Как поддерживать актуальность цен в свойствах? Ведь курсы валют могут меняться.

Вариант решения - это периодический пересчёт цен. Его можно реализовать через так называемый CRON, или же через агент. В данном случае мы рассмотрим реализацию через агент. Код такого агента приведён ниже.

function SetProductsPriceProperty($ID = 0)
{
    CModule::IncludeModule('iblock');
    CModule::IncludeModule('sale');
    CModule::IncludeModule('catalog');
    $maxStepSize = 3000;

    $res = CCatalogProduct::GetList(
        array("ID" => "ASC"),
        array(">ID" => (int)$ID),
        false,
        array("nTopCount" => $maxStepSize),
        array("ID","ELEMENT_IBLOCK_ID")
    );
    while ($r = $res->Fetch())
    {
        $minPrice = false;
        $maxPrice = false;

        $arPrice = CCatalogProduct::GetOptimalPrice($r["ID"], 1, array(2));
        $minPrice = $maxPrice = round($arPrice["DISCOUNT_PRICE"]);

        CIBlockElement::SetPropertyValuesEx(
            $r["ID"],
            $r["ELEMENT_IBLOCK_ID"],
            array(
                "MINIMUM_PRICE" => $minPrice,
                "MAXIMUM_PRICE" => $maxPrice,
            )
        );
        $ID = $r["ID"];
    }
    $res = CCatalogProduct::GetList(
        array("ID" => "ASC"),
        array(">ID" => (int)$ID),
        false,
        array("nTopCount" => 1),
        array("ID","ELEMENT_IBLOCK_ID")
    );
    if(!($r = $res->Fetch())) $ID = 0;

    return "SetProductsPriceProperty(".$ID.");";
}

В зависимости от производительности сервера, а также от периодичности смены курсов валют на вашем сайте, можно поменять значение переменной $maxStepSize. В примере за один шаг обрабатывается 3000 товаров. Также в примере используется метод GetOptimalPrice, который учитывает скидку на товары (для незарегистрированных пользователей). Если на вашем сайте скидки не используются, то рекомендую использовать метод получения цены из первого примера, т.к. работать он будет намного быстрее.

Но, конечно, лучший вариант - это использовать данный код через CRON, т.к. работа агентов может влиять на скорость загрузки сайта у клиента (если он попадёт на момент его срабатывания).

После того, как мы разместили нужный код на сайте, остаётся самое простое - настроить компоненты каталога на сортировку по свойству MINIMUM_PRICE.

25 Марта 2015
|
Турченко Иван
|
Просмотров: 16205
Комментарии (1)
Гость, 20.07.2016 14:31
Если инфоблок всетаки с торговыми предложениями, придется получить инфоблок SKU<br />
$intIBlockID = $r["ELEMENT_IBLOCK_ID"];<br />
        $mxResult = CCatalogSKU::GetInfoByProductIBlock(<br />
        $intIBlockID<br />
        ); <br />
<br />
А потом уже получать цены (как-то еще присобачить в цикл придется :)<br />
<br />
$diffPr=array();<br />
$rsOffers = CIBlockElement::GetList(<br />
    array("PRICE"=>"ASC"),<br />
    array('IBLOCK_ID' => $mxResult['IBLOCK_ID'], 'PROPERTY_'.$mxResult['SKU_PROPERTY_ID'] => $r["ID"]),<br />
    false,<br />
    false,<br />
    Array('ID')<br />
    );<br />
while ($arOffer = $rsOffers->GetNext())<br />
{<br />
$wiint++;<br />
$ar_price = GetCatalogProductPrice($arOffer["ID"], 1);<br />
<br />
$diffPr[]=$ar_price["PRICE"];<br />
    $diffPr=array_unique($diffPr);<br />
    sort($diffPr);<br />
echo "<pre>"; <br />
print_r ($diffPr);<br />
echo "</pre>";<br />
$dataar[]=array(<br />
            'minPr'=>$diffPr[0],<br />
            'maxPr'=>end($diffPr)<br />
            );<br />
}