Как программно сгруппировать элементы из разных справочников в макете табличного документа 1С по условию и задать имя группе?

Программист 1С v8.3 (Управляемые формы) Бухгалтерский учет
← На главную

При разработке отчетов в 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. НачатьГруппуСтрок(<ИмяГруппы>, <Открыта>): Этот метод начинает новую группу строк.
    • <ИмяГруппы> (строка): Необязательный параметр, позволяющий задать имя для создаваемой группы. Это имя будет отображаться рядом со значком свертывания/развертывания группы в табличном документе.
    • <Открыта> (булево): Необязательный параметр. Если Истина, группа будет открыта по умолчанию. Если Ложь, группа будет свернута.
  2. ЗакончитьГруппуСтрок(): Этот метод закрывает текущую активную группу. Все строки, выведенные между вызовом НачатьГруппуСтрок() и ЗакончитьГруппуСтрок(), будут принадлежать этой группе.

Давайте посмотрим, как это можно применить к коду автора, чтобы реализовать условную группировку:


// Предполагаем, что у нас есть переменная, которая будет хранить
// состояние, была ли группа уже открыта для текущего условия
ПеременнаяГруппаОткрыта = Ложь;
ИмяОсобойГруппы = "Особые статьи расходов и платежей"; // Задаем желаемое имя группы

Пока ВыборкаСтатьи.Следующий Цикл
    Если ВыборкаСтатьи.Уровень > Число(КоличествоУровней) Тогда
        Продолжить;
    КонецЕсли;

    // Проверяем условие для создания особой группы
    Если ТипЗнч(ВыборкаСтатьи.ВидСтатьи) = Тип("СправочникСсылка.ВидыСтатьиРасходовВидыТренингов") И ТипЗнч(ВыборкаСтатьи.ВидСтатьи) = Тип("СправочникСсылка.ВидыПлатежейСФ") Тогда
        // Если группа еще не была открыта, открываем ее
        Если НЕ ПеременнаяГруппаОткрыта Тогда
            // Можно вывести область-заголовок для группы, если требуется специальное оформление
            // Например, Макет.ПолучитьОбласть("ЗаголовокОсобойГруппы");
            // ОбластьЗаголовка.Параметры.ТекстЗаголовка = ИмяОсобойГруппы;
            // ТабДок.Вывести(ОбластьЗаголовка);

            // Начинаем программную группу
            ТабДок.НачатьГруппуСтрок(ИмяОсобойГруппы, Истина); // Истина - группа открыта по умолчанию
            ПеременнаяГруппаОткрыта = Истина;
        КонецЕсли;

        // Выводим текущий элемент, который попадает в особую группу
        СтрСтат = Макет.ПолучитьОбласть("СтрСтат");
        СтрСтат.Параметры.Заполнить(ВыборкаСтатьи);
        // Отступ может быть увеличен, чтобы показать вложенность внутри группы
        СтрСтат.Область(1,1,1,1).Отступ = ВыборкаСтатьи.Уровень * 2 + 2; // Добавляем дополнительный отступ
        // Применяем форматирование
        Если ВыборкаСтатьи.Уровень = 1 Тогда
            ШрифтЖирный = Новый Шрифт(Макет.Область("СтрСтат").Шрифт,,10,Истина);
            СтрСтат.Область(1, 1, 1, 7).Шрифт = ШрифтЖирный;
        КонецЕсли;
        // Выводим строку как часть текущей группы. Уровень можно использовать для внутреннего порядка или оставить по умолчанию.
        ТабДок.Вывести(СтрСтат); // Важно: здесь не нужно передавать уровень для группировки, она уже начата
    Иначе
        // Если до этого была открыта особая группа, и текущий элемент в нее не входит, закрываем ее
        Если ПеременнаяГруппаОткрыта Тогда
            ТабДок.ЗакончитьГруппуСтрок();
            ПеременнаяГруппаОткрыта = Ложь;
        КонецЕсли;

        // Обычный вывод для строк, не входящих в особую группу
        СтрСтат = Макет.ПолучитьОбласть("СтрСтат");
        СтрСтат.Параметры.Заполнить(ВыборкаСтатьи);
        СтрСтат.Область(1,1,1,1).Отступ = ВыборкаСтатьи.Уровень*2;
        Если ВыборкаСтатьи.Уровень = 1 Тогда
            ШрифтЖирный = Новый Шрифт(Макет.Область("СтрСтат").Шрифт,,10,Истина);
            СтрСтат.Область(1, 1, 1, 7).Шрифт = ШрифтЖирный;
        КонецЕсли;
        ТабДок.Вывести(СтрСтат, ВыборкаСтатьи.Уровень,,?(ВыборкаСтатьи.Уровень>2,Ложь,Истина));
    КонецЕсли;
КонецЦикла;

// В конце цикла, если группа была открыта, обязательно закрываем ее
Если ПеременнаяГруппаОткрыта Тогда
    ТабДок.ЗакончитьГруппуСтрок();
КонецЕсли;

Этот подход позволяет нам создавать группы именно тогда, когда это требуется по условию, и присваивать им произвольные имена. Обратите внимание на переменную ПеременнаяГруппаОткрыта, которая помогает отслеживать состояние, чтобы корректно открывать и закрывать группу только при смене условий. Если несколько последовательных строк попадают под условие, они будут выведены внутри одной группы.

Важный момент: для заголовка группы (того, что будет видно при свернутой группе) можно использовать отдельную область макета. Например, создать область "ЗаголовокГруппы", куда выводится текст "Особые статьи расходов и платежей", а затем вывести эту область *перед* вызовом НачатьГруппуСтрок(), либо внутри этой группы, задав ей минимальный уровень, если используется параметрический вывод. В примере выше, имя группы будет видно в самом значке свертывания/развертывания.

Решение 2: Использование параметров метода ТабличныйДокумент.Вывести()

Метод ТабличныйДокумент.Вывести() имеет расширенные параметры, которые позволяют управлять группировкой непосредственно при выводе каждой строки:

ТабличныйДокумент.Вывести(<Область>, <Уровень>, <ИмяГруппы>, <Открыта>)

Разберем эти параметры:

  1. <Область>: Область табличного документа, которую необходимо вывести.
  2. <Уровень> (число): Число, определяющее уровень вложенности выводимой строки. Используется для автоматической группировки. Соседние строки с одинаковым уровнем будут относиться к одной группе, если их родительская группа имеет меньший уровень. Строки с минимальным уровнем (часто 1) не объединяются в группу, а являются верхними элементами.
  3. <ИмяГруппы> (строка): Необязательный параметр, позволяющий задать имя группы, к которой относится выводимая строка. Это имя будет отображаться рядом со значком свертывания/развертывания группы. Если в одной группе есть несколько строк с разными ИмяГруппы, то будет использоваться имя первой строки. Для корректного именования группы рекомендуется задавать ИмяГруппы для заголовка группы.
  4. <Открыта> (булево): Необязательный параметр. Определяет, будет ли выведенная группа открыта или свернута по умолчанию.

Этот подход требует немного другой организации данных и вывода. Для того чтобы группа имела название, нам нужно вывести как минимум одну строку (обычно это заголовок группы) с этим названием и определенным уровнем, а затем остальные строки группы с уровнем на единицу большим.

Для реализации этого метода, как было подсказано в обсуждении (сообщение 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. Использование макетов для заголовков групп: Для более сложного и красивого оформления заголовков групп, особенно когда группа свернута, рекомендуется создать в макете отдельную область (например, "ЗаголовокГруппы"). В этой области можно разместить параметр для имени группы и любые другие декоративные элементы. При выводе группы, сначала выводим эту область с уровнем группы, а затем уже детали группы с уровнем на единицу большим.
  2. Автоотступ: Для визуального отделения вложенных элементов и улучшения читаемости иерархических отчетов, в свойствах ячеек макета можно установить свойство "АвтоОтступ". Это позволит системе автоматически добавлять отступы слева от текста в зависимости от уровня группировки, не требуя ручной установки Область.Отступ для каждой строки.
  3. Управление видимостью групп: После формирования табличного документа, вы можете программно управлять видимостью уровней группировок с помощью методов ТабДок.ПоказатьУровеньГруппировокСтрок(Уровень) и ТабДок.СвернутьУровеньГруппировокСтрок(Уровень). Это может быть полезно, если вы хотите предоставить пользователю отчет с определенным уровнем детализации по умолчанию.
  4. Система Компоновки Данных (СКД): Для создания сложных и гибких отчетов, где пользователи сами могут настраивать группировки, фильтры и оформление, настоятельно рекомендуется использовать Систему Компоновки Данных. СКД предоставляет мощный механизм для определения структуры отчета, включая динамические группировки по произвольным полям, вычисление ресурсов (итогов) и использование макетов для тонкой настройки вывода. Если ваш отчет становится очень сложным, переход на СКД может существенно упростить дальнейшую разработку и поддержку. В СКД вы можете определить группировку по полю, которое будет вычислено на основании вашей логики (например, то самое ИмяСпециальнойГруппы), а также указать макет для этой группировки.

Таким образом, для программной группировки элементов из разных справочников по условию с присвоением имени группе, мы имеем несколько мощных инструментов в арсенале 1С. Выбор конкретного метода зависит от сложности вашей логики и требуемой гибкости. Наиболее прямой путь — использование методов НачатьГруппуСтрок() и ЗакончитьГруппуСтрок() в сочетании с условной логикой в вашем цикле вывода. Предварительная подготовка данных в запросе или ТаблицеЗначений значительно упростит код вывода и сделает его более читаемым и поддерживаемым.

← На главную