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