Как создать и заполнить документ на сервере и открыть его форму на клиенте без сохранения в базу данных?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе разработки прикладных решений на платформе 1С:Предприятие 8.3 программисты регулярно сталкиваются с необходимостью автоматического создания и заполнения документов. Классический сценарий выглядит следующим образом: на основе некоторых внешних данных (или сложного аналитического запроса к базе данных) нам необходимо программно создать новый документ, заполнить его реквизиты, включая табличные части, а затем открыть форму этого документа на клиенте. При этом крайне важно, чтобы документ открылся в режиме «не сохранен», чтобы у пользователя оставалась возможность отредактировать данные и самостоятельно принять решение о записи или отмене операции. Преждевременная запись документа в базу данных нежелательна, так как она засоряет информационную базу неиспользуемыми черновиками.

На первый взгляд задача кажется простой, однако попытки реализовать её напрямую вызывают сложности из-за особенностей клиент-серверной архитектуры управляемых форм. Давайте подробно разберем, почему возникает эта проблема и как её правильно решить несколькими способами.

В чем главная сложность? Особенности работы с контекстом

Для того чтобы понять причину возникновения ошибок, проанализируем принципы клиент-серверного взаимодействия в 1С. Основная сложность заключается в несовместимости типов данных, существующих в различных контекстах:

  1. На сервере мы работаем с типом данных ДокументОбъект. Этот тип обладает полным набором свойств и методов для работы с данными в оперативной памяти и базе данных. Однако объект этого типа физически не может существовать на клиенте. Попытка передать ДокументОбъект напрямую на клиентскую сторону вызовет ошибку сериализации.
  2. На клиенте нам доступен только тип данных ДанныеФормыСтруктура (который визуализируется в виде реквизита Объект на управляемой форме) или объект типа ДокументСсылка (если документ уже сохранен в базе данных).

Таким образом, мы не можем просто создать объект ДокументОбъект на сервере и передать его переменной на клиент для последующего открытия формы. Выяснив причину этого ограничения, рассмотрим два наиболее популярных и архитектурно верных подхода к реализации нашей задачи.

Способ 1. Передача структуры данных через временное хранилище (Рекомендуемый метод)

Этот подход является наиболее правильным с точки зрения стандартов разработки фирмы «1С». Вместо того чтобы пытаться передавать тяжелые прикладные объекты, мы подготавливаем все необходимые данные на сервере, упаковываем их в легкую структуру, временно сохраняем в специальном буфере (временном хранилище) и передаем на клиент лишь строковый адрес. На клиенте мы открываем форму и передаем ей этот адрес в качестве параметра. В процессе инициализации форма самостоятельно забирает данные из хранилища и заполняет свой реквизит Объект.

Разберем этот метод по шагам.

Шаг 1. Подготовка данных на сервере. Создадим экспортную функцию (например, в общем модуле или модуле менеджера), которая сформирует структуру заполнения, поместит ее во временное хранилище и вернет адрес.


&НаСервереБезКонтекста
Функция ПодготовитьДанныеДляНовогоДокумента() Экспорт
    
    // Создаем структуру, где ключами будут имена реквизитов документа
    СтруктураДанных = Новый Структура;
    СтруктураДанных.Вставить("Организация", Справочники.Организации.НайтиПоРеквизиту("ИНН", "7701020304"));
    СтруктураДанных.Вставить("Склад", Справочники.Склады.НайтиПоНаименованию("Основной склад"));
    СтруктураДанных.Вставить("Комментарий", "Документ заполнен автоматически.");
    
    // Для заполнения табличной части подготовим таблицу значений
    ТаблицаТоваров = Новый ТаблицаЗначений;
    ТаблицаТоваров.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
    ТаблицаТоваров.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3)));
    ТаблицаТоваров.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
    ТаблицаТоваров.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
    
    // Заполним таблицу значений данными из нашего запроса или алгоритма
    НоваяСтрока = ТаблицаТоваров.Добавить();
    НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоКоду("00000015");
    НоваяСтрока.Количество = 5;
    НоваяСтрока.Цена = 1200;
    НоваяСтрока.Сумма = 6000;
    
    // Помещаем таблицу товаров в общую структуру данных
    СтруктураДанных.Вставить("Товары", ТаблицаТоваров);
    
    // Помещаем структуру во временное хранилище
    АдресХранилища = ПоместитьВоВременноеХранилище(СтруктураДанных, Новый УникальныйИдентификатор);
    
    Возврат АдресХранилища;
    
