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