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