Разработка отчетов в системе «1С:Предприятие 8» чаще всего ассоциируется с Системой компоновки данных (СКД). Однако существуют задачи (например, при подготовке к экзамену «1С:Специалист» или при работе в сильно кастомизированных старых конфигурациях), где требуется вывести данные в виде кросс-таблицы (матрицы) вручную. В данной статье мы разберем, как построить отчет, где по вертикали (в строках) выводятся материалы, а по горизонтали (в колонках) — склады, при этом используя только возможности встроенного языка и объект Запрос.
Перед нами стоит классическая техническая задача: сформировать отчет по остаткам в разрезе двух аналитик. Главное условие — полный отказ от СКД, построителя отчета и построителя запроса. Мы должны оперировать прямым обходом результата запроса и методом Присоединить() объекта ТабличныйДокумент. Также необходимо реализовать автоматическое скрытие колонок: если по конкретному складу нет остатков по выбранным позициям, колонка этого склада вообще не должна появляться в отчете.
Для создания кросс-отчета нам недостаточно просто получить список записей. Нам нужна иерархическая структура данных. Рассмотрим пример запроса к регистру накопления (или регистру бухгалтерии). Проанализируем, почему здесь критически важно использование предложения ИТОГИ ПО.
Текст запроса должен выглядеть следующим образом:
ТекстЗапроса =
"ВЫБРАТЬ
| Остатки.Материал КАК Материал,
| Остатки.Склад КАК Склад,
| Остатки.КоличествоОстаток КАК Количество
|ИЗ
| РегистрНакопления.ОстаткиМатериалов.Остатки(&Период, ) КАК Остатки
|ГДЕ
| Остатки.Материал В (&Материал) И Остатки.Склад В (&Склад)
|ИТОГИ
| СУММА(Количество)
|ПО
| Материал,
| Склад";
Использование ИТОГИ ПО позволяет нам в дальнейшем делать вложенные обходы выборки: сначала по материалам, а внутри каждого материала — по складам, на которых он числится.
Поскольку количество складов заранее неизвестно и зависит от остатков, мы не можем нарисовать макет с фиксированными колонками. Разберем алгоритм вывода шапки:
Выгрузить() и свернуть таблицу по колонке Склад.Рассмотрим пример кода для вывода шапки:
// Получаем уникальные склады для колонок
ТаблицаСкладов = РезультатЗапроса.Выгрузить();
ТаблицаСкладов.Свернуть("Склад");
ТабДок = Новый ТабличныйДокумент;
Макет = ПолучитьМакет("Макет");
// Выводим левый верхний угол
ОбластьШапкаЗаголовок = Макет.ПолучитьОбласть("Шапка|Заголовок");
ТабДок.Вывести(ОбластьШапкаЗаголовок);
// Выводим названия складов вправо
ОбластьШапкаСклад = Макет.ПолучитьОбласть("Шапка|Склад");
Для Каждого СтрСклад Из ТаблицаСкладов Цикл
ОбластьШапкаСклад.Параметры.ИмяСклада = СтрСклад.Склад;
ТабДок.Присоединить(ОбластьШапкаСклад);
КонецЦикла;
Теперь перейдем к самой сложной части — заполнению строк. Нам нужно вывести название материала в первую колонку, а затем «пройтись» по всем складам, которые мы вывели в шапке, и проставить количество там, где оно есть.
Проанализируем ситуацию: в выборке по конкретному материалу может не быть данных по всем складам. Поэтому мы должны сопоставлять текущий склад из выборки со списком складов из шапки. Посмотрим на реализацию двойного цикла:
ВыборкаМатериал = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаМатериал.Следующий() Цикл
// Выводим название материала в новую строку
ОбластьСтрокаЗаголовок = Макет.ПолучитьОбласть("Строка|Заголовок");
ОбластьСтрокаЗаголовок.Параметры.Материал = ВыборкаМатериал.Материал;
ТабДок.Вывести(ОбластьСтрокаЗаголовок);
// Теперь выводим данные по складам в ту же строку
ОбластьСтрокаДанные = Макет.ПолучитьОбласть("Строка|Данные");
Для Каждого СтрСклад Из ТаблицаСкладов Цикл
// Ищем, есть ли у этого материала остаток на текущем складе из шапки
ВыборкаСклад = ВыборкаМатериал.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
КоличествоОстаток = 0;
Пока ВыборкаСклад.Следующий() Цикл
Если ВыборкаСклад.Склад = СтрСклад.Склад Тогда
КоличествоОстаток = ВыборкаСклад.Количество;
Прервать;
КонецЕсли;
КонецЦикла;
ОбластьСтрокаДанные.Параметры.Количество = ?(КоличествоОстаток = 0, "", КоличествоОстаток);
ТабДок.Присоединить(ОбластьСтрокаДанные);
КонецЦикла;
КонецЦикла;
При реализации данного алгоритма следует обратить внимание на несколько критических моментов, которые часто упускают новички:
Присоединить(): В отличие от Вывести(), этот метод не создает новую строку в табличном документе, а добавляет область справа от уже выведенных в текущей строке.ТаблицаСкладов на основе реального результата запроса, колонки складов без остатков (в рамках сделанных отборов) не будут попадать в отчет.ВыборкаСклад внутри цикла по ТаблицаСкладов может работать медленно на очень больших объемах данных. Для оптимизации можно предварительно выгружать детальные записи в таблицу значений и использовать метод Найти().Субконто имеют составной тип данных. В запросе желательно использовать ВЫРАЗИТЬ(Субконто1 КАК Справочник.Номенклатура).Мы рассмотрели «ручной» способ формирования кросс-отчета. Этот метод, описанный в фундаментальных трудах по разработке на платформе 1С (например, у Е.Ю. Хрусталевой), позволяет полностью контролировать процесс вывода каждой ячейки. Несмотря на то, что в реальных проектах предпочтительнее использовать СКД из-за скорости разработки и гибкости настроек, понимание механизмов Вывести/Присоединить и иерархических выборок запроса является обязательным для квалифицированного специалиста.