Как в динамическом списке установить отбор элементов дерева с сохранением подчиненности

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с иерархическими справочниками в 1С программисты часто сталкиваются с задачей фильтрации данных в динамическом списке. Типичная ситуация: необходимо скрыть элементы, помеченные на удаление, но при этом сохранить структуру дерева. Стандартное поведение динамического списка (ДС) при наложении простого условия ГДЕ ПометкаУдаления = Ложь приводит к тому, что если родительский узел помечен на удаление, то все его «здоровые» потомки либо исчезают из вида (в режиме дерева), либо «вываливаются» в корень списка, разрывая логическую связь. В этой статье мы подробно разберем, как обойти это ограничение и заставить дерево «схлопываться», переподчиняя элементы вышестоящим доступным предкам.

Проблема разрывов иерархии в динамических списках

Рассмотрим подробнее, почему это происходит. Динамический список использует механизм системы компоновки данных (СКД). Когда для списка включен режим «Дерево», платформа строит иерархию на основе стандартного поля Родитель. Если запрос возвращает строку, у которой поле Родитель содержит ссылку, отсутствующую в текущей выборке (из-за отбора), механизм построения дерева не может найти узел-контейнер. В результате визуальная структура разрушается.

Как отмечается в обсуждении, изменить встроенный алгоритм построения дерева в самом тексте запроса ДС напрямую невозможно, так как иерархия — это свойство отображения данных платформой, а не просто результат выполнения запроса. Однако существует несколько стратегий решения этой задачи, которые мы проанализируем ниже.

Метод 1: Виртуальный родитель через вычисляемое поле в запросе

Если глубина вложенности вашего справочника заранее известна и невелика (например, не более 3–4 уровней), мы можем применить аналитический подход непосредственно в запросе динамического списка. Суть заключается в замене стандартного родителя на «ближайшего живого предка».

Проанализируем пример построения такого запроса:


ВЫБРАТЬ
    Спр.Ссылка КАК Ссылка,
    Спр.Наименование КАК Наименование,
    ВЫБОР
        КОГДА НЕ Спр.Родитель.ПометкаУдаления ТОГДА Спр.Родитель
        КОГДА НЕ Спр.Родитель.Родитель.ПометкаУдаления ТОГДА Спр.Родитель.Родитель
        КОГДА НЕ Спр.Родитель.Родитель.Родитель.ПометкаУдаления ТОГДА Спр.Родитель.Родитель.Родитель
        ИНАЧЕ ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
    КОНЕЦ КАК ВиртуальныйРодитель
ИЗ
    Справочник.Номенклатура КАК Спр
ГДЕ
    Спр.ПометкаУдаления = ЛОЖЬ

После реализации такого запроса необходимо в настройках динамического списка (раздел «Группировка» или свойства поля списка в основной схеме) указать, что иерархия должна строиться не по полю Родитель, а по нашему новому полю ВиртуальныйРодитель.

Важный нюанс: этот метод эффективен только для малой глубины дерева. Если уровней вложенности десять и более, запрос станет громоздким и будет медленно работать на больших объемах данных.

Метод 2: Реализация «Теневой иерархии» через служебный реквизит

Для высоконагруженных систем и справочников с неограниченной вложенностью лучшим решением считается создание вспомогательной структуры данных. Мы можем добавить в справочник дополнительный индексированный реквизит АктуальныйРодитель (тип: Ссылка на этот же справочник).

Разберем алгоритм работы по шагам:

  1. Добавляем реквизит АктуальныйРодитель в метаданные справочника.
  2. В модуле объекта или через подписку на событие ПередЗаписью реализуем логику вычисления этого поля. Программа должна рекурсивно подниматься вверх по дереву до тех пор, пока не найдет родителя, у которого признак (например, пометка удаления) не установлен.
  3. В динамическом списке мы будем выводить элементы, где ПометкаУдаления = Ложь, а строить дерево — по полю АктуальныйРодитель.

Пример кода для вычисления актуального родителя:


Функция НайтиБлижайшегоЖивогоПредка(ТекущийРодитель)
    Если ТекущийРодитель.Пустая() Тогда
        Возврат Справочники.Номенклатура.ПустаяСсылка();
    КонецЕсли;
    
    // Проверяем состояние текущего родителя
    Если Не ТекущийРодитель.ПометкаУдаления Тогда
        Возврат ТекущийРодитель;
    Иначе
        // Рекурсивный вызов для поиска выше по иерархии
        Возврат НайтиБлижайшегоЖивогоПредка(ТекущийРодитель.Родитель);
    КонецЕсли;
