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