Как скрыть "пустые" группы в иерархическом справочнике с RLS в 1С:Предприятии?

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

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

Постановка проблемы: RLS и "пустые" группы

Представьте, что вы работаете с самописной конфигурацией на базе БСП, где для справочника настроен RLS. Текст ограничения доступа выглядит примерно так:


Ограничение.Текст =
    "РазрешитьЧтениеИзменение
    |ГДЕ
    |    ЭтоГруппа
    |    ИЛИ 
    |ЗначениеРазрешено(Ссылка)";

Такое ограничение позволяет пользователю видеть все группы справочника (ЭтоГруппа), а также элементы, к которым у него есть доступ (ЗначениеРазрешено(Ссылка)). Проблема заключается в том, что даже если внутри группы нет ни одного разрешенного элемента (или подгруппы с разрешенными элементами), сама группа все равно будет видна. Пользователи ожидают, что такие "пустые" группы должны быть скрыты, чтобы в дереве справочника отображалась только релевантная информация.

Под "пустой" группой в данном контексте мы понимаем такую группу, которая:

  1. Не содержит разрешенных элементов, соответствующих настройкам RLS.
  2. Не содержит подгрупп, которые, в свою очередь, содержат разрешенные элементы.

То есть, если мы "раскроем" такую группу, она будет выглядеть абсолютно пустой.

Анализ возможных подходов и их ограничений

Для решения этой задачи было рассмотрено несколько вариантов:

  1. Создание собственного динамического списка с произвольным запросом.

    Этот подход предполагает написание сложного запроса, который должен рекурсивно анализировать иерархию справочника, чтобы определить, какие группы являются "пустыми". Для справочников с большой вложенностью (например, до 5 уровней) составление такого запроса становится весьма трудоемкой задачей, требующей глубоких знаний языка запросов 1С и внимательного отношения к производительности.

  2. Создание собственной формы справочника.

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

  3. Непосредственное решение через RLS.

    В "лоб" задача не решается только средствами RLS, поскольку RLS ограничивает доступ к данным на уровне записей, но не умеет анализировать содержимое подгрупп для принятия решения о видимости родительской группы. Шаблоны RLS ДляОбъекта и ПоЗначениям в данном случае недостаточно гибки для такой логики.

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

Решение проблемы: Условное оформление динамического списка

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

Принцип работы

Наша цель – сделать невидимыми те строки, которые представляют собой группы и при этом являются "пустыми". Для этого мы выполним следующие шаги:

  1. Определим список "непустых" групп. Нам потребуется выполнить запрос, который позволит идентифицировать все группы, содержащие хотя бы один разрешенный элемент или подгруппу с разрешенным элементом. Это самый сложный шаг, поскольку он требует обхода иерархии.
  2. Применим условное оформление. Используя список "непустых" групп, мы настроим условное оформление динамического списка так, чтобы все строки, являющиеся группами и не входящие в список "непустых" групп, становились невидимыми.

Шаг 1: Определение списка "непустых" групп

Для того чтобы скрыть "пустые" группы, нам необходимо сначала получить список всех групп, которые являются "непустыми". "Непустой" мы будем считать любую группу, которая либо сама содержит элементы, доступные пользователю по RLS, либо содержит подгруппы, которые в конечном итоге приводят к доступным элементам. Для построения такого списка нам потребуется рекурсивный запрос или несколько запросов с использованием временных таблиц.

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

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


// Шаг 1: Получаем все ССЫЛКИ на разрешенные элементы (не группы)
ВЫБРАТЬ РАЗЛИЧНЫЕ
    Справочник.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_РазрешенныеЭлементы
ИЗ
    Справочник.МойСправочник КАК Справочник
ГДЕ
    НЕ Справочник.ЭтоГруппа
    И ЗначениеРазрешено(Справочник.Ссылка); // Здесь уже сработает RLS

// Шаг 2: Рекурсивно поднимаемся по иерархии от разрешенных элементов,
// чтобы найти все их родительские группы, которые теперь считаются "непустыми".
// Временная таблица для хранения всех ссылок на непустые группы
ВЫБРАТЬ
    Т.Ссылка КАК Ссылка
ПОМЕСТИТЬ ВТ_НеПустыеГруппы
ИЗ
    ВТ_РазрешенныеЭлементы КАК Т

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    РодительскаяГруппа.Ссылка
ИЗ
    Справочник.МойСправочник КАК РодительскаяГруппа
ГДЕ
    РодительскаяГруппа.ЭтоГруппа
    И РодительскаяГруппа.Ссылка В
        (ВЫБРАТЬ
            Т.Родитель
        ИЗ
            ВТ_НеПустыеГруппы КАК Т
        ГДЕ
            Т.Родитель <> ЗНАЧЕНИЕ(Справочник.МойСправочник.ПустаяСсылка)
            И Т.Родитель <> НЕОПРЕДЕЛЕНО); // Это условное объединение может быть сложнее в зависимости от версии платформы и настроек

