Setting up product availability by stores in 1C-Bitrix
Customer picks a product on site, goes to the store — and finds out it's not there. 3 units in inventory, but they're in another branch. This happens when the site shows total inventory across all warehouses instead of actual availability at specific point.
Multi-warehouse inventory architecture
The catalog module supports warehouses starting with Bitrix 16.x. Inventory by warehouse stored in b_catalog_store_product:
-
PRODUCT_ID— ID of catalog element -
STORE_ID— ID of warehouse fromb_catalog_store -
AMOUNT— quantity on warehouse -
QUANTITY_RESERVED— reserved for orders
b_catalog_store — this is the catalog warehouse table. It's different from b_sale_store (pickup points for self-pickup). Connection "pickup point → warehouse" is established via custom field or manually — in standard Bitrix schema these two entities are separated.
Total inventory in b_catalog_product.QUANTITY — aggregate of all warehouses. With multi-warehouse tracking enabled (CCatalogStore::IsStoreProductQuantityEnable()) field QUANTITY updates automatically on b_catalog_store_product changes.
Binding warehouse to pickup point
Standard task: each pickup point (b_sale_store) corresponds to one or more warehouses (b_catalog_store). Create custom field in b_catalog_store:
CUserTypeEntity::Add([
'ENTITY_ID' => 'CAT_STORE',
'FIELD_NAME' => 'UF_SALE_STORE_ID',
'USER_TYPE_ID' => 'integer',
'MANDATORY' => 'N',
]);
After this when requesting availability by pickup point filter b_catalog_store_product via JOIN with b_catalog_store by UF_SALE_STORE_ID.
Displaying availability on product card
Query product availability across all linked warehouses:
$availability = \Bitrix\Catalog\StoreProductTable::getList([
'filter' => [
'PRODUCT_ID' => $productId,
'>AMOUNT' => 0,
],
'select' => ['STORE_ID', 'AMOUNT', 'QUANTITY_RESERVED'],
])->fetchAll();
// Get pickup point IDs via UF_SALE_STORE_ID
$storeIds = array_column($availability, 'STORE_ID');
$saleStores = \Bitrix\Catalog\StoreTable::getList([
'filter' => ['ID' => $storeIds],
'select' => ['ID', 'UF_SALE_STORE_ID'],
])->fetchAll();
Available quantity: AMOUNT - QUANTITY_RESERVED. If result ≤ 0 — product is reserved, physically on warehouse but not available for sale.
Reserving on order placement
When customer adds product to cart or places order, reservation should be created. This is done via \Bitrix\Catalog\StoreProductTable::update() with QUANTITY_RESERVED increase. Standard Bitrix mechanism does this on order status change, but not on adding to cart.
If you need to reserve at cart stage — add handler OnSaleBasketItemAdd:
AddEventHandler('sale', 'OnSaleBasketItemAdd', function(\Bitrix\Main\Event $event) {
$item = $event->getParameter('ENTITY');
// Increase QUANTITY_RESERVED on warehouse linked to nearest point
// with user geolocation or chosen store
});
Syncing with 1C
Inventory by warehouse comes from 1C via standard exchange or REST API. On exchange via CommerceML/XML file data is written to b_catalog_store_product via CCatalogStore::UpdateProductQuantity(). With direct REST — via method catalog.storeproduct.update.
Important: on 1C exchange it can send full inventory (rewrite) or delta. With delta protect against negative AMOUNT values: add CHECK constraint at DB level or check in code before update().
What we configure
- Checking multi-warehouse tracking is enabled and
b_catalog_store_productis populated - Custom field
UF_SALE_STORE_IDinb_catalog_storefor linking to catalog warehouses - Displaying availability by pickup points on product card
- Calculating available quantity accounting for
QUANTITY_RESERVED - Handler for reserving on adding to cart (if required)
- Scheme for syncing inventory from 1C with protection from negative values







