Developing a Warehouse Management Module for 1C-Bitrix
Bitrix has built-in stock tracking through catalog module: b_catalog_store (warehouses), b_catalog_store_product (stock by warehouse/product). For simple sites this suffices. When multiple real warehouses appear with different reservation logic, shipment priorities, inter-warehouse transfers, and 1C integration — standard functionality needs significant extension.
Standard Bitrix Limitations
- No concept of "reserved" per order — stock either exists or doesn't
- No warehouse selection logic for shipment during checkout
- No tracking of inter-warehouse movements with history
- No turnover and shortage analytics
Data Schema Extension
Module adds several tables:
Table b_catalog_store_reservation — order reserves:
| Field | Type | Purpose |
|---|---|---|
| ID | int auto_increment | — |
| STORE_ID | int | FK to b_catalog_store |
| PRODUCT_ID | int | Trading offer ID |
| ORDER_ID | int | FK to b_sale_order |
| QUANTITY | decimal(18,4) | Reserved quantity |
| RESERVED_AT | datetime | When reserved |
| RELEASED_AT | datetime | When released (NULL if active) |
Table b_catalog_store_movement — inter-warehouse transfers:
| Field | Type | Purpose |
|---|---|---|
| ID | int auto_increment | — |
| FROM_STORE_ID | int | Source warehouse |
| TO_STORE_ID | int | Destination warehouse |
| PRODUCT_ID | int | Product |
| QUANTITY | decimal(18,4) | Quantity |
| STATUS | enum | DRAFT, IN_TRANSIT, COMPLETED, CANCELLED |
| CREATED_BY | int | User ID |
| COMPLETED_AT | datetime | When completed |
Reservation on Checkout
On order creation, goods must be reserved. Logic connects via OnSaleOrderSaved event handler:
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'sale',
'OnSaleOrderSaved',
[ReservationService::class, 'onOrderSaved']
);
onOrderSaved checks order status implies reserve (e.g., N — new, P — paid). For each order item determines source warehouse and creates b_catalog_store_reservation record.
Warehouse Selection Strategy
Warehouse selection strategy configured in module settings:
- FIFO by receipt date — older goods shipped first
- Nearest warehouse — by customer geolocation (requires warehouse coordinates)
-
Warehouse priority —
SORTfield inb_catalog_store, lower = higher priority - Minimum stock — empty warehouses with least inventory first
If one warehouse lacks required quantity, system auto-splits shipment across warehouses.
Available Stock Calculation
Available stock = physical stock minus active reserves:
public static function getAvailable(int $storeId, int $productId): float
{
$physical = \Bitrix\Catalog\StoreProductTable::getList([
'filter' => ['=STORE_ID' => $storeId, '=PRODUCT_ID' => $productId],
'select' => ['AMOUNT'],
])->fetch()['AMOUNT'] ?? 0;
$reserved = \Bitrix\Main\Application::getConnection()->query(
"SELECT COALESCE(SUM(QUANTITY), 0) as RES
FROM b_catalog_store_reservation
WHERE STORE_ID = {$storeId}
AND PRODUCT_ID = {$productId}
AND RELEASED_AT IS NULL"
)->fetch()['RES'] ?? 0;
return max(0, (float)$physical - (float)$reserved);
}
Product page and cart show available stock, not physical.
Administrative Interface
Admin section (/bitrix/admin/) adds:
- Stock by warehouse — product × warehouse matrix with physical and available stock, filtering by category/warehouse
- Movements — create transfer document, confirm receipt at destination warehouse
- Reserves — active reserves list with manual release option
1C Integration
If project has 1C integration via standard mechanism (/bitrix/admin/1c_exchange.php), stock syncs via OnSuccessCatalogImport1C event handler. Reserves aren't overwritten — 1C stock interpreted as physical, available stock recalculated.
Development Timeline
| Scope | Components | Duration |
|---|---|---|
| Basic | Reservation on order, available stock, reserve release | 6–8 days |
| Standard | + Inter-warehouse transfers, shipment strategies, Admin-UI | 12–16 days |
| Extended | + Turnover analytics, 1C integration, warehouse mobile interface | 20–28 days |







