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