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