Setting up a trigger for goods receipt at a 1C-Bitrix warehouse

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
    1212
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815
  • 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
    565
  • 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
    657
  • image_crm_technotorgcomplex_453_0.webp
    Development based on Bitrix24 for the company TECHNOTORGKOMPLEKS
    980

Setting up stock arrival trigger in 1C-Bitrix

Product is out of stock, user clicks "Notify me when available" and... nothing. Email never arrives because no one configured the connection between stock replenishment event and the waiting list. This is a common case: the subscription form exists, stock is replenished, but automation isn't launched.

Where inventory and how it's updated

In Bitrix inventory lives in two places depending on configuration:

Simple catalog (without warehouse tracking): field CATALOG_QUANTITY in b_catalog_product. Updated directly via CCatalogProduct::Update() or via 1C exchange.

Multi-warehouse tracking (module catalog + warehouses): table b_catalog_store_product with fields PRODUCT_ID, STORE_ID, AMOUNT. Total inventory is aggregated. On 1C exchange via sale.crm.lead module or standard exchange data is written to b_catalog_store_product.

Inventory change event — OnProductUpdate in catalog module. It fires on any change to b_catalog_product record, including quantity:

AddEventHandler('catalog', 'OnProductUpdate', function($id, $fields) {
    if (isset($fields['QUANTITY']) && $fields['QUANTITY'] > 0) {
        // Product arrived at warehouse — was zero before
        checkAndNotifyWaitlist($id);
    }
});

Problem: OnProductUpdate fires on any product change, not only on stock replenishment. To filter only "appeared from zero" event you need to compare previous value. Before PHP event, data is still in DB, so read old value in OnBeforeProductUpdate:

AddEventHandler('catalog', 'OnBeforeProductUpdate', function($id, &$fields) {
    $old = \Bitrix\Catalog\ProductTable::getByPrimary($id, ['select' => ['QUANTITY']])->fetch();
    $fields['_OLD_QUANTITY'] = (float)($old['QUANTITY'] ?? 0);
});

Storing stock arrival subscriptions

The catalog module has no built-in mechanism for "notify on arrival". You need your own table:

CREATE TABLE bl_stock_notify (
    id          SERIAL PRIMARY KEY,
    product_id  INT NOT NULL,
    user_id     INT,
    email       VARCHAR(255) NOT NULL,
    created_at  TIMESTAMP DEFAULT NOW(),
    notified_at TIMESTAMP,
    UNIQUE (product_id, email)
);

Form on site writes to this table. Unique key (product_id, email) protects against duplicates on resubscription.

Replenishment handler

function checkAndNotifyWaitlist(int $productId): void
{
    $connection = \Bitrix\Main\Application::getConnection();
    $waitlist = $connection->query(
        "SELECT * FROM bl_stock_notify WHERE product_id = {$productId} AND notified_at IS NULL"
    )->fetchAll();

    if (empty($waitlist)) {
        return;
    }

    $product = \CIBlockElement::GetByID($productId)->GetNextElement();
    $name    = $product->GetField('NAME');
    $url     = $product->GetField('DETAIL_PAGE_URL');

    foreach ($waitlist as $row) {
        \Bitrix\Main\Mail\Event::send([
            'EVENT_NAME' => 'STOCK_ARRIVED',
            'LID'        => SITE_ID,
            'C_FIELDS'   => [
                'EMAIL'        => $row['email'],
                'PRODUCT_NAME' => $name,
                'PRODUCT_URL'  => 'https://' . $_SERVER['SERVER_NAME'] . $url,
            ],
        ]);

        $connection->queryExecute(
            "UPDATE bl_stock_notify SET notified_at = NOW() WHERE id = {$row['id']}"
        );
    }
}

Integration with multi-warehouse tracking

With multi-warehouse tracking, OnProductUpdate doesn't fire on b_catalog_store_product changes. For warehouse operations you need to subscribe to deeper-level events in catalog module — either use hook after warehouse document record via \Bitrix\Catalog\Document\DocumentTable.

Alternative approach: agent that checks b_catalog_store_product every 5 minutes for non-zero balances on products in waitlist. Less elegant but more stable with non-standard inventory update schemes (e.g., direct UPDATE via 1C connector).

What we configure

  • Handlers OnBeforeProductUpdate and OnProductUpdate with check for "0 → N" transition
  • Table bl_stock_notify and subscription form on site
  • Email template STOCK_ARRIVED in administrative mail events section
  • For multi-warehouse configuration — agent or warehouse document handler
  • Logic for partial arrival: if 2 units arrived and 10 subscribed — notify all or first two?