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