Как открыть форму второй дополнительной обработки с параметрами, не меняя её код

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

В процессе разработки в среде 1С:Предприятие 8 часто возникает задача интеграции различных программных модулей. Типичный сценарий: у вас есть сложная дополнительная обработка (например, система обмена данными с ФГИС Меркурий или маркировки), код которой крайне нежелательно или невозможно изменять (например, чтобы не усложнять последующие обновления). При этом пользователям требуется, чтобы из вашей собственной доработки по одной кнопке открывалась форма этой внешней обработки, автоматически переключалась на нужную страницу, заполнялся период и, самое главное, табличная часть заполнялась строго отфильтрованными документами.

В этой статье мы подробно разберем, почему стандартные подходы 1С не работают напрямую для сторонних форм, проанализируем ограничения платформы и построим изящное, полностью работоспособное решение.

Почему стандартные методы не работают?

Попытки решить эту задачу «в лоб» обычно наталкиваются на жесткие ограничения клиент-серверной архитектуры управляемых форм 1С:

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

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

Пошаговый разбор реализации решения

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

Шаг 1. Получение файла и подключение внешней обработки

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


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

&НаСервере
Функция ПодключитьДопОбработку(АдресВоВременномХранилище)
    Возврат ВнешниеОбработки.Подключить(АдресВоВременномХранилище);
КонецФункции

Шаг 2. Разработка серверной функции сбора данных

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


&НаСервере
Функция ПолучитьДанныеДляТаблицыНаСервере(Знач ДокументРеализация, Знач ДатаНачала, Знач ДатаОкончания)
    
    МассивРезультата = Новый Массив;
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |   РеализацияТоваровУслуг.Ссылка КАК Регистратор,
        |   РеализацияТоваровУслуг.ДоговорКонтрагента КАК Договор,
        |   РеализацияТоваровУслуг.Контрагент КАК Контрагент,
        |   РеализацияТоваровУслуг.СуммаДокумента КАК Сумма
        |ИЗ
        |   Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
        |ГДЕ
        |   РеализацияТоваровУслуг.Проведен = ИСТИНА
        |   И РеализацияТоваровУслуг.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания";
        
    Если ЗначениеЗаполнено(ДокументРеализация) Тогда
        Запрос.Текст = Запрос.Текст + " И РеализацияТоваровУслуг.Ссылка = &Реализация";
        Запрос.УстановитьПараметр("Реализация", ДокументРеализация);
    КонецЕсли;
    
    Запрос.УстановитьПараметр("ДатаНачала", НачалоДня(ДатаНачала));
    Запрос.УстановитьПараметр("ДатаОкончания", КонецДня(ДатаОкончания));
    
    ТаблицаДанных = Запрос.Выполнить().Выгрузить();
    
    // Конвертируем Таблицу Значений в Массив Структур для безопасной передачи на клиент
    Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
        СтруктураСтроки = Новый Структура;
        Для Каждого Колонка Из ТаблицаДанных.Колонки Цикл
            СтруктураСтроки.Вставить(Колонка.Имя, СтрокаТЗ[Колонка.Имя]);
        КонецЦикла;
        МассивРезультата.Добавить(СтруктураСтроки);
    КонецЦикла;
    
    Возврат МассивРезультата;
    
КонецФункции

Шаг 3. Клиентская процедура открытия и интерактивного заполнения

Теперь объединим весь процесс на клиенте. Мы откроем форму второй обработки, получим ссылку на нее в переменную, заполним обычные реквизиты формы, получим массив структур с сервера и заполним табличную часть второй формы построчно на клиенте.


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

Важные особенности и подводные камни решения

При использовании данного метода необходимо учитывать ряд нюансов платформы 1С:

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

← На главную