КонецФункции

Шаг 2. Вызов открытия формы на клиенте. В клиентской процедуре управляемой формы (например, в обработчике команды кнопки) вызовем нашу функцию для подготовки данных, а затем откроем форму нового документа, передав полученный адрес в параметрах.


&НаКлиенте
Процедура ВыполнитьКомандуСоздания(Команда)
    
    // Получаем адрес с подготовленными на сервере данными
    АдресДанных = ПодготовитьДанныеДляНовогоДокумента();
    
    // Формируем параметры для открытия формы нового документа
    ПараметрыФормы = Новый Структура;
    ПараметрыФормы.Вставить("АдресЗаполнения", АдресДанных);
    
    // Открываем форму нового документа (без записи в базу данных)
    ОткрытьФорму("Документ.РеализацияТоваровУслуг.ФормаОбъекта", ПараметрыФормы);
    
КонецПроцедуры

Шаг 3. Извлечение и применение данных при инициализации формы. Теперь перейдем в модуль формы самого документа РеализацияТоваровУслуг. Нам нужно прописать логику разбора параметров в стандартном событии ПриСозданииНаСервере.


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Проверяем наличие нашего уникального параметра
    Если Параметры.Свойство("АдресЗаполнения") И ЗначениеЗаполнено(Параметры.АдресЗаполнения) Тогда
        
        // Извлекаем структуру из временного хранилища
        ДанныеЗаполнения = ПолучитьИзВременногоХранилища(Параметры.АдресЗаполнения);
        
        Если ТипЗнч(ДанныеЗаполнения) = Тип("Структура") Тогда
            
            // Заполняем реквизиты шапки документа
            ЗаполнитьЗначенияСвойств(Объект, ДанныеЗаполнения);
            
            // Переносим строки табличной части
            Если ДанныеЗаполнения.Свойство("Товары") Тогда
                Объект.Товары.Загрузить(ДанныеЗаполнения.Товары);
            КонецЕсли;
            
            // Очищаем временное хранилище для освобождения памяти
            УдалитьИзВременногоХранилища(Параметры.АдресЗаполнения);
            
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

Этот способ крайне эффективен: мы сводим к минимуму трафик между клиентом и сервером и задействуем механизмы платформы для быстрого бинарного обмена данными.

Способ 2. Преобразование данных формы через ДанныеФормыВЗначение (Для закрытых конфигураций)

Рассмотрим другую ситуацию. Довольно часто разработчику приходится решать задачу в условиях жестких ограничений, например, когда конфигурация находится на поддержке и вносить изменения в модуль формы целевого документа запрещено правилами поддержки. В этом случае мы не сможем использовать событие ПриСозданииНаСервере открываемой формы.

Для таких случаев существует обходной маневр: мы можем получить пустую форму на клиенте программно, извлечь из нее структуру данных, отправить её на сервер, преобразовать в полноценный ДокументОбъект, заполнить его всеми необходимыми методами и вернуть обратно.

Посмотрим, как выглядит реализация этого алгоритма на клиенте:


&НаКлиенте
Процедура ЗаполнитьИОткрытьФормуБезИзмененияОбъекта(Команда)
    
    // 1. Создаем объект формы в памяти клиента (форма не открывается автоматически)
    НоваяФорма = ПолучитьФорму("Документ.РеализацияТоваровУслуг.ФормаОбъекта");
    
    // 2. Извлекаем данные формы (тип ДанныеФормыСтруктура)
    ДанныеФормы = НоваяФорма.Объект;
    
    // 3. Вызываем серверный метод для заполнения структуры формы
    ЗаполнитьСтруктуруФормыНаСервере(ДанныеФормы);
    
    // 4. Копируем заполненные на сервере данные обратно в форму
    КопироватьДанныеФормы(ДанныеФормы, НоваяФорма.Объект);
    
    // 5. Показываем форму пользователю
    НоваяФорма.Открыть();
    
КонецПроцедуры

Теперь разберем, что происходит на сервере. Главная прелесть этого метода в том, что на сервере мы превращаем переданные данные формы в полноценный прикладной объект 1С. Это позволяет использовать любые стандартные процедуры заполнения, прописанные разработчиками в модуле объекта (например, расчет цен, скидок, налогов или заполнение по умолчанию).


