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