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