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

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

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

Основная сложность здесь заключается не столько в выводе самих данных, сколько в подведении итогов по этим динамическим колонкам. В этой статье мы подробно разберем три проверенных практических метода решения этой задачи. Мы научимся работать с макетами через пересечение областей, использовать быстрый механизм построителя отчетов и программно настраивать Систему компоновки данных (СКД).

Способ 1. Программный вывод макета через пересечение областей

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

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

  1. Горизонтальные области (строки): создаем области Шапка, Строка и Подвал (где будут располагаться итоги).
  2. Вертикальные области (колонки): создаем область Основная (для статичных колонок, например, «Номенклатура» или «Контрагент») и область Динамическая (которая будет дублироваться в цикле для каждой новой колонки).

В результате пересечения этих линий платформа автоматически сформирует именованные области ячеек. Мы будем обращаться к ним по именам вида «Строка|Основная» или «Подвал|Динамическая». Это позволит нам гибко присоединять колонки вправо.

Рассмотрим детально программный алгоритм вывода такой таблицы и расчета итогов:


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

// Получаем области пересечения для шапки таблицы
ОбластьШапкаОсновная = Макет.ПолучитьОбласть("Шапка|Основная");
ОбластьШапкаДинамическая = Макет.ПолучитьОбласть("Шапка|Динамическая");

// Получаем области пересечения для строк данных
ОбластьСтрокаОсновная = Макет.ПолучитьОбласть("Строка|Основная");
ОбластьСтрокаДинамическая = Макет.ПолучитьОбласть("Строка|Динамическая");

// Получаем области пересечения для подвала (итогов)
ОбластьПодвалОсновная = Макет.ПолучитьОбласть("Подвал|Основная");
ОбластьПодвалДинамическая = Макет.ПолучитьОбласть("Подвал|Динамическая");

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

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

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

// 4. Выводим итоги по колонкам (самый важный шаг)
// Выводим статичную часть подвала с надписью "ИТОГО:"
ТабДок.Вывести(ОбластьПодвалОсновная);

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

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

Важное преимущество метода: Использование встроенного метода ТЗ.Итог("ИмяКолонки") работает на уровне платформы (написан на языке C++). Это работает в десятки раз быстрее, чем если бы мы вручную обходили ячейки сформированного табличного документа или суммировали значения в цикле на встроенном языке 1С.

Способ 2. Использование объекта «ПостроительОтчета»

Если перед вами не стоит задача создать строго индивидуальный или сложный дизайнерский макет, а нужно максимально быстро вывести таблицу значений с итогами на экран, то идеальным выбором станет использование объекта ПостроительОтчета.

Этот объект незаслуженно редко используется современными разработчиками, однако он идеально подходит для быстрой визуализации плоских таблиц с автоматическим расчетом итоговых строк.

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


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

// Инициализируем построитель отчета
Построитель = Новый ПостроительОтчета;

// Передаем нашу таблицу значений ТЗ в качестве источника данных
Построитель.ИсточникДанных = Новый ОписаниеИсточникаДанных(ТЗ);

// Построитель автоматически проанализирует типы колонок.
// Если требуется явно указать, по каким колонкам подводить итоги, 
// мы можем добавить их в коллекцию "Итоги":
Для Каждого Колонка Из ТЗ.Колонки Цикл
    Если Колонка.ТипЗначения.СодержитТип(Тип("Число")) Тогда
        Построитель.Итоги.Добавить(Колонка.Имя);
    КонецЕсли;
КонецЦикла;

// Выполняем расчет и генерируем отчет
Построитель.Выполнить();
Построитель.Вывести(ТабДокумент);

// Показываем готовый результат пользователю
ТабДокумент.Показать();

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

Способ 3. Программная настройка схемы компоновки данных (СКД)

Когда отчет требует не просто вывода итогов по колонкам, но и сложной иерархии, группировок строк, кросс-таблиц (динамические колонки и строки одновременно) или сложной фильтрации, нам на помощь приходит программная генерация СКД.