// Окончательный запрос, выбирающий все уникальные Ссылки непустых групп
ВЫБРАТЬ РАЗЛИЧНЫЕ
    Ссылка
ИЗ
    ВТ_НеПустыеГруппы
ГДЕ
    Ссылка <> ЗНАЧЕНИЕ(Справочник.МойСправочник.ПустаяСсылка)
    И Ссылка <> НЕОПРЕДЕЛЕНО;

Обратите внимание, что рекурсивные запросы могут быть сложны в реализации и могут повлиять на производительность. Альтернативно, можно построить такой список в коде 1С, многократно проходя по иерархии, но это также может быть медленно. После выполнения такого запроса мы получим таблицу значений или список ссылок, который будет содержать все "непустые" группы.

Шаг 2: Применение условного оформления

После того как мы получили список всех "непустых" групп, мы можем использовать его для настройки условного оформления динамического списка. Код для настройки условного оформления следует размещать в обработчике события ПриСозданииНаСервере формы динамического списка.

Рассмотрим шаги по программному добавлению условного оформления:

  1. Получаем коллекцию для условного оформления.

    Для постоянного (фиксированного) условного оформления, которое не может быть изменено пользователем, мы работаем с коллекцией ФиксированныеНастройки.УсловноеОформление.

    
    // Получаем коллекцию для условного оформления
    КоллекцияУсловногоОформления = Список.КомпоновщикНастроек.ФиксированныеНастройки.УсловноеОформление;
    
  2. Создаем новый элемент условного оформления.

    Каждое правило оформления представляет собой отдельный элемент.

    ЭлемОформления = КоллекцияУсловногоОформления.Элементы.Добавить();
  3. Настраиваем отбор для элемента оформления.

    Мы хотим, чтобы оформление применялось к строкам, которые являются группами И НЕ входят в список "непустых" групп. Для этого создаем два условия в отборе:

    
    // Условие 1: Это группа
    ЭлементОтбораЭтоГруппа = ЭлемОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ЭлементОтбораЭтоГруппа.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ЭтоГруппа");
    ЭлементОтбораЭтоГруппа.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
    ЭлементОтбораЭтоГруппа.ПравоеЗначение = Истина;
    ЭлементОтбораЭтоГруппа.Использование = Истина;
    
    // Условие 2: Ссылка НЕ входит в список "непустых" групп
    // Предполагаем, что 'РезНеПустыхГрупп' - это таблица значений с колонкой "Ссылка",
    // полученная из запроса выше.
    СписокНеПустыхГрупп = РезНеПустыхГрупп.ВыгрузитьКолонку("Ссылка"); // Преобразуем в список значений
    
    Если СписокНеПустыхГрупп.Количество() > 0 Тогда
        ЭлементОтбораНеПустая = ЭлемОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
        ЭлементОтбораНеПустая.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка");
        ЭлементОтбораНеПустая.ВидСравнения = ВидСравненияКомпоновкиДанных.НеВСписке;
        ЭлементОтбораНеПустая.ПравоеЗначение = СписокНеПустыхГрупп;
        ЭлементОтбораНеПустая.Использование = Истина;
    КонецЕсли;
    
    ЭлемОформления.Отбор.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.И;
    

    В этом примере РезНеПустыхГрупп – это результат выполнения запроса, который мы рассмотрели ранее, содержащий ссылки на все "непустые" группы. Важно, чтобы СписокНеПустыхГрупп был корректно сформирован как список значений для использования в отборе.

  4. Устанавливаем параметр оформления.

    Для скрытия строк используем параметр Видимость со значением Ложь.

    ЭлемОформления.Оформление.УстановитьЗначениеПараметра("Видимость", Ложь);
  5. Опционально: Обновляем компоновщик настроек.

    Хотя при работе с ФиксированныеНастройки это не всегда обязательно, иногда для полной уверенности в применении изменений может потребоваться перезагрузка настроек:

    
    // Список.КомпоновщикНастроек.ЗагрузитьНастройки(Список.КомпоновщикНастроек.Настройки); // Как было замечено в сообщении, может быть лишним.
    

