Setting Up Product Availability Display by Pickup Points in 1C-Bitrix
On a product page, you need to show: "In stock at 3 stores" or specifically "Central Mall — 5 pcs, Lenin St 15 — 2 pcs, West Mall — out of stock." The standard bitrix:catalog.element component shows only the total stock across all warehouses. Breakdown by pickup points requires a separate query to b_catalog_store_product and modification of the template.
Warehouse Stock Data Structure
-- Product stock across active warehouses
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' -- pickup points only
ORDER BY s.SORT ASC;
IS_SITE = 'Y' — flag indicating the warehouse is a pickup point for the site. Set in Catalog → Warehouses → [Edit].
PHP Code for Retrieving Stock by Pickup Points
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 [];
}
// Fetch stock in a single query for all warehouses
$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;
}
Integration into the Product Page Component Template
In the template.php of the bitrix:catalog.element component:
\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">
In stock at <?= $inStockCount ?> store<?= $inStockCount !== 1 ? 's' : '' ?>
</div>
<button class="toggle-stores" type="button">Show all stores</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'] . ' pcs.'
: 'Out of stock' ?>
</span>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<div class="out-of-stock">Not available in stores</div>
<?php endif; ?>
</div>
AJAX Update on Trade Offer Selection
For products with trade offers (sizes, colors), stock must be updated when the offer is changed:
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));
});
});
The AJAX endpoint /ajax/store-availability/ is a component or standalone PHP file returning JSON with the result of getStoreAvailability($offerId).
Setup Timelines
Stock retrieval function, product page template modification with store list display, AJAX update on trade offer change — 4–8 hours.







