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