Полный пример кода в обработчике ПриСозданииНаСервере:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)

    // ... ваш код инициализации формы ...

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

    // Установка параметра запроса, если нужно. Например, для текущего пользователя.
    // Запрос.УстановитьПараметр("ТекущийПользователь", ПараметрыСеанса.ТекущийПользователь);

    Попытка
        РезНеПустыхГрупп = Запрос.Выполнить().Выгрузить();
    Исключение
        Сообщить("Ошибка при получении списка непустых групп: " + ОписаниеОшибки());
        Возврат;
    КонецПопытки;

    СписокНеПустыхГрупп = РезНеПустыхГрупп.ВыгрузитьКолонку("Ссылка");

    // 2. Применяем условное оформление
    Если СписокНеПустыхГрупп.Количество() = 0 Тогда
        // Если нет ни одной непустой группы, возможно, нужно скрыть все группы.
        // Или оставить все группы видимыми, если это нежелательно.
        // В данном случае, если список пуст, условие "НеВСписке" будет применено ко всем,
        // что может скрыть все группы.
    КонецЕсли;

    КоллекцияУсловногоОформления = Список.КомпоновщикНастроек.ФиксированныеНастройки.УсловноеОформление;

    ЭлемОформления = КоллекцияУсловногоОформления.Элементы.Добавить();

    // Отбор: Это группа
    ЭлементОтбораЭтоГруппа = ЭлемОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
    ЭлементОтбораЭтоГруппа.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ЭтоГруппа");
    ЭлементОтбораЭтоГруппа.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
    ЭлементОтбораЭтоГруппа.ПравоеЗначение = Истина;
    ЭлементОтбораЭтоГруппа.Использование = Истина;

    // Отбор: Ссылка НЕ входит в список "непустых" групп
    // Это условие будет активно только если СписокНеПустыхГрупп не пуст
    Если СписокНеПустыхГрупп.Количество() > 0 Тогда
        ЭлементОтбораНеПустая = ЭлемОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
        ЭлементОтбораНеПустая.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка");
        ЭлебораОтбораНеПустая.ВидСравнения = ВидСравненияКомпоновкиДанных.НеВСписке; // Важно: НЕ ВСписке
        ЭлементОтбораНеПустая.ПравоеЗначение = СписокНеПустыхГрупп;
        ЭлементОтбораНеПустая.Использование = Истина;
    КонецЕсли;

    ЭлемОформления.Отбор.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.И; // Оба условия должны быть истинны

    // Устанавливаем оформление: Видимость = Ложь
    ЭлемОформления.Оформление.УстановитьЗначениеПараметра("Видимость", Ложь);

КонецПроцедуры

Важные нюансы и соображения

  1. Место размещения кода: Мы размещаем код в ПриСозданииНаСервере, так как именно на сервере формируется динамический список и его настройки. Использование ФиксированныеНастройки.УсловноеОформление гарантирует, что эти настройки будут применены всегда и не будут перезаписаны пользовательскими настройками.
  2. ПользовательскиеНастройки vs ФиксированныеНастройки: Мы используем Список.КомпоновщикНастроек.ФиксированныеНастройки.УсловноеОформление, чтобы избежать конфликтов с пользовательскими настройками и обеспечить стабильное поведение. Если бы мы добавляли элементы в ПользовательскиеНастройки, пришлось бы заботиться об их очистке или поиске уже добавленных элементов при каждом открытии формы.
  3. Производительность: Запрос для определения "непустых" групп может быть ресурсоемким, особенно на больших справочниках с глубокой иерархией. Внимательно тестируйте производительность. Возможно, потребуется оптимизировать запрос или кешировать результаты, если список справочника редко меняется.
  4. Событие ПриПолученииДанныхНаСервере: Начиная с версии 8.3.10, у динамического списка появилось событие ПриПолученииДанныхНаСервере. В нем мы можем программно обработать текущую коллекцию строк, что позволяет выполнять более тонкое условное оформление ячеек, используя вспомогательные поля, не содержащиеся в исходной таблице динамического списка. Этот подход может быть полезен, если логика определения "пустоты" группы сложна и требует обращения к дополнительным данным, не включенным в основной запрос динамического списка. Однако это также может замедлить работу при большом количестве данных.
  5. Иерархический список и кеширование: В режиме иерархического списка 1С по умолчанию считывает данные только текущей группы и ее родительских элементов. Если установлен режим просмотра в виде дерева, то считываются только данные открытых узлов дерева. Мы не рекомендуем единовременную загрузку данных динамического списка с иерархическим просмотром и начальным отображением дерева "Раскрывать все уровни", так как это приведет к множеству запросов к серверу и значительному снижению производительности.

Заключение

Задача скрытия "пустых" групп в иерархическом справочнике при использовании RLS является типовой для конфигураций на платформе 1С:Предприятие 8.3. Хотя "в лоб" она не решается только средствами RLS, применение программного условного оформления динамического списка позволяет добиться желаемого результата. Ключевым моментом является корректное и производительное определение списка "непустых" групп, на основе которого затем настраивается видимость строк. Внимательно проанализируйте требования к производительности и тестируйте решение на реальных данных.

← На главную