Событие сборки фильтра запроса к МойСклад
[OnBuildImportFilter] Изменение фильтра выборки из МойСклад
Событие OnBuildImportFilter вызывается в последней точке сборки строки filter=..., которая уходит в запрос к API МойСклад. Обработчик получает фильтр разобранным на части и может добавить свои части, выбросить части модуля или заменить их.
Событие даёт изменить состав выборки, не трогая код модуля: например, отсечь товары-ТП по значению доп. поля, ограничить импорт своим набором групп или добавить любое условие, которого нет в настройках.
Событие срабатывает всегда, даже когда обработчиков нет. Если ни один обработчик не вернул массив, фильтр остаётся ровно таким, каким его собрал модуль.
В каких пайплайнах срабатывает
| Пайплайн | Значение context | Где вызывается |
|---|---|---|
| Импорт сущностей (товары, модификации, услуги, комплекты) — агент и разовый импорт | entity | CRbsMoyskladStocks::buildAgentFilterStringForEntity() |
| Импорт цен — агент и разовый импорт | prices | CRbsMoyskladStocks::getFilterString() |
| Деактивация — агент и разовый импорт | deactivate | CRbsMoyskladStocks::buildAgentFilterStringForEntity() |
| Остатки комплектов | bundlestocks | CRbsMoyskladStocks::buildAgentFilterStringForEntity() |
| Проверка количества сущностей в настройках модуля | check | CRbsMoyskladStocks::getFilterString() |
| Диагностика: выборка ассортимента | diagnostic | CRbsMoyskladStocks::getFilterString() |
Не все запросы модуля к МойСклад строят фильтр через эти два метода. Событие не вызывается для:
- импорта остатков по складам и текущих остатков — там фильтр собирает
Config::getFilterStocksString(); - импорта групп товаров (
productfolder) агентом — там фильтр задан константойarchived=true;archived=false; - разового импорта групп товаров и разового импорта ассортимента в диагностике — там фильтр тоже задан константой;
- импорта скидок — запрос идёт без фильтра.
- Описание события
- Как сводятся несколько обработчиков
- Пример кода
- Примеры использования
Параметры события
| Параметр | Тип | Описание |
|---|---|---|
entity | string | Сущность МойСклад, для которой собирается фильтр: product, variant, bundle, service, productfolder |
context | string | Пайплайн, который запросил фильтр: entity, prices, deactivate, bundlestocks, check, diagnostic |
filter | array | Части фильтра. После обработки склеиваются через ; и уходят в запрос |
Возвращаемое значение
\Bitrix\Main\EventResult с параметром filter — новым массивом частей.
return new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::SUCCESS,
['filter' => $filter]
);
Что модуль считает ответом «не меняю»
Любой из этих вариантов оставляет фильтр модуля нетронутым:
- обработчик ничего не вернул;
- вернул
EventResultс типомERROR; - вернул
EventResultбез параметров; - в параметрах нет ключа
filterлибо в нём лежит не массив.
Части, которые не являются непустой строкой, из ответа обработчика отбрасываются.
Механизм событий Битрикс отдаёт всем обработчикам один и тот же исходный набор частей: второй обработчик не видит того, что сделал первый. Поэтому модуль сводит ответы по разницам, а не «побеждает последний».
Для каждого ответа модуль считает:
- какие исходные части обработчик выбросил (не вернул);
- какие части он добавил (которых не было в исходном наборе).
Итог = исходный набор − все выброшенные части + все добавленные части.
Свойства сведения
| Свойство | Что это значит на практике |
|---|---|
| Порядок регистрации обработчиков на результат не влияет | Два независимых кастома не затирают друг друга |
| Конфликт «один выбросил, другой оставил» решается в пользу выбрасывания | Часть, которую хоть кто-то убрал, в запрос не уйдёт |
| Дубли не добавляются | Одинаковую часть от двух обработчиков модуль добавит один раз |
| Исходные части сохраняют свой порядок, добавленные идут после них | Перестановка частей внутри ответа обработчика игнорируется |
Если обработчик вернёт ['filter' => []], из запроса пропадёт всё, включая инкрементальную часть updated>=... и фильтр по группам товаров. Агент уйдёт за полным каталогом МойСклад, и в логе об этом не будет ни строки.
Это законный способ сказать «фильтра быть не должно», и модуль его не запрещает. Но не возвращайте пустой массив по ветке ошибки — верните пришедший фильтр без изменений или EventResult::ERROR.
Исключение, выброшенное обработчиком, уходит наверх. В агенте импорта шаг завершится ошибкой, а в разовом импорте диагностики будет перехвачено и приведёт к запросу без фильтра. Оборачивайте свой код в try/catch.
//Пример кода нужно вставить в файл init.php
\Bitrix\Main\EventManager::getInstance()->addEventHandler(
'rbs.moyskladstocks',
'OnBuildImportFilter',
'OnBuildImportFilterHandler'
);
function OnBuildImportFilterHandler(\Bitrix\Main\Event $event)
{
$eventParams = $event->getParameters();
$entity = $eventParams['entity']; // product / variant / bundle / service / productfolder
$context = $eventParams['context']; // entity / prices / deactivate / bundlestocks / check / diagnostic
$filter = $eventParams['filter']; // массив частей фильтра
// Меняем фильтр только там, где это нужно
if ($entity !== 'product' || $context !== 'entity') {
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::ERROR);
}
// Добавляем свою часть к пришедшему набору
$attrHref = 'https://api.moysklad.ru/api/remap/1.2/entity/product/metadata/attributes/'
. 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee';
$filter[] = $attrHref . '=false';
return new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::SUCCESS,
['filter' => $filter]
);
}
filterСостав частей зависит от пайплайна. Например, для агента импорта товаров с инкрементом и фильтром по группе в массиве окажется:
[
0 => 'type=product;updated>=2026-09-21 10:00:00', // пользовательские части одной строкой
1 => 'pathName~=Каталог', // фильтр по группе товаров
2 => 'https://api.moysklad.ru/.../attributes/<id>=true', // фильтр по доп. полю
]
Обратите внимание: в пайплайнах entity, prices, deactivate, check и diagnostic собственные части пайплайна (type=..., archived=..., updated>=...) приходят одной склеенной строкой в первом элементе. Выбросить из неё только updated>=..., убрав элемент массива, нельзя — придётся заменить всю строку целиком.
Не импортировать товары определённой группы
function OnBuildImportFilterHandler(\Bitrix\Main\Event $event)
{
$eventParams = $event->getParameters();
$filter = $eventParams['filter'];
if ($eventParams['entity'] !== 'product') {
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::ERROR);
}
$filter[] = 'pathName!~Архив';
return new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::SUCCESS,
['filter' => $filter]
);
}
Снять инкремент и выгрузить всё для конкретного пайплайна
Модуль отдаёт пользовательские части одной строкой, поэтому чтобы убрать updated>=..., эту строку нужно заменить.
function OnBuildImportFilterHandler(\Bitrix\Main\Event $event)
{
$eventParams = $event->getParameters();
if ($eventParams['context'] !== 'prices') {
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::ERROR);
}
$result = [];
foreach ($eventParams['filter'] as $part) {
// выкидываем инкрементальное условие, остальное оставляем как есть
$cleaned = implode(';', array_filter(
explode(';', $part),
static fn($piece) => strpos($piece, 'updated>=') !== 0
));
if ($cleaned !== '') {
$result[] = $cleaned;
}
}
return new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::SUCCESS,
['filter' => $result]
);
}
Разные фильтры для разных профилей обмена
Событие вызывается от имени модуля rbs.moyskladstocks независимо от текущего профиля, поэтому один обработчик работает для всех профилей. Номер активного профиля можно получить прямо в обработчике.
function OnBuildImportFilterHandler(\Bitrix\Main\Event $event)
{
$profileId = \Rbs\MoyskladStocks\Config::getProfileId(); // 0 — основной профиль
if ($profileId !== 2) {
return new \Bitrix\Main\EventResult(\Bitrix\Main\EventResult::ERROR);
}
$filter = $event->getParameter('filter');
$filter[] = 'archived=false';
return new \Bitrix\Main\EventResult(
\Bitrix\Main\EventResult::SUCCESS,
['filter' => $filter]
);
}