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