При переходе платформы «1С:Предприятие» на управляемое приложение и клиент-серверную архитектуру разработчики столкнулись с важным изменением концепции: данные и интерфейс были строго разделены. Если в классическом «толстом» клиенте можно было легко создать объект на клиенте, заполнить его реквизиты и тут же открыть его форму одной строчкой кода, то в управляемом приложении (в тонком или веб-клиенте) такой подход не сработает. Объект типа ДокументОбъект живет исключительно на сервере, а методы визуального отображения и формы могут вызываться только на стороне клиента.
В этой статье мы подробно разберем, как правильно и в соответствии со стандартами разработки фирмы «1С» решать задачу открытия программно созданного документа — как в случае, когда документ уже физически записан в базу данных, так и в ситуации, когда нам нужно показать пользователю заполненный, но еще не сохраненный документ.
В традиционном толстом клиенте задача решалась буквально в одно действие:
НовыйДок = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
// Заполнение реквизитов...
НовыйДок.ПолучитьФорму("ФормаДокумента").Открыть();
Почему так делать нельзя в современных конфигурациях? Метод ПолучитьФорму() является устаревшим и нерекомендуемым. В управляемом приложении он приводит к неявным и непроизводительным вызовам сервера, сильно замедляет работу интерфейса и полностью несовместим с тонким клиентом, где переменная типа ДокументОбъект попросту не может существовать. Любая попытка протащить прикладной объект на клиент вызовет ошибку сериализации.
Давайте подробно разберем современные и правильные сценарии решения этой задачи.
---
Если логика вашей программы предполагает, что документ сначала создается на сервере, полностью заполняется и записывается в базу данных, то у вас на руках появляется готовая ссылка типа ДокументСсылка. Передать эту ссылку с сервера на клиент не составляет никакого труда — это легковесный тип данных.
Для открытия формы уже существующего (записанного) документа по стандартам 1С мы должны использовать глобальный метод ОткрытьФорму(). Чтобы открыть конкретную форму конкретного документа, нам нужно передать в параметры формы структуру с ключом "Ключ", значением которого будет выступать наша ссылка.
Рассмотрим по шагам, как выглядит этот процесс:
ОткрытьФорму().Посмотрим на практический пример реализации:
&НаКлиенте
Процедура СоздатьИОткрытьЗаписанныйДокумент()
// Шаг 1. Создаем и записываем документ на сервере, получая ссылку
СсылкаНаНовыйДокумент = СоздатьДокументНаСервере();
Если ЗначениеЗаполнено(СсылкаНаНовыйДокумент) Тогда
// Шаг 2. Подготавливаем параметры для открытия формы.
// Передаем ссылку в параметре "Ключ"
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("Ключ", СсылкаНаНовыйДокумент);
// Открываем конкретную форму (например, "ФормаДокумента")
ОткрытьФорму("Документ.РеализацияТоваровУслуг.Форма.ФормаДокумента", ПараметрыФормы);
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция СоздатьДокументНаСервере()
НовыйДок = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
НовыйДок.Дата = ТекущаяДатаСеанса();
НовыйДок.Организация = Справочники.Организации.НайтиПоРеквизиту("ИНН", "7700000000");
Попытка
НовыйДок.Записать(РежимЗаписиДокумента.Запись);
Возврат НовыйДок.Ссылка;
Исключение
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Не удалось записать документ: " + ОписаниеОшибки());
Возврат Неопределено;
КонецПопытки;
КонецФункций
Этот метод работает быстро, безопасно и полностью соответствует стандартам клиент-серверной разработки 1С.
Альтернатива: Если вам заранее не известен тип ссылки (например, вы пишете универсальный отчет или обработку) и требуется открыть основную форму объекта, можно воспользоваться асинхронным методом ПоказатьЗначение(). Этот метод пришел на замену устаревшему и нерекомендуемому модальному методу ОткрытьЗначение().
// Асинхронное открытие значения в управляемом интерфейсе
ОписаниеОповещения = Новый ОписаниеОповещения("ПослеЗакрытияФормы", ЭтотОбъект);
ПоказатьЗначение(ОписаниеОповещения, СсылкаНаНовыйДокумент);
---
Часто бизнес-логика требует иного поведения: нужно создать новый документ, предварительно заполнить в нем кучу реквизитов и табличных частей, но не записывать его в базу данных автоматически. Документ должен открыться на экране интерактивно, чтобы пользователь мог проверить заполненные данные, внести правки ручным вводом и самостоятельно нажать кнопку «Записать и закрыть» либо отказаться от создания документа.
Поскольку объекта в базе данных еще нет, ссылки тоже нет. Мы имеем дело только с набором данных в оперативной памяти. Рассмотрим три основных и наиболее эффективных способа реализации этой задачи.
Данный подход идеален, если структура передаваемых данных проста (нет огромных табличных частей с сотнями строк), и вы можете вносить изменения в модуль объекта создаваемого документа.
В платформе 1С реализован штатный механизм инициализации новых объектов. При вызове метода ОткрытьФорму() мы можем передать структуру параметров с ключом "ЗначенияЗаполнения". Платформа автоматически перенаправит эти параметры в обработчик события модуля объекта ОбработкаЗаполнения().
Разберем по шагам реализацию этого механизма:
Шаг 1. Код вызова формы на клиенте:
&НаКлиенте
Процедура ОткрытьНовыйДокументСПараметрами()
// Формируем структуру с данными заполнения
ДанныеЗаполнения = Новый Структура;
ДанныеЗаполнения.Вставить("Организация", Объект.Организация);
ДанныеЗаполнения.Вставить("Контрагент", Объект.Контрагент);
ДанныеЗаполнения.Вставить("Склад", Объект.Склад);
// Оборачиваем ее в параметры открытия формы
ПараметрыФормы = Новый Структура("ЗначенияЗаполнения", ДанныеЗаполнения);
// Открываем форму нового документа
ОткрытьФорму("Документ.РеализацияТоваровУслуг.Форма.ФормаДокумента", ПараметрыФормы);
КонецПроцедуры
Шаг 2. Код в модуле объекта создаваемого документа (например, РеализацияТоваровУслуг):
Процедура ОбработкаЗаполнения(ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка)
// Проверяем, что к нам пришла именно заполненная структура параметров
Если ТипЗнч(ДанныеЗаполнения) = Тип("Структура") Тогда
ЗаполнитьЗначенияСвойств(ЭтотОбъект, ДанныеЗаполнения);
// Здесь же можно дописать логику автоматического расчета цен,
// валют, заполнения ответственного и т.д.
КонецЕсли;
КонецПроцедуры
Что делать, если вам нужно передать в новый документ сложную структуру данных, например, перенести туда большую таблицу товаров, рассчитанную в обработке подбора? Передавать такие объемы через параметры клиента нерационально, так как это перегружает веб-трафик и замедляет работу.
В этом случае идеальным решением является использование механизма временного хранилища. Мы сохраняем данные во временной памяти на сервере, получаем текстовый адрес (строку-ссылку), передаем этот адрес на клиент, а затем из формы нового документа считываем данные по адресу.
Проанализируем этот алгоритм на практическом примере:
Шаг 1. Подготовка и передача данных на клиенте/сервере обработки:
&НаКлиенте
Процедура ОткрытьЧерезХранилище()
// Получаем уникальный идентификатор текущей формы для привязки временного хранилища
АдресДанных = ПоместитьДанныеВоВременноеХранилищеНаСервере(ЭтаФорма.УникальныйИдентификатор);
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("АдресЗаполнения", АдресДанных);
ОткрытьФорму("Документ.РеализацияТоваровУслуг.Форма.ФормаДокумента", ПараметрыФормы);
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПоместитьДанныеВоВременноеХранилищеНаСервере(УникальныйИдентификаторФормы)
// Создаем структуру, имитирующую данные документа
ДанныеДокумента = Новый Структура;
ДанныеДокумента.Вставить("Организация", Справочники.Организации.ПустаяСсылка());
// Заполняем сложную таблицу товаров
ТаблицаТоваров = Новый ТаблицаЗначений;
ТаблицаТоваров.Колонки.Добавить("Номенклатура");
ТаблицаТоваров.Колонки.Добавить("Количество");
ТаблицаТоваров.Колонки.Добавить("Цена");
// Заполняем строки...
НоваяСтрока = ТаблицаТоваров.Добавить();
НоваяСтрока.Номенклатура = Справочники.Номенклатуры.НайтиПоНаименованию("Товар 1");
НоваяСтрока.Количество = 10;
НоваяСтрока.Цена = 100;
ДанныеДокумента.Вставить("Товары", ТаблицаТоваров);
// Помещаем во временное хранилище и возвращаем его адрес
Возврат ПоместитьВоВременноеХранилище(ДанныеДокумента, УникальныйИдентификаторFormы);
КонецФункции
Шаг 2. Чтение данных в модуле формы открываемого документа (в обработчике ПриСозданииНаСервере):
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем наличие параметра с адресом во временном хранилище
Если Параметры.Свойство("АдресЗаполнения") И ЗначениеЗаполнено(Параметры.АдресЗаполнения) Тогда
// Получаем наши данные из хранилища
ДанныеДляЗаполнения = ПолучитьИзВременногоХранилища(Параметры.АдресЗаполнения);
Если ТипЗнч(ДанныеДляЗаполнения) = Тип("Структура") Тогда
// Заполняем реквизиты шапки документа
ЗаполнитьЗначенияСвойств(Объект, ДанныеДляЗаполнения);
// Заполняем табличную часть документа
Если ДанныеДляЗаполнения.Свойство("Товары") Тогда
Объект.Товары.Загрузить(ДанныеДляЗаполнения.Товары);
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Этот метод незаменим, если вы разрабатываете внешнюю обработку или отчет для типовой конфигурации (например, 1С:ERP, 1С:КА или 1С:УТ) и у вас нет технической возможности или разрешения изменять стандартные формы и модули объектов конфигурации.
В этом случае мы можем применить технологию заполнения формы «на лету» на сервере через приведение типов к прикладному объекту:
ПолучитьФорму(). Нам вернется пустая форма.ДокументОбъект с помощью метода ДанныеФормыВЗначение().ЗначениеВДанныеФормы().Открыть() у заполненной формы.Рассмотрим детальный пример реализации:
&НаКлиенте
Процедура ЗаполнитьИОткрытьФормуБезИзмененияКонфигурации()
// 1. Получаем пустую форму нового документа
ФормаДокумента = ПолучитьФорму("Документ.РеализацияТоваровУслуг.Форма.ФормаДокумента");
// 2. Вызываем серверный метод для ее полного заполнения
ЗаполнитьФормуНаСервере(ФормаДокумента);
// 3. Открываем уже заполненную форму на клиенте
ФормаДокумента.Открыть();
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ЗаполнитьФормуНаСервере(ФормаДокумента)
// Извлекаем прикладной объект из структуры данных формы
ДокументОбъект = ДанныеФормыВЗначение(ФормаДокумента.Объект, Тип("ДокументОбъект.РеализацияТоваровУслуг"));
// Производим произвольное программное заполнение объекта
ДокументОбъект.Дата = ТекущаяДатаСеанса();
ДокументОбъект.Организация = Справочники.Организации.НайтиПоКоду("00-000001");
// Пример заполнения табличной части
НоваяСтрока = ДокументОбъект.Товары.Добавить();
НоваяСтрока.Номенклатура = Справочники.Номенклатуры.НайтиПоКоду("00-000005");
НоваяСтрока.Количество = 5;
// Преобразуем заполненный объект обратно в структуру данных формы
ЗначениеВДанныеФормы(ДокументОбъект, ФормаДокумента.Объект);
КонецПроцедуры
Этот изящный способ позволяет заполнить форму абсолютно любой сложности без внесения изменений в метаданные конфигурации.
---
В сложных современных конфигурациях (1С:ERP, 1С:Комплексная автоматизация, 1С:Управление торговлей) широко используется подсистема «Свойства» из состава Библиотеки Стандартных Подсистем (БСП). Она отвечает за динамическое добавление дополнительных реквизитов и сведений к объектам.
Часто программисты сталкиваются со следующей проблемой: при попытке открыть программно созданный незаписанный объект в режиме «Только просмотр» динамические дополнительные реквизиты на форме не отображаются или выводятся некорректно.
Выясним причину: Отрисовка и инициализация дополнительных реквизитов на форме физически происходит в процедуре ПриСозданииНаСервере открываемой формы с использованием программного интерфейса БСП (модуль УправлениеСвойствами). В этот момент подсистема обращается к табличной части создаваемого объекта ДополнительныеРеквизиты.
Чтобы доп. реквизиты отображались корректно на форме незаписанного документа, выполните следующие правила:
ДополнительныеРеквизиты самого объекта."ТолькоПросмотр" со значением Истина в метод ОткрытьФорму(), чтобы БСП смогла корректно отработать инициализацию динамических полей, а уже затем заблокировать их для редактирования.Пример программного заполнения ТЧ дополнительных реквизитов перед открытием формы:
// В коде заполнения объекта на сервере (например, в Варианте В)
НовыйРеквизит = ДокументОбъект.ДополнительныеРеквизиты.Добавить();
НовыйРеквизит.Свойство = ПВХ.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("Бренд");
НовыйРеквизит.Значение = Справочники.ЗначенияСвойствОбъектов.НайтиПоНаименованию("НашБренд");
---
Подведем итоги анализа различных вариантов программного открытия форм документов:
ОткрытьФорму() с параметром "Ключ". Это самый быстрый и надежный стандартный путь."ЗначенияЗаполнения".Временным хранилищем.ДанныеФормыВЗначение() и ЗначениеВДанныеФормы().ПолучитьФорму().Открыть() в современном коде управляемого приложения, так как он снижает производительность вашей системы и может привести к ошибкам выполнения.