Developing a 1C-Bitrix warehouse inventory filter

Our company is engaged in the development, support and maintenance of Bitrix and Bitrix24 solutions of any complexity. From simple one-page sites to complex online stores, CRM systems with 1C and telephony integration. The experience of developers is confirmed by certificates from the vendor.
Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1175
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    811
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Development based on Bitrix, Bitrix24, 1C for the company Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Development based on 1C Enterprise for MIRSANBEL
    747
  • image_crm_dolbimby_434_0.webp
    Website development on CRM Bitrix24 for DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    976

Stock Availability Filtering for 1C-Bitrix

The "in stock only" filter is one of the most in-demand features in an online store. Users do not want to see made-to-order or out-of-stock items when they need to make an urgent purchase. In Bitrix with the trading catalog module, stock levels are stored in b_catalog_store_product, and filtering by them requires an understanding of how the trading catalog module manages availability.

Stock Storage Architecture

  • b_catalog_product — the QUANTITY field (total stock), QUANTITY_TRACE (is stock tracking enabled?), CAN_BUY_ZERO (allow purchase when stock is zero?)
  • b_catalog_store_product — stock levels per warehouse (STORE_ID, PRODUCT_ID, AMOUNT)
  • b_catalog_product_set — kits and bundles (separate logic)

For trade offers (SKU) — stock levels are stored at the offer level, not the parent product level.

"In Stock" Filter via GetList

// Only products with non-zero stock
$filter = [
    'IBLOCK_ID' => $iblockId,
    'ACTIVE'    => 'Y',
    // Stock > 0 or purchase allowed with zero stock
    [
        'LOGIC' => 'OR',
        ['>CATALOG_QUANTITY' => 0],
        ['CATALOG_CAN_BUY_ZERO' => 'Y'],
    ],
    // Product with stock tracking
    'CATALOG_QUANTITY_TRACE' => 'Y',
];

Or a simpler version for catalogs without CAN_BUY_ZERO:

$filter['>CATALOG_QUANTITY'] = 0;

Filtering by Specific Warehouse

For a multi-warehouse setup (delivery from different cities):

// Products in stock at warehouse ID=3 (e.g., Moscow warehouse)
$storeId = 3;
$minAmount = 1;

// Retrieve IDs of products available at the specified warehouse
$productIds = [];
$rs = \Bitrix\Catalog\StoreProductTable::getList([
    'filter' => [
        'STORE_ID' => $storeId,
        '>AMOUNT'  => $minAmount,
    ],
    'select' => ['PRODUCT_ID'],
]);
while ($row = $rs->fetch()) {
    $productIds[] = (int)$row['PRODUCT_ID'];
}

if (!empty($productIds)) {
    $filter['ID'] = $productIds;
} else {
    // No products at the warehouse — return an empty result
    return ['items' => [], 'total' => 0];
}

Filtering by Trade Offer (SKU) Availability

For products with variants — check that at least one trade offer is in stock:

// Find parent products that have at least one offer in stock
$parentIds = [];

$offersRs = \CIBlockElement::GetList(
    [],
    [
        'IBLOCK_ID' => OFFERS_IBLOCK_ID, // trade offers infoblock
        'ACTIVE'    => 'Y',
        '>CATALOG_QUANTITY' => 0,
    ],
    false,
    false,
    ['ID', 'PROPERTY_CML2_LINK'] // CML2_LINK — link to the parent product
);

while ($offer = $offersRs->Fetch()) {
    $parentId = $offer['PROPERTY_CML2_LINK_VALUE'];
    if ($parentId) $parentIds[] = (int)$parentId;
}

$parentIds = array_unique($parentIds);

// Filter the main catalog
if (!empty($parentIds)) {
    $filter['ID'] = $parentIds;
}

AJAX "In Stock Only" Toggle

// "In Stock Only" checkbox
const inStockToggle = document.getElementById('filter-in-stock')

inStockToggle.addEventListener('change', () => {
  const filters = getActiveFilters()
  filters.inStock = inStockToggle.checked

  loadCatalog({
    ...filters,
    page: 1,
  })
})

// Restore state from URL
const urlParams = new URLSearchParams(window.location.search)
if (urlParams.get('in_stock') === '1') {
  inStockToggle.checked = true
}

Displaying Stock Quantity

Show the user a specific stock count or an availability level:

// In the product card template
$quantity = $arItem['CATALOG_QUANTITY'];
$availability = match(true) {
    $quantity <= 0  => ['label' => 'Out of stock',   'class' => 'out-of-stock'],
    $quantity <= 3  => ['label' => 'Last few items', 'class' => 'low-stock'],
    $quantity <= 10 => ['label' => 'Low stock',      'class' => 'limited-stock'],
    default         => ['label' => 'In stock',        'class' => 'in-stock'],
};

Updating Availability When Receiving Data from 1C

If stock levels are synchronized from 1C via CommerceML or REST, the filter cache must be invalidated on update:

// In the stock update handler
AddEventHandler('catalog', 'OnProductQuantityChange', function($productId) {
    \Bitrix\Main\Data\Cache::clearByTag("catalog_filter_section_{$sectionId}");
    \CBitrixComponent::clearComponentCache('bitrix:catalog.smart.filter');
});

Timeline

"In Stock" checkbox with AJAX filtering and URL state — 4–8 hours. Multi-warehouse filter with city selection and SKU logic — 2–3 business days.