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