В практике разработки на платформе «1С:Предприятие 8.3» часто возникает задача, когда необходимо сформировать таблицу данных во внешней обработке (например, рассчитать сложные цены или собрать данные из внешнего API), а затем передать этот массив в отчет на Системе компоновки данных (СКД) для красивого вывода, группировки и последующего анализа. Начинающие разработчики часто сталкиваются с трудностями при передаче таблиц значений, потерей пользовательских настроек отборов и непониманием механизмов работы СКД с внешними источниками данных.
В этой статье мы подробно разберем, как правильно реализовать передачу таблицы значений (ТЗ) из обработки в отчет СКД, рассмотрим особенности для управляемых и обычных форм, решим проблему "слетающих" отборов и изучим продвинутый высокопроизводительный метод передачи данных через Менеджер временных таблиц.
---
Основная сложность при переходе с обычных форм на управляемые заключается в том, что тип данных ТаблицаЗначений является мутабельным. Это означает, что его нельзя напрямую передать с клиента на сервер или передать как параметр формы при открытии. Попытка написать простой код вида ФормаОтчета.Параметры.Вставить("ВнешняяТЗ", ТЗ) вызовет критическую ошибку выполнения.
В обычных формах (в толстом клиенте) данные могли храниться непосредственно в оперативной памяти единого сеанса, поэтому там работала простая передача ТЗ в реквизит формы. В управляемом приложении нам необходимо использовать специальный мост — Временное хранилище.
---
Рассмотрим классический и наиболее правильный алгоритм передачи таблицы данных в управляемом приложении по шагам.
В вашем отчете необходимо зайти в конструктор схемы компоновки данных и выполнить следующие действия:
ТаблицаЦен).Номенклатура с типом ссылки на справочник и Цена с типом числа).В форме обработки, откуда пользователь нажимает кнопку формирования отчета, мы должны сформировать таблицу значений на сервере, поместить ее во временное хранилище, получить адрес этого хранилища и передать его в параметрах открытия формы отчета.
&НаКлиенте
Процедура СформироватьОтчет(Команда)
// Шаг 2.1. Формируем ТЗ на сервере и помещаем во временное хранилище
АдресХранилища = ПолучитьАдресТЗНаСервере();
// Шаг 2.2. Подготавливаем параметры для открытия формы отчета
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("АдресВнешнейТЗ", АдресХранилища);
ПараметрыФормы.Вставить("СформироватьПриОткрытии", Истина);
// Шаг 2.3. Открываем форму нашего отчета
ОткрытьФорму("Отчет.ОстаткиПоПартиямСЦеной.Форма.ФормаОтчета", ПараметрыФормы);
КонецПроцедуры
&НаСервере
Функция ПолучитьАдресТЗНаСервере()
// Создаем и типизируем таблицу значений
ТЗ = Новый ТаблицаЗначений;
ТЗ.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
ТЗ.Колонки.Добавить("Цена", Новый ОписаниеТипов("Число",,, Новый КвалификаторыЧисла(12, 2)));
// Заполняем таблицу данными (для примера)
НоваяСтрока = ТЗ.Добавить();
НоваяСтрока.Номенклатура = Справочники.Номенклатура.НайтиПоКоду("00000001");
НоваяСтрока.Цена = 150.00;
// Помещаем таблицу во временное хранилище.
// В качестве уникального идентификатора передаем идентификатор формы обработки.
Возврат ПоместитьВоВременноеХранилище(ТЗ, ЭтаФорма.УникальныйИдентификатор);
КонецФункции
В самом отчете нам необходимо создать реквизит объекта с именем АдресВнешнейТЗ (тип: Строка). Это нужно для того, чтобы модуль объекта отчета имел доступ к адресу во временном хранилище при компоновке результата. В модуле формы отчета перехватываем переданный параметр:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем, передан ли параметр из обработки
Если Параметры.Свойство("АдресВнешнейТЗ") Тогда
Отчет.АдресВнешнейТЗ = Параметры.АдресВнешнейТЗ;
КонецЕсли;
КонецПроцедуры
Вся магия сбора данных и вывода результата происходит в предопределенном обработчике события ПриКомпоновкеРезультата модуля объекта отчета. Здесь мы отключаем стандартный вывод и инициализируем процессор компоновки вручную:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
// Отключаем стандартный вывод, так как будем выводить программно
СтандартнаяОбработка = Ложь;
// Проверяем, заполнена ли ссылка на временное хранилище
Если НЕ ЗначениеЗаполнено(ЭтотОбъект.АдресВнешнейТЗ) Тогда
Возврат;
КонецЕсли;
// Извлекаем таблицу значений из временного хранилища
ТаблицаЦен = ПолучитьИзВременногоХранилища(ЭтотОбъект.АдресВнешнейТЗ);
// Получаем схему из макета
СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
// КРИТИЧЕСКИ ВАЖНО: Получаем текущие настройки компоновщика (а не дефолтные!)
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
ДанныеРасшифровки = Новый ДанныеРасшифровкиКомпоновкиДанных;
// Создаем макет компоновки
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
// Передаем нашу таблицу значений во внешний набор данных.
// Ключ структуры "ТаблицаЦен" должен в точности совпадать с именем Набора Данных - Объекта в СКД!
ВнешнийНаборДанных = Новый Структура("ТаблицаЦен", ТаблицаЦен);
// Инициализируем процессор компоновки данных
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновкиДанных.Инициализировать(МакетКомпоновки, ВнешнийНаборДанных, ДанныеРасшифровки);
// Очищаем и выводим результат в табличный документ
ДокументРезультат.Очистить();
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных);
КонецПроцедуры
---
Проанализируем частую ошибку программистов, которая упоминается в Сообщении 1 и Сообщении 9 форума. Авторы используют следующую конструкцию:
Настройки = СхемаКомпоновкиДанных.НастройкиПоУмолчанию;
Почему это в корне неверно? Когда пользователь интерактивно открывает отчет, настраивает отборы на форме (например, выбирает конкретный склад, период или контрагента), все эти настройки записываются в КомпоновщикНастроек формы отчета. Если при формировании вы берете НастройкиПоУмолчанию напрямую из Схемы, вы полностью сбрасываете и игнорируете все пользовательские настройки, параметры и установленные им отборы. На выходе получается пустой лист или отчет без фильтров.
Правильное решение: Всегда получайте актуальные настройки через компоновщик отчета:
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
---
Если вам нужно, чтобы при нажатии кнопки в обработке отчет не просто открылся с таблицей данных, но и сразу применил определенный фильтр (например, ограничил выборку по массиву номенклатуры), мы можем передать структуру отбора через стандартные параметры открытия управляемой формы.
Платформа 1С умеет автоматически сопоставлять ключ структуры Отбор с доступными полями компоновщика настроек СКД. Посмотрим на пример вызова:
&НаКлиенте
Процедура СформироватьОтчетСОтбором(Команда)
АдресХранилища = ПолучитьАдресТЗНаСервере();
// Создаем массив номенклатуры для отбора
МассивНоменклатуры = Новый Массив;
МассивНоменклатуры.Добавить(ПредопределенноеЗначение("Справочник.Номенклатура.ПустаяСсылка")); // Замените на реальные ссылки
// Заполняем структуру отборов. Имя ключа должно совпадать с именем поля в СКД!
СтруктураОтбора = Новый Структура;
СтруктураОтбора.Вставить("Номенклатура", МассивНоменклатуры);
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("АдресВнешнейТЗ", АдресХранилища);
ПараметрыФормы.Вставить("Отбор", СтруктураОтбора); // Передаем отбор
ПараметрыФормы.Вставить("СформироватьПриОткрытии", Истина);
ОткрытьФорму("Отчет.ОстаткиПоПартиямСЦеной.Форма.ФормаОтчета", ПараметрыФормы);
КонецПроцедуры
Благодаря тому, что в модуле объекта мы используем метод КомпоновщикНастроек.ПолучитьНастройки(), переданный нами массив номенклатуры автоматически применится в качестве элемента отбора типа "В списке" при генерации отчета.
---
При использовании стандартного метода «Набор данных - Объект» в СКД соединение внешних данных с таблицами базы данных (например, остатками или продажами из регистров) происходит в оперативной памяти компьютера (на сервере приложений 1С). Если таблица значений содержит десятки тысяч строк, такое соединение будет выполняться крайне медленно.
Современные стандарты разработки рекомендуют загружать внешнюю ТЗ прямо в базу данных в качестве временной таблицы СУБД с помощью Менеджера Временных Таблиц (МВТ). Тогда СКД сможет выполнить полноценный и быстрый SQL-запрос ЛЕВОЕ СОЕДИНЕНИЕ на уровне СУБД.
Рассмотрим, как реализовать этот продвинутый механизм:
ВТ_ВнешняяТаблицаЦен:
ВЫБРАТЬ
ВнешниеЦены.Номенклатура КАК Номенклатура,
ВнешниеЦены.Цена КАК Цена,
ЦеныРегистра.ЦенаСтарая КАК ЦенаСтарая
ИЗ
ВТ_ВнешняяТаблицаЦен КАК ВнешниеЦены
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК ЦеныРегистра
ПО ВнешниеЦены.Номенклатура = ЦеныРегистра.Номенклатура
При сохранении запроса СКД выдаст предупреждение, что таблица ВТ_ВнешняяТаблицаЦен не найдена. Игнорируем его. Чтобы конструктор СКД не выдавал ошибок, рекомендуется объявить поля таблицы с помощью функций ВЫРАЗИТЬ.
Теперь перепишем процедуру ПриКомпоновкеРезультата в модуле объекта отчета следующим образом:
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
Если НЕ ЗначениеЗаполнено(ЭтотОбъект.AddressВнешнейТЗ) Тогда
Возврат;
КонецЕсли;
// 1. Получаем ТЗ из хранилища
ТаблицаЦен = ПолучитьИзВременногоХранилища(ЭтотОбъект.АдресВнешнейТЗ);
// 2. Инициализируем Менеджер Временных Таблиц и помещаем туда ТЗ
МенеджерВТ = Новый МенеджерВременныхТаблиц;
ЗапросМВТ = Новый Запрос;
ЗапросМВТ.МенеджерВременныхТаблиц = МенеджерВТ;
ЗапросМВТ.Текст =
"ВЫБРАТЬ
| ТаблицаИсточник.Номенклатура КАК Номенклатура,
| ТаблицаИсточник.Цена КАК Цена
|ПОМЕСТИТЬ ВТ_ВнешняяТаблицаЦен
|ИЗ
| &ТаблицаИсточник КАК ТаблицаИсточник";
ЗапросМВТ.УстановитьПараметр("ТаблицаИсточник", ТаблицаЦен);
ЗапросМВТ.Выполнить();
// 3. Стандартная инициализация макета
СхемаКомпоновкиДанных = ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных");
Настройки = КомпоновщикНастроек.ПолучитьНастройки();
ДанныеРасшифровки = Новый ДанныеРасшифровкиКомпоновкиДанных;
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(СхемаКомпоновкиДанных, Настройки, ДанныеРасшифровки);
// 4. Инициализируем процессор и передаем Менеджер ВТ шестым параметром!
ПроцессорКомпоновкиДанных = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновкиДанных.Инициализировать(
МакетКомпоновки,
Новый Структура, // Внешний набор данных теперь передавать НЕ нужно!
ДанныеРасшифровки,
Ложь,
Ложь,
МенеджерВТ // Передаем заполненный Менеджер ВТ
);
// 5. Выводим результат
ДокументРезультат.Очистить();
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВТабличныйДокумент;
ПроцессорВывода.УстановитьДокумент(ДокументРезультат);
ПроцессорВывода.Вывести(ПроцессорКомпоновкиДанных);
// Обязательно закрываем менеджер после работы для освобождения памяти СУБД
МенеджерВТ.Закрыть();
КонецПроцедуры
Этот способ является наиболее профессиональным. Он исключает зависание сервера при обработке больших массивов информации, позволяет использовать всю мощь языка запросов 1С при соединении таблиц и полностью сохраняет возможность использования стандартных отборов, группировок и условного оформления СКД.