Как предотвратить автоматическую повторную выгрузку объекта в 1С, который уже был передан через обмен данными?

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

При работе с механизмами обмена данными в 1С:Предприятии часто возникает задача выгружать определенные объекты (документы, справочники) только один раз. То есть, если объект уже был выгружен в другую информационную базу, то при последующих изменениях в текущей базе его не следует автоматически регистрировать к обмену повторно. Автоматическое повторное формирование сообщения обмена для уже переданного объекта может привести к избыточному трафику, ошибкам при загрузке или нежелательному обновлению данных в принимающей базе. Мы рассмотрим различные подходы к решению этой проблемы, а также проанализируем, почему некоторые интуитивные методы оказываются неэффективными.

Почему стандартные механизмы и "Номер отправленного сообщения" не подходят для отслеживания факта выгрузки?

Первоначальная идея, которая часто приходит в голову при столкновении с такой задачей, это использовать поле НомерОтправленного, которое можно увидеть во вкладке "регистрация изменений для объекта" или в таблице изменений плана обмена (Документы.ТвойДокумент.Изменения). Интуитивно кажется, что наличие ненулевого номера может служить признаком того, что объект уже был выгружен. Однако, давайте разберем, почему этот подход ошибочен и не позволяет надежно отслеживать факт выгрузки.

Во-первых, НомерОтправленного служит для разделения "версий" измененных данных и разрешения коллизий при обмене, а не как флаг "объект выгружен". Это порядковый номер сообщения обмена, в котором данный объект был отправлен. Он является частью механизма обмена для обеспечения целостности и последовательности данных.

Во-вторых, и это критически важно, после успешной загрузки данных удаленной стороной и обработки ответного сообщения, механизм обмена очищает таблицу изменений плана обмена. Это означает, что записи, связанные с переданными объектами, удаляются, и поле НомерОтправленного (или аналогичные данные) для этого объекта в регистре изменений обнуляется или перестает существовать в контексте зарегистрированных изменений. Таким образом, при следующем изменении объекта, он снова будет зарегистрирован с НомерСообщения = 0, как будто он выгружается впервые. Это полностью исключает возможность использования этого поля для долговременного отслеживания факта предыдущей выгрузки.

В-третьих, можно подумать об использовании служебных регистров сведений, таких как СоответствиеОбъектовДляОбмена, СоответствияОбъектовИнформационныхБаз или ПубличныеИдентификаторы (в зависимости от версии БСП). Эти регистры действительно хранят информацию о связи объектов между базами. Однако, эти регистры в основном используются на стороне *приемника* для поиска и сопоставления объектов при *загрузке* данных, а не на стороне *источника* для отслеживания факта *выгрузки*. На стороне источника эти регистры могут быть пустыми или не содержать нужной информации о том, какие именно объекты были отправлены в конкретный узел обмена. Следовательно, они не являются надежным инструментом для решения нашей задачи.

Итак, мы приходим к выводу, что встроенных механизмов, которые напрямую решали бы задачу однократной автоматической выгрузки объекта, нет. Нам потребуется разработать собственное решение.

Решение 1: Использование собственного регистра сведений для отслеживания факта выгрузки

Самый надежный и прозрачный способ решить эту проблему – это завести собственный регистр сведений, который будет хранить информацию о том, какие объекты и в какой узел обмена уже были успешно выгружены. Этот подход дает нам полный контроль над логикой и позволяет четко отслеживать необходимый статус объекта.

