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

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

Вспомогательная диалоговая форма:

Результат заполнения табличной части документа:

Как видно, прораммно добавленный реквизит табличной части "Кор. счета учета" не отображает заполненные данные, а выводит значение "Настроить" , хотя реквизит табличной части "НастройкаСчетовУчета" заполнен.
Такой подход устраивает до тех пор, пока в коде обработки объекта на сервере не потребуется передать форму заполняемого документа для какой-то дополнительной обработки в серверные модули. В рассматриваемом примере при заполнении табличной части "Прочие доходы" документа "Отражение прочих доходов и расходов" необходимо для каждой строки вызывать процедуру серверного модуля для обновления представления настройки счетов учета:
НастройкаСчетовУчетаСервер.ОбновитьПредставлениеНастройки(ФормаДокумента, "Объект.ПрочиеДоходы");
Прямая передача формы (формы клиентского приложения) по ссылке на сервер невозможна. Т.е. нет возможности на клиенте в процедуре ЗаполнитьДокумент выполнить такой код для передачи формы на сервер для дополнительной обработки:
…
ЗаполнитьОбъектНаСервере(ОбъектДокумента, ФормаДокумента);
…
Будет выводиться ошибка:

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

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

Вспомогательная диалоговая форма:

Результат заполнения табличной части документа:

Как видно, прораммно добавленный реквизит табличной части "Кор. счета учета" корректно отображает заполненные данные.
В итоге еще раз уточню, что цель статьи - желание не прибегать к использованию расширений, а использовать только внешнюю обработку заполнения объекта для случая, когда необходимо использовать серверный контекст формы заполняемого объекта как этого требует программная логика заполнения объекта. Не исключаю, что все приведенные рассуждения это стрельба из пушки по воробьям, раздувание из мухи слонов или просто изобретение велосипедного велосипеда, а может даже и насилие над подсистемой подключаемых команд БСП. Возможно, что есть другие методически грамотные и архитектурно правильные решения. Прошу поделится решениями в комментариях.
Использовалась демо-база 1С:ERP Управление предприятием 2 (2.5.22.76) на 1С:Предприятие 8.3 (8.3.27.1644).