Для того чтобы система компоновки данных корректно восприняла динамические колонки нашей таблицы значений и рассчитала по ним итоги, выполним следующие шаги:

  1. Создадим программно новую схему компоновки данных СхемаКомпоновкиДанных.
  2. Добавим источник данных и программный набор данных типа «НаборДанных-Объект».
  3. Динамически заполним описание полей набора данных на основе структуры колонок нашей таблицы значений.
  4. Добавим числовые поля в коллекцию ПоляИтогов (Ресурсы) схемы компоновки, указав для них агрегатную функцию Сумма(ИмяКолонки).
  5. Настроим структуру вывода (Выбранные поля и Группировки).

Рассмотрим, как выглядит этот процесс в коде:


// Создаем программную схему компоновки
Схема = Новый СхемаКомпоновкиДанных;

// Добавляем источник данных
ИсточникДанных = Схема.ИсточникиДанных.Добавить();
ИсточникДанных.Имя = "ИсточникДанных1";
ИсточникДанных.ТипИсточникаДанных = "Local";

// Добавляем набор данных - объект
НаборДанных = Схема.НаборыДанных.Добавить(Тип("НаборДанныхОбъектСхемыКомпоновкиДанных"));
НаборДанных.Имя = "НаборДанныхТЗ";
НаборДанных.ИмяОбъекта = "ТаблицаДляСКД";
НаборДанных.ИсточникДанных = "ИсточникДанных1";

// Заполняем поля набора данных и ресурсы схемы
Для Каждого Колонка Из ТЗ.Колонки Цикл
    // Создаем новое поле в наборе данных
    ПолеНабора = НаборДанных.Поля.Добавить(Тип("ПолеНабораДанныхСхемыКомпоновкиДанных"));
    ПолеНабора.Имя = Колонка.Имя;
    ПолеНабора.Поле = Колонка.Имя;
    ПолеНабора.Заголовок = Колонка.Заголовок;
    ПолеНабора.ТипЗначения = Колонка.ТипЗначения;
    
    // Если колонка числовая, регистрируем ее как ресурс (поле итогов)
    Если Колонка.ТипЗначения.СодержитТип(Тип("Число")) Тогда
        ПолеИтога = Схема.ПоляИтогов.Добавить();
        ПолеИтога.ПутьКДанным = Колонка.Имя;
        ПолеИтога.Выражение = "Сумма(" + Колонка.Имя + ")";
    КонецЕсли;
КонецЦикла;

// Настраиваем компоновщик настроек по умолчанию
КомпоновщикНастроек = Новый КомпоновщикНастроекКомпоновкиДанных;
КомпоновщикНастроек.Инициализировать(Новый ИсточникДоступныхНастроекКомпоновкиДанных(Схема));

// Формируем структуру отчета (простая таблица без группировок для вывода детальных записей)
Настройки = КомпоновщикНастроек.Настройки;
Группировка = Настройки.Структура.Добавить(Тип("ГруппировкаКомпоновкиДанных"));
Группировка.Использование = Истина;

 АвтоВыбранныеПоля = Группировка.Выбор.Элементы.Добавить(Тип("АвтоВыбранноеПолеКомпоновкиДанных"));
АвтоВыбранныеПоля.Использование = Истина;

// Добавляем все колонки в выбранные поля компоновщика
Для Каждого Колонка Из ТЗ.Колонки Цикл
    ВыбранноеПоле = Настройки.Выбор.Элементы.Добавить(Тип("ВыбранноеПолеКомпоновкиДанных"));
    ВыбранноеПоле.Использование = Истина;
    ВыбранноеПоле.Поле = Новый ПолеКомпоновкиДанных(Колонка.Имя);
КонецЦикла;

// Выполняем компоновку данных
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(Схема, Настройки);

// Передаем нашу ТЗ во внешний набор данных
ВнешниеНаборыДанных = Новый Структура;
ВнешниеНаборыДанных.Вставить("ТаблицаДляСКД", ТЗ);

ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, ВнешниеНаборыДанных);

// Выводим результат в табличный документ
ТабДокумент = Новый ТабличныйДокумент;
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ТабДокумент);
ПроцессорВывода.Вывести(ПроцессорКомпоновки);

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

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

Резюме: какой метод выбрать для вашей задачи?

Мы подробно проанализировали три технологических подхода к выводу динамических колонок с итогами. Чтобы сделать правильный выбор в реальной разработке, рекомендуем придерживаться простых правил:

← На главную