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

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

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

Почему открывается только одна форма? Разбор механизма уникальности

Для того чтобы понять, почему платформа ведет себя именно так, проанализируем внутренние механизмы работы управляемых форм. Когда мы открываем форму существующего (уже записанного в базу данных) документа, платформа автоматически использует его уникальную ссылку Ссылка в качестве ключа уникальности. Это гарантирует, что для одного и того же элемента базы данных не откроется два разных окна.

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

Решением этой проблемы является правильное использование параметра Уникальность в методах ОткрытьФорму или ПолучитьФорму.

Рассмотрим параметры метода ОткрытьФорму, а именно четвертый параметр:

ОткрытьФорму(ИмяФормы, Параметры, Владелец, Уникальность, Окно, НавигационнаяСсылка, ОписаниеОповещенияОЗакрытии, РежимОткрытияОкна)

В качестве параметра Уникальность мы можем передать:

  1. Ложь (значение по умолчанию) — система ищет уже открытую форму с ключом уникальности по умолчанию. Если находит — активизирует её.
  2. Истина — система полностью игнорирует поиск существующих форм и гарантированно открывает абсолютно новое уникальное окно.
  3. Уникальный идентификатор — мы можем явно сгенерировать UUID с помощью конструктора Новый УникальныйИдентификатор(). Платформа будет искать форму именно по этому ключу. Это чрезвычайно полезно, если в дальнейшем нам понадобится программно обращаться к конкретному окну из других клиентских процедур.

Способ 1. Простое открытие нескольких пустых форм

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


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

При выполнении этого кода на экране пользователя откроется ровно пять отдельных вкладок с новыми незаписанными документами.

Способ 2. Передача параметров заполнения через обработчик "ОбработкаЗаполнения"

В реальной практике просто открыть пустые формы требуется редко. Обычно нам необходимо передать в каждый документ свои уникальные данные (контрагента, склад, список товаров и т.д.).

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

Разберем этот вариант по шагам.

Шаг 1. Написание кода вызова на клиенте:


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

Шаг 2. Настройка обработки заполнения в модуле объекта:

В модуле объекта создаваемого документа (например, Документ.РеализацияТоваровУслуг) необходимо описать логику разбора переданных параметров:


Процедура ОбработкаЗаполнения(ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка)
    
    Если ТипЗнч(ДанныеЗаполнения) = Тип("Структура") Тогда
        
        // Заполняем реквизиты объекта из переданной структуры
        Если ДанныеЗаполнения.Свойство("Контрагент") Тогда
            ЭтотОбъект.Контрагент = ДанныеЗаполнения.Контрагент;
        КонецЕсли;
        
        Если ДанныеЗаполнения.Свойство("Комментарий") Тогда
            ЭтотОбъект.Комментарий = ДанныеЗаполнения.Комментарий;
        КонецЕсли;
        
    КонецЕсли;
    
КонецПроцедуры

Этот способ является наиболее производительным, так как платформа берет на себя всю работу по сериализации данных и передаче их между слоями системы.

Способ 3. Прямое заполнение данных формы без изменения модуля объекта

Рассмотрим ситуацию, когда конфигурация находится на поддержке без возможности изменения (или вы не хотите вносить изменения в типовые модули объектов). В этом случае мы можем получить форму программно, извлечь ее структуру данных, заполнить ее на сервере и вернуть обратно в форму.

Проанализируем пример кода, реализующий этот подход:


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

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

Этот метод дает разработчику полную свободу действий. Мы можем использовать любые экспортные методы модуля объекта для расчета скидок, налогов и автоматического заполнения связанных реквизитов, не затрагивая при этом логику самого открытия форм.

Способ 4. Использование временного хранилища для объемных данных

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

Рассмотрим схему работы этого паттерна:

  1. На сервере подготавливается структура данных для каждого документа.
  2. Каждая структура помещается во временное хранилище методом ПоместитьВоВременноеХранилище(). Мы получаем уникальный адрес (строку вида e1cib/tempstorage/...).
  3. На клиент возвращается массив этих адресов.
  4. В цикле на клиенте открываются формы новых документов. В параметры открытия передается полученный адрес во временном хранилище.
  5. Внутри самой управляемой формы документа в обработчике ПриСозданииНаСервере мы проверяем наличие этого параметра. Если он передан, мы извлекаем структуру из хранилища по адресу, заполняем реквизиты формы и удаляем данные из хранилища для освобождения оперативной памяти сервера.

Посмотрим на пример реализации.

Код вызова на клиенте:


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

Код заполнения в модуле формы документа в событии ПриСозданииНаСервере:


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

Архитектурные ограничения и UX-проблемы: почему стоит подумать дважды

Несмотря на то, что технически задача открытия множества незаписанных окон решается достаточно просто, с точки зрения архитектурного дизайна и удобства пользователей (UX/UI) этот подход имеет существенные недостатки. Опытные разработчики и руководители проектов часто стараются переубедить заказчиков в целесообразности такой реализации по следующим причинам:

Рекомендуемая альтернатива: Групповая обработка

Вместо открытия десятков незаписанных окон гораздо правильнее реализовать промежуточное рабочее место для пользователя (специальную обработку):

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

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

← На главную