Как передать таблицу значений из обработки в отчет СКД с сохранением отборов?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В практике разработки на платформе «1С:Предприятие 8.3» часто возникает задача, когда необходимо сформировать таблицу данных во внешней обработке (например, рассчитать сложные цены или собрать данные из внешнего API), а затем передать этот массив в отчет на Системе компоновки данных (СКД) для красивого вывода, группировки и последующего анализа. Начинающие разработчики часто сталкиваются с трудностями при передаче таблиц значений, потерей пользовательских настроек отборов и непониманием механизмов работы СКД с внешними источниками данных.

В этой статье мы подробно разберем, как правильно реализовать передачу таблицы значений (ТЗ) из обработки в отчет СКД, рассмотрим особенности для управляемых и обычных форм, решим проблему "слетающих" отборов и изучим продвинутый высокопроизводительный метод передачи данных через Менеджер временных таблиц.

---

Понимание проблемы: Обычные vs Управляемые формы

Основная сложность при переходе с обычных форм на управляемые заключается в том, что тип данных ТаблицаЗначений является мутабельным. Это означает, что его нельзя напрямую передать с клиента на сервер или передать как параметр формы при открытии. Попытка написать простой код вида ФормаОтчета.Параметры.Вставить("ВнешняяТЗ", ТЗ) вызовет критическую ошибку выполнения.

В обычных формах (в толстом клиенте) данные могли храниться непосредственно в оперативной памяти единого сеанса, поэтому там работала простая передача ТЗ в реквизит формы. В управляемом приложении нам необходимо использовать специальный мост — Временное хранилище.

---

Решение №1: Реализация в Управляемых формах через Временное хранилище

Рассмотрим классический и наиболее правильный алгоритм передачи таблицы данных в управляемом приложении по шагам.

Шаг 1. Подготовка схемы компоновки данных (СКД)

В вашем отчете необходимо зайти в конструктор схемы компоновки данных и выполнить следующие действия:

  1. Добавить новый Набор данных - объект.
  2. В поле "Имя объекта содержащего данные" указать имя, по которому мы будем передавать ТЗ (например, ТаблицаЦен).
  3. Добавить поля набора данных с точным указанием их имен и типов (например, Номенклатура с типом ссылки на справочник и Цена с типом числа).

Шаг 2. Код в форме внешней обработки (отправитель)

В форме обработки, откуда пользователь нажимает кнопку формирования отчета, мы должны сформировать таблицу значений на сервере, поместить ее во временное хранилище, получить адрес этого хранилища и передать его в параметрах открытия формы отчета.


&НаКлиенте
Процедура СформироватьОтчет(Команда)
    
    // Шаг 2.1. Формируем ТЗ на сервере и помещаем во временное хранилище
    АдресХранилища = ПолучитьАдресТЗНаСервере();
    
    // Шаг 2.2. Подготавливаем параметры для открытия формы отчета
    ПараметрыФормы = Новый Структура;
    ПараметрыФормы.Вставить("АдресВнешнейТЗ", АдресХранилища);
    ПараметрыФормы.Вставить("СформироватьПриОткрытии", Истина);
    
    // Шаг 2.3. Открываем форму нашего отчета
    ОткрытьФорму("Отчет.ОстаткиПоПартиямСЦеной.Форма.ФормаОтчета", ПараметрыФормы);
    
КонецПроцедуры

&НаСервере
Функция ПолучитьАдресТЗНаСервере()
    
    // Создаем и типизируем таблицу значений
    ТЗ = Новый ТаблицаЗначений;
    ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
    ТЗ.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число",,, Новый КвалификаторыЧисла(12, 2)));
    
    // Заполняем таблицу данными (для примера)
    НоваяСтрока = ТЗ.Добавить();
    НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоКоду("00000001");
    НоваяСтрока.Цена = 150.00;
    
    // Помещаем таблицу во временное хранилище. 
    // В качестве уникального идентификатора передаем идентификатор формы обработки.
    Возврат ПоместитьВоВременноеХранилище(ТЗ, ЭтаФорма.УникальныйИдентификатор);
    
КонецФункции

Шаг 3. Код в форме отчета (приемник)

В самом отчете нам необходимо создать реквизит объекта с именем АдресВнешнейТЗ (тип: Строка). Это нужно для того, чтобы модуль объекта отчета имел доступ к адресу во временном хранилище при компоновке результата. В модуле формы отчета перехватываем переданный параметр:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Проверяем, передан ли параметр из обработки
    Если Параметры.Свойство("АдресВнешнейТЗ") Тогда
        Отчет.АдресВнешнейТЗ = Параметры.АдресВнешнейТЗ;
    КонецЕсли;
    
