//infostart.ru/projects/827/
В данном примере строится отчет на базе трех группировок которые определяются при открытии формы, таким для использования рекурсии в других целях достаточно изменить запрос и эту процедуру. Хотя при наличии времени возможно описать универсальную процедуру в которой можно предоставить пользователю на первом шаге выбрать из метаданных интересующие его объекты, а на втором - атрибуты этих объектов, которые и будут являться группировками запроса. Далее на основании этих данных автоматически формировать ТекстЗапроса.
Перем КолвоГруппировок;
//___________________________________________________*
Процедура Группировка(ИмяЗапроса, ОбщКолвоГруппировок, ТекущаяГруппировка, СписЗначГрупп, ИмяТаблицы) Далее
//___________________________________________________*
Процедура ПриОткрытии()
СЗРасш.ДобавитьЗначение("Документ", "Документ");
СЗРасш.ДобавитьЗначение("День", "День");
СЗРасш.ДобавитьЗначение("Неделя", "Неделя");
СЗРасш.ДобавитьЗначение("Месяц", "Месяц");
СЗРасш.ДобавитьЗначение("Квартал", "Квартал");
СЗРасш.ДобавитьЗначение("Год", "Год");
СЗГрупп.ДобавитьЗначение("Сотрудник", "Сотрудник");
СЗГрупп.ДобавитьЗначение("Блюдо", "Блюдо");
СЗГрупп.ДобавитьЗначение("Интервал", "Интервал");
Форма.СЗРасш.Видимость(0);
Форма.ИмяСЗРасш.Видимость(0);
КолвоГруппировок = СЗГрупп.РазмерСписка();
КонецПроцедуры
Непосредственно сам запрос. Параллельно в процедуре выполняется описание вспомогательных переменных для дальнейшей работы рекурсии
//___________________________________________________*
Процедура Сформировать()
Перем Запрос, ТекстЗапроса, Таб;
ГруппировкаВыбрана = 0;
//определение выборки хотя бы одной группировки
Для Сч = 1 По КолвоГруппировок Цикл
Если СЗГрупп.Пометка(Сч) = 1 Тогда
ГруппировкаВыбрана = 1;
КонецЕсли;
КонецЦикла;
Если ГруппировкаВыбрана = 0 Тогда
Предупреждение("Вы не указали ни одной группировки", 60);
Возврат;
КонецЕсли;
//определение указания расшифровки по интервалу
Если (СЗГрупп.Пометка(СЗГрупп.НайтиЗначение("Интервал"),) = 1) И (Форма.СЗРасш.Видимость() = 0) Тогда
Предупреждение("Вы не выбрали периодичность интервала", 60);
ВидимостьРасшифровки();
Возврат;
КонецЕсли;
//Создание объекта типа Запрос
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(Сформировать)
|Период с ВыбНачПериода по ВыбКонПериода;
|ОбрабатыватьДокументы Проведенные;
|Обрабатывать НеПомеченныеНаУдаление;
|Сотрудник = Документ.ЗаказСотрудника.Сотрудник;
|Блюдо = Документ.ЗаказСотрудника.Блюдо;
|Количество = Документ.ЗаказСотрудника.Количество;
|Стоимость = Документ.ЗаказСотрудника.Сумма;
|Функция КолСумма = Сумма(Количество);
|Функция СтоимСумма = Сумма(Стоимость);
|"//}}ЗАПРОС
;
//доп СЗ для вызова процедуры-рекурсии группировка
//у данного списка значений - две функции:
//во-первых, его размер будет говорить о том сколько раз будет запущена рекурсия
//Во-вторых, значения, помещенные в него - идентификаторы группировок к которым мы будем //обращаться в рекурсии
СЗГруппРек = СоздатьОбъект("СписокЗначений");
Для Сч = 1 По КолвоГруппировок Цикл
Если СЗГрупп.Пометка(Сч) = 1 Тогда
Если СЗГрупп.ПолучитьЗначение(Сч,) = "Интервал" Тогда
Интервал = СЗРасш.ПолучитьЗначение(СЗРасш.ТекущаяСтрока());
ТекстЗапроса = ТекстЗапроса + "Группировка " + Интервал + ";";
СЗГруппРек.ДобавитьЗначение(Интервал, Интервал);
Иначе
ЗначениеГруппировки = СЗГрупп.ПолучитьЗначение(Сч);
ТекстЗапроса = ТекстЗапроса + "Группировка " + ЗначениеГруппировки + ";";
СЗГруппРек.ДобавитьЗначение(ЗначениеГруппировки, ЗначениеГруппировки);
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Если ошибка в запросе, то выход из процедуры
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
Возврат;
КонецЕсли;
СписокОбъектов = "";
//Формируем заголовок колонок табличной части
Для Сч = 1 По КолвоГруппировок Цикл
Если СЗГрупп.Пометка(Сч) = 1 Тогда
СЗГрупп.ПолучитьЗначение(Сч,СписокОбъектов);
Если Сч =1 Тогда
Заголовок = СписокОбъектов;
Иначе
Заголовок = Заголовок + "/" + СписокОбъектов;
КонецЕсли;
КонецЕсли;
КонецЦикла;
// Подготовка к заполнению выходных форм данными запроса
Таб = СоздатьОбъект("Таблица");
Таб.ИсходнаяТаблица("Сформировать");
// Заполнение полей "Заголовок"
Таб.ВывестиСекцию("Заголовок");
Состояние("Заполнение выходной таблицы...");
Таб.Опции(0, 0, Таб.ВысотаТаблицы(), 0);
//определяем количество циклов
ОбщКолвоРезГруппировок = СЗГруппРек.РазмерСписка();
//В этом месте по обычаю находились бы вложенные циклы с обходом по группировкам
//результатов выполнения запроса.
//Мы же здесь вызываем рекурсию. В процедуру передаем ссылку на сам запрос, для обращения
//к его результатам, общее количество группировок, номер первой группировки - так как
//здесь мы запускаем внешний цикл и таблицу в которую формируем отчет
Группировка(Запрос, ОбщКолвоРезГруппировок, 1, СЗГруппРек, Таб);
// Заполнение полей "Итого"
Таб.ВывестиСекцию("Итого");
// Вывод заполненной формы
Таб.ТолькоПросмотр(1);
Таб.Показать("Сформировать", "");
КонецПроцедуры
И вот собственно сама рекурсия. Процедура вызывается всегда с 4-мя параметрами.
1. Ссылка на запрос к которму мы обращаемся
2. Общее количество группировок.
3. Один из двух основных параметров - номер группировки к которой будет идти обращение
4. Второй основной параметр - список значений в котором содержатся в иерархическом порядке идентификаторы группировок к которым происходит обращение в данном вызове процедуры. Группировка определяется исходя из значения предыдущего параметра
5. И собственно говоря таблица в которую выводим значения
В принципе параметры 1,2 и 5 можно сделать общими для всего модуля, тогда их можно будет убрать из параметров процедуры
Общая идея процедуры такова - при входе в процедуру получаем идентификатор текущей группировки и запускаем по ней цикл. Здесь же обращаемся к атрибутам группировки и заполняем соответствующие переменные и выводим секции в таблицу.
Далее увеличиваем номер текущей группировки - то есть готовимся к обходу результатов по следующей (вложенной) группировке. Сравниваем полученный номер с общим количеством группировок и если он меньше то запускаем рекурсивную процедуру с теми же параметрами но уже с номером вложенной группировки и т.д. В самой вложенной группировке переменная ТекущаяГруппировка будет на единицу больше общего количества группировок, а посему рекурсию мы больше не запускаем. Уменьшаем номер группировки до текущего и переходим на следующий виток цикла. Как только мы доберемся до конца цикла мы просто выйдем из данной (самой вложенной) процедуры и вернемся в вышестоящую, там уменьшаем номер группировки до значения текущей на этом уровне группировки, и переходим на следующий виток цикла текущей группировки, где опять вызовем самую вложенную группировку и .д.
В конечном итоге после выполнения всех вложенных проццедур мы вернемся в процедуру Сформировать() где покажем заполненную в рекурсии таблицу, и закончим работу модуля.
//___________________________________________________*
//сама рекурсия
Процедура Группировка(ИмяЗапроса, ОбщКолвоГруппировок, ТекущаяГруппировка, СписЗначГрупп, ИмяТаблицы)
Пока ИмяЗапроса.Группировка(ТекущаяГруппировка) = 1 Цикл
// Заполнение полей группировки
АтрибутГруппировки = "";
СписЗначГрупп.ПолучитьЗначение(ТекущаяГруппировка, АтрибутГруппировки);
Объект = ИмяЗапроса.ПолучитьАтрибут(АтрибутГруппировки);
СуммаПоОбъекту = ИмяЗапроса.КолСумма;
СтоимПоОбъекту = ИмяЗапроса.СтоимСумма;
Попытка
Если Объект.ЭтоГруппа() = 1 Тогда
ИмяСекцииДляВывода = "Группа";
Иначе
ИмяСекцииДляВывода = "Группировка" + ТекущаяГруппировка;
КонецЕсли;
ИмяТаблицы.ВывестиСекцию(ИмяСекцииДляВывода);
Исключение
ИмяТаблицы.ВывестиСекцию("Группировка" + ТекущаяГруппировка);
КонецПопытки;
//увеличиваем номер группировки для входа во внутренний цикл
ТекущаяГруппировка = ТекущаяГруппировка + 1;
Если ТекущаяГруппировка <= ОбщКолвоГруппировок Тогда
Группировка(ИмяЗапроса, ОбщКолвоГруппировок, ТекущаяГруппировка, СписЗначГрупп, ИмяТаблицы);
КонецЕсли;
//уменьшаем номер группировки для входа во внешний цикл
ТекущаяГруппировка = ТекущаяГруппировка - 1;
КонецЦикла;
КонецПроцедуры