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