При разработке сложных систем автоматизации на базе 1С:Предприятие 8.2 (УПП, КА 1.1) программисты часто сталкиваются с необходимостью использовать готовые механизмы типовых отчетов для получения данных в своих обработках. Одной из самых популярных подсистем является Универсальный отчет, построенный на базе ПостроителяОтчета. Однако при попытке программного выгрузки результата в ТаблицуЗначений часто возникает проблема: в итоговой таблице присутствуют числовые показатели (ресурсы), но отсутствуют аналитические разрезы, такие как Контрагент или Договор.
В этой статье мы подробно разберем, почему так происходит, и как правильно настроить объект отчета, чтобы получить полную структуру данных без визуального открытия формы.
Для начала проанализируем ситуацию. Когда мы создаем объект отчета программно через Отчеты.НазваниеОтчета.Создать(), мы получаем экземпляр объекта с настройками "по умолчанию". В типовых конфигурациях логика заполнения этих настроек (какие поля выводить, какие фильтры ставить) часто разнесена между модулем объекта и модулем формы.
Рассмотрим стандартный подход, который часто приводит к ошибке (отсутствию колонок):
ОтчетОсн = Отчеты.ДебиторскаяЗадолженностьПоСрокамДолга.Создать();
ОтчетОсн.УниверсальныйОтчет.ДатаНач = ДатаНач;
ОтчетОсн.УниверсальныйОтчет.ДатаКон = ДатаКон;
// Попытка выгрузить результат
ТЗ = ОтчетОсн.УниверсальныйОтчет.ПостроительОтчета.Результат.Выгрузить();
В данном случае ТЗ, скорее всего, будет содержать только общие итоги. Это происходит потому, что ПостроительОтчета еще не "знает", в каких разрезах нужно сгруппировать данные. Колонки аналитики в Универсальном отчете — это не просто выбранные поля, это Измерения (группировки строк).
Выясним причину, по которой колонки появляются при открытии формы. В модуле формы любого типового отчета в процедуре ПриОткрытии или ПередОткрытием вызываются процедуры инициализации. Чтобы решить задачу программно, нам нужно имитировать это поведение в коде.
Рассмотрим правильную последовательность действий:
УстановитьНачальныеНастройки(). Это "зашьет" в построитель базовый состав полей.ГруппировкиСтрок.Пример модифицированного кода:
ОтчетОсн = Отчеты.ДебиторскаяЗадолженностьПоСрокамДолга.Создать();
// 1. Инициализируем стандартные настройки
ОтчетОсн.УстановитьНачальныеНастройки();
// 2. Устанавливаем периоды
ОтчетОсн.УниверсальныйОтчет.ДатаНач = НужнаяДата;
ОтчетОсн.УниверсальныйОтчет.ДатаКон = НужнаяДата;
// 3. Работаем напрямую с ПостроителемОтчета для добавления измерений
Построитель = ОтчетОсн.УниверсальныйОтчет.ПостроительОтчета;
// Очистим текущие группировки, если нужно сформировать свою структуру
Построитель.ГруппировкиСтрок.Очистить();
// Добавляем Контрагента и Договор в группировки
Построитель.ГруппировкиСтрок.Добавить("Контрагент");
Построитель.ГруппировкиСтрок.Добавить("Договор");
// 4. Обязательно проверяем ВыбранныеПоля
// Если поля уже есть в группировках, они попадут в результат.
// Но показатели (суммы) должны быть в ВыбранныхПолях.
// 5. Формируем запрос и выгружаем
Построитель.Выполнить();
ТЗ = Построитель.Результат.Выгрузить();
Проанализируем техническую разницу между ВыбранныеПоля и ГруппировкиСтрок. В ПостроителеОтчета поля, добавленные только в ВыбранныеПоля, отображаются в детальных записях. Однако Универсальный отчет чаще всего работает в режиме итогов. Если вы хотите получить "плоскую" таблицу, где в каждой строке заполнен контрагент и его сумма, эти поля обязательно должны быть в коллекции ГруппировкиСтрок.
Также обратим внимание на метод УниверсальныйОтчет.ДобавитьГруппировку(). В типовых конфигурациях рекомендуется использовать именно его вместо прямого обращения к построителю, так как он корректно обрабатывает метаданные:
ОтчетОсн.УниверсальныйОтчет.ДобавитьГруппировку("Контрагент");
ОтчетОсн.УниверсальныйОтчет.ДобавитьГруппировку("Договор");
Иногда даже после добавления группировок Результат.Выгрузить() возвращает пустые колонки. Это может быть связано с тем, что текст запроса построителя еще не сформирован (не собраны части текста из настроек). В Универсальном отчете за это отвечает метод СформироватьОтчет().
Рассмотрим прием, который гарантирует сборку отчета:
// Создаем фиктивный табличный документ
ТабДок = Новый ТабличныйДокумент;
// Этот метод заставляет объект пересобрать текст запроса со всеми фильтрами и группировками
ОтчетОсн.УниверсальныйОтчет.СформироватьОтчет(ТабДок);
// Теперь выгружаем данные из построителя, который уже точно отработал
ТЗ = ОтчетОсн.УниверсальныйОтчет.ПостроительОтчета.Результат.Выгрузить();
Хотя нам не нужен сам ТабличныйДокумент, вызов СформироватьОтчет инициализирует внутренние механизмы подсистемы, которые могут пропускаться при простом обращении к Результат.Выполнить().
Если вам необходимо получить не просто плоскую таблицу, а сохранить подчиненность (например, чтобы видеть, какие договоры относятся к конкретному контрагенту), при выгрузке следует использовать параметр обхода:
// Выгрузка с сохранением структуры группировок
ТЗ = Построитель.Результат.Выгрузить(ОбходРезультатаПостроителяОтчета.ПоГруппировкам);
В этом случае ТЗ будет содержать древовидную структуру, где строки с договорами будут вложены в строки с контрагентами. Для большинства задач программной обработки данных удобнее использовать ОбходРезультатаПостроителяОтчета.Прямой, что даст плоскую таблицу, удобную для поиска и фильтрации через НайтиСтроки().
Выясним специфику этого конкретного отчета. Он отличается тем, что его колонки (интервалы задолженности: "1-3 дня", "4-7 дней" и т.д.) формируются динамически. Если вы программно меняете Интервал, обязательно убедитесь, что параметры построителя обновлены.
Рассмотрим, как это выглядит в коде:
ОтчетОсн.Интервал = Интервал; // Установка ссылки на справочник интервалов
// После изменения интервала или дат обязательно вызываем:
ОтчетОсн.УстановитьНачальныеНастройки();
// Или вручную заполняем параметры построителя, если метод выше затирает ваши настройки.
Подведем итог: для успешного получения колонок "Контрагент" и "Договор" в ТЗ из Универсального отчета, мы должны убедиться, что эти поля добавлены в ГруппировкиСтрок построителя отчета, и что сам отчет проинициализирован вызовом УстановитьНачальныеНастройки() или программным переносом логики из модуля формы в наш алгоритм.