Как сгруппировать данные по двум уровням в печатной форме 1С

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

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

Разбор проблемы: почему не работает простой цикл

Рассмотрим частую ошибку: разработчик пишет запрос без предложения ИТОГИ ПО, получает плоскую таблицу результатов и пытается прямо в цикле выводить области макета с заголовками групп. В этом случае на каждую строчку табличной части выводится заголовок группы, так как данные не разделены на иерархические уровни.

Выясним причину: чтобы механизм выборки 1С понимал иерархию, запрос должен явно возвращать итоговые записи по нужным полям. Только в этом случае выборка результатов сможет работать в режиме ОбходРезультатаЗапроса.ПоГруппировкам.

Правильный подход: добавление секции ИТОГИ в запрос

Сформулируем алгоритм решения задачи:

  1. Добавляем в запрос секцию ИТОГИ ПО с указанием полей верхнего и подчиненного уровней (в нашем примере: МестоХранения, ТемпРежим).
  2. Получаем выборку верхнего уровня через метод Запрос.Выполнить().Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам).
  3. Используем вложенные циклы Пока Выборка.Следующий() Цикл для каждого уровня группировки.

Проанализируем пример кода формирования запроса и обработки результатов:


Функция Печать() Экспорт

    СвойствоН = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("Температурный режим");

    ЗапросДанных = Новый Запрос;
    ЗапросДанных.Текст = 
        "ВЫБРАТЬ РАЗРЕШЕННЫЕ
        |	ПеремещениеТоваровТовары.НомерСтроки КАК НомерСтроки,
        |	ПеремещениеТоваровТовары.Номенклатура КАК Номенклатура,
        |	ПеремещениеТоваровТовары.Количество КАК Количество,
        |	ПеремещениеТоваровТовары.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
        |	ПеремещениеТоваровТовары.СерияНоменклатуры КАК СерияНоменклатуры,
        |	Штрихкоды.ТипШтрихкода КАК ТипШтрихкода,
        |	МИНИМУМ(Штрихкоды.Штрихкод) КАК Штрихкод,
        |	ЕСТЬНУЛЛ(МестаХраненияНоменклатуры.МестоХранения, ""Место хранения не задано"") КАК МестоХранения,
        |	ЕСТЬНУЛЛ(ЗначенияСвойствОбъектов.Значение, ""Без темп. режима"") КАК ТемпРежим
        |ИЗ
        |	Документ.ПеремещениеТоваров.Товары КАК ПеремещениеТоваровТовары
        |	ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Штрихкоды КАК Штрихкоды
        |		ПО ПеремещениеТоваровТовары.Номенклатура = Штрихкоды.Владелец
        |		И ПеремещениеТоваровТовары.ЕдиницаИзмерения = Штрихкоды.ЕдиницаИзмерения
        |	ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.МестаХраненияНоменклатуры КАК МестаХраненияНоменклатуры
        |		ПО ПеремещениеТоваровТовары.Номенклатура = МестаХраненияНоменклатуры.Номенклатура
        |		И МестаХраненияНоменклатуры.Склад = &Склад
        |	ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
        |		ПО ПеремещениеТоваровТовары.Номенклатура = ЗначенияСвойствОбъектов.Объект
        |		И ЗначенияСвойствОбъектов.Свойство = &Свойство
        |ГДЕ
        |	ПеремещениеТоваровТовары.Ссылка = &Ссылка
        |СГРУППИРОВАТЬ ПО
        |	МестаХраненияНоменклатуры.МестоХранения,
        |	ЗначенияСвойствОбъектов.Значение,
        |	ПеремещениеТоваровТовары.НомерСтроки,
        |	ПеремещениеТоваровТовары.Номенклатура,
        |	ПеремещениеТоваровТовары.Количество,
        |	ПеремещениеТоваровТовары.ЕдиницаИзмерения,
        |	ПеремещениеТоваровТовары.СерияНоменклатуры,
        |	Штрихкоды.ТипШтрихкода
        |УПОРЯДОЧИТЬ ПО
        |	МестоХранения,
        |	ТемпРежим,
        |	НомерСтроки
        |ИТОГИ ПО
        |	МестоХранения,
        |	ТемпРежим";

    ЗапросДанных.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
    ЗапросДанных.УстановитьПараметр("Склад", СсылкаНаОбъект.СкладОтправитель);
    ЗапросДанных.УстановитьПараметр("Свойство", СвойствоН);

    РезультатЗапроса = ЗапросДанных.Выполнить();
    ВыборкаМестаХранения = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);

    ТабДок = Новый ТабличныйДокумент;
    Макет = ПолучитьМакет("Накладная");
    
    ОблШапкаТабл = Макет.ПолучитьОбласть("ШапкаТаблицы");
    ОблМестоХран = Макет.ПолучитьОбласть("ТабМестоХранения");
    ОблМестоОтрыва = Макет.ПолучитьОбласть("МестоОтрыва");
    ОблТемпРежим = Макет.ПолучитьОбласть("ТабТемпРежим");
    ОблСтрТаблицы = Макет.ПолучитьОбласть("СтрокаТаблицы");

    ТабДок.Вывести(ОблШапкаТабл);

    ПерваяИтерация = Истина;

    Пока ВыборкаМестаХранения.Следующий() Цикл

        // Выводим линию отрыва строго МЕЖДУ группами мест хранения
        Если НЕ ПерваяИтерация Тогда
            ТабДок.Вывести(ОблМестоОтрыва);
        Иначе
            ПерваяИтерация = Ложь;
        КонецЕсли;

        ОблМестоХран.Параметры.МестоХранения = ВыборкаМестаХранения.МестоХранения;
        ТабДок.Вывести(ОблМестоХран);

        ВыборкаТемпРежим = ВыборкаМестаХранения.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
        Пока ВыборкаТемпРежим.Следующий() Цикл

            ОблТемпРежим.Параметры.ТемпературныйРежим = ВыборкаТемпРежим.ТемпРежим;
            ТабДок.Вывести(ОблТемпРежим);

            ВыборкаТовары = ВыборкаТемпРежим.Выбрать();
            Пока ВыборкаТовары.Следующий() Цикл

                ОблСтрТаблицы.Параметры.Номенклатура = ВыборкаТовары.Номенклатура;
                ОблСтрТаблицы.Параметры.Количество = ВыборкаТовары.Количество;
                ОблСтрТаблицы.Параметры.ЕдиницаИзмерения = ВыборкаТовары.ЕдиницаИзмерения;
                ОблСтрТаблицы.Параметры.Штрихкод = ВыборкаТовары.Штрихкод;
                
                ТабДок.Вывести(ОблСтрТаблицы);

            КонецЦикла;

        КонецЦикла;

    КонецЦикла;

    Возврат ТабДок;

КонецФункции

Обработка частных случаев и тонкости настройки

Рассмотрим важные нюансы, которые часто возникают при реализации иерархических печатных форм:

  1. Обработка незаполненных значений (NULL): Если условие фильтрации по дополнительным реквизитам или складам поместить в секцию ГДЕ, строки без указанных мест хранения или температурных режимов полностью выпадут из печатной формы. Чтобы этого не произошло, перенесем условия соединения в секцию ПО и используем функцию ЕСТЬНУЛЛ(). Это позволит корректно вывести позиции без свойств в отдельную группу (например, «Без темп. режима»).
  2. Исключение лишней разделительной линии в конце документа: Чтобы область разрыва (линия отрыва) выводилась только между блоками мест хранения и не появлялась в самом конце документа, используется булевый флаг (например, ПерваяИтерация). Разделитель выводится перед каждой последующей группой верхнего уровня, кроме первой.
  3. Сортировка в итоговых запросах: При использовании ИТОГИ ПО предложение УПОРЯДОЧИТЬ ПО должно содержать группировочные поля в строгом порядке иерархии (МестоХранения, ТемпРежим, НомерСтроки). Если указать сортировку только по НомерСтроки, иерархический обход выборок вернет непредсказуемый порядок записей.
  4. Использование визуальной автогруппировки: Для создания сворачиваемых группировок прямо в табличном документе можно применить методы ТабДок.НачатьАвтогруппировкуСтрок() и ТабДок.ЗакончитьАвтогруппировкуСтрок(), выходя при этом на метод ТабДок.Вывести(Область, Выборка.Уровень).
← На главную