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

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

При разработке в среде 1С:Предприятие 8.3 (Управляемые формы) программисты часто сталкиваются с задачей автоматизации создания подчиненных документов. Типичный пример: на основании строк табличной части одного документа (например, ЗаказНаПроизводство) требуется создать несколько отдельных документов другого типа (например, СборкаЗапасов) по одному документу на каждую строку. При этом бизнес-логика требует не просто записать эти документы в базу данных в фоновом режиме, а показать их пользователю на экране незаписанными, чтобы он мог проверить данные, при необходимости отредактировать их и самостоятельно принять решение о записи или отмене.

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

Почему старый подход с вызовом из модуля объекта не работает?

Основная ошибка, которую совершают начинающие разработчики (и которая наглядно показана в первом сообщении темы), заключается в попытке вызвать метод открытия формы ОткрытьФорму() внутри процедуры ОбработкаЗаполнения модуля объекта целевого документа.

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

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

Архитектура правильного решения: три важных правила

Чтобы реализовать корректное открытие множества незаписанных документов в цикле, нам необходимо решить три ключевые проблемы:

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

Рассмотрим подробнее по шагам, как реализовать каждый из этих этапов.

Шаг 1. Подготовка метаданных и связывание по уникальному идентификатору (UUID)

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

  1. В документе-источнике (например, ЗаказНаПроизводство) в табличную часть Продукция добавим новый реквизит:
    • Имя: КлючСвязиСтроки
    • Тип: Строка (длина 36, для хранения UUID) или УникальныйИдентификатор.
  2. В этой же табличной части добавим реквизит ДокументСборки с типом ссылки на создаваемый документ ДокументСсылка.СборкаЗапасов. Это позволит выводить ссылку на созданный документ прямо в строке ТЧ.
  3. В создаваемом документе СборкаЗапасов добавим реквизиты шапки:
    • ДокументОснование: ДокументСсылка.ЗаказНаПроизводство
    • КлючСвязиСтроки: Строка (длина 36).

Теперь напишем серверный механизм генерации уникальных ключей для строк ТЧ документа-источника при его записи. В модуле объекта документа ЗаказНаПроизводство разместим следующий код:


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

Шаг 2. Реализация логики открытия форм в модуле родительского документа

Переходим к разработке интерфейсной части. На форме документа-источника создадим кнопку «Создать документы сборки» и привяжем к ней клиентскую процедуру.

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

В модуле формы документа-источника напишем следующий код:


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

&НаКлиенте
Процедура ПослеПодтвержденияЗаписи(РезультатВопроса, ДополнительныеПараметры) Экспорт
    
    Если РезультатВопроса = КодВозвратаДиалога.Да Тогда
        Если Записать() Тогда
            ВыполнитьСозданиеПодчиненныхДокументов();
        КонецЕсли;
    КонецЕсли;
    
КонецПроцедуры

Теперь детально разберем саму процедуру создания и открытия форм в цикле. Обратите внимание на использование четвертого параметра метода ОткрытьФорму() — именно он отвечает за уникальность каждого открываемого окна:


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

Шаг 3. Настройка приема данных в целевом документе

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

Откроем модуль объекта документа СборкаЗапасов и пропишем алгоритм разбора параметров:


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

Шаг 4. Реализация интерактивной обратной связи через Оповещения

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

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


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

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


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

Альтернативный продвинутый подход: Использование Временного хранилища

Проанализируем другую ситуацию: что делать, если из одной строки ТЧ родительского документа требуется передавать в дочерний документ не 3-4 простых реквизита, а огромный объем сложных структурированных данных (например, спецификации, вложенные таблицы, дерево настроек)? Передача сверхбольших структур через параметры формы на клиенте может сильно замедлить работу интерфейса и перегрузить сетевой трафик между клиентом и сервером.

Для таких случаев применяется профессиональный паттерн проектирования на базе Временного хранилища:

  1. На сервере родительского документа формируется сложная структура данных и помещается во временное хранилище с помощью функции ПоместитьВоВременноеХранилище(Данные, УникальныйИдентификаторФормы). Метод возвращает строковый адрес вида e1cib/tempstorage/....
  2. В параметры открытия формы дочернего документа передается только этот короткий адрес:
    ПараметрыФормы.Вставить("АдресДанныхЗаполнения", АдресВХ);
  3. В модуле целевой формы в событии ПриСозданииНаСервере этот адрес извлекается из параметров, данные считываются функцией ПолучитьИзВременногоХранилища(Адрес) и раскладываются по реквизитам формы дочернего документа.

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

Резюме

Мы успешно решили поставленную задачу и разработали архитектурно верное решение. Перенос логики генерации окон на сторону клиента с использованием механизма Уникальность позволил обойти ограничения клиент-серверного разделения. Внедрение уникальных ключей строк ТЧ полностью защитило логику программы от изменения порядка строк пользователем, а механизм Оповестить обеспечил оперативную и красивую обратную связь в интерфейсе программы.

← На главную