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