Как программно вывести группировки в колонки (кросс-таблицу) во внешней обработке 1С?

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

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

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

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

Решение 1. Программный вывод встроенной СКД в обработке

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

Для начала разберем, как должна быть настроена структура в самом макете СКД. Чтобы получить кросс-таблицу, в настройках схемы компоновки структура должна иметь тип Таблица (а не Список). В настройках этой таблицы:

  1. В ветку Строки добавляем группировку по полю, которое будет отвечать за строки (например, Период или Дата).
  2. В ветку Колонки добавляем группировку по полю, которое должно разворачиваться по горизонтали (например, Валюта).
  3. Вкладка Ресурсы должна содержать числовой показатель (например, Курс или Сумма), который будет выводиться на пересечении строк и колонок.

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


Процедура СформироватьНаСервере()
    // Получаем объект обработки из реквизита формы
    ОбработкаОбъект = РеквизитФормыВЗначение("Объект");
    
    // Получаем макет СКД, встроенный во внешнюю обработку
    Схема = ОбработкаОбъект.ПолучитьМакет("Макет"); 
    
    // Инициализируем объекты компоновки
    КомпоновщикМакета   = Новый КомпоновщикМакетаКомпоновкиДанных;  
    КомпоновщикНастроек = Новый КомпоновщикНастроекКомпоновкиДанных;
    ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
    ПроцессорВывода     = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;    
    ТабДок              = Новый ТабличныйДокумент;  
    
    // Связываем компоновщик настроек со схемой компоновки данных
    ИсточникДоступныхНастроек = Новый ИсточникДоступныхНастроекКомпоновкиДанных(Схема); 
    КомпоновщикНастроек.Инициализировать(ИсточникДоступныхНастроек);
    
    // Получаем настройки по умолчанию из нашей схемы
    Настройки = Схема.НастройкиПоУмолчанию;   
    
    // --- Установка параметров данных программы ---
    // Пример установки параметров дат (НачалоПериода и КонецПериода)
    ПараметрНачало = Настройки.ПараметрыДанных.Элементы.Найти("НачалоПериода"); 
    Если ПараметрНачало <> Неопределено Тогда
        ПараметрНачало.Использование = Истина;
        ПараметрНачало.Value = Период.ДатаНачала;
    КонецЕсли;
    
    ПараметрКонец = Настройки.ПараметрыДанных.Элементы.Найти("КонецПериода"); 
    Если ПараметрКонец <> Неопределено Тогда
        ПараметрКонец.Использование = Истина;
        ПараметрКонец.Value = Период.ДатаОкончания;
    КонецЕсли;
    
    // --- Программная установка динамических отборов ---
    // Добавим отбор по конкретному реквизиту (например, "Дом" не равен определенному значению)
    ПолеОтбораДом = Новый ПолеКомпоновкиДанных("Дом");
    ОтборПоДом = Настройки.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ОтборПоДом.ЛевоеЗначение    = ПолеОтбораДом;
    ОтборПоДом.ВидСравнения     = ВидСравненияКомпоновкиДанных.НеРавно;
    ОтборПоДом.ПравоеЗначение   = Справочники.ркДома.НайтиПоКоду("000000002");
    ОтборПоДом.Использование    = Истина;
    
    // Выполняем компоновку макета
    МакетКомпоновки = КомпоновщикМакета.Выполнить(Схема, Настройки);    
    
    // Инициализируем процессор компоновки данных
    ПроцессорКомпоновки.Инициализировать(МакетКомпоновки, , , Истина); 
    
    // Выводим результат в табличный документ формы
    ПроцессорВывода.УстановитьДокумент(Результат); // "Результат" - это ТабличныйДокумент на форме
    ПроцессорВывода.Вывести(ПроцессорКомпоновки);  
КонецПроцедуры

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

Решение 2. Динамическое конструирование структуры СКД кодом

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

Рассмотрим, как программно описать структуру таблицы компоновки данных в коде 1С:


// Очищаем существующую структуру настроек
Настройки.Структура.Очистить();

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

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

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

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

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

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

