При работе с распределенными информационными базами (РИБ) разработчики часто сталкиваются с необходимостью оптимизации трафика и объема передаваемых данных. Популярным решением является отключение авторегистрации для большинства объектов конфигурации в главном узле. Однако, когда возникает задача выборочно передать конкретный документ из главного узла в периферийный, мы сталкиваемся с серьезной технической проблемой. Если просто зарегистрировать сам документ, в узле-приемнике мы получим битые ссылки («Объект не найден») вместо значений его реквизитов, а также полностью пустые движения по регистрам накопления, сведений или бухгалтерии.
В этой статье мы подробно разберем, как правильно реализовать механизм ручной регистрации документов со всеми зависимыми данными, выясним причины возникновения технических сложностей и рассмотрим эффективные программные решения.
Давайте проанализируем внутреннее устройство механизмов обмена платформы «1С:Предприятие 8». План обмена фиксирует изменения конкретных ссылок. Когда мы регистрируем документ на узле обмена, платформа запоминает только то, что изменен сам документ. При формировании файла обмена в него выгружаются реквизиты шапки и табличных частей этого документа.
Если реквизит документа имеет ссылочный тип (например, справочник Контрагенты или Номенклатура), в XML-файл обмена записывается уникальный идентификатор (GUID) этого элемента. Когда периферийная база считывает этот файл, она пытается найти элемент с таким GUID в своей локальной базе данных. Если авторегистрация для справочников отключена и данный элемент ранее не передавался, система не сможет его сопоставить и создаст «битую» ссылку со строковым представлением «Объект не найден...».
Аналогичная ситуация происходит и с регистрами. Движения документа (записи в регистрах накопления, сведений, бухгалтерии) физически хранятся в отдельных таблицах базы данных, а не внутри самого документа. Если мы не зарегистрируем изменения по этим регистрам для узла обмена отдельно, платформа просто не выгрузит их в сообщение обмена. В результате в периферийной базе появится документ, который не делает никаких движений по учету.
Рассмотрим, как правильно зарегистрировать движения документа по всем регистрам, в которых он выступает регистратором. Передавать движения необходимо через объект типа НаборЗаписей с установленным отбором по регистратору. При этом считывать данные из базы или записывать их заново не требуется — достаточно подготовить пустой набор в памяти и передать его в план обмена.
Разберем по шагам универсальный алгоритм, который автоматически обходит все метаданные движений регистрируемого документа:
Процедура ЗарегистрироватьДвиженияДокумента(СсылкаНаДокумент, УзелОбмена) Export
МетаданныеДокумента = СсылкаНаДокумент.Метаданные();
// Обходим все движения, которые предусмотрены для данного документа в конфигураторе
Для Каждого МетаданныеДвижения Из МетаданныеДокумента.Движения Цикл
ИмяРегистра = МетаданныеДвижения.Имя;
// Определяем тип регистра и создаем соответствующий набор записей
Если Метаданные.РегистрыНакопления.Найти(ИмяРегистра) <> Неопределено Тогда
НаборЗаписей = РегистрыНакопления[ИмяРегистра].СоздатьНаборЗаписей();
ИначеЕсли Метаданные.РегистрыСведений.Найти(ИмяРегистра) <> Неопределено Тогда
НаборЗаписей = РегистрыСведений[ИмяРегистра].СоздатьНаборЗаписей();
ИначеЕсли Метаданные.РегистрыБухгалтерии.Найти(ИмяРегистра) <> Неопределено Тогда
НаборЗаписей = РегистрыБухгалтерии[ИмяРегистра].СоздатьНаборЗаписей();
ИначеЕсли Метаданные.РегистрыРасчета.Найти(ИмяРегистра) <> Неопределено Тогда
НаборЗаписей = РегистрыРасчета[ИмяРегистра].СоздатьНаборЗаписей();
Иначе
Продолжить;
КонецЕсли;
// Устанавливаем отбор по нашему документу-регистратору
НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);
// Регистрируем изменения набора записей на узле обмена
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, НаборЗаписей);
КонецЦикла;
КонецПроцедуры
Этот код гарантирует, что все движения, сделанные документом в главной базе, будут успешно сериализованы и переданы в периферийный узел в том же сообщении обмена.
Для предотвращения появления битых ссылок нам необходимо зарегистрировать все справочники и другие ссылочные объекты, на которые ссылается документ в своих реквизитах и табличных частях. Однако здесь кроется серьезная опасность: если мы начнем бездумно регистрировать все ссылки подряд, мы можем спровоцировать «эффект домино». Ссылка из документа ведет на договор, ссылка из договора — на контрагента, из контрагента — на классификатор стран, и в итоге на обмен зарегистрируется половина информационной базы.
Чтобы избежать этой проблемы, мы должны разработать алгоритм рекурсивного обхода с жесткими ограничениями:
Пользователи, Классификаторы, Организации), которые должны быть одинаковыми во всех базах изначально.Рассмотрим пример функции, которая анализирует реквизиты объекта и формирует массив зависимых ссылок для регистрации:
Процедура ЗарегистрироватьСвязанныеСсылки(ОбъектСсылка, УзелОбмена, ЗарегистрированныеОбъекты = Неопределено, Глубина = 1)
Если ЗарегистрированныеОбъекты = Неопределено Тогда
ЗарегистрированныеОбъекты = Новый Соответствие;
КонецЕсли;
// Предотвращаем бесконечную рекурсию и ограничиваем глубину поиска
Если Глубина > 2 Или ЗарегистрированныеОбъекты[ОбъектСсылка] <> Неопределено Тогда
Возврат;
КонецЕсли;
ЗарегистрированныеОбъекты.Вставить(ОбъектСсылка, Истина);
// Проверяем тип объекта
МетаданныеОбъекта = ОбъектСсылка.Метаданные();
Если Не ОбщегоНазначения.ЭтоСправочник(МетаданныеОбъекта) Тогда
Возврат; // Нас интересуют в первую очередь элементы справочников
КонецЕсли;
// Регистрируем сам элемент
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, ОбъектСсылка);
// Обрабатываем родительские элементы для иерархических справочников
Если МетаданныеОбъекта.Иерархический Тогда
ТекущийРодитель = ОбъектСсылка.Родитель;
Пока ЗначениеЗаполнено(ТекущийРодитель) Цикл
ПланыОбмена.ЗарегистрироватьИзменения(УзелОбмена, ТекущийРодитель);
ТекущийРодитель = ТекущийРодитель.Родитель;
КонецЦикла;
КонецЕсли;
// Получаем объект для анализа его реквизитов
Попытка
ДанныеОбъекта = ОбъектСсылка.ПолучитьОбъект();
Исключение
Возврат; // Объект заблокирован или отсутствует
КонецПопытки;
Если ДанныеОбъекта = Неопределено Тогда
Возврат;
КонецЕсли;
// Обходим стандартные и обычные реквизиты объекта
Для Каждого Реквизит Из МетаданныеОбъекта.Реквизиты Цикл
ЗначениеРеквизита = ДанныеОбъекта[Реквизит.Имя];
Если Справочники.ТипВсеСсылки().СодержитТип(ТипЗнч(ЗначениеРеквизита)) И ЗначениеЗаполнено(ЗначениеРеквизита) Тогда
// Рекурсивно уходим на следующий уровень
ЗарегистрироватьСвязанныеСсылки(ЗначениеРеквизита, УзелОбмена, ЗарегистрированныеОбъекты, Глубина + 1);
КонецЕсли;
КонецДля;
// Аналогично можно обойти табличные части при необходимости
Для Каждого ТЧ Из МетаданныеОбъекта.ТабличныеЧасти Цикл
Для Каждого СтрокаТЧ Из ДанныеОбъекта[ТЧ.Имя] Цикл
Для Каждого РеквизитТЧ Из ТЧ.Реквизиты Цикл
ЗначениеРеквизита = СтрокаТЧ[РеквизитТЧ.Имя];
Если Справочники.ТипВсеСсылки().СодержитТип(ТипЗнч(ЗначениеРеквизита)) И ЗначениеЗаполнено(ЗначениеРеквизита) Тогда
ЗарегистрироватьСвязанныеСсылки(ЗначениеРеквизита, УзелОбмена, ЗарегистрированныеОбъекты, Глубина + 1);
КонецЕсли;
КонецДля;
КонецДля;
КонецДля;
КонецПроцедуры
Если ваша конфигурация разработана на основе Библиотеки стандартных подсистем (БСП), вам не обязательно писать собственные велосипеды для обхода метаданных. В подсистеме обмена данными БСП уже заложен штатный, высокооптимизированный механизм решения этой проблемы.
Для этого в правилах регистрации объектов (ПРО) для конкретных справочников (например, контрагенты, договоры, номенклатура) настраивается режим выгрузки «Выгружать при необходимости»:
Если задача носит административный характер и программирование не требуется, можно воспользоваться встроенной в БСП обработкой «Регистрация изменений для обмена данными»:
Таким образом, сочетание правильной регистрации движений через наборы записей, ограничение рекурсивного обхода ссылок или использование встроенных возможностей БСП позволяет полностью решить проблему миграции документов «в ручном режиме» без потери целостности данных.