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