События каталога на сохранение цены товара в Bitrix (PriceOnBeforeAdd / Update)
Почему это не классический ORM-хук
Для таблицы цен в модуле catalog многие привыкают искать знакомую пару событий ORM по классам наподобие \Bitrix\Catalog\Model\Price или напрямую по PriceTable. На практике сохранение цены проходит через внутренний слой модели каталога, и «ожидаемые» универсальные обработчики на уровень таблицы либо не вызываются в вашем сценарии, либо до них сложнее достучаться предсказуемо.
Для вмешательства в поля строки перед записью удобнее подписаться на именованные события самого каталога. Типичные точки входа перед добавлением и обновлением записи цены называются PriceOnBeforeAdd и PriceOnBeforeUpdate; есть и сопутствующие варианты на удаление и «после» операции.
Где навесить регистрацию
Разумное место — init.php проекта или автозагружаемый bootstrap модуля, где уже включаются зависимости. Модуль в первом параметре — строка catalog, вторым идёт символьное имя события, третьим — статический метод вашего класса.
use Bitrix\Main\EventManager;
$dispatcher = EventManager::getInstance();
$dispatcher->addEventHandler(
'catalog',
'PriceOnBeforeAdd',
'\Acme\Catalog\RetailPriceScaler::enforceMarkupOnWrite'
);
$dispatcher->addEventHandler(
'catalog',
'PriceOnBeforeUpdate',
'\Acme\Catalog\RetailPriceScaler::enforceMarkupOnWrite'
);Разбор параметров и поправка полей
Обработчик получает объект события каталожной модели: удобно типизировать как \Bitrix\Catalog\Model\Event. Из параметров интересует массив fields с колонками, которые отправятся в базу.
Чтобы не гадать насчёт целочисленных денежных полей на разных редакциях, синхронно меняйте и сумму цены (PRICE), и её масштабированное значение (PRICE_SCALE), если последнее участвует в вашей сборке данных. Результат для ядра оформляется экземпляром \Bitrix\Catalog\Model\EventResult: метод modifyFields() перечисляет только те ключи массива строки, которые вы переопределили.
namespace Acme\Catalog;
use Bitrix\Catalog\Model\Event;
use Bitrix\Catalog\Model\EventResult;
final class RetailPriceScaler
{
public static function enforceMarkupOnWrite(Event $pulse): EventResult
{
$ledger = new EventResult();
// Пример ограничения по контексту обмена (раскомментируйте при необходимости):
// if ($GLOBALS['APPLICATION']->GetCurPage() === '/bitrix/admin/1c_exchange.php') {
// return $ledger;
// }
$rawRow = $pulse->getParameter('fields');
if (!is_array($rawRow)) {
return $ledger;
}
$upliftFactor = 2.00; // условная наценка; вынести в настройку модуля / Option
foreach (['PRICE', 'PRICE_SCALE'] as $columnSlug) {
if (!array_key_exists($columnSlug, $rawRow)) {
continue;
}
$rawRow[$columnSlug] = round((float) $rawRow[$columnSlug] * $upliftFactor, 4);
}
$pulse->setParameter('fields', $rawRow);
$overwriteSlice = [];
if (isset($rawRow['PRICE'])) {
$overwriteSlice['PRICE'] = $rawRow['PRICE'];
}
if (isset($rawRow['PRICE_SCALE'])) {
$overwriteSlice['PRICE_SCALE'] = $rawRow['PRICE_SCALE'];
}
if ($overwriteSlice !== []) {
$ledger->modifyFields($overwriteSlice);
}
return $ledger;
}
}В реальном обмене с учётными системами коэффициент лучше брать из настроек, а не держать в коде литералом; отдельно продумайте ветку для базовых типов цен и перерасчёта зависимых строк, если включена автоматика наценок в каталоге.
См. также (оригинальные заметки)
Не хотите копаться сами?
Починю за 1-3 дня. Без предоплаты — оплата по результату.
15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии