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