КонецПроцедуры

Шаг 4. Код в модуле объекта отчета

Вся магия сбора данных и вывода результата происходит в предопределенном обработчике события ПриКомпоновкеРезультата модуля объекта отчета. Здесь мы отключаем стандартный вывод и инициализируем процессор компоновки вручную:


Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
    
    // Отключаем стандартный вывод, так как будем выводить программно
    СтандартнаяОбработка = Ложь;
    
    // Проверяем, заполнена ли ссылка на временное хранилище
    Если НЕ ЗначениеЗаполнено(ЭтотОбъект.АдресВнешнейТЗ) Тогда
        Возврат;
    КонецЕсли;
    
    // Извлекаем таблицу значений из временного хранилища
    ТаблицаЦен = ПолучитьИзВременногоХранилища(ЭтотОбъект.АдресВнешнейТЗ);
    
    // Получаем схему из макета
    СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
    
    // КРИТИЧЕСКИ ВАЖНО: Получаем текущие настройки компоновщика (а не дефолтные!)
    Настройки = КомпоновщикНастроек.ПолучитьНастройки();
    
    ДанныеРасшифровки = Новый ДанныеРасшифровкиКомпоновкиДанных;
    
    // Создаем макет компоновки
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
    
    // Передаем нашу таблицу значений во внешний набор данных.
    // Ключ структуры "ТаблицаЦен" должен в точности совпадать с именем Набора Данных - Объекта в СКД!
    ВнешнийНаборДанных = Новый Структура("ТаблицаЦен", ТаблицаЦен);
    
    // Инициализируем процессор компоновки данных
    ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, ВнешнийНаборДанных, ДанныеРасшифровки);
    
    // Очищаем и выводим результат в табличный документ
    ДокументРезультат.Очистить();
    
    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
    ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных);
    
КонецПроцедуры

---

Критическая ошибка: Почему пропадают отборы и параметры пользователя?

Проанализируем частую ошибку программистов, которая упоминается в Сообщении 1 и Сообщении 9 форума. Авторы используют следующую конструкцию:

Настройки = СхемаКомпоновкиДанных.НастройкиПоУмолчанию;

Почему это в корне неверно? Когда пользователь интерактивно открывает отчет, настраивает отборы на форме (например, выбирает конкретный склад, период или контрагента), все эти настройки записываются в КомпоновщикНастроек формы отчета. Если при формировании вы берете НастройкиПоУмолчанию напрямую из Схемы, вы полностью сбрасываете и игнорируете все пользовательские настройки, параметры и установленные им отборы. На выходе получается пустой лист или отчет без фильтров.

Правильное решение: Всегда получайте актуальные настройки через компоновщик отчета:

Настройки = КомпоновщикНастроек.ПолучитьНастройки();

---

Как программно передать отбор по номенклатуре из обработки?

Если вам нужно, чтобы при нажатии кнопки в обработке отчет не просто открылся с таблицей данных, но и сразу применил определенный фильтр (например, ограничил выборку по массиву номенклатуры), мы можем передать структуру отбора через стандартные параметры открытия управляемой формы.

Платформа 1С умеет автоматически сопоставлять ключ структуры Отбор с доступными полями компоновщика настроек СКД. Посмотрим на пример вызова:


&НаКлиенте
Процедура СформироватьОтчетСОтбором(Команда)
    
    АдресХранилища = ПолучитьАдресТЗНаСервере();
    
    // Создаем массив номенклатуры для отбора
    МассивНоменклатуры = Новый Массив;
    МассивНоменклатуры.Добавить(ПредопределенноеЗначение("Справочник.Номенклатура.ПустаяСсылка")); // Замените на реальные ссылки
    
    // Заполняем структуру отборов. Имя ключа должно совпадать с именем поля в СКД!
    СтруктураОтбора = Новый Структура;
    СтруктураОтбора.Вставить("Номенклатура", МассивНоменклатуры);
    
    ПараметрыФормы = Новый Структура;
    ПараметрыФормы.Вставить("АдресВнешнейТЗ", АдресХранилища);
    ПараметрыФормы.Вставить("Отбор", СтруктураОтбора); // Передаем отбор
    ПараметрыФормы.Вставить("СформироватьПриОткрытии", Истина);
    
    ОткрытьФорму("Отчет.ОстаткиПоПартиямСЦеной.Форма.ФормаОтчета", ПараметрыФормы);
    
КонецПроцедуры

