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

Программист 1С v8.3 (Обычные формы)
← На главную

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

В данной статье мы подробно разберем, как правильно реализовать такую печать на платформе 1С:Предприятие 8.2 и 8.3 для обычных и управляемых форм. Мы проанализируем две популярные методики решения этой задачи, разберем типичные ошибки начинающих разработчиков и предоставим готовые шаблоны кода для быстрого внедрения.

Анализ типичных ошибок при разработке группировки

Прежде чем перейти к правильным алгоритмам, давайте разберем три критические ошибки, которые часто совершают программисты при первой попытке реализовать подобный функционал.

  1. Выборка табличной части как вложенной таблицы в запросе. Попытка построить запрос вида ВЫБРАТЬ Ссылка, ТабличнаяЧасть.(Поле1, Поле2) ИЗ Документ... приводит к получению иерархического набора данных, где строки табличной части лежат во вложенных таблицах. При такой структуре запроса платформа 1С накладывает жесткое ограничение: использовать предложение ИТОГИ ПО для полей вложенной таблицы напрямую невозможно. Конструктор запроса просто заблокирует эту операцию. Для исправления ситуации необходимо делать плоский запрос к самой табличной части.
  2. Инициализация табличного документа внутри цикла. Ошибка в жизненном цикле объектов выглядит следующим образом: внутри цикла обхода строк пишется строка ТабДокумент = Новый ТабличныйДокумент;. В результате на каждой итерации цикла предыдущий сформированный документ уничтожается в памяти, а создается новый пустой. На выходе пользователь увидит печатную форму, содержащую исключительно последнюю строку выборки. Создавать объект ТабличныйДокумент необходимо строго один раз до начала каких-либо циклов вывода.
  3. Попытка вывода группировок в одном плоском цикле. Без использования механизмов итогов или контроля смены значения разработчики пытаются вывести заголовки групп в одном общем цикле обхода строк. Это приводит либо к дублированию заголовков перед каждой строкой, либо к сложной, трудночитаемой логике со множеством флагов и условий. Решается это либо использованием двухуровневого обхода выборки, либо предварительной сортировкой.

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

Это классический, наиболее эффективный и архитектурно правильный способ решения задачи. Он заключается в том, что мы обращаемся в запросе напрямую к табличной части документа как к самостоятельной таблице базы данных. Это позволяет нам беспрепятственно использовать группировочные итоги и реализовать чистый двухуровневый обход данных.

Проанализируем этот механизм подробнее и разберем его по шагам.

Шаг 1. Формирование правильного запроса. Нам необходимо выбрать все нужные поля из таблицы Документ.ИмяДокумента.ИмяТабличнойЧасти, установить фильтр по конкретному документу-ссылке и добавить секцию ИТОГИ ПО по нашему группировочному полю. Посмотрим на пример такого запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ГрафикПоверки.ВидИзмерения КАК ВидИзмерения,
    |	ГрафикПоверки.Наименование КАК Наименование,
    |	ГрафикПоверки.ТипСИ КАК ТипСИ,
    |	ГрафикПоверки.ЗаводскойНомер КАК ЗаводскойНомер,
    |	ГрафикПоверки.КлассТочностиПогрешность КАК Точность,
    |	ГрафикПоверки.ПределДиапазонИзмерений КАК Предел,
    |	ГрафикПоверки.ПериодичностьПоверки КАК Период,
    |	ГрафикПоверки.ДатаПоследнейПоверки КАК ДатаПоверки
    |ИЗ
    |	Документ.График.ГрафикПоверки КАК ГрафикПоверки
    |ГДЕ
    |	ГрафикПоверки.Ссылка = &Ссылка
    |ИТОГИ ПО
    |	ВидИзмерения";

Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
РезультатЗапроса = Запрос.Выполнить();

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

Шаг 2. Двухуровневый обход результата запроса. Теперь нам необходимо правильно обойти полученный результат. Для этого мы будем использовать метод Выбрать() с параметром обхода по группировкам на первом уровне, и стандартный обход детальных записей на втором уровне. Разберем этот код:


ТабДокумент = Новый ТабличныйДокумент;
Макет = ПолучитьМакет("Макет"); // Или получение макета из менеджера документа

// Получаем области макета для вывода шапки, заголовка группы и строк
ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьГруппы = ...; // Область "ВидИзмерения"
ОбластьСтроки = Макет.ПолучитьОбласть("Строка");

// Выводим общую шапку документа
ТабДокумент.Вывести(ОбластьШапка);

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

КонецЦикла;

// Отображаем сформированный документ пользователю
ТабДокумент.Показать();

Проанализируем логику работы этого алгоритма. На внешнем шаге цикла метод РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам) возвращает только уникальные значения поля ВидИзмерения. Мы выводим область-заголовок группы в печатную форму. На внутреннем шаге мы вызываем метод Выбрать() без параметров уже у объекта текущей группировки ВыборкаГруппы. Это позволяет нам получить детальные строки, которые относятся исключительно к текущему виду измерения, и последовательно вывести их под созданным заголовком.

Решение 2: Группировка и сортировка с помощью Таблицы Значений (объектный подход)

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

Проанализируем этот подход по шагам:

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

Посмотрим на практическую реализацию этого метода в коде:


ТабДокумент = Новый ТабличныйДокумент;
Макет = ПолучитьМакет("Макет");

ОбластьШапка = Макет.ПолучитьОбласть("Шапка");
ОбластьГруппы = Макет.ПолучитьОбласть("ВидИзмерения");
ОбластьСтроки = Макет.ПолучитьОбласть("Строка");

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

// Шаг 1: Выгружаем табличную часть в Таблицу Значений
ТаблицаДанных = СсылкаНаДокумент.ГрафикПоверки.Выгрузить();

// Шаг 2: Сортируем таблицу по группировочному полю
ТаблицаДанных.Сортировать("ВидИзмерения");

// Шаг 3: Переменная для контроля изменения значения группы
ТекущийВидИзмерения = Неопределено;

// Шаг 4: Линейный обход строк
Для Каждого СтрокаТЗ Из ТаблицаДанных Цикл
    
    // Если значение группы изменилось - выводим заголовок новой группы
    Если СтрокаТЗ.ВидИзмерения <> ТекущийВидИзмерения Тогда
        
        ТекущийВидИзмерения = СтрокаТЗ.ВидИзмерения;
        
        ОбластьГруппы = Макет.ПолучитьОбласть("ВидИзмерения");
        ОбластьГруппы.Параметры.ВидИзмерения = ТекущийВидИзмерения;
        ТабДокумент.Вывести(ОбластьГруппы);
        
    КонецЕсли;
    
    // Выводим детальную строку данных
    ОбластьСтроки = Макет.ПолучитьОбласть("Строка");
    ЗаполнитьЗначенияСвойств(ОбластьСтроки.Параметры, СтрокаТЗ);
    ТабДокумент.Вывести(ОбластьСтроки);
    
КонецЦикла;

ТабДокумент.Показать();

Этот метод чрезвычайно прост в реализации. Использование системной процедуры ЗаполнитьЗначенияСвойств() позволяет значительно сократить объем рутинного кода по заполнению параметров области макета, если имена параметров макета совпадают с именами колонок таблицы значений.

Сравнение методов: какое решение выбрать?

Выбор конкретного метода зависит от архитектурных особенностей вашей задачи и состояния данных:

← На главную