&НаСервереБезКонтекста
Процедура ЗаполнитьСтруктуруФормыНаСервере(ДанныеФормы)
    
    // Преобразуем структуру данных формы в реальный ДокументОбъект
    ДокументОбъект = ДанныеФормыВЗначение(ДанныеФормы, Тип("ДокументОбъект.РеализацияТоваровУслуг"));
    
    // Теперь мы можем работать с ним как с обычным документом!
    ДокументОбъект.Дата = ТекущаяДата();
    
    // Заполняем реквизиты шапки
    ДокументОбъект.Организация = Справочники.Организации.НайтиПоРеквизиту("ИНН", "7701020304");
    
    // Можем вызывать даже встроенные процедуры заполнения документа
    ДокументОбъект.Заполнить(Неопределено);
    
    // Заполняем табличную часть документа
    НоваяСтрока = ДокументОбъект.Товары.Добавить();
    НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию("Консультационные услуги");
    НоваяСтрока.Количество = 1;
    НоваяСтрока.Цена = 15000;
    НоваяСтрока.Сумма = 15000;
    
    // Преобразуем заполненный объект обратно в тип ДанныеФормыСтруктура
    ЗначениеВДанныеФормы(ДокументОбъект, ДанныеФормы);
    
КонецПроцедуры

Этот метод незаменим при интеграции со сторонними или закрытыми решениями, так как он полностью выполняется «снаружи» открываемого объекта и не требует вмешательства в его структуру.

Способ 3. Передача параметров через стандартный механизм ЗначенияЗаполнения

Если вам требуется заполнить лишь базовые, простые реквизиты шапки нового документа (например, организацию, склад или контрагента), платформа предлагает встроенный механизм, не требующий написания сложного программного кода в модулях формы.

Для этого достаточно сформировать структуру простых значений и передать её под именем ЗначенияЗаполнения в параметрах метода ОткрытьФорму. Платформа самостоятельно сопоставит ключи этой структуры с реквизитами создаваемого документа и заполнит их при открытии.

Давайте посмотрим на простой пример:


&НаКлиенте
Процедура ОткрытьФормуСПростымЗаполнением(Команда)
    
    // Формируем структуру простых реквизитов шапки
    СтруктураЗаполнения = Новый Структура;
    СтруктураЗаполнения.Вставить("Организация", ВыбраннаяОрганизация);
    СтруктураЗаполнения.Вставить("Контрагент", ВыбранныйКонтрагент);
    СтруктураЗаполнения.Вставить("ДоговорКонтрагента", ВыбранныйДоговор);
    
    // Упаковываем её в параметры формы
    ПараметрыФормы = Новый Структура;
    ПараметрыФормы.Вставить("ЗначенияЗаполнения", СтруктураЗаполнения);
    
    // Открываем форму — сопоставление и заполнение платформа сделает сама
    ОткрытьФорму("Документ.РеализацияТоваровУслуг.ФормаОбъекта", ПараметрыФормы);
    
КонецПроцедуры

Важно помнить, что при использовании этого метода платформа неявно вызывает стандартный обработчик события ОбработкаЗаполнения в модуле объекта документа. Если у вас нет необходимости заполнять сложные многострочные таблицы, этот метод станет наиболее лаконичным решением.

Типичные ошибки, которых следует избегать

При реализации механизмов заполнения форм программисты часто совершают ряд архитектурных ошибок. Давайте проанализируем их, чтобы не допускать в будущем:

  1. Попытка вернуть ДокументОбъект с сервера на клиент. Попытка написать в серверной функции код вида Возврат НовыйДокумент; приведет к ошибке выполнения системы, поскольку этот тип данных не может существовать на клиенте.
  2. Запись документа ради получения ссылки. Иногда разработчики, устав бороться с контекстными ошибками, принудительно записывают документ в базу данных прямо на сервере, а затем открывают его форму по полученной ссылке. Это плохой тон разработки. Если пользователь решит закрыть открывшуюся форму без сохранения (например, передумает совершать операцию), в вашей базе данных останется пустой или недозаполненный документ-мусор.
  3. Прямое обращение к элементам формы через точку. Разработчики, привыкшие к обычным формам (1С 8.2), часто пытаются использовать код типа Форма.Элементы.Организация = Значение;. В управляемом приложении элементы формы — это лишь декорации, визуальное отражение данных. Любое заполнение должно производиться только через изменение структуры данных — реквизита Объект (например, Объект.Организация = Значение;).

Используя рассмотренные нами подходы к созданию и заполнению управляемых форм, вы сможете реализовывать надежные, быстродействующие и гибкие механизмы автоматизации в 1С:Предприятии 8.3, полностью соответствующие высоким стандартам разработки.

← На главную