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