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