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