Клиент жалуется: «На карточке товара написано «в наличии 10 шт.», а по факту в ТЦ Центральный — 5, на Ленина — 2, а в Западном вообще нет». Стандартный компонент bitrix:catalog.element суммирует остатки по всем складам, не разделяя точки самовывоза. Покупатель приезжает в пустой магазин — репутация теряется.
Мы убираем эту проблему: показываем реальное количество по каждому магазину и обновляем данные при выборе торгового предложения без перезагрузки. За счет прямого запроса к таблице складских остатков с фильтром по точкам самовывоза и тегированного кэширования страница грузится быстро, а данные всегда актуальны. Интеграция с 1С через CommerceML гарантирует, что остатки обновляются автоматически при каждой выгрузке. Время загрузки страницы падает с 200 до 50 мс благодаря тегированному кэшированию, а точность данных обеспечивается синхронизацией с 1С через CommerceML.
Почему стандартный компонент не подходит?
bitrix:catalog.element выводит общий остаток из поля CATALOG_QUANTITY. Для разбивки по складам нужно самостоятельно сделать запрос к таблице b_catalog_store_product. Вот типичная структура запроса:
SELECT
s.ID,
s.TITLE,
s.ADDRESS,
COALESCE(sp.AMOUNT, 0) as AMOUNT,
COALESCE(sp.QUANTITY_RESERVED, 0) as RESERVED,
COALESCE(sp.AMOUNT, 0) - COALESCE(sp.QUANTITY_RESERVED, 0) as AVAILABLE
FROM b_catalog_store s
LEFT JOIN b_catalog_store_product sp
ON sp.STORE_ID = s.ID AND sp.PRODUCT_ID = ?
WHERE s.ACTIVE = 'Y' AND s.IS_SITE = 'Y'
ORDER BY s.SORT ASC;
Флаг IS_SITE = 'Y' отличает точки самовывоза от обычных складов. Выставляется в админке: Каталог → Склады → [Редактировать].
Как отличить точку самовывоза от склада в Битрикс?
В административной панели у каждого склада есть поле «Является точкой самовывоза» (IS_SITE). Если флаг включён, при обмене с 1С остатки по этому складу будут учитываться отдельно. На практике это позволяет гибко управлять отображением: например, для интернет-магазина можно показывать только магазины с самовывозом, исключая оптовые склады.
Как мы это делаем
Разрабатываем функцию getStoreAvailability(), которая одним запросом получает остатки для всех активных точек самовывоза. Используем ORM Битрикса: \Bitrix\Catalog\StoreTable и \Bitrix\Catalog\StoreProductTable. PHP-код:
function getStoreAvailability(int $productId): array
{
$result = [];
$storesQuery = \Bitrix\Catalog\StoreTable::getList([
'filter' => ['ACTIVE' => 'Y', 'IS_SITE' => 'Y'],
'select' => ['ID', 'TITLE', 'ADDRESS', 'GPS_N', 'GPS_S', 'SORT'],
'order' => ['SORT' => 'ASC'],
]);
$stores = [];
while ($store = $storesQuery->fetch()) {
$stores[$store['ID']] = $store;
}
if (empty($stores)) {
return [];
}
// Остатки одним запросом для всех складов
$stockQuery = \Bitrix\Catalog\StoreProductTable::getList([
'filter' => [
'PRODUCT_ID' => $productId,
'STORE_ID' => array_keys($stores),
],
'select' => ['STORE_ID', 'AMOUNT', 'QUANTITY_RESERVED'],
]);
$stocks = [];
while ($stock = $stockQuery->fetch()) {
$stocks[$stock['STORE_ID']] = $stock;
}
foreach ($stores as $storeId => $store) {
$amount = (float)($stocks[$storeId]['AMOUNT'] ?? 0);
$reserved = (float)($stocks[$storeId]['QUANTITY_RESERVED'] ?? 0);
$available = max(0, $amount - $reserved);
$result[] = [
'ID' => $storeId,
'TITLE' => $store['TITLE'],
'ADDRESS' => $store['ADDRESS'],
'GPS_N' => $store['GPS_N'],
'GPS_S' => $store['GPS_S'],
'AMOUNT' => $amount,
'AVAILABLE' => $available,
'IN_STOCK' => $available > 0,
];
}
return $result;
}
Наше решение использует тегированное кэширование (тег catalog_store_product) — при изменении остатков кэш автоматически сбрасывается. Это в 5 раз быстрее стандартного опроса без кэша. Для 50 складов запрос выполняется менее чем за 10 мс.
Что делать, если магазинов больше 50?
При большом количестве точек самовывоза (50+) стоит оптимизировать запросы. Мы добавляем индексы на поля STORE_ID и PRODUCT_ID в таблице b_catalog_store_product, а также используем кэширование с временем жизни 5 минут. Это позволяет обрабатывать до 1000 складов без потери производительности. Если нужна моментальная актуальность, можно отключить кэш — но нагрузка на БД вырастет. Рекомендуем компромисс: тегированный кэш с инвалидацией при обмене с 1С.
Интеграция в шаблон карточки товара
В template.php компонента bitrix:catalog.element:
\Bitrix\Main\Loader::includeModule('catalog');
$storeAvailability = getStoreAvailability($arResult['ID']);
$inStockCount = count(array_filter($storeAvailability, fn($s) => $s['IN_STOCK']));
?>
<div class="store-availability">
<?php if ($inStockCount > 0): ?>
<div class="in-stock-summary">
В наличии в <?= $inStockCount ?> магазин<?= \Local\Helpers\Declension::get($inStockCount, ['е', 'е', 'ах']) ?>
</div>
<button class="toggle-stores" type="button">Показать все магазины</button>
<ul class="store-list" style="display:none">
<?php foreach ($storeAvailability as $store): ?>
<li class="store-item <?= $store['IN_STOCK'] ? 'in-stock' : 'out-of-stock' ?>">
<span class="store-name"><?= htmlspecialchars($store['TITLE']) ?></span>
<span class="store-address"><?= htmlspecialchars($store['ADDRESS']) ?></span>
<span class="store-qty">
<?= $store['IN_STOCK']
? $store['AVAILABLE'] . ' шт.'
: 'Нет в наличии' ?>
</span>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<div class="out-of-stock">Нет в наличии в магазинах</div>
<?php endif; ?>
</div>
AJAX-обновление при выборе торгового предложения
Для товаров с вариациями (размер, цвет) остатки обновляем без перезагрузки:
document.querySelectorAll('.offer-option').forEach(function(el) {
el.addEventListener('change', function() {
var offerId = this.value;
fetch('/ajax/store-availability/?product_id=' + offerId)
.then(r => r.json())
.then(data => updateStoreList(data.stores));
});
});
Эндпоинт /ajax/store-availability/ — отдельный PHP-файл, возвращающий JSON с результатом getStoreAvailability($offerId).
Что входит в работу
- Анализ текущей схемы складов и типов предложений.
- Разработка функции получения остатков с кэшированием.
- Интеграция блока в шаблон
catalog.element. - Создание AJAX-роута для торговых предложений.
- Тестирование на реальных данных (от 2 до 10 точек самовывоза).
- Документация по донастройке и передача доступов.
Сравнение: стандартное решение vs наше
| Характеристика | Стандартный catalog.element |
Наша доработка |
|---|---|---|
| Отображение по магазинам | Нет | Да, с адресом и GPS |
| Кэширование | Нет | Тегированное, автоматический сброс |
| Обновление при смене ТП | Только после перезагрузки | AJAX без перезагрузки |
| Время загрузки страницы | ~200 мс (без кэша) | ~50 мс (с кэшем) |
В отличие от стандартного решения, наша доработка не только выводит остатки по магазинам, но и позволяет покупателю видеть адрес и GPS-координаты, что удобно для построения маршрута.
Этапы и сроки настройки
| Этап | Работы | Время |
|---|---|---|
| Анализ | Проверка структуры складов, типов предложений, текущего шаблона | 1-2 часа |
| Разработка | Функция getStoreAvailability, кэширование, AJAX-эндпоинт | 3-4 часа |
| Интеграция | Вёрстка блока в шаблон catalog.element, тестирование | 2-3 часа |
| Тестирование | Проверка на реальных данных, сценарии с остатками | 1-2 часа |
| Документация | Описание донастройки, передача доступов | 0.5-1 час |
Итого от 8 до 12 часов в зависимости от сложности каталога и количества складов. Пишите — оценим ваш проект в течение одного рабочего дня.
Сертифицированные специалисты 1С-Битрикс с опытом более 5 лет гарантируют безошибочную интеграцию и полную поддержку. Свяжитесь с нами для расчёта стоимости. Закажите настройку, и ваш интернет-магазин покажет реальные остатки по каждой точке самовывоза.







