При использовании торговых предложений в Битриксе при разработке каталога возникает логичный вопрос: как отсортировать/отфильтровать товары с торговыми предложениями по наличии и по цене? Из-за того, что для таких товаров цена и наличие записывается не у товаров, а у предложений, скрытие из листингов товаров с нулевым остатком приводит к исчезновению из листинга товаров с торговыми предложениями.

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

Наличие на складе

Для сортировки и фильтрации по наличию используем стандартное поле модуля catalog QUANTITY(CATALOG_QUANTITY), при изменении наличия торговых предложений находим остальные предложения у товара и обновляем его доступное количество:

AddEventHandler("catalog", "OnProductAdd","UpdateProductQuantity");
AddEventHandler("catalog", "OnProductUpdate","UpdateProductQuantity");

function UpdateProductQuantity($id, $arFields)
{   
    $quantity = $arFields['QUANTITY'];

    if (CModule::IncludeModule('catalog') && CModule::IncludeModule('iblock')) {
        $arProductInfo = CCatalogSKU::GetProductInfo($id);
        if (is_array($arProductInfo)) {
            $arOffersInfo = CCatalogSKU::GetInfoByProductIBlock($arProductInfo['IBLOCK_ID']);
            $arFilter = array(
                'IBLOCK_ID' => OFFERS_IBLOCK_ID,
                "PROPERTY_CML2_LINK" => $arProductInfo['ID'],
                "!ID" => $id,
            );

            $obOffersList = CIBlockElement::GetList(array("SORT"=>"ASC"), $arFilter, false, false, array("CATALOG_QUANTITY"));
            while ($arOffers = $obOffersList->Fetch()) {
                $quantity += $arOffers["CATALOG_QUANTITY"];
            }

            $arFieldsProduct = array(
                "QUANTITY" => $quantity,
            ); 
            CCatalogProduct::Update($arProductInfo['ID'], $arFieldsProduct);
        }
    }
}

После этого мы может товар с торговыми предложениями наравне с обычными товарами сортировать или фильтровать по полям CATALOG_QUANTITY и CATALOG_AVAILABLE, а также использовать параметр HIDE_NOT_AVAILABLE компонента bitrix:catalog или bitrix:catalog.section:

Скрыть отсутствующие товары:

"HIDE_NOT_AVAILABLE" => "N",

Отсортировать по популярности, но показать сначала товары в наличии, затем отсутствующие:

"ELEMENT_SORT_FIELD" => "CATALOG_AVAILABLE",
"ELEMENT_SORT_ORDER" => "DESC",
"ELEMENT_SORT_FIELD2" => "SHOWS",
"ELEMENT_SORT_ORDER2" => "DESC",

Отсортировать по наличию:

"ELEMENT_SORT_FIELD" => "CATALOG_AVAILABLE",
"ELEMENT_SORT_ORDER" => "DESC",

Цена

Для сортировки и фильтрации по цене товаров используем свойство MINIMUM_PRICE, в нём уже из коробки записывается минимальная цена среди торговых предложений. Если у всех торговых предложений одинаковая цена, можно воспользоваться облегчённым вариантом, который при изменении цены товара/торгового предложения обновляет свойство PRICE у товара(свойство нужно создать самому):

AddEventHandler("catalog", "OnPriceAdd", "UpdateProductPropertyPrice");
AddEventHandler("catalog", "OnPriceUpdate", "UpdateProductPropertyPrice");

function UpdateProductPropertyPrice($id, $arFields)
{
    if (CModule::IncludeModule("iblock") && CModule::IncludeModule("catalog")) {
        $elementId = $arFields["PRODUCT_ID"];
        $resElement = CIBlockElement::GetByID($elementId);
        if($arElement = $resElement->GetNext()) {
            if ($arElement["IBLOCK_ID"] == OFFERS_IBLOCK_ID) {
                $resOffer = CIBlockElement::GetProperty(OFFERS_IBLOCK_ID, $elementId, "sort", "asc", array("CODE" => "CML2_LINK"));
                if ($arOffer = $resOffer->GetNext()) {
                    $elementId = $arOffer["VALUE"];
                }
            }
        }

        CIBlockElement::SetPropertyValuesEx($elementId, CATALOG_IBLOCK_ID, array("PRICE" => $arFields["PRICE"]));
    }
}

Для сортировки по цене, например, по убыванию указываем параметры bitrix:catalog или bitrix:catalog.section:

"ELEMENT_SORT_FIELD" => "PROPERTY_PRICE",
"ELEMENT_SORT_ORDER" => "DESC",

В кусках кода используются константы CATALOG_IBLOCK_ID и OFFERS_IBLOCK_ID, их нужно объявить или заменить на ID соответственно инфоблока товаров и товарных предложений.


Я не претендую на уникальность или безупречность приведенных решений, код представлен как есть.