Configuring Unified Inventory Management Online and Offline 1С-Bitrix
Site shows "5 units in stock". Meanwhile, the last three units are being sold in the store. An hour later, an online customer orders 4 units — and the manager refuses them. This happens when online and offline work with different inventory databases.
Sources of truth about inventory
In a typical configuration: 1C is the master source of inventory, Bitrix is the consumer. 1C knows about all inventory movements: receipts, online sales, offline sales, inventory counts, write-offs. Bitrix receives current data through synchronization.
The problem is synchronization frequency. Standard CommerceML exchange works by schedule — every 15–60 minutes. During this time, several units may be sold in the offline store, while the site continues to show old inventory.
Real-time inventory update on offline sale
To minimize discrepancy — integrate the cashbox with Bitrix in real time. On each offline sale, immediate webhook to Bitrix:
// /local/ajax/pos-sale-callback.php
$data = json_decode(file_get_contents('php://input'), true);
foreach ($data['items'] as $item) {
$product = \CCatalogProduct::GetByID($item['sku']);
if (!$product) continue;
// Decrease inventory on specific store
\Bitrix\Catalog\StoreProductTable::update(
['PRODUCT_ID' => $item['product_id'], 'STORE_ID' => $data['store_id']],
['AMOUNT' => new \Bitrix\Main\DB\SqlExpression('AMOUNT - ?', $item['quantity'])]
);
// Recalculate total inventory in b_catalog_product
\CCatalogProduct::RecalcQuantity($item['product_id']);
}
RecalcQuantity() recalculates b_catalog_product.QUANTITY as the sum across all stores in b_catalog_store_product. After this, the product cache should be invalidated — via BXClearCache(false, '/catalog/') or tagged cache.
Online inventory reservation
Common practice for multi-channel stores: allocate separate "online store" in b_catalog_store, whose inventory is intended only for the online store. Physically, goods may be on one store, but logically divided.
Offline sales decrease "physical store", while online orders are reserved from "online store". At night, 1C replenishes the online store from the physical one by a specified proportion.
Physical store: 100 units
Online quota: 30% = 30 units → stored in b_catalog_store_product (STORE_ID = online_store)
Offline quota: 70% = 70 units → not in Bitrix
This prevents overselling, but reduces available inventory for online sales.
Working with warehouse accounting documents
In Bitrix, the catalog module supports warehouse documents: \Bitrix\Catalog\Document\DocumentTable. Document types: A — receipt, S — sale, M — movement, R — return.
When conducting a document via \Bitrix\Catalog\Document\DocumentController::conduct(), inventory in b_catalog_store_product is automatically recalculated. This is the correct way to move inventory — via documents, not direct UPDATE.
For offline sales: when receiving webhook from cashbox, create document type S with sale items and conduct it. This ensures complete history of goods movement and correct warehouse accounting.
Inventory verification and reconciliation
Discrepancies between 1C and Bitrix are inevitable. It's important to find them. Once per day, run a reconciliation agent:
// Get inventory from 1C via REST API
$bx1cItems = get1cQuantities();
// Compare with b_catalog_store_product
foreach ($bx1cItems as $sku => $qty) {
$bitrixQty = StoreProductTable::getList([
'filter' => ['PRODUCT.XML_ID' => $sku],
'select' => ['AMOUNT'],
])->fetch()['AMOUNT'] ?? 0;
if (abs($bitrixQty - $qty) > 0) {
logDiscrepancy($sku, $bitrixQty, $qty);
}
}
The discrepancy log allows deciding: correct Bitrix per 1C (1C is master) or signal about integration issue.
What we configure
- Webhook-endpoint to receive data about offline cashbox sales
- Update
b_catalog_store_productvia sale type documents - Call
RecalcQuantity()and cache invalidation after each movement - Inventory split scheme (online quota vs physical store) if needed
- Nightly reconciliation agent with 1C and discrepancy logging







