Как правильно отфильтровать данные при обмене, если объект перемещен из разрешенной папки?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При настройке обмена данными между базами 1С часто возникает классическая архитектурная дилемма: как ограничить выгрузку только определенным набором данных (например, из конкретной «Папки 44») и при этом гарантировать, что при выходе объекта из этой папки он корректно исчезнет из базы-приемника. Если просто перестать регистрировать изменения по объекту, в базе-приемнике останется «фантомная» запись, которая больше не актуальна. Разберем детально, как решить эту задачу эффективно и без избыточной нагрузки на систему.

Анализ проблемы и стандартных подходов

Рассмотрим ситуацию: объект Номенклатура находится в разрешенной группе. Он успешно мигрировал в Базу 2. Затем пользователь в Базе 1 перемещает его в запрещенную группу. Если в правилах регистрации стоит жесткий фильтр по родителю, то это изменение (перемещение) не будет зарегистрировано для узла обмена. В итоге База 2 никогда не узнает, что объект сменил родителя, и он останется там в старой папке. Проанализируем основные способы решения этой проблемы.

Способ 1. Использование обработчика «ПриОтправкеДанныхПодчиненному»

Этот метод считается одним из самых простых в реализации. Логика заключается в том, чтобы принимать решение об отправке объекта непосредственно в момент формирования пакета выгрузки. Даже если объект зарегистрирован к обмену, мы можем подменить его на УдалениеОбъекта прямо «на лету».

Рассмотрим пример кода в модуле ПланОбмена:


Процедура ПриОтправкеДанныхПодчиненному(ЭлементДанных, ОтправкаЭлемента)
    
    // Проверяем, что отправляемый объект — это справочник Номенклатура
    Если ТипЗнч(ЭлементДанных) = Тип("СправочникОбъект.Номенклатура") Тогда
        
        // Определяем нашу разрешенную папку (лучше использовать доп. свойство или предопределенный элемент)
        Папка44 = Справочники.Номенклатура.НайтиПоНаименованию("Папка44");
        
        // Если элемент не принадлежит нужной иерархии
        Если Не ЭлементДанных.ПринадлежитЭлементу(Папка44) Тогда
            // Вместо данных отправляем информацию об удалении
            ЭлементДанных = Новый УдалениеОбъекта(ЭлементДанных.Ссылка);
        КонецЕсли;
        
    КонецЕсли;

КонецПроцедуры

Важный нюанс: как заметили коллеги в обсуждении, этот способ может породить «миллион удалений». Если в Базе 1 меняется номенклатура в других (запрещенных) папках, система будет пытаться отправить УдалениеОбъекта для каждой такой позиции, даже если её никогда не было в Базе 2. Это создает лишний трафик и нагрузку.

Способ 2. Регистрация через вспомогательный регистр («Зеркало обмена»)

Чтобы избежать массовой отправки ненужных удалений, профессиональным подходом считается использование служебного регистра сведений. Назовем его СоставВыгруженныхОбъектов. В нем мы будем хранить информацию о том, какие ссылки уже «уехали» в конкретный узел обмена.

Разберем алгоритм работы по шагам:

  1. Создаем регистр сведений с измерениями УзелОбмена и Объект (тип Ссылка).
  2. В подписке на событие ПриЗаписи номенклатуры регистрируем объект на узле обмена всегда, если он изменился (без фильтра по папке).
  3. В обработчике ПриОтправкеДанныхПодчиненному выполняем проверку:

Процедура ПриОтправкеДанныхПодчиненному(ЭлементДанных, ОтправкаЭлемента)
    
    Если ТипЗнч(ЭлементДанных) = Тип("СправочникОбъект.Номенклатура") Тогда
        
        НужныйРодитель = ЭлементДанных.ПринадлежитЭлементу(Папка44);
        ОбъектУжеБылВыгружен = РегистрыСведений.СоставВыгруженныхОбъектов.СоздатьМенеджерЗаписи();
        ОбъектУжеБылВыгружен.Узел = ЭтотОбъект.Ссылка;
        ОбъектУжеБылВыгружен.Объект = ЭлементДанных.Ссылка;
        ОбъектУжеБылВыгружен.Прочитать();
        
        Если НужныйРодитель Тогда
            // Если объект в нужной папке — выгружаем и фиксируем это в регистре
            Если Не ОбъектУжеБылВыгружен.Выбран() Тогда
                ОбъектУжеБылВыгружен.Узел = ЭтотОбъект.Ссылка;
                ОбъектУжеБылВыгружен.Объект = ЭлементДанных.Ссылка;
                ОбъектУжеБылВыгружен.Записать();
            КонецЕсли;
        Иначе
            // Если не в папке, но раньше там был (есть запись в регистре)
            Если ОбъектУжеБылВыгружен.Выбран() Тогда
                ЭлементДанных = Новый УдалениеОбъекта(ЭлементДанных.Ссылка);
                ОбъектУжеБылВыгружен.Удалить(); // Стираем память о выгрузке
            Иначе
                // Объект не в папке и раньше не выгружался — игнорируем отправку
                ОтправкаЭлемента = ОтправкаЭлементаДанных.Игнорировать;
            КонецЕсли;
        КонецЕсли;
        
    КонецЕсли;

КонецПроцедуры

Этот метод гарантирует, что УдалениеОбъекта отправится только один раз — именно в тот момент, когда товар «покинул» разрешенную зону. Все последующие изменения этого товара в «чужих» папках не вызовут никакой реакции обмена.

Способ 3. Метод «Мягкого удаления»

Выясним причину, почему физическое удаление из Базы 2 может быть опасным. Если номенклатура из «Папки 44» уже была использована в документах Базы 2, то при получении сообщения УдалениеОбъекта ссылка в документах станет «битой» (Объект не найден). Это нарушает целостность учета.

Проанализируем альтернативу: вместо удаления можно отправлять объект с признаком Заблокирован или перемещать его в специальную архивную папку в приемнике. Для этого:

Способ 4. Очистка через сравнение списков

Если данных очень много, а онлайн-контроль перемещений слишком нагружает систему, можно использовать регламентную сверку. Разберем этот сценарий:

  1. Раз в сутки База 2 выгружает список всех GUID номенклатуры, которые у нее есть в иерархии «Папки 44».
  2. База 1 получает этот список и проверяет: «А все ли эти GUID у меня до сих пор находятся в Папке 44?».
  3. Если База 1 находит расхождения, она принудительно регистрирует эти элементы на узле обмена и в обработчике выгрузки отправляет команды на удаление только для них.

Важные рекомендации по реализации

При реализации любого из методов рекомендуем придерживаться следующих правил:

  1. Избегайте поиска по наименованию: Код НайтиПоНаименованию("Папка44") крайне ненадежен. Лучше использовать поиск по уникальному идентификатору (GUID), дополнительный реквизит или признак в коде.
  2. Используйте подписки на события: Чтобы не вносить изменения в типовые модули справочников, используйте подписку на событие ПриЗаписи.
  3. Проверяйте иерархию быстро: Метод ПринадлежитЭлементу() работает на уровне платформенного SQL-запроса и достаточно эффективен даже для глубокой вложенности.

Выбор конкретного метода зависит от объемов ваших данных и требований к чистоте базы-приемника. Для небольших баз достаточно Способа 1, для высоконагруженных систем с жестким контролем трафика оптимальным будет Способ 2 с использованием вспомогательного регистра.

← На главную