При разработке сложных отчетов на схеме компоновки данных (СКД) часто возникает задача: построить отчет на основе внешней таблицы значений, которая должна заполняться динамически. Для формирования этой таблицы программе требуются параметры, установленные пользователем на форме — например, Дата начала и Дата окончания периода. Однако разработчики часто сталкиваются с тем, что привычные методы передачи данных через реквизиты отчета не работают, а значения параметров «теряются» при выполнении кода на сервере.
В этой статье мы подробно разберем, почему реквизиты отчета могут «обнуляться» в момент формирования результата, как правильно и безопасно извлечь установленные пользователем даты периода внутри предопределенной процедуры ПриКомпоновкеРезультата и как связать эти даты с построением внешнего набора данных.
Начинающие разработчики часто пытаются решить задачу «в лоб»: создают реквизит отчета с типом СтандартныйПериод, связывают его с полем на форме, а затем пытаются обратиться к этому реквизиту напрямую в модуле объекта. На первый взгляд, это логично. Но при формировании отчета в режиме предприятия реквизит неожиданно оказывается пустым.
Выясним причину такого поведения системы. В современных конфигурациях «1С:Предприятия» (особенно построенных на базе Библиотеки стандартных подсистем — БСП) по умолчанию включено фоновое формирование отчетов. Процесс происходит следующим образом:
КомпоновщикНастроек), которые были сериализованы на клиенте.ПриКомпоновкеРезультата нового сеанса они инициализируются значениями по умолчанию (то есть будут пустыми).Единственным надежным мостом для передачи параметров между формой отчета и кодом генерации в модуле объекта является сам компоновщик настроек СКД.
Иногда в коде можно встретить вариант получения значения параметра напрямую по его индексу в коллекции настроек:
Период = Настройки.ПараметрыДанных.Элементы.Получить(0).Значение;
ДатаНачала = Период.ДатаНачала;
ДатаОкончания = Период.ДатаОкончания;
Проанализируем этот подход. Метод крайне опасен и категорически не рекомендуется к использованию в реальных задачах. Если в будущем вы решите изменить порядок параметров в схеме компоновки, добавите новые параметры или удалите неиспользуемые, индекс нужного вам параметра сместится. В результате отчет начнет выдавать непредсказуемые ошибки или, что еще хуже, молча подставлять неверные типы данных в ваши алгоритмы.
Для безопасного получения параметров в процедуре ПриКомпоновкеРезультата необходимо выполнить два важных условия:
ПолучитьНастройки() компоновщика настроек. Он автоматически объединяет основные настройки отчета с пользовательскими настройками, которые клиент изменил на форме.ПараметрКомпоновкиДанных), используя встроенный метод поиска НайтиЗначениеПараметра(). Это полностью исключает зависимость от порядка следования параметров.Рассмотрим пример правильной реализации в модуле объекта отчета:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
// Получаем результирующие настройки с учетом действий пользователя на форме
АктуальныеНастройки = КомпоновщикНастроек.ПолучитьНастройки();
// Инициализируем переменные дат дефолтными значениями
ДатаНачала = '00010101';
ДатаОкончания = '00010101';
// Ищем параметр "Период" по его имени в схеме компоновки данных
ПараметрПериод = АктуальныеНастройки.ПараметрыДанных.НайтиЗначениеПараметра(Новый ПараметрКомпоновкиДанных("Период"));
Если ПараметрПериод <> Неопределено И ПараметрПериод.Использование Тогда
// Параметр найден и активен. Проверим его тип.
// Обычно параметр "Период" имеет тип "СтандартныйПериод"
Если ТипЗнч(ПараметрПериод.Value) = Тип("СтандартныйПериод") Тогда
ДатаНачала = ПараметрПериод.Value.ДатаНачала;
ДатаОкончания = ПараметрПериод.Value.ДатаОкончания;
ИначеЕсли ТипЗнч(ПараметрПериод.Значение) = Тип("СтандартныйПериод") Тогда
// В зависимости от контекста свойство может называться Значение или Value
ДатаНачала = ПараметрПериод.Значение.ДатаНачала;
ДатаОкончания = ПараметрПериод.Значение.ДатаОкончания;
КонецЕсли;
КонецЕсли;
// Проверим корректность заполнения дат
Если Не ЗначениеЗаполнено(ДатаНачала) Или Не ЗначениеЗаполнено(ДатаОкончания) Тогда
ВызватьИсключение "Для формирования отчета необходимо обязательно заполнить период!";
КонецЕсли;
КонецПроцедуры
При работе с объектом СтандартныйПериод важно помнить об одной особенности поведения платформы: если пользователь выбрал вариант периода «Без ограничения» или очистил одно из полей дат, свойства ДатаНачала или ДатаОкончания вернут пустую дату ('00010101000000').
Если пустую начальную дату запросы баз данных обычно обрабатывают корректно (считая началом времен), то пустая конечная дата может привести к неполной или некорректной выборке данных (например, в виртуальных таблицах срезов или оборотов). Рекомендуется делать дополнительную проверку и принудительно устанавливать конец текущего дня или максимальную дату в системе:
Если Не ЗначениеЗаполнено(ДатаОкончания) Тогда
// Если дата окончания не задана, устанавливаем конец сегодняшнего дня
ДатаОкончания = КонецДня(ТекущаяДата());
КонецЕсли;
Рассмотрим по шагам комплексный пример. Предположим, нам нужно перехватить формирование отчета, извлечь выбранный период, выполнить запрос к регистру сведений за этот период, выгрузить результат в таблицу значений и передать эту таблицу обратно в СКД в качестве внешнего набора данных.
Для этого в схеме компоновки должен быть создан набор данных с типом Набор данных - объект и именем, например, ВнешнийНаборДанныхПериода.
Реализуем этот сценарий программно в процедуре ПриКомпоновкеРезультата:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
// 1. Отключаем стандартный вывод отчета платформой, так как мы будем выводить его вручную
СтандартнаяОбработка = Ложь;
// 2. Получаем актуальные настройки компоновщика
АктуальныеНастройки = КомпоновщикНастроек.ПолучитьНастройки();
// 3. Извлекаем даты периода безопасным способом
ПараметрПериод = АктуальныеНастройки.ПараметрыДанных.НайтиЗначениеПараметра(Новый ПараметрКомпоновкиДанных("Период"));
Если ПараметрПериод = Неопределено Или Не ПараметрПериод.Использование Тогда
ОбщегоНазначенияКлиентСервер.СообщитьПользователю("Не указан период формирования отчета!");
Возврат;
КонецЕсли;
ЗначениеПараметра = ПараметрПериод.Значение;
ДатаНачала = ЗначениеПараметра.ДатаНачала;
// Корректируем время окончания периода до конца секунды
ДатаОкончания = КонецДня(ЗначениеПараметра.ДатаОкончания);
// 4. Формируем таблицу значений на основе полученных дат
ТаблицаДляОтчета = ПолучитьТаблицуДанныхИзРегистра(ДатаНачала, ДатаОкончания);
// 5. Инициализируем структуру внешних наборов данных для процессора компоновки
ВнешниеНаборыДанных = Новый Структура;
// Ключ структуры должен строго совпадать с именем Набора данных - объекта в вашей схеме СКД
ВнешниеНаборыДанных.Вставить("ВнешнийНаборДанныхПериода", ТаблицаДляОтчета);
// 6. Запускаем стандартный программный цикл компоновки макета и вывода результата
СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, АктуальныеНастройки, ДанныеРасшифровки);
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных, ДанныеРасшифровки, Истина);
ДокументРезультат.Очистить();
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);
КонецПроцедуры
Функция ПолучитьТаблицуДанныхИзРегистра(ДатаНачала, ДатаОкончания)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КурсыВалютСрезПоследних.Валюта КАК Валюта,
| КурсыВалютСрезПоследних.Курс КАК Курс,
| КурсыВалютСрезПоследних.Кратность КАК Кратность
|ИЗ
| РегистрСведений.КурсыВалют.СрезПоследних(&КонецПериода, ) КАК КурсыВалютСрезПоследних
|// Дополнительные условия отбора, если это необходимо";
// В данном примере мы используем дату окончания периода для получения среза последних
Запрос.УстановитьПараметр("КонецПериода", ДатаОкончания);
Возврат Запрос.Выполнить().Выгрузить();
КонецФункции
Использование этого шаблона гарантирует стабильную работу вашего отчета как при интерактивном запуске пользователем на клиенте, так и при автоматическом формировании в регламентных и фоновых заданиях на сервере. Ваши параметры никогда не потеряются, а код будет устойчив к любым изменениям структуры макета компоновки данных.