КонецФункции

Процедура ПередЗаписью(Отказ)
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
    
    АктуальныйРодитель = НайтиБлижайшегоЖивогоПредка(Родитель);
КонецПроцедуры

Преимущество этого метода в том, что динамический список будет работать максимально быстро, так как вся тяжелая работа по вычислению связей была проделана в момент записи элемента, а не в момент открытия списка пользователем.

Метод 3: Использование рекурсивных запросов (ОТТ) в новых версиях платформы

Начиная с версии платформы 8.3.24, в 1С появилась поддержка общих табличных выражений (CTE), включая рекурсивные запросы. Это позволяет решить задачу «схлопывания» иерархии на лету на уровне СУБД без добавления лишних реквизитов.

Проанализируем ситуацию: мы можем составить запрос, который для каждой строки динамически вычисляет цепочку предков и выбирает первого, подходящего под условия фильтра. Рассмотрим примерную структуру такого запроса:


ВЫБРАТЬ
    Спр.Ссылка,
    Спр.Наименование
ИЗ
    Справочник.Номенклатура КАК Спр
ГДЕ
    Спр.Ссылка В (
        // Здесь используется логика выбора элементов через рекурсивное ОТТ, 
        // исключающее помеченные на удаление узлы из цепочки связей
    )

Этот метод является наиболее технологичным, однако он требует обновления платформы и понимания работы механизмов рекурсии в SQL.

Метод 4: Использование параметра с массивом разрешенных групп

Как предлагалось в обсуждении на форуме (сообщение 4), можно пойти по пути RLS-подобного решения. Мы можем программно собрать массив всех групп, которые должны отображаться (включая тех родителей, у которых есть «живые» подчиненные, даже если сами родители скрыты по условию), и передать этот массив в параметр запроса динамического списка.

Выясним причину, почему это не всегда удобно: если справочник содержит десятки тысяч элементов, массив &МассивРазрешенныхГрупп станет слишком большим, что приведет к ошибке переполнения параметров запроса в SQL или резкому падению производительности. Тем не менее, для небольших справочников это рабочий вариант:

ГДЕ (ПометкаУдаления = ЛОЖЬ) ИЛИ (Ссылка В (&МассивРазрешенныхГрупп))

В этом случае в дереве останутся «пунктирные» или недоступные группы для сохранения структуры, но они не будут мешать пользователю работать с актуальными данными.

Метод 5: Полный отказ от динамического списка в пользу Дерева значений

Если требования бизнеса к визуализации дерева крайне специфичны и ни один из методов выше не подходит, рассмотрим радикальный вариант: замена динамического списка объектом ДеревоЗначений.

Плюсы решения:

Минусы решения:

Посмотрим на пример заполнения дерева значений с «пропуском» помеченных родителей:


Процедура ЗаполнитьДеревоНаСервере()
    // Получаем все нужные данные одним запросом
    Запрос = Новый Запрос("ВЫБРАТЬ Ссылка, Родитель, Наименование ИЗ Справочник.Номенклатура ГДЕ ПометкаУдаления = Ложь");
    ТаблицаДанных = Запрос.Выполнить().Выгрузить();
    
    // Обходим таблицу и строим иерархию вручную
    Для Каждого Строка Из ТаблицаДанных Цикл
        АктуальныйРодитель = НайтиБлижайшегоЖивогоПредка(Строка.Родитель);
        ДобавитьСтрокуВДерево(Строка, АктуальныйРодитель);
    КонецЦикла;
КонецПроцедуры

Итоги и рекомендации

Подводя итоги, можно сказать следующее: стандартный динамический список в 1С не умеет «на лету» перестраивать иерархию при выпадении промежуточных узлов.

Для простых задач (до 3 уровней) используйте выбор в запросе (Метод 1).
Для надежной и быстрой работы в крупных системах внедряйте служебный реквизит «Актуальный родитель» (Метод 2).
Если вы работаете на современном стеке (8.3.24+), попробуйте рекурсивные запросы (Метод 3).
И только в крайних случаях, когда нужно полное управление интерфейсом, переходите на Дерево значений.

Выбор конкретного метода зависит от масштаба вашей задачи и готовности вносить изменения в структуру метаданных конфигурации. Надеемся, что предложенные варианты помогут вам реализовать удобный и интуитивно понятный интерфейс подбора для ваших пользователей.

← На главную