Как вывести данные по всем договорам контрагента в один макет 1С без дублирования шапки отчета

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

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

В этой статье мы подробно разберем, почему возникает данная проблема, как правильно разделить макет на области в конфигураторе, спроектировать оптимальный запрос с итогами и написать эффективный код вывода данных с использованием передовых практик 1С.

Анализ проблемы: почему дублируется шапка отчета?

Рассмотрим ситуацию, описанную в сообщении 13. У автора при выводе каждого нового договора в табличный документ заново выводятся строки с названием организации ("Общество с ограниченной ответственностью...") и заголовком отчета ("Платежная дисциплина на 1 июня 2020 г.").

Выясним причину этого поведения. Она кроется в двух классических ошибках проектирования:

  1. Неправильное разделение областей в макете. В конфигураторе общие реквизиты отчета (название компании, заголовок, имя контрагента) и переменные данные договора (номер, дата, вид имущества) объединены в одну общую горизонтальную область (например, с именем Шапка).
  2. Вывод общей шапки внутри цикла. В программном коде метод ТабДок.Вывести(ОбластьШапка) вызывается на каждой итерации цикла обхода договоров.

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

Шаг 1. Проектирование структуры макета в конфигураторе

Откроем макет нашего отчета в конфигураторе. Нам нужно разделить его на несколько именованных горизонтальных областей (строк). Каждая область должна отвечать только за свою логическую часть отчета. Выделим строки макета, нажмем Таблица — Имена — Назначить имя и создадим следующие области:

  1. ШапкаОтчета (выводится один раз в самом начале формирования документа). Сюда мы помещаем:
    • Название нашей организации (параметр Организация).
    • Заголовок отчета ("Платежная дисциплина на...").
    • Наименование контрагента (параметр Контрагент), так как контрагент у нас один для всего отчета, и нет смысла повторять его имя для каждого договора.
  2. ШапкаДоговора (выводится в цикле для каждого нового договора). Эта область должна содержать только данные конкретного договора:
    • Номер и дату договора (параметр Договор).
    • Специфические реквизиты договора, например, вид арендуемого имущества (параметр ВидИмущества).
  3. ШапкаТаблицы (выводится один раз для каждого договора сразу после его шапки). Содержит шапку таблицы с колонками: "Дата платежа", "Сумма по графику", "Фактическая оплата", "Просрочка (дней)".
  4. Строка или Детали (выводится в цикле по строкам оплат текущего договора). Содержит параметры детальных записей (например, ДатаПлатежа, СуммаОплаты).
  5. ПодвалТаблицы (выводится после вывода всех оплат по конкретному договору). Содержит итоговые суммы по текущему договору (параметр СуммаИтого).
  6. ПустаяСтрока (небольшая пустая область для визуального разделения блоков договоров).

Шаг 2. Разработка оптимального запроса с расчетом итогов

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

Проанализируем пример такого запроса:


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

Запрос.УстановитьПараметр("Контрагент", КонтрагентОтбора); // Значение берется из шапки отчета
РезультатЗапроса = Запрос.Выполнить();

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

Шаг 3. Реализация алгоритма вывода данных в коде

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


// Создаем результирующий табличный документ
ТабДок = Новый ТабличныйДокумент;
ТабДок.Очистить();

// Получаем макет отчета
Макет = ПолучитьМакет("МакетОтчета");

// --- ЭТАП 1: Вывод ОБЩЕЙ ШАПКИ отчета (ВЫПОЛНЯЕТСЯ ОДИН РАЗ ВНЕ ЦИКЛА) ---
ОбластьШапкаОтчета = Макет.ПолучитьОбласть("ШапкаОтчета");
ОбластьШапкаОтчета.Параметры.Организация = РуководительОрганизации;
ОбластьШапкаОтчета.Параметры.Контрагент  = КонтрагентОтбора;
ОбластьШапкаОтчета.Параметры.ДатаОтчета  = ТекущаяДата();
ТабДок.Вывести(ОбластьШапкаОтчета);

// --- ЭТАП 2: Обход договоров контрагента (первый уровень группировки) ---
ВыборкаДоговор = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "Договор");

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

// Выводим готовый документ на экран
ТабДок.Показать("Платежная дисциплина по договорам");

Шаг 4. Контроль разбиения страниц (метод ПроверитьВывод)

В сообщении 1 один из участников форума иронично заметил: "1 за 1 5 окон подряд, вы там бумагу не бережете?". Действительно, при выводе нескольких таблиц подряд на печать может возникнуть ситуация, когда заголовок нового договора выведется в самом низу страницы, а таблица с платежами перенесется на следующую страницу. Это выглядит непрофессионально.

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

Модифицируем наш цикл обхода договоров, добавив проверку перед выводом шапки договора:


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

Шаг 5. Интерактивность: группировка и сворачивание договоров

Для удобства пользователя, анализирующего отчет на экране компьютера, мы можем сгруппировать строки по каждому договору. Это позволит сворачивать и разворачивать детальные оплаты по конкретным договорам с помощью стандартных кнопок группировки 1С ("плюсиков" и "минусиков").

Для этого используются методы НачатьГруппуСтрок() и ЗакончитьГруппуСтрок() табличного документа. Посмотрим, как применить их в коде:


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

// Включаем отображение сетки и панели группировок
ТабДок.ПоказатьСетку = Ложь;
ТабДок.ПоказатьЗаголовки = Ложь;
ТабДок.ТолькоПросмотр = Истина;

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

Подводя итог, отметим: разделение макета на изолированные логические зоны в сочетании со сквозным обходом группировок запроса — это базовый золотой стандарт разработки не-СКД отчетов в среде 1С:Предприятие. Эти простые правила уберегут ваши отчеты от хаотичного дублирования информации и сделают их удобными как для чтения с экрана, так и для вывода на печать.

← На главную