Решение: Скрытие товаров без установленной цены для групп пользователей в Bitrix

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

Проблема: Для конкретного покупателя значительная часть товаров отображается с пометкой «Нет в наличии». Причина - отсутствие установленной цены в том типе цен, который соответствует группе данного пользователя.

Цель: Необходимо настроить систему таким образом, чтобы для пользователя автоматически скрывались товары, для которых не определена цена в типе цены его группы. Стандартные механизмы платформы Bitrix с этой задачей не справляются.

Техническое решение

Для реализации требуется добавить кастомный обработчик событий в файл /local/php_interface/init.php. Предложенный код выполняет следующую последовательность действий:

  1. Отключает стандартные фильтры каталога, которые мешают корректной работе.
  2. Определяет группу текущего пользователя и связанные с ней доступные типы цен.
  3. Фильтрует список товаров, оставляя только те, для которых существует активная цена в разрешённых для пользователя типах цен.
  4. Корректирует навигацию для отображения актуального количества найденных товаров.

Код решения:

// /local/php_interface/init.php
use Bitrix\Main\EventManager;
use Bitrix\Main\Loader;
use Bitrix\Main\Application;

$eventManager = EventManager::getInstance();

// 1. Отключаем стандартные фильтры по наличию и ценам
$eventManager->addEventHandler('iblock', 'OnBeforeIBlockElementGetList', function(&$arFilter) {
    $keysToRemove = [
        'CATALOG_AVAILABLE', 'AVAILABLE', '=CATALOG_AVAILABLE',
        'CATALOG_PRICE_', '>CATALOG_PRICE_', '!=CATALOG_PRICE_'
    ];
    
    foreach ($keysToRemove as $key) {
        foreach (array_keys($arFilter) as $filterKey) {
            if (strpos($filterKey, $key) === 0) {
                unset($arFilter[$filterKey]);
            }
        }
    }
});

// 2. Отключаем стандартную проверку доступности товаров
$eventManager->addEventHandler('catalog', 'OnGetAvailableItems', function() {
    return false;
});

// 3. Основная логика фильтрации после получения списка товаров
$eventManager->addEventHandler('iblock', 'OnAfterIBlockElementGetList', function(&$arResult) {
    if (empty($arResult) || !Loader::includeModule('catalog') || !Loader::includeModule('sale')) {
        return;
    }
    
    global $USER;
    $userId = $USER->GetID();
    $userGroups = $USER->GetUserGroupArray();
    
    // Определяем типы цен, доступные группам пользователя
    $allowedPriceTypes = [];
    $priceTypeRes = \Bitrix\Catalog\GroupTable::getList([
        'select' => ['ID', 'NAME'],
        'filter' => ['=GROUP_ACCESS.GROUP_ID' => $userGroups]
    ]);
    
    while ($priceType = $priceTypeRes->fetch()) {
        $allowedPriceTypes[] = (int)$priceType['ID'];
    }
    
    // Если привязок нет - разрешаем все типы цен
    if (empty($allowedPriceTypes)) {
        $priceTypeRes = \Bitrix\Catalog\GroupTable::getList(['select' => ['ID']]);
        while ($priceType = $priceTypeRes->fetch()) {
            $allowedPriceTypes[] = (int)$priceType['ID'];
        }
    }
    
    if (empty($allowedPriceTypes)) {
        return;
    }
    
    // Собираем ID товаров из результата
    $elementIds = [];
    foreach ($arResult as $item) {
        if (isset($item['ID'])) {
            $elementIds[] = (int)$item['ID'];
        }
    }
    
    if (empty($elementIds)) {
        return;
    }
    
    // Ищем товары с ценами в разрешённых типах
    $connection = Application::getConnection();
    $sql = "
        SELECT DISTINCT cp.PRODUCT_ID 
        FROM b_catalog_price cp
        WHERE cp.PRODUCT_ID IN (" . implode(',', $elementIds) . ")
        AND cp.CATALOG_GROUP_ID IN (" . implode(',', $allowedPriceTypes) . ")
        AND cp.PRICE > 0
        AND (cp.QUANTITY_FROM IS NULL OR cp.QUANTITY_FROM = 0)
        AND (cp.QUANTITY_TO IS NULL OR cp.QUANTITY_TO = 0)
    ";
    
    $result = $connection->query($sql);
    $productsWithPrices = [];
    
    while ($row = $result->fetch()) {
        $productsWithPrices[(int)$row['PRODUCT_ID']] = true;
    }
    
    // Фильтруем итоговый массив
    $filteredResult = [];
    foreach ($arResult as $item) {
        if (isset($item['ID']) && isset($productsWithPrices[(int)$item['ID']])) {
            $filteredResult[] = $item;
        }
    }
    
    $arResult = $filteredResult;
    
    // Корректируем данные навигации
    if (isset($GLOBALS['NAV_RESULT']) && is_object($GLOBALS['NAV_RESULT'])) {
        $GLOBALS['NAV_RESULT']->NavRecordCount = count($filteredResult);
        $GLOBALS['NAV_RESULT']->SelectedRows = count($filteredResult);
    }
});

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