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