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