При доработке типового отчета БП, возник вопрос, как сделать формирование внешнего отчета в фоне. Результатами исследования захотелось поделится. Использовалась БСП версии 3.0.1.
Сразу стоит отметить, что отчет просто открытый через Файл - Открыть запустить в фоне нельзя (если только он до этого не был добавлен в справочник ДополнительныеОтчетыИОбработки)
Для дополнительного отчета (добавленного в справочник ДополнительныеОтчетыИОбработки) есть два варианта реализации:
1. Использование типовой формы отчета из БСП.
Для этого не указываем (и не создаем) основную форму отчета. Условием формирования в фоновым режиме для формы отчета БСП является его отключенный "Безопасный режим". Т.е. в функции СведенияОВнешнейОбработке указываем
ПараметрыРегистрации.БезопасныйРежим = Ложь; // !!! Что бы выполнялось в фоне на сервере через БСП форму отчета
Есть прекрасная статья по этому варианту Подсистема "Варианты отчетов". Используете ли Вы ее правильно? Там "Безопасный режим" в примере сброшен, написано про фоновое формирование, но не описана взаимосвязь.
2. Использование своей формы отчета
Она будет нестандартной для пользователя. В ней нужно реализовывать запуск отчета в фоновом процессе самому. Но можно запустить в фоне и безопасный отчет.
Есть статья Пример формирования внешнего отчета программно и в фоновом режиме, реализующая этот вариант. Но в ней есть ряд недоработок. Используется устаревшая процедура ДлительныеОперации.ЗапуститьВыполнениеВФоне.
К тому же БСП предоставляет готовые механизмы обмена между фоново запущеной командой дополнительного отчета/обработки и ожидающим ее процедурой формы. Нет необходимости создавать временное хранилище и даже помещать в него данные. Но остаётся вопрос, будут ли так работать будущие версии БСП. Возможно, надёжнее самостоятельно помещать данные во временное хранилище.
Привожу свою реализацию формирования отчета со своей формой в фоновом режиме. Первый вариант (с формой БСП) легко реализуем и так. Достаточно отключить основную форму от отчета.
В ней я так же уделил внимание работе со своевременным освобождением памяти хранилища. Иначе, если пользователь, не закрывая форму отчета, много раз нажмет сформировать, памяти может не хватить.
Все действия и поведение системы подробно описаны в комментариях. Понять в контексте кода проще, чем абстрактные описания.
Отчет простой, выводит версии подсистем в базе данных (версия БСП и т.п.). Для использования достаточно заменить схему компоновки данных и оставить нужную кнопку формирования.
#Область ПрограммныйИнтерфейс
// Функция возвращает структуру параметров для подсистемы БСП ДополнительныеОтчетыИОбработки
Функция СведенияОВнешнейОбработке() Экспорт
// Нужно указать версию БСП, чтобы передавались параметры команде и от команды
ПараметрыРегистрации = ДополнительныеОтчетыИОбработки.СведенияОВнешнейОбработке("2.2.2.1");
// Дополнительный отчет - общий отчет
// Отчет - отчет привязанный к объекту, объектам
ПараметрыРегистрации.Вид = ДополнительныеОтчетыИОбработкиКлиентСервер.ВидОбработкиДополнительныйОтчет();
ПараметрыРегистрации.Версия = "1.0.1.0";
ПараметрыРегистрации.БезопасныйРежим = Ложь; // !!! Что бы выполнялось в фоне на сервере через БСП форму отчета
// Позволяет доопределить настройки общей формы отчетов
// Если установить в Истина, нужно создать функцию ОпределитьНастройкиФормы
// Которая дополнит стандартную форму отчета
//ПараметрыРегистрации.ОпределитьНастройкиФормы = Истина; // Нужно добавить процедуру ОпределитьНастройкиФормы
// Команды задают возможные действия
Команда = ПараметрыРегистрации.Команды.Добавить();
Команда.Представление = НСтр("ru = 'Отобразить версии подсистем'");
Команда.Идентификатор = "СформироватьОтчет";
// Тип команды задает действие.
// ТипКомандыОткрытиеФормы - для отчета без формы будет открыта стандартная форма отчетов БСП, если отчет привязан к варианты отчетов
// Привязка идет по хранилищу вариантов отчетов. Должно стоять Хранилища настроек.ХранилищеВариантовОтчетов в
// корне конфигурации или в самом отчете.
// Есть проблема сохранения пользовательских вариантов!!! Могут перетираться
Команда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыОткрытиеФормы();
Команда.ПоказыватьОповещение = Ложь;
Возврат ПараметрыРегистрации;
КонецФункции
// СтандартныеПодсистемы.ВариантыОтчетов
// Настройки общей формы отчета подсистемы "Варианты отчетов".
// Изменяет настройки стандартной формы отчета
// Здесь можно задать процедуры, выполняющиеся при событиях
//
// Параметры:
// Форма - УправляемаяФорма, Неопределено - Форма отчета или форма настроек отчета.
// Неопределено когда вызов без контекста.
// КлючВарианта - Строка, Неопределено - Имя предопределенного
// или уникальный идентификатор пользовательского варианта отчета.
// Неопределено когда вызов без контекста.
// Настройки - Структура - см. возвращаемое значение
// ОтчетыКлиентСервер.ПолучитьНастройкиОтчетаПоУмолчанию().
//
Процедура ОпределитьНастройкиФормы(Форма, КлючВарианта, Настройки) Экспорт
Настройки.ФормироватьСразу = Ложь;
//Настройки.РазрешеноИзменятьСтруктуру = ОбщегоНазначенияКлиентСервер.РежимОтладки();
//Настройки.События.ПриСозданииНаСервере = Истина; // Нужно создать процедуру ПриСозданииНаСервере
КонецПроцедуры
// Конец СтандартныеПодсистемы.ВариантыОтчетов
// Выполнение команды обработки.
// Результат команды она должна поместить в уже созданную структуру ПараметрыВыполненияКоманды.РезультатВыполнения
//
&НаСервере
Процедура ВыполнитьКоманду(ИмяКоманды,ПараметрыВыполненияКоманды) Экспорт
Если ИмяКоманды = "СформироватьОтчет" Тогда
//СформироватьОтчет(ПараметрыВыполненияКоманды,ПараметрыВыполненияКоманды.АдресРезультата);
СформироватьОтчет(ПараметрыВыполненияКоманды.ПараметрыОтчета,ПараметрыВыполненияКоманды.РезультатВыполнения);
КонецЕсли;
КонецПроцедуры
// Формирует отчет. Вызывается из ВыполнитьКоманду или из формы
// Эта процедура по сути должна сформировать табличный документ отчета и поместить его в РезультатВыполнения.Результат
// Сама реализация может быть любой, и попроще. Я скопировал просто рабочую
// Можно добавить РезультатВыполнения.Статус (Сообщение и т.д.)
//
// Параметры:
// ПараметрыОтчета - Структура - Параметры отчета
// РезультатВыполнения - Структура - Результат постороения отчета
// * Результат - ТабличныйДокумент - Отчет
// * ДанныеРасшифровки - ДанныеРасшифровкиКомпоновкиДанных - данные расшифровки
//
Процедура СформироватьОтчет(ПараметрыОтчета,РезультатВыполнения) Экспорт
ДанныеРасшифровки = Новый ДанныеРасшифровкиКомпоновкиДанных;
Результат = Новый ТабличныйДокумент;
СхемаКомпоновкиДанных = ПараметрыОтчета.СхемаКомпоновкиДанных;
// Или можно так, если схема одна. Не передавая в качестве параметров.
// СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
КомпоновщикНастроекДанных = Новый КомпоновщикНастроекКомпоновкиДанных;
КомпоновщикНастроекДанных.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(СхемаКомпоновкиДанных));
КомпоновщикНастроекДанных.ЗагрузитьНастройки(КомпоновщикНастроек.Настройки);//копируем настройки
КомпоновщикНастроекДанных.ЗагрузитьПользовательскиеНастройки(ПараметрыОтчета.НастройкиПользователя);//копируем пользовательские настройки
Настройки = КомпоновщикНастроекДанных.ПолучитьНастройки();
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных,Настройки,ДанныеРасшифровки);
ВнешниеНаборыДанных = Новый Структура; // Сюда можно поместить наборы данных ТЗ
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки,ВнешниеНаборыДанных,ДанныеРасшифровки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(Результат);
ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных);
РезультатВыполнения.Вставить("Результат", Результат);
РезультатВыполнения.Вставить("ДанныеРасшифровки", ДанныеРасшифровки);
// БСП сама поместит РезультатВыполнения во временное хранилище.
// Вопрос только не изменится ли поведение в будущем
// и не надежнее ли просто с параметрами передать адрес хранилища, что бы здесь самим в него поместить данные
КонецПроцедуры
#КонецОбласти
#Область ОбработчикиСобытийФормы
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// получим объект отчет
ВнешнийОтчет = РеквизитФормыВЗначение("Отчет"); // Если обработка, то ("Объект");
// если отчет открыт из справочника проверим заполненность ссылки
Параметры.Свойство("ДополнительнаяОбработкаСсылка", ДополнительнаяОбработкаСсылка);
// Идентификатор команды. Который укзан в СведенияОВнешнейОбработке, в данном случае для команды открытия формы
// но можно и руками указать, это же наш отчет :)
// Нужно, если разное поведение в зависимости от команды
Параметры.Свойство("ИдентификаторКоманды", ИдентификаторКоманды);
// если пустая значит открыти из вне, нужно поискать ее в справочнике
Если ДополнительнаяОбработкаСсылка.Пустая() Тогда
ДополнительнаяОбработкаСсылка = Справочники.ДополнительныеОтчетыИОбработки.НайтиПоНаименованию(ВнешнийОтчет.Метаданные().Синоним);
ИдентификаторКоманды = "СформироватьОтчет";
КонецЕсли;
// если забыли добавить в справочник выполнить в фоне не получится :-(
Элементы.СформироватьОтчетВФоне.Доступность = НЕ ДополнительнаяОбработкаСсылка.Пустая();
// Либо используем авто создаваемое хранилище и удаляем его, что бы не было переполнения памяти
// либо используем один раз созданное хранилище из реквизита
//АдресРезультата=ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор); // Одно хранилище на все время жизни формы
КонецПроцедуры
#КонецОбласти
#Область ОбработчикиКомандФормы
// Формирование отчета в фоне программно
&НаКлиенте
Процедура СформироватьОтчетВФоне(Команда)
ВыполнитьВФоне();
КонецПроцедуры
// Сформировать отчет в фоне через параметр компоновщика
//
&НаКлиенте
Процедура СформироватьОтчетРежимКомпоновкиВФоне(Команда)
ЭтаФорма.СкомпоноватьРезультат(РежимКомпоновкиРезультата.Фоновый);
КонецПроцедуры
// Процедура - Сформировать отчет программно не в фоне
//
&НаКлиенте
Процедура СформироватьОтчетПрограммно(Команда)
СформироватьОтчетПрограммноНаСервере();
КонецПроцедуры
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
// Выполнить отчет в фоне.
// Запускает НачатьВыполнениеВФоне и вешает на нее обработчик ожидания ФормированиеВФонеЗавершено
// Разделение на функции запуска и ожидания нужно, т.к. выполнение в фоне идет на сервере, а ожидание на клиенте
// Функция окончания ожидания тоже только на клиенте
//
&НаКлиенте
Процедура ВыполнитьВФоне()
// Результат отчета формируется
ОбщегоНазначенияКлиентСервер.УстановитьСостояниеПоляТабличногоДокумента(Элементы.Результат, "ФОРМИРОВАНИЕОТЧЕТА");
ОчиститьСообщения();
Результат = Новый ТабличныйДокумент; // для чистоты результата
// Запускаем длительную операцию
// Получаем структуру со статусом
ДлительнаяОперация = НачатьВыполнениеВФоне();
// Подключаем обработчик ожидания
ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
ОповещениеОЗавершении = Новый ОписаниеОповещения("ФормированиеВФонеЗавершено", ЭтотОбъект);
ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация, ОповещениеОЗавершении, ПараметрыОжидания);
// Если закончится до вызова, то обработчик ожидания все-равно выполнится
// По окончании вызовется ФормированиеВФонеЗавершено
КонецПроцедуры
// Стартует выполнение в фоне
// Ее завершения ожидает запустившая ее ВыполнитьВФоне
//
// Возвращаемое значение:
// Структура - параметры выполнения задания (ДлительнаяОперация):
// * Статус - Строка - "Выполняется", если задание еще не завершилось;
// "Выполнено", если задание было успешно выполнено;
// "Ошибка", если задание завершено с ошибкой;
// "Отменено", если задание отменено пользователем или администратором.
// * ИдентификаторЗадания - УникальныйИдентификатор - если Статус = "Выполняется", то содержит
// идентификатор запущенного фонового задания.
// * АдресРезультата - Строка - адрес временного хранилища, в которое будет
// помещен (или уже помещен) результат работы процедуры.
// * АдресДополнительногоРезультата - Строка - если установлен параметр ДополнительныйРезультат,
// содержит адрес дополнительного временного хранилища,
// в которое будет помещен (или уже помещен) результат работы процедуры.
// * КраткоеПредставлениеОшибки - Строка - краткая информация об исключении, если Статус = "Ошибка".
// * ПодробноеПредставлениеОшибки - Строка - подробная информация об исключении, если Статус = "Ошибка".
//
&НаСервере
Функция НачатьВыполнениеВФоне()
// Имя процедуры, которая будет выполнятся в фоне
// Она может быть в общем модуле, модуле менеджера
// Процедуру формы так запустить нельзя
// Указываем процедуру общего модуля БСП. Она стартанет команду нашего отчета, зарегестрированную в СведенияОВнешнейОбработке
// Для этого в параметрах выполнения нужно указать ссылку на наш отчет в справочнике и идентификатор команды
ИмяПроцедуры = "ДополнительныеОтчетыИОбработки.ВыполнитьКоманду"; // Что будет выполнятся в фоне
ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
ПараметрыВыполнения.НаименованиеФоновогоЗадания = НСтр("ru='Выполнение отчета...'");
// Либо используем авто создаваемое хранилище и удаляем его, что бы не было переполнения памяти
// либо используем один раз созданное хранилище из реквизита
//ПараметрыВыполнения.АдресРезультата = АдресРезультата; // Используем хранилище формы, если не указать будет создано автоматом
// Указываем процедуре общего модуля ДополнительныеОтчетыИОбработки.ВыполнитьКоманду, что нужно стартануть
ПараметрыПроцедуры = Новый Структура; // Создаем структуру, заполняем нужными нам параметрами
ПараметрыПроцедуры.Вставить("ДополнительнаяОбработкаСсылка", ДополнительнаяОбработкаСсылка); // Какой доп отчет
// ИдентификаторКоманды - берется из идентификатора команды из СведенияОВнешнейОбработке
// Команда.Идентификатор = "СформироватьОтчет";
// При этом выполняется процедура, в зависимости от типа команды (клиентская или серверная)
// Например: Команда.Использование = ДополнительныеОтчетыИОбработкиКлиентСервер.ТипКомандыОткрытиеФормы();
// Подразумеваем, что для типа открытие формы будет вызвана ВыполнитьКоманду на сервере и ей передан идентификатор "СформироватьОтчет"
ПараметрыПроцедуры.Вставить("ИдентификаторКоманды", ИдентификаторКоманды); // Какая команда доп отчета.
ПараметрыПроцедуры.Вставить("ПараметрыОтчета", ПолучитьПараметрыОтчета()); // Параметры формирования отчета, для команды
// Адрес хранилища указывать не обязательно. Можно указать в ПараметрыВыполнения.АдресРезультата, но процедура создаст его сама
// с привязкой к форме. Вопрос только в переполнении памяти от постоянного создания?
// Результат работы процедуры отчета в ВыполнитьКоманду нужно помещать в ПараметрыПроцедуры.РезультатВыполнения
// эта структура будет так же создана, нужно только наполнить ее нужными нам полями в ВыполнитьКоманду.
// И эту структуру процедуры БСП поместят в хранилище по адресу ДлительнаяОперация.АдресРезультата
ОбщегоНазначенияКлиентСервер.УстановитьСостояниеПоляТабличногоДокумента(Элементы.Результат, "ФОРМИРОВАНИЕОТЧЕТА");
ДлительнаяОперация = ДлительныеОперации.ВыполнитьВФоне(ИмяПроцедуры, ПараметрыПроцедуры, ПараметрыВыполнения);
Возврат ДлительнаяОперация;
КонецФункции
// Формирование в фоне завершено. Может быть только на клиенте
//
// В Хранилище по адресу РезультатФоновогоЗадания.АдресРезультата находится структура заполняемая командой
// внешнего отчета в ПараметрыВыполненияКоманды.РезультатВыполнения
//
// Параметры:
// РезультатФоновогоЗадания - Структура - Результат выполнения длительной операции
// * Статус - Строка - "Выполняется", если задание еще не завершилось;
// "Выполнено", если задание было успешно выполнено;
// "Ошибка", если задание завершено с ошибкой;
// "Отменено", если задание отменено пользователем или администратором.
// * ИдентификаторЗадания - УникальныйИдентификатор - если Статус = "Выполняется", то содержит
// идентификатор запущенного фонового задания.
// * АдресРезультата - Строка - адрес временного хранилища, в которое будет
// помещен (или уже помещен) результат работы процедуры.
// * АдресДополнительногоРезультата - Строка - если установлен параметр ДополнительныйРезультат,
// содержит адрес дополнительного временного хранилища,
// в которое будет помещен (или уже помещен) результат работы процедуры.
// * КраткоеПредставлениеОшибки - Строка - краткая информация об исключении, если Статус = "Ошибка".
// * ПодробноеПредставлениеОшибки - Строка - подробная информация об исключении, если Статус = "Ошибка".
// ДополнительныеПараметры - Струткура - Доп параметры, преданные из вызывающей ожидание процедуры.
//
&НаКлиенте
Процедура ФормированиеВФонеЗавершено(РезультатФоновогоЗадания, ДополнительныеПараметры) Экспорт
Если РезультатФоновогоЗадания.Статус="Выполнено" Тогда
// Отображаем отчет, т.к. ПолучитьИзВременногоХранилища работает только на сервере, вызываем серверную процедуру
ОтобразитьДанныеФоновогоОтчета(РезультатФоновогоЗадания.АдресРезультата);
КонецЕсли;
КонецПроцедуры
// Отображает отчет сформированный в фоне. Читая его из временного хранилища
// Параметры:
// АдресРезультата - Строка - Адрес временного хранилища с результатом выполнения фоновой команды отчета
// * Результат - ТабличныйДокумент - Отчет
// * ДанныеРасшифровки - ДанныеРасшифровкиКомпоновкиДанных - данные расшифровки
//
&НаСервере
Процедура ОтобразитьДанныеФоновогоОтчета(АдресРезультата)
РезультатВыполнения = ПолучитьИзВременногоХранилища(АдресРезультата);
// Данные расшифровки поместить в хранилище, что бы работала расшифровка
// Память переполнится, но стандартная процедура Сформировать неизвестно как работает, может так же не удаляет
ДанныеРасшифровки = ПоместитьВоВременноеХранилище(РезультатВыполнения.ДанныеРасшифровки, УникальныйИдентификатор);
ЭтаФорма.Результат = РезультатВыполнения.Результат; // Этаформа.Результат - что бы не путались реквизит формы с параметром процедуры
ОбщегоНазначенияКлиентСервер.УстановитьСостояниеПоляТабличногоДокумента(Элементы.Результат, "НеИспользовать");
// Либо используем авто создаваемое хранилище и удаляем его, что бы не было переполнения памяти
// либо используем один раз созданное хранилище из реквизита
УдалитьИзВременногоХранилища(АдресРезультата); // дабы память не переполнялась
КонецПроцедуры
// Возвращает параметры отчета
// Возможно это все можно получить в СформироватьОтчет.
//
// Возвращаемое значение:
// Структура - параметры отчета для программного формирования
// * СхемаКомпоновкиДанных - СхемаКомпоновкиДанных - Схема компоновки данных
// * НастройкиПользователя - ПользовательскиеНастройкиКомпоновкиДанных - настройки пользователя
&НаСервере
Функция ПолучитьПараметрыОтчета()
ОтчетОбъект = РеквизитФормыВЗначение("Отчет");
ПараметрыОтчета = Новый Структура;
ПараметрыОтчета.Вставить("СхемаКомпоновкиДанных" , ОтчетОбъект.СхемаКомпоновкиДанных);
ПараметрыОтчета.Вставить("НастройкиПользователя" , Отчет.КомпоновщикНастроек.ПользовательскиеНастройки);
Возврат ПараметрыОтчета;
КонецФункции // ПолучитьПараметрыОтчета()
// Формирует отчет программно, не в фоне.
// Использует ту же процедуру отчета СформироватьОтчет, что и фоновая. Но обходится без вызова команды и временного хранилища.
//
&НаСервере
Процедура СформироватьОтчетПрограммноНаСервере()
ОбщегоНазначенияКлиентСервер.УстановитьСостояниеПоляТабличногоДокумента(Элементы.Результат, "ФОРМИРОВАНИЕОТЧЕТА");
// Формируем параметры отчета
ПараметрыОтчета = ПолучитьПараметрыОтчета(); // Параметры нужны и фоновой
РезультатВыполнения = Новый Структура(); // Для получения результата формирования
// Выполняем процедуру отчета по формированию
ОтчетОбъект = РеквизитФормыВЗначение("Отчет");
ОтчетОбъект.СформироватьОтчет(ПараметрыОтчета, РезультатВыполнения);
// Данные расшифровки поместить в хранилище, что бы работала расшифровка
// Память переполнится, но стандартная процедура Сформировать неизвестно как работает, может так же не удаляет
ДанныеРасшифровки = ПоместитьВоВременноеХранилище(РезультатВыполнения.ДанныеРасшифровки, УникальныйИдентификатор); // Что с памятью?
// Отображаем результат
Результат=РезультатВыполнения.Результат;
ОбщегоНазначенияКлиентСервер.УстановитьСостояниеПоляТабличногоДокумента(Элементы.Результат, "НеИспользовать");
КонецПроцедуры
#КонецОбласти
Update: исправил работу с данными расшифровки по замечанию в комментарии (добавлена пара строчек в модуль формы и реквизит формы ДанныеРасшифровки).