Событие сборки фильтра запроса к МойСклад
[OnBuildImportFilter] Изменение фильтра выборки из МойСклад
На более ранних версиях событие не вызывается.
Событие OnBuildImportFilter вызывается в последней точке сборки строки filter=..., которая уходит в запрос к API МойСклад. Обработчик получает фильтр разобранным на части и может добавить свои части, выбросить части модуля или заменить их.
Событие даёт изменить состав выборки, не трогая код модуля: например, отсечь товары-ТП по значению доп. поля, ограничить импорт своим набором групп или добавить любое условие, которого нет в настройках.
Событие срабатывает всегда, даже когда обработчиков нет. Если ни один обработчик не вернул массив, фильтр остаётся ровно таким, каким его собрал модуль.
В каких пайплайнах срабатывает
Новый пайплайн может принести новое значение. Меняйте фильтр только для известных вам контекстов, а для остальных возвращайте его без изменений.
| Пайплайн | Значение context | Где вызывается |
|---|---|---|
| Импорт сущностей (товары, модификации, услуги, комплекты) — агент | entity | CRbsMoyskladStocks::buildAgentFilterStringForEntity() |
Устаревший импорт по API-запросу — CRbsMoyskladStocks::importByApiRequest(), если его зовёт ваш код | entity | CRbsMoyskladStocks::getFilterString(); для модификаций — без частей фильтра по группам и доп. полю |
| Разовый импорт тех же сущностей, запущенный вручную — в диагностике (и превью запроса, и сам запуск) и со страницы настроек | entity_once | 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, entity_once, 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либо в нём лежит не массив.
Части, которые не являются непустой строкой, из ответа обработчика отбрасываются.
Любой тип результата, кроме ERROR, считается ответом. В том числе обработчик может вернуть просто массив ['filter' => $filter] без EventResult — Битрикс обернёт его сам, и модуль его учтёт.
Механизм событий Битрикс отдаёт всем обработчикам один и тот же исходный набор частей: второй обработчик не видит того, что сделал первый. Поэтому модуль сводит ответы по разницам, а не «побеждает последний».
Для каждого ответа модуль считает:
- какие исходные части обработчик выбросил (не вернул);
- какие части он добавил (которых не было в исходном наборе).
Итог = исходный набор − все выброшенные части + все добавленные части.
Свойства сведения
| Свойство | Что это значит на практике |
|---|---|
| Порядок регистрации обработчиков на результат не влияет | Два независимых кастома не затирают друг друга |
| Конфликт «один выбросил, другой оставил» решается в пользу выбрасывания | Часть, которую хоть кто-то убрал, в запрос не уйдёт |
| Дубли не добавляются | Одинаковую часть от двух обработчиков модуль добавит один раз |
| Исходные части сохраняют свой порядок, добавленные идут после них | Перестановка частей внутри ответа обработчика игнорируется |
Если обработчик вернёт ['filter' => []], из запроса пропадёт всё, включая инкрементальную часть updated>=... и фильтр по группам товаров. Агент уйдёт за полным каталогом МойСклад, и в логе об этом не будет ни строки.
Это законный способ сказать «фильтра быть не должно», и модуль его не запрещает. Но не возвращайте пустой массив по ветке ошибки — верните пришедший фильтр без изменений или EventResult::ERROR.
Модуль перехватывает исключение обработчика молча: ответы обработчиков, зарегистрированных до упавшего, применяются, обработчики после него не вызываются, импорт продолжается с тем фильтром, что получился. Записи в лог модуля об этом нет — если вам важно знать о своих ошибках, ловите их сами.
//Пример кода нужно вставить в файл 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 / entity_once / 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 => 'archived=true;archived=false;updated>=2026-09-21 10:00:00', // собственные части пайплайна одной строкой
1 => 'pathName~=Каталог;pathName~=Распродажа', // фильтр по группам товаров
2 => 'https://api.moysklad.ru/.../attributes/<id>=true', // фильтр по доп. полю
]
Условие type=... в собственные части импорта сущностей попадает только для модификаций (type=variant). Без импорта архивных нет и частей archived=....
Фильтр по группам — один элемент массива, даже если групп несколько: условия склеены через ;. Для модификаций условия имеют вид productFolder=<href группы>, для остальных сущностей — pathName~=<имя группы>.
Обратите внимание: в пайплайнах entity, entity_once, prices, bundlestocks, check и diagnostic собственные части пайплайна (type=..., archived=..., updated>=...) приходят одной склеенной строкой в первом элементе. Выбросить из неё только updated>=..., убрав элемент массива, нельзя — придётся заменить всю строку целиком.
В пайплайне deactivate так приходит только деактивация архивных. При деактивации по доп. полю и по группам части приходят по одной — каждое условие (archived=true, archived=false, type=..., productFolder!=..., 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]
);
}