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