Обновление цен и остатков в 1С-Битрикс через Push & Pull
Иногда необходимо, чтобы цена и остаток товара в каталоге 1С-Битрикс обновлялись прямо на открытой странице — без перезагрузки и дополнительного действия со стороны пользователя.
Например, такая задача возникает, если:
Для этого в 1С-Битрикс можно использовать модуль Push & Pull.
Событие отправляется только тогда, когда действительно произошло изменение.
Проще всего добавить ID товара в data-атрибут контейнера:
Например:
Например, необходимо одновременно обновить:
Например:
Такой подход удобен, если логика формирования цены сложная и лучше повторно получить итоговые данные с сервера, чем пытаться полностью передать их через Pull-событие.
Например, при добавлении товара в корзину и оформлении заказа сервер всё равно должен повторно проверить:
Интерфейс должен обновляться через Push & Pull, но окончательное решение всегда остаётся за сервером.
Поэтому его можно использовать только для данных, которые допустимо передавать всем подключённым пользователям.
Например:
В таком случае необходимо отправлять события конкретному пользователю или группе пользователей.
Например:
При этом JavaScript всё равно может изменить уже загруженный DOM, однако сами динамические данные каталога лучше проектировать с учётом композита.
Цена, остаток, персональные скидки и другие быстро меняющиеся значения обычно должны находиться в динамической части страницы.
Его можно использовать для:
Общий принцип достаточно простой:
Вернуться к блогу
Например, такая задача возникает, если:
- 1С регулярно выгружает новые цены и остатки;
- товары активно резервируются другими покупателями;
- стоимость меняется автоматически;
- страница каталога долго остаётся открытой;
- каталог используется как рабочий B2B-интерфейс.
Для этого в 1С-Битрикс можно использовать модуль Push & Pull.
Как работает Push & Pull
Общая схема выглядит следующим образом:
PHP → Push-сервер → WebSocket → JavaScript → DOM
Последовательность работы:- На сервере изменяется цена или остаток товара.
- PHP отправляет Push & Pull событие.
- Сервер очередей передаёт событие подключённым браузерам.
- JavaScript получает событие.
- На странице изменяется цена или остаток нужного товара.
Событие отправляется только тогда, когда действительно произошло изменение.
Подключаем модуль Push & Pull
На серверной стороне первым делом подключаем необходимые модули:
use Bitrix\Main\Loader;
Loader::includeModule('pull');
Loader::includeModule('catalog');
Далее создадим две функции: одну для изменения цены, вторую — для изменения остатка.Отправляем событие при изменении цены
Для публичного каталога можно отправлять событие в общий канал:
function sendPriceUpdateEvent(
int $productId,
float $price,
string $currency = 'RUB'
): void
{
\CPullStack::AddShared([
'module_id' => 'catalog_live',
'command' => 'priceChanged',
'params' => [
'productId' => $productId,
'price' => $price,
'currency' => $currency,
'timestamp' => time(),
],
]);
}
В событие передаются:- ID товара;
- новая цена;
- валюта;
- время формирования события.
Отправляем событие при изменении остатка
Аналогично создаём событие для количества товара:
function sendStockUpdateEvent(
int $productId,
float $quantity
): void
{
\CPullStack::AddShared([
'module_id' => 'catalog_live',
'command' => 'stockChanged',
'params' => [
'productId' => $productId,
'quantity' => $quantity,
'timestamp' => time(),
],
]);
}
Теперь необходимо вызвать эти функции в момент изменения данных каталога.Отслеживаем изменение цены
Обработчики можно зарегистрировать, например, в:
/local/php_interface/init.php
Подключаем EventManager:
use Bitrix\Main\EventManager;
$eventManager = EventManager::getInstance();
И регистрируем обработчик изменения цены:
$eventManager->addEventHandler(
'catalog',
'Bitrix\Catalog\Model\Price::OnAfterUpdate',
static function ($event) {
$id = (int)$event->getParameter('id');
$fields = (array)$event->getParameter('fields');
if (
$id > 0
&& !empty($fields['PRODUCT_ID'])
&& array_key_exists('PRICE', $fields)
) {
sendPriceUpdateEvent(
(int)$fields['PRODUCT_ID'],
(float)$fields['PRICE'],
(string)($fields['CURRENCY'] ?? 'RUB')
);
}
}
);
После обновления цены Push & Pull получит событие с новыми данными товара.Отслеживаем изменение остатка
Для количества товара можно добавить отдельный обработчик:
$eventManager->addEventHandler(
'catalog',
'Bitrix\Catalog\Model\Product::OnAfterUpdate',
static function ($event) {
$id = (int)$event->getParameter('id');
$fields = (array)$event->getParameter('fields');
if (
$id > 0
&& array_key_exists('QUANTITY', $fields)
) {
sendStockUpdateEvent(
$id,
(float)$fields['QUANTITY']
);
}
}
);
Если на проекте используется складской учёт и требуется обновлять остатки отдельных складов, обработчик необходимо адаптировать под используемую модель хранения остатков.Подготавливаем HTML карточки товара
JavaScript должен понимать, какой элемент на странице относится к изменившемуся товару.Проще всего добавить ID товара в data-атрибут контейнера:
<div
class="product-card"
data-product-id="<?=$arItem['ID']?>"
>
<span class="product-price">
<?=$arItem['PRICE']?> руб.
</span>
<span class="product-stock in-stock">
В наличии: <?=$arItem['QUANTITY']?> шт.
</span>
</div>
Теперь при получении события можно найти нужную карточку через значение data-product-id.Подписываемся на изменение цены в JavaScript
В файле script.js шаблона компонента добавляем подписку:
BX.ready(function () {
BX.PULL.subscribe({
moduleId: 'catalog_live',
command: 'priceChanged',
callback: function (params) {
var card = document.querySelector(
'[data-product-id="' + params.productId + '"]'
);
if (!card) {
return;
}
var priceElement = card.querySelector('.product-price');
if (!priceElement) {
return;
}
priceElement.textContent = new Intl.NumberFormat(
'ru-RU',
{
style: 'currency',
currency: params.currency || 'RUB'
}
).format(params.price);
priceElement.classList.add('price-updated');
setTimeout(function () {
priceElement.classList.remove('price-updated');
}, 2000);
}
});
});
Когда сервер отправит команду priceChanged, JavaScript найдёт карточку товара и заменит цену непосредственно в DOM.Подписываемся на изменение остатка
Добавляем вторую подписку:
BX.PULL.subscribe({
moduleId: 'catalog_live',
command: 'stockChanged',
callback: function (params) {
var card = document.querySelector(
'[data-product-id="' + params.productId + '"]'
);
if (!card) {
return;
}
var stockElement = card.querySelector('.product-stock');
if (!stockElement) {
return;
}
if (params.quantity > 0) {
stockElement.textContent =
'В наличии: ' + params.quantity + ' шт.';
stockElement.className =
'product-stock in-stock';
} else {
stockElement.textContent =
'Нет в наличии';
stockElement.className =
'product-stock out-of-stock';
}
}
});
Теперь остаток товара также меняется без перезагрузки страницы.Добавляем визуальную подсветку изменения
Чтобы пользователь заметил, что цена только что обновилась, можно кратковременно подсветить её.Например:
.price-updated {
animation: priceFlash 0.6s ease-in-out 3;
}
@keyframes priceFlash {
0%,
100% {
background-color: transparent;
}
50% {
background-color: #fff3cd;
}
}
.product-stock.in-stock {
color: #28a745;
}
.product-stock.out-of-stock {
color: #dc3545;
}
После изменения цена несколько раз подсветится, а остаток получит соответствующий класс.Если необходимо обновить карточку целиком
Иногда изменение цены влияет не только на одно поле.Например, необходимо одновременно обновить:
- цену;
- старую цену;
- размер скидки;
- остаток;
- доступность кнопки «Купить»;
- торговое предложение;
- другие параметры карточки.
Например:
BX.PULL.subscribe({
moduleId: 'catalog_live',
command: 'priceChanged',
callback: function (params) {
BX.ajax.runComponentAction(
'vendor:catalog.product',
'getProductData',
{
mode: 'class',
data: {
productId: params.productId
}
}
).then(function (response) {
var card = document.querySelector(
'[data-product-id="' + params.productId + '"]'
);
if (!card) {
return;
}
var price = card.querySelector('.product-price');
var stock = card.querySelector('.product-stock');
if (price) {
price.textContent =
response.data.price + ' руб.';
}
if (stock) {
stock.textContent =
response.data.quantity > 0
? 'В наличии: '
+ response.data.quantity
+ ' шт.'
: 'Нет в наличии';
}
});
}
});
Название компонента и action в этом примере условные — серверный action необходимо реализовать в своём компоненте.Такой подход удобен, если логика формирования цены сложная и лучше повторно получить итоговые данные с сервера, чем пытаться полностью передать их через Pull-событие.
Почему нельзя доверять Push-событию при оформлении заказа
Данные в интерфейсе обновляются практически в реальном времени, но Push & Pull не должен становиться источником истины для критических операций.Например, при добавлении товара в корзину и оформлении заказа сервер всё равно должен повторно проверить:
- текущую цену;
- доступное количество;
- скидки;
- правила корзины;
- доступность конкретного торгового предложения.
Интерфейс должен обновляться через Push & Pull, но окончательное решение всегда остаётся за сервером.
Публичные и персональные цены
В приведённом выше примере используется:
\CPullStack::AddShared()
Этот метод отправляет событие в общий канал.Поэтому его можно использовать только для данных, которые допустимо передавать всем подключённым пользователям.
Например:
- публичная цена товара;
- общий остаток;
- статус наличия.
В таком случае необходимо отправлять события конкретному пользователю или группе пользователей.
Например:
\Bitrix\Pull\Event::add(
$userId,
[
'module_id' => 'catalog_live',
'command' => 'priceChanged',
'params' => [
'productId' => $productId,
'price' => $price,
'currency' => $currency,
],
]
);
Композитный режим
Если на сайте используется композитный кеш, первоначальный HTML страницы может быть закеширован.При этом JavaScript всё равно может изменить уже загруженный DOM, однако сами динамические данные каталога лучше проектировать с учётом композита.
Цена, остаток, персональные скидки и другие быстро меняющиеся значения обычно должны находиться в динамической части страницы.
Куда размещать код
Один из вариантов структуры проекта:- /local/php_interface/init.php — регистрация обработчиков;
- /local/php_interface/pull_events.php — функции отправки Push & Pull событий;
- script.js шаблона компонента — JS-подписки;
- template.php — data-атрибуты товаров;
- style.css — оформление и анимация обновлений.
/local/php_interface/pull_events.php
а в init.php подключить его:
require_once(
$_SERVER['DOCUMENT_ROOT']
. '/local/php_interface/pull_events.php'
);
Так обработчики и вспомогательная логика не будут собраны в одном большом init.php.Проверяем работу Push & Pull
После установки необходимо проверить всю цепочку.- Открыть страницу каталога.
- Открыть консоль браузера.
- Проверить наличие объекта BX.PULL.
- Изменить цену товара в административной части.
- Проверить, обновилась ли цена на открытой странице.
- Изменить остаток.
- Проверить изменение количества без перезагрузки.
console.log(BX.PULL);
Если Push & Pull не работает, необходимо отдельно проверить сервер очередей и настройки модуля.Если события не приходят
В первую очередь стоит проверить:- подключён ли модуль pull;
- инициализирован ли BX.PULL на странице;
- работает ли сервер очередей;
- совпадают ли moduleId и command на PHP- и JS-стороне;
- срабатывает ли серверный обработчик изменения товара;
- есть ли на странице элемент с нужным data-product-id;
- нет ли ошибок JavaScript в консоли.
Где можно применять обновление данных в реальном времени
Push & Pull подходит не только для обычного каталога товаров.Его можно использовать для:
- обновления остатков после обмена с 1С;
- показа изменений цены во время акции;
- B2B-каталогов;
- мониторинга наличия товаров;
- аукционов;
- динамического ценообразования;
- резервирования товаров;
- отображения изменения статусов;
- различных внутренних панелей и дашбордов.
Итог
Push & Pull позволяет обновлять данные каталога 1С-Битрикс без постоянных AJAX-запросов и без перезагрузки страницы.Общий принцип достаточно простой:
- Отслеживаем изменение данных на сервере.
- Отправляем Push & Pull событие.
- Подписываемся на него в браузере.
- Находим нужный товар по data-атрибуту.
- Обновляем необходимые элементы DOM.