Благодаря тому, что в модуле объекта мы используем метод КомпоновщикНастроек.ПолучитьНастройки(), переданный нами массив номенклатуры автоматически применится в качестве элемента отбора типа "В списке" при генерации отчета.

---

Современная высокопроизводительная альтернатива: Использование Менеджера Временных Таблиц (МВТ)

При использовании стандартного метода «Набор данных - Объект» в СКД соединение внешних данных с таблицами базы данных (например, остатками или продажами из регистров) происходит в оперативной памяти компьютера (на сервере приложений 1С). Если таблица значений содержит десятки тысяч строк, такое соединение будет выполняться крайне медленно.

Современные стандарты разработки рекомендуют загружать внешнюю ТЗ прямо в базу данных в качестве временной таблицы СУБД с помощью Менеджера Временных Таблиц (МВТ). Тогда СКД сможет выполнить полноценный и быстрый SQL-запрос ЛЕВОЕ СОЕДИНЕНИЕ на уровне СУБД.

Рассмотрим, как реализовать этот продвинутый механизм:

  1. В схеме СКД создаем обычный Набор данных - запрос.
  2. В тексте запроса мы сразу обращаемся к еще не созданной временной таблице ВТ_ВнешняяТаблицаЦен:

ВЫБРАТЬ
    ВнешниеЦены.Номенклатура КАК Номенклатура,
    ВнешниеЦены.Цена КАК Цена,
    ЦеныРегистра.ЦенаСтарая КАК ЦенаСтарая
ИЗ
    ВТ_ВнешняяТаблицаЦен КАК ВнешниеЦены
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК ЦеныРегистра
        ПО ВнешниеЦены.Номенклатура = ЦеныРегистра.Номенклатура

При сохранении запроса СКД выдаст предупреждение, что таблица ВТ_ВнешняяТаблицаЦен не найдена. Игнорируем его. Чтобы конструктор СКД не выдавал ошибок, рекомендуется объявить поля таблицы с помощью функций ВЫРАЗИТЬ.

Теперь перепишем процедуру ПриКомпоновкеРезультата в модуле объекта отчета следующим образом:


Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
    
    СтандартнаяОбработка = Ложь;
    
    Если НЕ ЗначениеЗаполнено(ЭтотОбъект.AddressВнешнейТЗ) Тогда
        Возврат;
    КонецЕсли;
    
    // 1. Получаем ТЗ из хранилища
    ТаблицаЦен = ПолучитьИзВременногоХранилища(ЭтотОбъект.АдресВнешнейТЗ);
    
    // 2. Инициализируем Менеджер Временных Таблиц и помещаем туда ТЗ
    МенеджерВТ = Новый МенеджерВременныхТаблиц;
    
    ЗапросМВТ = Новый Запрос;
    ЗапросМВТ.МенеджерВременныхТаблиц = МенеджерВТ;
    ЗапросМВТ.Текст = 
        "ВЫБРАТЬ
        |   ТаблицаИсточник.Номенклатура КАК Номенклатура,
        |   ТаблицаИсточник.Цена КАК Цена
        |ПОМЕСТИТЬ ВТ_ВнешняяТаблицаЦен
        |ИЗ
        |   &ТаблицаИсточник КАК ТаблицаИсточник";
    
    ЗапросМВТ.УстановитьПараметр("ТаблицаИсточник", ТаблицаЦен);
    ЗапросМВТ.Выполнить();
    
    // 3. Стандартная инициализация макета
    СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
    Настройки = КомпоновщикНастроек.ПолучитьНастройки();
    
    ДанныеРасшифровки = Новый ДанныеРасшифровкиКомпоновкиДанных;
    КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
    МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
    
    // 4. Инициализируем процессор и передаем Менеджер ВТ шестым параметром!
    ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
    ПроцессорКомпоновкиДанных.Инициализировать(
        МакетКомпоновки, 
        Новый Структура, // Внешний набор данных теперь передавать НЕ нужно!
        ДанныеРасшифровки, 
        Ложь, 
        Ложь, 
        МенеджерВТ      // Передаем заполненный Менеджер ВТ
    );
    
    // 5. Выводим результат
    ДокументРезультат.Очистить();
    ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
    ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
    ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных);
    
    // Обязательно закрываем менеджер после работы для освобождения памяти СУБД
    МенеджерВТ.Закрыть();
    
КонецПроцедуры

Этот способ является наиболее профессиональным. Он исключает зависание сервера при обработке больших массивов информации, позволяет использовать всю мощь языка запросов 1С при соединении таблиц и полностью сохраняет возможность использования стандартных отборов, группировок и условного оформления СКД.

← На главную