Решение 3. Ручной вывод через макет без СКД (метод Присоединить)

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

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

Выясним по шагам, как настроить макет для ручного вывода:

  1. Создаем вертикальную секцию ОбластьШапка_Лево (ячейка "Дата" в левом верхнем углу).
  2. Создаем вертикальную секцию ОбластьШапка_Колонка (ячейка с параметром ВалютаНаименование для вывода динамических колонок).
  3. Создаем вертикальную секцию ОбластьСтрока_Лево (строка с параметром Дата).
  4. Создаем вертикальную секцию ОбластьСтрока_Колонка (пересечение строки и колонки, где будет выводиться параметр Курс).

Проанализируем программный алгоритм вывода такой кросс-таблицы:


Процедура ВывестиКроссТаблицуВручную(РезультатЗапроса, ВалютыСписок)
    
    Макет = ПолучитьМакет("МакетРучной");
    ТабДок = Новый ТабличныйДокумент;
    
    // 1. Выводим шапку таблицы
    ОбластьШапкаЛево = Макет.ПолучитьОбласть("ОбластьШапка_Лево");
    ТабДок.Вывести(ОбластьШапкаЛево); // Метод Вывести() начинает новую строку
    
    ОбластьШапкаКолонка = Макет.ПолучитьОбласть("ОбластьШапка_Колонка");
    Для Каждого ТекВалюта Из ВалютыСписок Цикл
        ОбластьШапкаКолонка.Параметры.ВалютаНаименование = ТекВалюта.Наименование;
        ТабДок.Присоединить(ОбластьШапкаКолонка); // Присоединяем колонки справа в строку шапки
    КонецЦикла;
    
    // Сворачиваем выборку по датам для уникальных строк
    ТаблицаДаты = РезультатЗапроса.Скопировать();
    ТаблицаДаты.Свернуть("Дата");
    
    // 2. Выводим строки с данными
    Для Каждого СтрокаДата Из ТаблицаДаты Цикл
        // Выводим левую часть строки (саму Дату)
        ОбластьСтрокаЛево = Макет.ПолучитьОбласть("ОбластьСтрока_Лево");
        ОбластьСтрокаЛево.Параметры.Дата = Формат(СтрокаДата.Дата, "ДФ=dd.MM.yyyy");
        ТабДок.Вывести(ОбластьСтрокаЛево); // Переходим на новую строку и выводим дату
        
        // В цикле по всем доступным валютам ищем курс на текущую дату
        ОбластьЯчейки = Макет.ПолучитьОбласть("ОбластьСтрока_Колонка");
        Для Каждого ТекВалюта Из ВалютыСписок Цикл
            
            // Ищем строку в основном результате запроса по паре "Дата + Валюта"
            ПараметрыПоиска = Новый Структура("Дата, Валюта", СтрокаДата.Дата, ТекВалюта);
            НайденныеСтроки = РезультатЗапроса.НайтиСтроки(ПараметрыПоиска);
            
            Если НайденныеСтроки.Количество() > 0 Тогда
                ОбластьЯчейки.Параметры.Курс = НайденныеСтроки[0].Курс;
            Иначе
                ОбластьЯчейки.Параметры.Курс = "-"; // Курс не найден
            КонецЕсли;
            
            // Присоединяем ячейку курса справа к текущей строке
            ТабДок.Присоединить(ОбластьЯчейки);
        КонецЦикла;
    КонецЦикла;
    
    // Выводим готовый документ на экран
    ТабДок.Показать();
    
КонецПроцедуры

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

Оптимизация и работа с группировками динамических колонок

Если динамических колонок в отчете формируется слишком много (например, выводятся показатели за каждый день месяца), таблица становится нечитаемой. В этом случае полезно сгруппировать колонки, чтобы пользователь мог сворачивать и разворачивать их.

Для реализации группировок колонок в ручном режиме вывода мы можем использовать встроенные методы работы с областями табличного документа. Разберем пример:


// Перед началом вывода группы колонок вызываем метод начала группы
ТабДок.НачатьГруппуКолонок("ГруппаВалюты", Истина);

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

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

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

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

← На главную