Разберем по шагам, как это можно реализовать:

  1. Создание нового регистра сведений:

    Нам потребуется создать новый регистр сведений в конфигурации. Предложим ему имя, например, ОбъектыУжеВыгружены. Этот регистр должен быть:

    • Непериодическим: Нас интересует только текущий факт выгрузки, а не его история во времени.
    • Независимым: Его записи не должны зависеть от других объектов.

    У регистра должны быть следующие измерения:

    • ОбъектИсточника (Тип: СправочникСсылка.ТвойСправочник, ДокументСсылка.ТвойДокумент, т.е. Ссылка на объект, который мы отслеживаем).
    • УзелОбмена (Тип: ПланОбменаСсылка.ТвойПланОбмена). Это позволит отслеживать выгрузку в конкретный узел, если обменов несколько.

    Ресурс может быть не нужен, или можно добавить булевый ресурс, например, Выгружен, хотя наличие записи в регистре уже будет означать "выгружен".

  2. Регистрация факта выгрузки в регистре сведений:

    Нам необходимо зафиксировать факт выгрузки объекта после того, как он был успешно передан. Это можно сделать несколькими способами:

    • В обработчике обмена данными: Самый логичный момент – после завершения выгрузки данных, но до очистки регистра изменений. В стандартных обменах (КД2/КД3) обычно есть возможность добавить код после выгрузки или на этапе конвертации данных. Если вы используете собственный обработчик выгрузки, то запись в регистр нужно делать там.

      Пример (псевдокод, нужно адаптировать к вашему механизму выгрузки):

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

  3. Проверка факта выгрузки перед регистрацией изменений:

    Теперь, когда у нас есть механизм для отслеживания выгруженных объектов, мы можем использовать его для предотвращения повторной регистрации к обмену. Это делается в правилах регистрации объектов для обмена (обычно в обработчике "Перед обработкой" или "ПриЗаписи" плана обмена, если это управляемые формы).

    Мы посмотрим на пример кода для обработчика "ПередОбработкой" в правилах регистрации:

    
    // Проверяем, если это не процесс выгрузки (то есть идет обычная запись объекта)
    // и объект не должен быть зарегистрирован повторно.
    // Переменная 'Выгрузка' - это параметр обработчика, который указывает,
    // выполняется ли операция в режиме выгрузки данных.
    Если Не Выгрузка Тогда
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ ПЕРВЫЕ 1
            |    ОбъектыУжеВыгружены.ОбъектИсточника
            |ИЗ
            |    РегистрСведений.ОбъектыУжеВыгружены КАК ОбъектыУжеВыгружены
            |ГДЕ
            |    ОбъектыУжеВыгружены.ОбъектИсточника = &ОбъектИсточника
            |    И ОбъектыУжеВыгружены.УзелОбмена = &УзелОбмена";
                
        Запрос.УстановитьПараметр("ОбъектИсточника", Объект.Ссылка);
        Запрос.УстановитьПараметр("УзелОбмена", Параметры.Узел); // Параметры.Узел - это текущий узел обмена
                
        РезультатЗапроса = Запрос.Выполнить();
                
        Если Не РезультатЗапроса.Пустой() Тогда
            // Если запись найдена, значит объект уже был выгружен в этот узел
            // Отменяем регистрацию изменений для данного объекта
            Отказ = Истина;
        КонецЕсли;
    КонецЕсли;
    

    В этом коде:

    • Объект – это объект данных, который изменяется и потенциально подлежит регистрации.
    • Параметры.Узел – это ссылка на узел плана обмена, для которого происходит регистрация изменений.
    • Отказ = Истина – ключевая строка, которая отменяет регистрацию изменений для текущего объекта.

    Таким образом, мы гарантируем, что объект будет зарегистрирован к обмену только если он еще не помечен в нашем регистре как выгруженный для данного узла.

Решение 2: Использование фиктивного узла обмена для отметки объектов

Альтернативный подход, который может быть полезен, если вы хотите избежать создания нового регистра сведений в конфигурации, это использование существующего механизма регистрации изменений плана обмена, но с применением "фиктивного" узла. Эта идея предполагает, что мы будем использовать факт регистрации объекта по некому специальному узлу как индикатор того, что объект уже был "выгружен".

Разберем принцип работы этого решения:

  1. Создание "фиктивного" узла обмена:

    Мы создаем в нашем плане обмена новый узел, который на самом деле не предназначен для реального обмена данными с какой-либо базой. Назовем его, например, "УзелДляОтметкиВыгруженных". Этот узел не будет использоваться в настроенных транспортах обмена, и его сообщения никогда не будут отправляться или приниматься. Его единственная цель – хранить записи о регистрации изменений для объектов.

  2. Регистрация объекта к обмену по фиктивному узлу:

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

    Пример (псевдокод, нужно интегрировать в логику после реальной выгрузки):

    
    // Получаем ссылку на фиктивный узел
    ФиктивныйУзел = ПланыОбмена.ТвойПланОбмена.НайтиПоКоду("ФиктивныйУзелКод"); 
            
    // Регистрируем объект по фиктивному узлу
    ПланыОбмена.ЗарегистрироватьИзменения(ФиктивныйУзел, ВыгруженныйОбъект);
    
  3. Проверка наличия регистрации по фиктивному узлу в правилах регистрации для реального обмена:

    Теперь, в обработчике "ПередОбработкой" правил регистрации для *реальных* узлов обмена, мы будем проверять, есть ли запись о регистрации изменений для текущего объекта по нашему "УзлуДляОтметкиВыгруженных". Если такая запись есть, значит объект уже был выгружен ранее, и мы отменяем его регистрацию для текущего реального обмена.

    Пример кода запроса для проверки (адаптированный код из форума):

    
    // Получаем ссылку на фиктивный узел (можно получить один раз и передать параметром)
    ФиктивныйУзел = ПланыОбмена.ТвойПланОбмена.НайтиПоКоду("ФиктивныйУзелКод");
            
    Если Не Выгрузка И ЗначениеЗаполнено(ФиктивныйУзел) Тогда
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ ПЕРВЫЕ 1
            |    ДокументИзменения.Ссылка
            |ИЗ
            |    Документы.Договор.Изменения КАК ДокументИзменения
            |ГДЕ
            |    ДокументИзменения.Узел = &ФиктивныйУзел
            |    И ДокументИзменения.Ссылка = &Ссылка";
                
        Запрос.УстановитьПараметр("ФиктивныйУзел", ФиктивныйУзел);
        Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);
                
        Результат = Запрос.Выполнить();
        Если Не Результат.Пустой() Тогда
            // Если объект уже зарегистрирован по фиктивному узлу,
            // значит он был выгружен ранее, и мы отменяем его регистрацию для текущего узла.
            Отказ = Истина;
        КонецЕсли;
    КонецЕсли;
    

    Здесь Документы.Договор.Изменения – это таблица регистрации изменений для конкретного вида документа. Вам нужно будет адаптировать это под свой тип объекта.

