Как в 1С настроить отбор в форме списка по подразделению текущего пользователя?

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

Приветствуем вас, уважаемые разработчики! При проектировании бизнес-приложений на платформе «1С:Предприятие 8.3» (особенно в конфигурациях по расчету заработной платы и управлению персоналом) часто возникает задача разграничения видимости данных в интерфейсе. Типичный пример: руководитель подразделения или начальник цеха при открытии справочника Сотрудники должен видеть исключительно своих подчиненных, в то время как администраторы и финансовые директора должны иметь полный доступ ко всему списку.

В этой статье мы подробно разберем, как правильно реализовать подобный отбор в управляемых формах. Мы начнем с базовых концепций, проанализируем ошибки начинающих разработчиков, рассмотрим несколько альтернативных способов реализации (от простых динамических отборов до безопасных решений через фиксированные настройки и параметры запроса) и выберем наиболее оптимальный вариант с точки зрения производительности и безопасности данных.

Давайте вместе разберем эту задачу по шагам.

Анализ исходной проблемы и архитектурных ошибок

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


&НаКлиенте
Процедура ПриОткрытии(Отказ)
    Если (Не ОбщегоНазначения.ЭтоАдмин И Не ОбщегоНазначения.ЭтоФин) Тогда
        Польз = ОбщегоНазначения.ТекущийПользователь;
        УстОтбор(Польз);
    КонецЕсли;
КонецПроцедуры

В данном фрагменте кода кроются сразу три серьезные архитектурные проблемы:

  1. Выполнение кода на клиенте: Попытка получить текущего пользователя через вызов ОбщегоНазначения.ТекущийПользователь на клиенте заставляет систему совершать лишние серверные вызовы. В управляемом приложении любые инициализационные действия, связанные с чтением данных из базы, должны выполняться строго на сервере.
  2. Использование события ПриОткрытии: Событие ПриОткрытии срабатывает на клиенте, когда форма уже практически создана и подготовлена к отображению. Если мы устанавливаем отборы здесь, форма сначала инициализируется без отбора, а затем принудительно обновляется. Это вызывает визуальное «моргание» интерфейса и лишнюю нагрузку на СУБД. Правильное место для первичной настройки динамического списка — серверное событие ПриСозданииНаСервере.
  3. Уязвимость интерфейса: Если мы добавляем обычный элемент отбора в коллекцию Список.Отбор.Элементы, пользователь сможет легко обойти это ограничение. Достаточно зайти в меню «Еще» -> «Настроить список» и просто снять галочку с программно установленного отбора.

Теперь, понимая эти нюансы, мы рассмотрим правильные методы реализации этой задачи.

Способ №1: Установка отбора в Фиксированных настройках динамического списка

Если перед нами стоит задача сделать так, чтобы пользователь физически не мог отключить или изменить установленный нами отбор через стандартные настройки интерфейса, мы должны использовать коллекцию ФиксированныеНастройки компоновщика настроек динамического списка.

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

Для начала нам понадобится получить подразделение текущего пользователя. Напишем вспомогательную функцию на сервере, которая определяет подразделение. Затем применим отбор:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Проверим, нужно ли применять ограничение для текущего пользователя
    Если РольДоступна("ПолныеПрава") Или ОбщегоНазначения.ЭтоФинДиректор() Тогда
        Возврат; // Администраторам и фин. директорам показываем всех
    КонецЕсли;
    
    // Получаем текущего пользователя системы
    ТекущийПользователь = Пользователи.ТекущийПользователь();
    
    // Находим подразделение, к которому относится данный пользователь
    ПодразделениеПользователя = ПолучитьПодразделениеПользователя(ТекущийПользователь);
    
    Если ЗначениеЗаполнено(ПодразделениеПользователя) Тогда
        УстановитьФиксированныйОтборПоПодразделению(ПодразделениеПользователя);
    КонецЕсли;
    
КонецПроцедуры

