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