Настройка триггера поступления товара на склад 1С-Битрикс
Товар заканчивается, пользователь нажимает «Уведомить о поступлении» и... ничего. Письмо не приходит никогда, потому что никто не настроил связь между событием пополнения склада и списком ожидающих. Это частый кейс: форма подписки есть, склад пополняется, но автоматика не запущена.
Где хранятся остатки и как они обновляются
В Битрикс остатки товаров живут в двух местах в зависимости от конфигурации:
Простой каталог (без складского учёта): поле CATALOG_QUANTITY в b_catalog_product. Обновляется напрямую через CCatalogProduct::Update() или через обмен с 1С.
Многоскладской учёт (модуль catalog + склады): таблица b_catalog_store_product с полями PRODUCT_ID, STORE_ID, AMOUNT. Общий остаток агрегируется. При обмене 1С через модуль sale.crm.lead или через стандартный обмен данные пишутся в b_catalog_store_product.
Событие изменения остатка — OnProductUpdate в модуле catalog. Оно срабатывает при любом изменении записи в b_catalog_product, включая количество:
AddEventHandler('catalog', 'OnProductUpdate', function($id, $fields) {
if (isset($fields['QUANTITY']) && $fields['QUANTITY'] > 0) {
// Товар поступил на склад — была нулевая позиция
checkAndNotifyWaitlist($id);
}
});
Проблема: OnProductUpdate срабатывает при любом изменении продукта, не только при пополнении склада. Чтобы отфильтровать именно событие «появился из нуля», нужно сравнивать предыдущее значение. До PHP-события данные ещё в БД, поэтому читаем старое значение в OnBeforeProductUpdate:
AddEventHandler('catalog', 'OnBeforeProductUpdate', function($id, &$fields) {
$old = \Bitrix\Catalog\ProductTable::getByPrimary($id, ['select' => ['QUANTITY']])->fetch();
$fields['_OLD_QUANTITY'] = (float)($old['QUANTITY'] ?? 0);
});
Хранение подписок на поступление
Модуль catalog не имеет встроенного механизма «уведомить о поступлении». Нужна собственная таблица:
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)
);
Форма на сайте пишет в эту таблицу. Уникальный ключ (product_id, email) защищает от дублей при повторных подписках.
Обработчик пополнения
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']}"
);
}
}
Интеграция с многоскладским учётом
При многоскладском учёте событие OnProductUpdate не срабатывает при изменении b_catalog_store_product. Для складских операций нужно подписаться на события модуля catalog более глубокого уровня — либо использовать хук после записи документа складского учёта через \Bitrix\Catalog\Document\DocumentTable.
Альтернативный подход: агент, который каждые 5 минут проверяет b_catalog_store_product на появление ненулевых остатков по товарам из листа ожидания. Менее элегантно, но работает стабильнее при нестандартных схемах обновления остатков (например, при прямом UPDATE через 1С-коннектор).
Что настраиваем
- Обработчики
OnBeforeProductUpdateиOnProductUpdateс проверкой перехода «0 → N» - Таблицу
bl_stock_notifyи форму подписки на сайте - Шаблон письма
STOCK_ARRIVEDв административном разделе почтовых событий - Для многоскладской конфигурации — агент или обработчик документов склада
- Логику частичного поступления: если пришло 2 единицы, а подписалось 10 — уведомить всех или первых двух?







