При проектировании обмена данными между конфигурациями «1С» с помощью инструмента «Конвертация данных 2.1» (КД 2.1) разработчики часто сталкиваются со сложной задачей. Типичный сценарий: в базе-источнике существует документ, который делает движения по регистру (например, по регистру накопления). В базе-приемнике сам этот документ не создается, а его данные переносятся напрямую в записи регистра сведений. Возникает закономерный вопрос: как сделать так, чтобы при отмене проведения или полном физическом удалении документа в источнике соответствующие записи регистра в приемнике также гарантированно удалялись?
Если не предусмотреть этот механизм, база-приемник быстро наполнится «мусорными» записями, которые потеряли связь с первичными документами. В рамках этой статьи мы подробно разберем несколько проверенных на практике способов решения данной проблемы — от стандартных механизмов планов обмена до программных методов с использованием запросов к таблицам изменений.
Прежде чем перейти к реализации, проанализируем, как платформа «1С:Предприятие» и конфигурация «Конвертация данных» работают с изменениями регистров. Существует два принципиально разных сценария поведения системы в источнике:
УдалениеОбъекта, содержащий его уникальный идентификатор (UUID). При этом физические записи регистра в источнике также исчезают, но стандартный механизм регистрации изменений по регистру может отработать неочевидным образом.Рассмотрим пошагово, как настроить правила обмена для корректной обработки обоих сценариев.
Этот способ является наиболее простым и предпочтительным, если обмен настроен через планы обмена с авторегистрацией, а перенос осуществляется по принципу «Регистр в Регистр».
Когда документ в базе-источнике распроводится, его движения очищаются. При этом в таблице регистрации изменений плана обмена регистрируется факт изменения набора записей регистра для данного регистратора. Поскольку физических записей в регистре больше нет, при выгрузке этого изменения КД 2.1 автоматически формирует в XML-файле специальный узел с признаком пустого набора.
Выясним, как выглядит структура такого узла в файле обмена:
<НаборЗаписейРегистра Нпп="1" Тип="РегистрНакопленияЗапись.Взаиморасчеты" ИмяПравила="Взаиморасчеты" ПустойНабор="true">
<Отбор>
<Свойство Имя="Регистратор" Тип="ДокументСсылка.РеализацияТоваровУслуг">
<Ссылка>
<Свойство Имя="{УникальныйИдентификатор}">
<Значение>ce4c5983-22de-11e9-94f6-005056851d41</Значение>
</Свойство>
</Ссылка>
</Свойство>
</Отбор>
</НаборЗаписейРегистра>
Обратим внимание на ключевой атрибут ПустойНабор="true". При чтении этого узла в базе-приемнике обработка загрузки (например, УниверсальныйОбменДаннымиXML) автоматически выполнит следующие действия:
Важное условие для настройки ПКО: Чтобы этот механизм сработал, в правилах конвертации объектов (ПКО) должно быть настроено сопоставление регистра-источника с регистром-приемником. На стороне приемника обязательно должен обрабатываться объект типа РегистрСведений.Запись, так как стандартные обработки загрузки не умеют напрямую обрабатывать запись регистра накопления, если приемником не является аналогичный регистр накопления.
Ситуация усложняется, если документ в источнике удаляется полностью (непосредственно, мимо корзины/пометки на удаление). В этот момент в базе данных источника физически больше нет ни документа, ни его движений. Ссылка на документ превращается в «битую» (объект не найден).
Тем не менее, в таблице регистрации изменений плана обмена для этого регистра все еще остаются записи по измерениям (если это независимый регистр сведений) или по регистратору. Нам нужно вытащить эти "осиротевшие" записи, передать их в приемник и очистить там.
Рассмотрим, как с помощью запроса найти записи, которые были зарегистрированы к обмену, но уже отсутствуют в физической таблице регистра. Напишем и проанализируем следующий запрос:
ВЫБРАТЬ
ТвойРегистрИзменения.Измерение1 КАК Измерение1,
ТвойРегистрИзменения.Измерение2 КАК Измерение2,
ТвойРегистрИзменения.Измерение3 КАК Измерение3
ИЗ
РегистрСведений.ТвойРегистр.Изменения КАК ТвойРегистрИзменения
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ТвойРегистр КАК ТвойРегистр
ПО ТвойРегистр.Измерение1 = ТвойРегистрИзменения.Измерение1
И ТвойРегистр.Измерение2 = ТвойРегистрИзменения.Измерение2
И ТвойРегистр.Измерение3 = ТвойРегистрИзменения.Измерение3
ГДЕ
ТвойРегистрИзменения.Узел = &Узел
И ТвойРегистр.Измерение1 ЕСТЬ NULL
Разберем логику работы запроса по шагам:
РегистрСведений.ТвойРегистр.Изменения. Она содержит список наборов ключевых полей (измерений), которые были изменены и ожидают выгрузки для конкретного узла обмена.ЛЕВОЕ СОЕДИНЕНИЕ с реальной физической таблицей регистра по всем ключевым измерениям.ГДЕ мы накладываем условие, что значение ключевого поля из реальной таблицы ЕСТЬ NULL. Это означает, что запись была зарегистрирована к обмену, но физически в регистре ее больше нет — то есть она была удалена.Получив этот список удаленных записей, мы можем принудительно выгрузить их в КД через произвольный алгоритм (например, в правилах выгрузки данных — ПВД), передав их в специальное ПКО удаления. После успешной выгрузки нам необходимо вручную очистить регистрацию этих изменений в источнике, чтобы не выгружать их повторно:
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Создаем набор записей для очистки регистрации
НаборЗаписей = РегистрыСведений.ТвойРегистр.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Измерение1.Установить(Выборка.Измерение1);
НаборЗаписей.Отбор.Измерение2.Установить(Выборка.Измерение2);
НаборЗаписей.Отбор.Измерение3.Установить(Выборка.Измерение3);
// Прочитаем пустой набор (убедимся, что его нет физически)
НаборЗаписей.Прочитать();
// Удаляем регистрацию изменений для данного пустого набора
ПланыОбмена.УдалитьРегистрациюИзменений(УзелОбмена, НаборЗаписей);
КонецЦикла;
Если в плане обмена зарегистрировано физическое удаление самого документа-регистратора, в XML-файл выгружается узел УдалениеОбъекта. На стороне приемника при чтении этого узла мы можем перехватить событие удаления и программно очистить подчиненные ему регистры.
Для реализации этого подхода откроем настройки правил обмена в КД 2.1 и перейдем в глобальный обработчик конвертации «При получении информации об удалении». Напишем в нем следующий алгоритм:
// Проверяем тип удаляемого объекта в источнике
Если ТипЗнч(ДанныеУдаления) = Тип("УдалениеОбъекта") Тогда
// Выясняем, относится ли удаляемый объект к нашему документу
ИмяТипаИсточника = НайтиТипПоСсылкаИсточника(ДанныеУдаления.Ссылка);
Если ИмяТипаИсточника = "ДокументСсылка.РеализацияТоваровУслуг" Тогда
// В приемнике ищем записи регистра сведений, связанные с этой ссылкой
// Допустим, в приемнике в регистре сведений "ДвиженияДокументов" есть измерение "Документ"
НаборЗаписей = РегистрыСведений.ДвиженияДокументов.СоздатьНаборЗаписей();
// Так как ссылка в приемнике может не существовать физически,
// мы можем искать по Уникальному Идентификатору (UUID) или по сопоставленной ссылке
СсылкаПриемника = ПолучитьСсылкуПриемникаПоИсточнику(ДанныеУдаления.Ссылка);
Если ЗначениеЗаполнено(СсылкаПриемника) Тогда
НаборЗаписей.Отбор.Документ.Установить(СсылкаПриемника);
// Записываем пустой набор записей (это удалит старые записи из БД)
НаборЗаписей.Записать(Истина);
КонецЕсли;
КонецЕсли;
КонецЕсли;
Этот способ крайне надежен, так как он гарантирует, что при физическом исчезновении документа из системы все его «хвосты» в связанных регистрах сведений приемника будут гарантированно зачищены в момент загрузки пакета данных.
Если вы не используете стандартную автоматическую регистрацию движений регистра, а выгружаете данные через произвольный запрос в ПВД (Правила выгрузки данных), вы можете принудительно инициировать генерацию пустого набора записей с помощью встроенной функции КД 2.1 — ВыгрузитьРегистр().
Проанализируем ситуацию: в ПВД мы перебираем документы, которые были изменены или помечены на удаление. Если документ помечен на удаление или распроведен, мы можем явно вызвать процедуру выгрузки пустого регистра, передав туда пустую выборку или установив отбор по регистратору, по которому в базе заведомо нет записей:
// Пример кода в обработчике ПВД "Перед выгрузкой"
Если ДокументОбъект.Проведен = Ложь Тогда
// Передаем пустую таблицу или просто вызываем выгрузку регистра с отбором.
// КД увидит, что физических записей по данному отбору нет,
// и сформирует в XML узел с признаком ПустойНабор="true".
ВходящиеДанные = Новый Структура;
ВходящиеДанные.Вставить("Регистратор", ДокументОбъект.Ссылка);
ВыгрузитьРегистр(ДокументОбъект.Ссылка, , ВходящиеДанные, "ИмяПравилаКонвертацииРегистра");
КонецЕсли;
При таком подходе на стороне приемника отработает стандартный платформенный алгоритм замещения набора, который полностью удалит старые записи по данному регистратору без написания сложного программного кода в обработчиках загрузки.
Подводя итоги, дадим несколько практических советов по выбору оптимального решения:
РегистрНакопленияЗапись (Источник) в РегистрСведенийЗапись (Приемник). Система сама сделает всю работу с помощью признака ПустойНабор="true" при отмене проведения документов.ЛЕВОГО СОЕДИНЕНИЯ, чтобы своевременно выявлять и вычищать записи, удаленные в базе-источнике.