При настройке регулярных обменов между конфигурациями 1С часто возникает архитектурная задача: в базе-источнике информация разделена по двум разным справочникам, а в базе-приемнике ее необходимо объединить в один объект. Типичный пример — перенос номенклатуры из устаревшей конфигурации «Управление торговлей», где параметры маркировки, классификаторы или специфические коды ТН ВЭД хранятся в отдельном подчиненном или связанном справочнике (назовем его Справочник2), в то время как в современной «Рознице» все эти сведения должны записываться напрямую в реквизиты справочника Номенклатура.
Если решать эту задачу «в лоб» созданием двух отдельных правил конвертации объектов (ПКО), мы неизбежно столкнемся с двойной записью объектов в приемнике, нарушением последовательности заполнения реквизитов и критическим падением производительности обмена. В рамках этой статьи мы подробно разберем, как правильно реализовать такое объединение, оптимизировать запросы к базе данных и решить сопутствующие проблемы с регистрацией изменений на узлах планов обмена.
Проанализируем ситуацию и разберем по шагам три эффективных метода решения этой задачи, начиная от базовой настройки через входящие данные и заканчивая продвинутым глобальным кэшированием для высоконагруженных систем.
Самый простой и логичный способ собрать данные из двух таблиц источника в рамках одного ПКО — это использование структуры ВходящиеДанные. Этот механизм позволяет программно вычислить недостающие реквизиты в одном месте и передать их для заполнения свойств объекта-приемника.
Выясним причину, почему нельзя делать запросы в обработчиках каждого отдельного правила конвертации свойств (ПКС). Если для каждого из 10 реквизитов писать собственный запрос в ПКС «Перед выгрузкой», то при выгрузке 1000 товаров система сделает 10 000 отдельных запросов к СУБД. Это приведет к зависанию базы данных. Поэтому мы напишем один единственный запрос, который соберет все нужные реквизиты из связанного справочника, и поместим их в структуру.
Рассмотрим по шагам, как это настроить в КД 2.1:
Номенклатура (например, Номенклатура_Основное).
// Инициализируем структуру для передачи параметров
ВходящиеДанные = Новый Структура;
// Задаем значение по умолчанию, чтобы ПКС гарантированно отработало
Значение = "ВычисленоПрограммно";
// Строим запрос к связанному справочнику
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спр2.ОсобенностьУчета КАК ОсобенностьУчета,
| Спр2.КодТНВЭД КАК КодТНВЭД,
| Спр2.ТабачнаяПродукция КАК ТабачнаяПродукция,
| Спр2.ОбувнаяПродукция КАК ОбувнаяПродукция
|ИЗ
| Справочник.Справочник2 КАК Спр2
|ГДЕ
| Спр2.Номенклатура = &НоменклатураСсылка";
Запрос.УстановитьПараметр("НоменклатураСсылка", Источник.Ссылка);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
// Заполняем входящие данные свойствами из полученной выборки
ВходящиеДанные.Вставить("ОсобенностьУчета", Выборка.ОсобенностьУчета);
ВходящиеДанные.Вставить("КодТНВЭД", Выборка.КодТНВЭД);
ВходящиеДанные.Вставить("ТабачнаяПродукция", Выборка.ТабачнаяПродукция);
ВходящиеДанные.Вставить("ОбувнаяПродукция", Выборка.ОбувнаяПродукция);
КонецЕсли;
ОсобенностьУчета, КодТНВЭД), который должен заполниться из связанного справочника, создадим ПКС. В поле «Источник» оставим пустоту, в поле «Приемник» укажем соответствующий реквизит.При такой настройке при выгрузке каждого элемента номенклатуры сначала выполнится наш запрос, результаты запишутся в структуру ВходящиеДанные, а затем платформа КД 2.1 автоматически распределит эти значения по соответствующим реквизитам объекта приемника.
Предыдущий метод отлично работает при небольших объемах данных. Однако при постоянном обмене через планы обмена, когда за один сеанс могут выгружаться десятки тысяч позиций номенклатуры в составе документов, даже один запрос на один объект в цикле может существенно замедлить процесс.
Для радикального ускорения выгрузки применим метод предварительного кэширования данных в оперативную память. Мы выберем все соответствия между номенклатурой и связанным справочником **одним пакетным запросом** в самом начале выгрузки и сохраним их в глобальный параметр конвертации.
Разберем по шагам реализацию этого алгоритма:
КэшДополнительныхСвойств.Соответствие:
// Создаем соответствие для быстрого поиска по ссылке-ключу
КэшСвойств = Новый Соответствие;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Спр2.Номенклатура КАК НоменклатураСсылка,
| Спр2.ОсобенностьУчета КАК ОсобенностьУчета,
| Спр2.КодТНВЭД КАК КодТНВЭД,
| Спр2.ТабачнаяПродукция КАК ТабачнаяПродукция,
| Спр2.ОбувнаяПродукция КАК ОбувнаяПродукция
|ИЗ
| Справочник.Справочник2 КАК Спр2
|ГДЕ
| НЕ Спр2.Номенклатура.ПометкаУдаления"; // Оптимизируем выборку при необходимости
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Формируем структуру параметров для конкретной номенклатуры
СтруктураПараметров = Новый Структура;
СтруктураПараметров.Вставить("ОсобенностьУчета", Выборка.ОсобенностьУчета);
СтруктураПараметров.Вставить("КодТНВЭД", Выборка.КодТНВЭД);
СтруктураПараметров.Вставить("ТабачнаяПродукция", Выборка.ТабачнаяПродукция);
СтруктураПараметров.Вставить("ОбувнаяПродукция", Выборка.ОбувнаяПродукция);
// Записываем структуру в кэш с ключом-ссылкой номенклатуры
КэшСвойств.Вставить(Выборка.НоменклатураСсылка, СтруктураПараметров);
КонецЦикла;
// Сохраняем кэш в глобальных параметрах обмена
Параметры.Вставить("КэшДополнительныхСвойств", КэшСвойств);
Номенклатура. В фиктивном ПКС, расположенном на самом верху списка свойств, нам больше не нужно делать тяжелые обращения к базе данных. Перепишем обработчик «Перед выгрузкой» следующим образом:
ВходящиеДанные = Новый Структура;
Значение = "ВычисленоИзКэша";
// Проверяем наличие глобального кэша в параметрах конвертации
Если Параметры.Свойство("КэшДополнительныхСвойств") И Параметры.КэшДополнительныхСвойств <> Неопределено Тогда
// Получаем данные из оперативной памяти без обращений к СУБД
ДанныеОбъекта = Параметры.КэшДополнительныхСвойств.Получить(Источник.Ссылка);
Если ДанныеОбъекта <> Неопределено Тогда
// Копируем значения свойств в структуру ВходящиеДанные
ЗаполнитьЗначенияСвойств(ВходящиеДанные, ДанныеОбъекта);
КонецЕсли;
КонецЕсли;
Этот подход сокращает нагрузку на сервер баз данных до абсолютного минимума. Вместо тысяч мелких запросов система выполняет ровно один пакетный запрос, после чего моментально сопоставляет данные в оперативной памяти.
Рассмотрим еще один интересный и гибкий архитектурный подход, который часто применяется профессиональными разработчиками при проектировании обменов. Если вы хотите полностью контролировать состав выгружаемой выборки, можно использовать Правила выгрузки данных (ПВД) с произвольным алгоритмом.
Идея заключается в том, чтобы перехватить стандартную выборку регистрируемых объектов номенклатуры, собрать их в массив, сформировать по ним один точечный запрос со всеми необходимыми левыми соединениями к подчиненным справочникам и выгрузить объекты программно.
Посмотрим на пример реализации данного алгоритма:
Номенклатура устанавливаем способ выборки данных — **Произвольный алгоритм**.
// Накапливаем ссылки на выгружаемые объекты номенклатуры
Если НЕ Параметры.Свойство("МассивНоменклатуры") Тогда
Параметры.Вставить("МассивНоменклатуры", Новый Массив);
КонецЕсли;
Параметры.МассивНоменклатуры.Добавить(Объект.Ссылка);
// Отменяем стандартную автоматическую выгрузку объекта
Отказ = Истина;
Если Параметры.Свойство("МассивНоменклатуры") И Параметры.МассивНоменклатуры.Количество() > 0 Тогда
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Код КАК Код,
| Номенклатура.Наименование КАК Наименование,
| Спр2.ОсобенностьУчета КАК ОсобенностьУчета,
| Спр2.КодТНВЭД КАК КодТНВЭД
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Справочник2 КАК Спр2
| ПО Спр2.Номенклатура = Номенклатура.Ссылка
|ГДЕ
| Номенклатура.Ссылка В (&МассивНоменклатуры)";
Запрос.УстановитьПараметр("МассивНоменклатуры", Параметры.МассивНоменклатуры);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// Формируем структуру входящих данных для передачи в ПКО
Реквизиты = Новый Структура("ОсобенностьУчета, КодТНВЭД");
ЗаполнитьЗначенияСвойств(Реквизиты, Выборка);
// Программно вызываем выгрузку объекта по конкретному ПКО
ВыгрузитьПоПравилу(Выборка.Ссылка, , Реквизиты, , "Номенклатура_Основное");
КонецЦикла;
КонецЕсли;
Важное примечание: Этот способ идеально подходит для пакетного переноса данных, однако следует помнить, что ПВД с произвольным алгоритмом могут не вызываться при некоторых сценариях интерактивного обмена или узкоспециализированных обработках он-лайн обмена, работающих исключительно по зарегистрированным изменениям планов обмена. Для постоянного обмена первый и второй методы являются более надежными.
При регулярном обмене разработчики часто сталкиваются с неприятной системной ошибкой: после успешной передачи файла обмена в базу-приемник регистрация изменений для объектов номенклатуры в источнике не снимается автоматически. При этом в XML-файле обмена в секции регистрации объектов можно увидеть пустые параметры сопоставления метаданных, например:
<ИнформацияОРегистрацииОбъекта ПустойНабор="false" ТипПриемника="" ТипИсточника="СправочникСсылка.Номенклатура" ... />
Выясним причину этого поведения. Пустое значение реквизита ТипПриемника="" означает, что в правилах конвертации (ПКО) для данного объекта не задан класс метаданных на стороне приемника или нарушена целостность структуры метаданных в самой КД 2.1.
Когда база-приемник получает пакет данных, она пытается записать сопоставление ГУИДов в специальный системный регистр сведений СоответствияОбъектовИнформационныхБаз. Поля «ТипИсточника» и «ТипПриемника» являются ключевыми измерениями этого регистра. Запись записи с пустым значением ТипПриемника вызывает скрытую транзакционную ошибку СУБД на стороне приемника. В результате пакет подтверждения отправляется обратно без отметки об успешном сопоставлении, и база-источник не снимает регистрацию изменений с этих объектов.
Решение проблемы:
СправочникСсылка.Номенклатура или аналогичный в приемнике).При постоянном обмене на планах обмена возникает важный нюанс: пользователь в базе-источнике может изменить свойства (например, изменить признак маркировки или код ТН ВЭД) в Справочнике2, при этом сам элемент справочника Номенклатура останется нетронутым.
Поскольку Справочник2 не выгружается в приемник напрямую и не имеет собственного активного ПКО с авторегистрацией, данные изменения никогда не попадут в базу-приемник, пока кто-нибудь принудительно не перепишет карточку номенклатуры. Чтобы решить эту проблему, настроим Правила регистрации объектов (ПРО) для Справочник2.
В конфигурации-источнике в модуле управления регистрацией (или в правилах регистрации КД) в обработчике события «При регистрации изменения» для Справочник2 напишем следующий скрипт:
// При изменении подчиненного или связанного справочника регистрируем основную номенклатуру
Если ЗначениеЗаполнено(Объект.Номенклатура) Тогда
// Получаем узел обмена, для которого выполняется регистрация
ПланыОбмена.ЗарегистрироватьИзменения(Узел, Объект.Номенклатура);
КонецЕсли;
// Отменяем регистрацию самого элемента Справочник2, чтобы не засорять очередь обмена
Отказ = Истина;
Данное простое правило гарантирует: любое изменение дополнительных реквизитов во вспомогательной таблице мгновенно инициирует повторную отправку основного объекта номенклатуры со всеми актуальными и объединенными свойствами.
В некоторых случаях использование структуры ВходящиеДанные может быть избыточным или неудобным (например, если логика заполнения свойств в приемнике должна зависеть от сложных условий, вычисляемых непосредственно в момент записи объекта). Для таких ситуаций разработчики используют механизм ПараметрыОбъекта.
В ПКС источника мы можем направить значение в параметр XML-узла, указав имя параметра в настройках правила. При чтении файла на стороне приемника эти параметры будут доступны в обработчиках ПКО. Рассмотрим пример кода на стороне приемника в обработчике «После загрузки»:
// Проверяем наличие переданных параметров объекта из XML
Если ПараметрыОбъекта <> Неопределено Тогда
// Получаем переданный из источника код ТН ВЭД
КодТНВЭД = ПараметрыОбъекта.Получить("ПереданныйКодТНВЭД");
Если ЗначениеЗаполнено(КодТНВЭД) Тогда
// Производим поиск или создание элементов в подчиненных классификаторах приемника
СсылкаНаКлассификатор = Справочники.КлассификаторТНВЭД.НайтиПоКоду(КодТНВЭД);
Если ЗначениеЗаполнено(СсылкаНаКлассификатор) Тогда
Объект.ТНВЭД = СсылкаНаКлассификатор;
// Выполняем запись объекта, если это необходимо
Объект.Записать();
КонецЕсли;
КонецЕсли;
КонецЕсли;
Такой подход обеспечивает максимальную гибкость интеграции, разделяя логику извлечения данных в источнике и логику их интеграции/сопоставления в базе-приемнике.