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