При разработке отчетов в системе 1С:Предприятие программисты часто сталкиваются с задачей вывода данных в виде кросс-таблицы (двумерной матрицы). Классический пример такой задачи — вывод сумм продаж или платежей по месяцам, где количество месяцев заранее неизвестно или может меняться в зависимости от выбранного пользователем периода. В современной платформе 1С для этих целей чаще всего применяется Система компоновки данных (СКД), которая автоматически формирует колонки по горизонтали. Однако бывают ситуации, когда необходимо доработать старый отчет, обойтись без СКД или реализовать нестандартный вывод вручную через программный код и классический макет табличного документа.
В этой статье мы подробно разберем, как реализовать динамический горизонтальный вывод колонок в табличный документ 1С, используя проверенные временем механизмы работы с областями макета, а также методы Вывести() и Присоединить().
Прежде чем переходить к написанию кода, важно понять базовый алгоритм работы платформы 1С с табличным документом при программном выводе. Платформа использует виртуальный курсор для заполнения ячеек. Основными инструментами управления этим курсором являются два метода:
Критически важное правило: нельзя начать вывод строки с метода Присоединить(). Самая первая (левая) ячейка любой строки обязательно должна быть выведена методом Вывести(). Все последующие ячейки в этой же строке, которые должны располагаться справа, добавляются методом Присоединить(). Нарушение этого правила приведет к непредсказуемому смещению ячеек и нарушению структуры отчета.
Для создания динамической горизонтальной таблицы в макете конфигуратора нам необходимо настроить специальную структуру именованных областей. Вместо создания одной большой строки мы разделим макет на пересекающиеся горизонтальные и вертикальные секции.
Рассмотрим по шагам, как правильно разметить макет в конфигураторе:
Шапка.Строка.ФиксКолонка.ДинамическаяКолонка.В результате на пересечении этих областей мы получаем четыре уникальные зоны, к которым сможем обращаться из кода:
Шапка|ФиксКолонка — левый верхний угол таблицы (заголовок левой колонки, например, "Контрагент").Шапка|ДинамическаяКолонка — ячейка шапки, куда будет выводиться название месяца (будет многократно присоединяться в цикле).Строка|ФиксКолонка — левая ячейка строки данных с именем контрагента.Строка|ДинамическаяКолонка — ячейка строки данных, куда будет выводиться сумма за конкретный месяц.Рассмотрим практическую реализацию. Предположим, у нас есть Таблица Значений с именем ТЗ_Данные, содержащая колонки Контрагент, Месяц (тип Дата) и Сумма. Нам необходимо построить отчет, где строки — это контрагенты, а колонки — месяцы, в которых были платежи.
Проанализируем и выполним следующий программный код:
// Создаем результирующий табличный документ и получаем макет
ТабДок = Новый ТабличныйДокумент;
Макет = ПолучитьМакет("Макет");
// Получаем области пересечения из макета
ОбластьШапкаЛево = Макет.ПолучитьОбласть("Шапка|ФиксКолонка");
ОбластьШапкаМесяц = Макет.ПолучитьОбласть("Шапка|ДинамическаяКолонка");
ОбластьСтрокаЛево = Макет.ПолучитьОбласть("Строка|ФиксКолонка");
ОбластьСтрокаМесяц = Макет.ПолучитьОбласть("Строка|ДинамическаяКолонка");
// Сначала определим уникальный список месяцев для формирования колонок
ТаблицаКолонок = ТЗ_Данные.Скопировать();
ТаблицаКолонок.Свернуть("Месяц");
ТаблицаКолонок.Сортировать("Месяц Возр");
// --- ШАГ 1. ВЫВОД ШАПКИ ТАБЛИЦЫ ---
// Выводим крайнюю левую ячейку шапки (начинаем строку)
ТабДок.Вывести(ОбластьШапкаЛево);
// В цикле присоединяем названия месяцев по горизонтали справа
Для Каждого СтрКолонка Из ТаблицаКолонок Цикл
// Форматируем дату для вывода названия месяца (например, "Январь 2023")
ОбластьШапкаМесяц.Параметры.ИмяМесяца = Формат(СтрКолонка.Mesyac, "ДФ='MMMM yyyy'");
ТабДок.Присоединить(ОбластьШапкаМесяц);
КонецЦикла;
// --- ШАГ 2. ВЫВОД СТРОК С ДАННЫМИ ---
// Получаем уникальный список контрагентов для строк
ТаблицаСтрок = ТЗ_Данные.Скопировать();
ТаблицаСтрок.Свернуть("Контрагент");
Для Каждого СтрКонтрагент Из ТаблицаСтрок Цикл
// Начинаем новую строку данных: выводим имя контрагента слева
ОбластьСтрокаЛево.Параметры.Контрагент = СтрКонтрагент.Контрагент;
ТабДок.Вывести(ОбластьСтрокаЛево);
// Теперь для текущего контрагента обходим все выведенные ранее месяцы
Для Каждого СтрКолонка Из ТаблицаКолонок Цикл
// Ищем сумму по текущему контрагенту и текущему месяцу
ПараметрыОтбора = Новый Структура("Контрагент, Месяц", СтрКонтрагент.Контрагент, СтрКолонка.Месяц);
НайденныеСтроки = ТЗ_Данные.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
ОбластьСтрокаМесяц.Параметры.Сумма = НайденныеСтроки[0].Сумма;
Иначе
// Если движений в этом месяце не было, выводим ноль или оставляем пустым
ОбластьСтрокаМесяц.Параметры.Сумма = 0;
EndIf;
// Присоединяем ячейку с суммой по горизонтали справа
ТабДок.Присоединить(ОбластьСтрокаМесяц);
КонецЦикла;
КонецЦикла;
// Отображаем готовый отчет пользователю
ТабДок.Показать();
При работе с динамическими таблицами "ручной" сборки важно учитывать несколько неочевидных особенностей платформы 1С, которые уберегут вас от ошибок форматирования и проблем с производительностью:
1. Кэширование областей макета. Обратите внимание, что в примере кода получение областей из макета с помощью метода ПолучитьОбласть() выполняется до начала основных циклов. Это критически важно для оптимизации. Обращение к макету — ресурсоемкая операция. Если вы будете вызывать метод ПолучитьОбласть() внутри циклов по строкам и колонкам, производительность отчета при больших объемах данных резко снизится.
2. Независимость параметров при присоединении. Часто у начинающих разработчиков возникает вопрос: "Если мы используем один и тот же объект области ОбластьСтрокаМесяц в цикле и постоянно меняем его параметр Сумма, не затрутся ли значения в уже выведенных колонках?". Нет, не затрутся. В момент вызова метода Присоединить() или Вывести() платформа копирует текущее состояние области макета вместе со всеми заполненными параметрами в итоговый табличный документ. Последующие изменения параметров переменной области никак не влияют на уже сформированные ячейки отчета.
3. Проверка выхода за границы листа. Если колонок (месяцев) оказывается слишком много, отчет может не уместиться на стандартный лист формата А4 по ширине. Чтобы контролировать этот процесс программно, вы можете использовать метод табличного документа ПроверитьПрисоединение(). В отличие от метода ПроверитьВывод(), который проверяет заполнение по вертикали, этот метод проверяет, помещается ли присоединяемая область по горизонтали с учетом установленных параметров печати. Использование этого метода позволяет вовремя принять решение о переносе данных или программно переключить ориентацию страницы в ландшафтную:
ТабДок.ОриентацияСтраницы = ОриентацияСтраницы.Ландшафт;
4. Оформление объединенных ячеек. Будьте осторожны при использовании объединенных ячеек в присоединяемых областях. Если ваша присоединяемая область по вертикали состоит из нескольких объединенных строк, убедитесь, что высота всех строк этой области жестко зафиксирована в макете. В противном случае, при включенном свойстве ячеек "Автовысота", строки таблицы могут некрасиво разъехаться по высоте.
Разобранный метод вывода областей через пересечение строк и колонок в сочетании с методами Вывести() и Присоединить() позволяет создавать гибкие, динамические отчеты любой сложности, сохраняя полный контроль над внешним видом и логикой заполнения каждой ячейки документа.