&НаСервере
Функция ПолучитьПодразделениеПользователя(Пользователь)
    // В зависимости от структуры вашей конфигурации, подразделение может храниться
    // в реквизите справочника Пользователи или в специальном регистре сведений.
    // Предположим, что в справочнике "Пользователи" есть реквизит "Подразделение"
    Возврат Пользователь.Подразделение;
КонецФункции

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

Этот способ надежно скрывает записи других подразделений в интерфейсе списка и не позволяет пользователю сбросить настройки фильтрации.

Способ №2: Использование произвольного запроса в динамическом списке (Рекомендуемый по производительности)

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

Если в подразделении числятся сотни человек, СУБД придется строить тяжелый запрос с огромным перечислением идентификаторов в секции ГДЕ Ссылка В (...). Более того, при добавлении нового сотрудника в базу список не обновится динамически до тех пор, пока пользователь полностью не перезапустит форму. Это неэффективно и неудобно.

Гораздо более изящным и быстрым решением является использование произвольного запроса в самом динамическом списке. Мы можем напрямую связать таблицу сотрудников с параметрами сеанса или динамическими параметрами запроса.

Разберем, как это сделать:

  1. Откроем форму списка справочника Сотрудники в конфигураторе.
  2. Выделим реквизит формы Список (имеющий тип ДинамическийСписок) и в палитре свойств установим флаг «Произвольный запрос».
  3. Нажмем кнопку «Открыть» напротив свойства «Настройка списка» и отредактируем текст запроса следующим образом:

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

Теперь нам остается только инициализировать параметры нашего запроса в модуле формы при ее создании на сервере. Давайте посмотрим на код:


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    
    // Определяем права пользователя
    ЭтоАдминистратор = РольДоступна("ПолныеПрава") Или ОбщегоНазначения.ЭтоФинДиректор();
    
    Если ЭтоАдминистратор Тогда
        // Отключаем фильтрацию для привилегированных пользователей
        Список.Параметры.УстановитьЗначениеПараметра("ОграничитьДоступПоПодразделению", Ложь);
        Список.Параметры.УстановитьЗначениеПараметра("ТекущееПодразделение", Справочники.Подразделения.ПустаяСсылка());
    Иначе
        // Определяем подразделение пользователя
        ТекущийПользователь = Пользователи.ТекущийПользователь();
        ПодразделениеПользователя = ПолучитьПодразделениеПользователя(ТекущийПользователь);
        
        Список.Параметры.УстановитьЗначениеПараметра("ОграничитьДоступПоПодразделению", Истина);
        Список.Параметры.УстановитьЗначениеПараметра("ТекущееПодразделение", ПодразделениеПользователя);
    EndЕсли;
    
КонецПроцедуры

В чем главные преимущества такого подхода? Во-первых, СУБД моментально отрабатывает условие соединения и фильтрации на уровне индексов таблицы. Во-вторых, данные в списке всегда остаются актуальными: если другой пользователь добавит нового сотрудника в этот цех, он мгновенно появится на экране без переоткрытия формы.

Способ №3: Ограничение доступа на уровне записей (RLS) как максимальная защита

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

Если ваша задача — это реальная конфиденциальность данных, то решать её нужно на уровне платформы с помощью механизма RLS (Row-Level Security).

Давайте кратко рассмотрим алгоритм настройки RLS для нашей задачи:

  1. Создаем в конфигураторе новую роль, например, РуководительПодразделения.
  2. В дереве метаданных открываем эту роль, переходим на вкладку «Права», находим наш справочник Сотрудники и выбираем право «Чтение».
  3. В нижней части окна нажимаем кнопку добавления ограничения доступа и пишем условие ограничения (запрос):

ГДЕ Подразделение = &ПараметрСеанса.ТекущееПодразделение

Для корректной работы этого механизма в модуле сеанса (при начале работы системы) необходимо инициализировать параметр сеанса ТекущееПодразделение значением подразделения вошедшего пользователя.

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

Подведем итоги

Мы проанализировали различные методы установки отбора по текущему пользователю в форме списка 1С. В зависимости от требований к безопасности и архитектуре системы, используйте следующие рекомендации:

← На главную