При разработке отчетов на схеме компоновки данных (СКД) часто возникает задача интерактивной связи между ними. Например, пользователю, анализирующему отчет со сравнительными показателями, может потребоваться детальная расшифровка по конкретной номенклатуре или складу. При двойном клике на ячейку система должна открыть детальный отчет (например, ведомость по товарам) с уже наложенными фильтрами из строки, по которой был произведен клик.
В управляемом приложении 1С:Предприятие 8 эта задача усложняется строгим разделением контекста выполнения кода на клиент и сервер. Неаккуратная работа с объектами расшифровки приводит к трудночитаемым системным ошибкам. В этой статье мы подробно разберем, почему возникают такие ошибки и как правильно реализовать передачу параметров между отчетами.
Рассмотрим классическую ситуацию: разработчик пытается обработать расшифровку в модуле управляемой формы отчета. При двойном клике на ячейку вызывается клиентское событие РезультатОбработкаРасшифровки. Попытка передать полученные данные напрямую на сервер для анализа вызывает ошибку следующего вида:
Ошибка при вызове метода контекста (ПолучитьРекурсивноСтруктуруОтбора)
по причине:
Ошибка передачи данных между клиентом и сервером. Значение недопустимого типа.
по причине:
Ошибка преобразования данных XDTO:
Запись значения свойства 'param':
форма: Элемент
имя: {http://v8.1c.ru/8.2/managed-application/modules}param по причине:
Ошибка отображения типов:
Отсутствует отображение для типа 'ЗначенияПолейРасшифровкиКомпоновкиДанных'
Проанализируем ситуацию и выясним причину такого поведения платформы. Тип данных ЗначенияПолейРасшифровкиКомпоновкиДанных, а также сопутствующие объекты компоновки (такие как ДанныеРасшифровкиКомпоновкиДанных и элементы коллекции полей) являются строго серверными. Они не могут быть сериализованы в формат XML/XDTO для передачи по сетевому каналу между клиентом и сервером.
Попытка поместить эти объекты в массив или структуру на клиенте и передать эту структуру в серверную процедуру также потерпит неудачу. Платформа попытается сериализовать все вложенные элементы структуры и выдаст ту же ошибку преобразования XDTO.
Главное правило работы с расшифровкой в управляемых формах: любые манипуляции с объектами расшифровки, обход дерева группировок и чтение значений полей должны выполняться исключительно на стороне сервера. Клиентская процедура должна выступать лишь инициатором процесса, передавая на сервер простые типы данных — идентификатор расшифровки (тип Число) и адрес временного хранилища, где лежат данные расшифровки (тип Строка).
Другой частой ошибкой при адаптации старого кода обычных форм под управляемый интерфейс является вызов метода ПолучитьРодителей() у элементов, которые его не поддерживают. Рассмотрим, как устроена иерархия объектов расшифровки СКД.
В данных расшифровки элементы чередуются в зависимости от уровня группировки:
ЭлементРасшифровкиКомпоновкиДанныхПоля — это непосредственно те детальные поля или показатели, на которые кликнул пользователь.ЭлементРасшифровкиКомпоновкиДанныхГруппировка — объект, описывающий саму строку группировки отчета.ЭлементРасшифровкиКомпоновкиДанныхПоля, соответствующий более высокому уровню иерархии (например, группа номенклатуры или склад).Метод ПолучитьПоля() у элемента расшифровки возвращает коллекцию объектов типа ЗначениеПоляРасшифровкиКомпоновкиДанных. Эти объекты содержат только имя поля и его конкретное значение (например, "Номенклатура" и ссылка на элемент справочника). У этого объекта нет и не может быть метода ПолучитьРодителей(). Пытаясь вызвать его на этом уровне, разработчик закономерно получает ошибку выполнения.
Чтобы собрать полную структуру отборов, нам необходимо рекурсивно подниматься вверх по иерархии элементов расшифровки, обращаясь к методу ПолучитьРодителей() только у объектов типа ЭлементРасшифровкиКомпоновкиДанныхПоля или ЭлементРасшифровкиКомпоновкиДанныхГруппировка.
Посмотрим на пример правильной реализации. Нам потребуется связать клиентское событие формы и серверную функцию, которая выполнит всю "тяжелую" работу по анализу данных.
Добавим в модуль формы отчета следующий код:
&НаКлиенте
Процедура РезультатОбработкаРасшифровки(Элемент, Расшифровка, СтандартнаяОбработка, ДополнительныеПараметры)
// Отменяем стандартное действие платформы, чтобы не открывать стандартную карточку значения
СтандартнаяОбработка = Ложь;
// Получаем структуру отборов на сервере, передавая только примитивные типы
СтруктураОтбора = ПолучитьОтборыРасшифровкиНаСервере(ДанныеРасшифровки, Расшифровка);
// Настраиваем параметры открытия нового отчета
ПараметрыФормы = Новый Структура;
ПараметрыФормы.Вставить("Отбор", СтруктураОтбора);
ПараметрыФормы.Вставить("СформироватьПриОткрытии", Истина);
// Открываем форму ведомости с установленными фильтрами
ОткрытьФорму("Отчет.ВедомостьПоТоварамНаСкладах.Форма", ПараметрыФормы, ЭтаФорма);
КонецПроцедуры
Теперь рассмотрим серверную часть. Функция ПолучитьОтборыРасшифровкиНаСервере извлекает объект расшифровки из временного хранилища по его адресу и запускает рекурсивный обход элементов:
&НаСервере
Функция ПолучитьОтборыРасшифровкиНаСервере(Знач АдресДанныхРасшифровки, Знач ИдентификаторРасшифровки)
СтруктураОтборов = Новый Структура;
ДанныеРасшифровкиОбъект = ПолучитьИзВременногоХранилища(АдресДанныхРасшифровки);
Если ДанныеРасшифровкиОбъект = Неопределено Тогда
Возврат СтруктураОтборов;
КонецЕсли;
// Получаем элемент, по которому кликнули в табличном документе
СтартовыйЭлемент = ДанныеРасшифровкиОбъект.Элементы.Получить(ИдентификаторРасшифровки);
// Запускаем рекурсивный сбор параметров
СобратьОтборыРекурсивно(СтартовыйЭлемент, СтруктураОтборов);
Возврат СтруктураОтборов;
КонецФункции
&НаСервере
Процедура СобратьОтборыРекурсивно(ТекущийЭлемент, СтруктураОтборов)
Если ТекущийЭлемент = Неопределено Тогда
Возврат;
КонецЕсли;
// Проверяем, содержит ли элемент детальные поля данных
Если ТипЗнч(ТекущийЭлемент) = Тип("ЭлементРасшифровкиКомпоновкиДанныхПоля") Тогда
Для Каждого ПолеРасшифровки Из ТекущийЭлемент.ПолучитьПоля() Цикл
ИмяПоля = Строка(ПолеРасшифровки.Поле);
// Чтобы не затереть более точные отборы нижних уровней общими родительскими,
// добавляем поле в структуру только один раз
Если Не СтруктураОтборов.Свойство(ИмяПоля) Тогда
СтруктураОтборов.Вставить(ИмяПоля, ПолеРасшифровки.Value);
КонецЕсли;
КонецЦикла;
КонецЕсли;
// Рекурсивно переходим к родительским элементам группировок
Для Каждого Родитель Из ТекущийЭлемент.ПолучитьРодителей() Цикл
СобратьОтборыРекурсивно(Родитель, СтруктураОтборов);
КонецЦикла;
КонецПроцедуры
Этот алгоритм поднимается по иерархии отчетов любой сложности, собирая все установленные группировки. Если пользователь сделал клик на ячейке с детальными данными номенклатуры "Шоколад Аленка" на складе "Основной", функция вернет структуру, содержащую оба этих значения. При передаче структуры в параметры формы другого отчета, разработанного на базе БСП, система автоматически применит эти отборы к настройкам компоновки нового отчета.
Если вы ведете разработку в типовой конфигурации (например, Управление торговлей 11, ERP 2 или Комплексная автоматизация), лучшим решением будет использование штатных возможностей Библиотеки Стандартных Подсистем (БСП). В БСП заложена готовая подсистема работы с отчетами, которая позволяет кастомизировать расшифровку без необходимости написания сложного серверного кода для парсинга ячеек.
Для вызова красивого интерактивного меню, где пользователь сможет сам выбрать, какой именно детальный отчет открыть, мы можем задействовать метод общего модуля БСП КомпоновкаДанныхКлиент.ОбработкаРасшифровкиСДополнительнымМеню. Этот метод берет на себя всю рутину по анализу клика, формированию контекстного меню и передаче параметров в открываемые отчеты.
Рассмотрим, как выглядит этот лаконичный и профессиональный подход в модуле формы вашего отчета:
&НаКлиенте
Процедура РезультатОбработкаРасшифровки(Элемент, Расшифровка, СтандартнаяОбработка, ДополнительныеПараметры)
// Задаем массивы для хранения описания отчетов
МенюОтчетов = Новый Массив;
МенюДействий = Новый Массив;
// Описываем первый вариант расшифровки - Ведомость по товарам организаций
ПараметрыОтчета1 = Новый Структура;
ПараметрыОтчета1.Вставить("Имя", "ВедомостьПоТоварамОрганизаций");
ПараметрыОтчета1.Вставить("Заголовок", НСтр("ru = 'Ведомость по товарам организаций'"));
ПараметрыОтчета1.Вставить("ИмяОтчета", "Отчет.ВедомостьПоТоварамОрганизаций");
// Описываем второй вариант расшифровки - Ведомость по товарам на складах
ПараметрыОтчета2 = Новый Структура;
ПараметрыОтчета2.Вставить("Имя", "ВедомостьПоТоварамНаСкладах");
ПараметрыОтчета2.Вставить("Заголовок", НСтр("ru = 'Ведомость по товарам на складах'"));
ПараметрыОтчета2.Вставить("ИмяОтчета", "Отчет.ВедомостьПоТоварамНаСкладах");
// Определяем список полей, которые мы хотим передать в качестве отборов
ПоляРасшифровки = Новый Массив;
ПоляРасшифровки.Добавить("Номенклатура");
ПоляРасшифровки.Добавить("Характеристика");
ПоляРасшифровки.Добавить("Склад");
ПараметрыОтчета1.Вставить("ПоляРасшифровки", ПоляРасшифровки);
ПараметрыОтчета2.Вставить("ПоляРасшифровки", ПоляРасшифровки);
// Добавляем настроенные структуры в общее меню
МенюОтчетов.Добавить(ПараметрыОтчета1);
МенюОтчетов.Добавить(ПараметрыОтчета2);
Если МенюОтчетов.Количество() > 0 Тогда
ПараметрыРасшифровки = Новый Структура;
ПараметрыРасшифровки.Вставить("МенюОтчетов", МенюОтчетов);
ПараметрыРасшифровки.Вставить("МенюДействий", МенюДействий);
ПараметрыРасшифровки.Вставить("Расшифровка", Расшифровка);
// Передаем управление стандартному обработчику БСП
КомпоновкаДанныхКлиент.ОбработкаРасшифровкиСДополнительнымМеню(ЭтаФорма, ПараметрыРасшифровки, СтандартнаяОбработка);
КонецЕсли;
КонецПроцедуры
Используя этот метод, вы избавляетесь от необходимости ручной работы с рекурсией на сервере. Механизмы БСП автоматически проанализируют ячейку, извлекут значения для полей "Номенклатура", "Характеристика" и "Склад", выведут пользователю красивое окно выбора отчета и передадут параметры в открываемую форму.
Этот способ является наиболее предпочтительным при доработке типовых конфигураций 1С, так как он гарантирует корректную работу отчетов после обновлений платформы и прикладных решений, сохраняя единый UI/UX интерфейс для конечных пользователей.