Разработка фильтрации по наличию на складе 1С-Битрикс
Фильтр «Только в наличии» — один из наиболее часто запрашиваемых после ценового диапазона. На первый взгляд просто: добавить чекбокс и условие в фильтр. Сложность появляется когда нужно учитывать несколько складов, торговые предложения (SKU), зарезервированные остатки и реальные данные из 1С с задержкой синхронизации.
Модель хранения остатков в 1С-Битрикс
Остатки хранятся в таблице b_catalog_store_product (многоскладская модель) и b_catalog_product (поле QUANTITY). Модуль каталога предоставляет класс CCatalogStoreProduct для работы с многоскладскими остатками.
Для торговых предложений — остатки хранятся у предложений, не у родительского товара. Фильтр «в наличии» должен находить товары, у которых хоть одно предложение имеет ненулевой остаток.
Базовый фильтр по наличию
// Простой случай: одиночные товары без торговых предложений
if (!empty($_GET['in_stock'])) {
$arFilter['>CATALOG_QUANTITY'] = 0;
$arFilter['CATALOG_AVAILABLE'] = 'Y';
}
CATALOG_AVAILABLE = 'Y' — флаг доступности, который учитывает не только количество, но и настройки доступности товара (можно купить при нулевом остатке или нет).
Фильтрация с учётом торговых предложений
function getInStockProductIds(int $catalogIblockId, int $offersIblockId): array
{
// Товары с прямыми остатками
$directIds = [];
$res = CIBlockElement::GetList(
[],
[
'IBLOCK_ID' => $catalogIblockId,
'ACTIVE' => 'Y',
'>CATALOG_QUANTITY' => 0,
'CATALOG_AVAILABLE' => 'Y',
],
false,
false,
['ID']
);
while ($row = $res->GetNext()) {
$directIds[] = $row['ID'];
}
// Товары через предложения с остатком
$offerParentIds = [];
$res = CIBlockElement::GetList(
[],
[
'IBLOCK_ID' => $offersIblockId,
'ACTIVE' => 'Y',
'>CATALOG_QUANTITY' => 0,
'CATALOG_AVAILABLE' => 'Y',
],
false,
false,
['PROPERTY_CML2_LINK']
);
while ($row = $res->GetNext()) {
if ($row['PROPERTY_CML2_LINK_VALUE']) {
$offerParentIds[] = intval($row['PROPERTY_CML2_LINK_VALUE']);
}
}
return array_unique(array_merge($directIds, $offerParentIds));
}
// Применение в фильтре каталога
if (!empty($_GET['in_stock'])) {
$inStockIds = getInStockProductIds(CATALOG_IBLOCK_ID, OFFERS_IBLOCK_ID);
$arFilter['ID'] = !empty($inStockIds) ? $inStockIds : [0];
}
Многоскладской учёт
При нескольких складах — фильтрация по конкретному складу или по суммарному остатку:
function getProductIdsByWarehouse(int $warehouseId, int $minQty = 1): array
{
$connection = \Bitrix\Main\Application::getConnection();
$sql = "
SELECT DISTINCT sp.PRODUCT_ID
FROM b_catalog_store_product sp
INNER JOIN b_iblock_element ie ON ie.ID = sp.PRODUCT_ID
WHERE sp.STORE_ID = " . intval($warehouseId) . "
AND sp.AMOUNT >= " . intval($minQty) . "
AND ie.ACTIVE = 'Y'
";
$res = $connection->query($sql);
$ids = [];
while ($row = $res->fetch()) {
$ids[] = $row['PRODUCT_ID'];
}
return $ids;
}
// Фильтр по конкретному складу
if (!empty($_GET['warehouse_id'])) {
$warehouseId = intval($_GET['warehouse_id']);
$ids = getProductIdsByWarehouse($warehouseId);
$arFilter['ID'] = !empty($ids) ? $ids : [0];
}
UI фильтра с количеством на складе
// Получение складов для UI
$warehouses = [];
$res = CCatalogStore::GetList(['SORT' => 'ASC'], ['ACTIVE' => 'Y'], false, false, ['ID', 'TITLE', 'ADDRESS']);
while ($wh = $res->Fetch()) {
$warehouses[$wh['ID']] = $wh;
}
// Подсчёт для каждого склада
foreach ($warehouses as $whId => $wh) {
$count = count(getProductIdsByWarehouse($whId));
$warehouses[$whId]['COUNT'] = $count;
}
?>
<div class="filter-block filter-block--warehouse">
<label class="filter-toggle">
<input type="checkbox" name="in_stock" value="1"
<?= !empty($_GET['in_stock']) ? 'checked' : '' ?>>
<span>Только в наличии</span>
</label>
<?php if (count($warehouses) > 1): ?>
<div class="warehouse-filter">
<span class="filter-label">Склад:</span>
<?php foreach ($warehouses as $whId => $wh): ?>
<label>
<input type="radio" name="warehouse_id" value="<?= $whId ?>"
<?= ($_GET['warehouse_id'] ?? '') == $whId ? 'checked' : '' ?>>
<?= htmlspecialchars($wh['TITLE']) ?> (<?= $wh['COUNT'] ?>)
</label>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
Кеширование для производительности
Запрос всех товаров в наличии при каждом обращении к каталогу — дорогостоящая операция на больших каталогах. Кешируем список ID:
$cacheKey = 'in_stock_ids_' . CATALOG_IBLOCK_ID;
$cacheTime = 300; // 5 минут
$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($cache->initCache($cacheTime, $cacheKey, '/catalog/filter/')) {
$inStockIds = $cache->getVars();
} else {
$inStockIds = getInStockProductIds(CATALOG_IBLOCK_ID, OFFERS_IBLOCK_ID);
$cache->startDataCache();
$cache->endDataCache($inStockIds);
}
Кеш инвалидируется при изменении остатков через обработчик события OnCatalogStoreDocumentUpdate.
Сроки выполнения
Базовый фильтр «в наличии» без торговых предложений — 3–5 часов. Полная реализация с торговыми предложениями, многоскладским учётом, кешированием и UI выбора склада — 2–3 рабочих дня.







