Как реализовать ввод на основании с интерактивным отбором строк в 1С?

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

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

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

Ограничения платформы и контекст исполнения

Разберем фундаментальные принципы платформы 1С, которые определяют архитектуру решения:

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

Основное решение: выбор из списка значений без создания лишних форм

Проанализируем наиболее компактный и производительный подход. Для решения задачи нет необходимости создавать отдельные общие формы. Достаточно использовать асинхронный метод платформы ПоказатьВыборИзСписка().

Рассмотрим по шагам логику настройки создаваемого документа (назовем его Документ2):

  1. В объекте метаданных Документ2 создаем реквизит ДокументОснование с типом ссылки на исходный документ (Документ1).
  2. В модуле объекта Документ2 в событии ОбработкаЗаполнения фиксируем ссылку на основание:
    
    Процедура ОбработкаЗаполнения(ДанныеЗаполнения, ТекстЗаполнения, СтандартнаяОбработка)
        Если ТипЗнч(ДанныеЗаполнения) = Тип("ДокументСсылка.Документ1") Тогда
            ЭтотОбъект.ДокументОснование = ДанныеЗаполнения;
        КонецЕсли;
    КонецПроцедуры
    
  3. В форме документа Документ2 создаем реквизит формы СписокКонтрагентовДляВыбора с типом СписокЗначений.
  4. В обработчике ПриСозданииНаСервере проверяем факт ввода на основании и подготавливаем уникальный список значений запросом:
    
    &НаСервере
    Процедура ПриСозданииНаСервере(Отказ, СтандартныеОбработки)
        // Проверяем, что создается новый документ на основании
        Если Параметры.Ключ.Пустая() И ЗначениеЗаполнено(Объект.ДокументОснование) Тогда
            Если ТипЗнч(Объект.ДокументОснование) = Тип("ДокументСсылка.Документ1") Тогда
                ПодготовитьСписокКонтрагентов();
            КонецЕсли;
        КонецЕсли;
    КонецПроцедуры
    
    &НаСервере
    Процедура ПодготовитьСписокКонтрагентов()
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ РАЗЛИЧНЫЕ
            |   ТЧ.Контрагент КАК Контрагент
            |ИЗ
            |   Документ.Документ1.ТабличнаяЧасть КАК ТЧ
            |ГДЕ
            |   ТЧ.Ссылка = &Ссылка";
        Запрос.УстановитьПараметр("Ссылка", Объект.ДокументОснование);
        
        Выборка = Запрос.Выполнить().Выбрать();
        Пока Выборка.Следующий() Цикл
            СписокКонтрагентовДляВыбора.Добавить(Выборка.Контрагент);
        КонецЦикла;
        
        // Если контрагент всего один, заполняем сразу на сервере
        Если СписокКонтрагентовДляВыбора.Количество() = 1 Тогда
            ЗаполнитьТабличнуюЧастьПоКонтрагенту(СписокКонтрагентовДляВыбора[0].Значение);
            СписокКонтрагентовДляВыбора.Очистить();
        КонецЕсли;
    КонецПроцедуры
    
  5. В обработчике ПриОткрытии на клиенте выводим окно выбора, если вариантов контрагентов несколько:
    
    &НаКлиенте
    Процедура ПриОткрытии(Отказ)
        Если СписокКонтрагентовДляВыбора.Количество() > 1 Тогда
            Оповещение = Новый ОписаниеОповещения("ПослеВыбораКонтрагента", ЭтотОбъект);
            ПоказатьВыборИзСписка(Оповещение, СписокКонтрагентовДляВыбора);
        КонецЕсли;
    КонецПроцедуры
    
    &НаКлиенте
    Процедура ПослеВыбораКонтрагента(ВыбранныйЭлемент, ДополнительныеПараметры) Экспорт
        Если ВыбранныйЭлемент = Неопределено Тогда
            // Пользователь отказался от выбора, закрываем форму или оставляем пустой
            Возврат;
        КонецЕсли;
        
        ЗаполнитьТабличнуюЧастьПоКонтрагенту(ВыбранныйЭлемент.Значение);
    КонецПроцедуры
    
    &НаСервере
    Процедура ЗаполнитьТабличнуюЧастьПоКонтрагенту(ВыбранныйКонтрагент)
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ
            |   ТЧ.Номенклатура КАК Номенклатура,
            |   ТЧ.Количество КАК Количество,
            |   ТЧ.Цена КАК Цена,
            |   ТЧ.Сумма КАК Сумма
            |ИЗ
            |   Документ.Документ1.ТабличнаяЧасть КАК ТЧ
            |ГДЕ
            |   ТЧ.Ссылка = &Ссылка
            |   И ТЧ.Контрагент = &Контрагент";
        Запрос.УстановитьПараметр("Ссылка", Объект.ДокументОснование);
        Запрос.УстановитьПараметр("Контрагент", ВыбранныйКонтрагент);
        
        Объект.ТабличнаяЧасть.Загрузить(Запрос.Выполнить().Выгрузить());
    КонецПроцедуры
    

Вариант с вызовом вспомогательной формы

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

Рассмотрим правила передачи данных между основной и вспомогательной формой:

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

Вынос выбора фильтра до открытия целевого документа

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

Смысл подхода заключается в том, что вопрос пользователю задается в момент нажатия пользовательской команды создания на основании:

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

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

← На главную