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