Плюсы и минусы этого подхода:

Как реализовать проверку в обработчике "ПередОбработкой" правил регистрации

Независимо от выбранного метода (собственный регистр или фиктивный узел), основная логика предотвращения регистрации изменений будет сосредоточена в обработчике "ПередОбработкой" правил регистрации объекта. Давайте подробнее рассмотрим его структуру и параметры.

Общая структура обработчика:


Процедура ПередОбработкой(Объект, Параметры, Отказ, Выгрузка)
    // Здесь располагается наша логика проверки
    // ...
    
    // Если по результатам проверки объект не должен быть зарегистрирован,
    // устанавливаем Отказ = Истина.
    // Если Отказ не установлен в Истина, регистрация продолжится.
КонецПроцедуры

Ключевые параметры:

Пример кода для собственного регистра в "ПередОбработкой":

Давайте еще раз рассмотрим пример кода, который бы использовал наш собственный регистр ОбъектыУжеВыгружены:


Процедура ПередОбработкой(Объект, Параметры, Отказ, Выгрузка)
    // Мы хотим предотвратить автоматическую регистрацию только при обычной записи/проведении объекта,
    // а не когда система уже находится в процессе выгрузки данных.
    // Это позволяет, например, вручную зарегистрировать объект, если очень нужно.
    Если Не Выгрузка Тогда
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ ПЕРВЫЕ 1
            |    ОбъектыУжеВыгружены.ОбъектИсточника
            |ИЗ
            |    РегистрСведений.ОбъектыУжеВыгружены КАК ОбъектыУжеВыгружены
            |ГДЕ
            |    ОбъектыУжеВыгружены.ОбъектИсточника = &ОбъектИсточника
            |    И ОбъектыУжеВыгружены.УзелОбмена = &УзелОбмена";
        
        Запрос.УстановитьПараметр("ОбъектИсточника", Объект.Ссылка);
        Запрос.УстановитьПараметр("УзелОбмена", Параметры.Узел); // Узел, для которого идет текущая регистрация
        
        Попытка
            РезультатЗапроса = Запрос.Выполнить();
            Если Не РезультатЗапроса.Пустой() Тогда
                // Если запись найдена, это значит, что объект уже был выгружен в этот узел ранее.
                // Следовательно, мы отменяем автоматическую регистрацию изменений.
                Отказ = Истина;
            КонецЕсли;
        Исключение КакогоЛибоИсключения
            // В случае ошибки выполнения запроса (например, регистр еще не создан,
            // или некорректно заполнены параметры), можем обработать ее.
            // Для целей безопасности можно установить Отказ = Истина, чтобы не допустить
            // неконтролируемой выгрузки, или записать в журнал регистрации.
            ЗаписатьВЖурналРегистрации("Ошибка при проверке выгруженных объектов: " + КакогоЛибоИсключения.Описание);
            // Отказ = Истина; // Можно раскомментировать, если хотите, чтобы в случае ошибки объект не регистрировался
        КонецПопытки;
    КонецЕсли;
КонецПроцедуры

В этом примере мы выполняем запрос к нашему регистру сведений ОбъектыУжеВыгружены. Если для текущего объекта (Объект.Ссылка) и текущего узла обмена (Параметры.Узел) найдена запись, это означает, что объект уже был выгружен. В этом случае мы устанавливаем Отказ = Истина, что предотвращает регистрацию изменений.

Важные моменты при реализации:

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

← На главную