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