При разработке и кастомизации систем на базе 1С:Зарплата и управление персоналом часто возникает задача точечного разграничения прав доступа. Типовой механизм RLS (ограничение доступа на уровне записей) не всегда обладает достаточной гибкостью, особенно когда перечень доступных подразделений для пользователя хранится во внешней системе (например, Oracle) или меняется динамически без привязки к штатному расписанию. В данной статье мы подробно разберем, как реализовать программный фильтр в формах списков документов, чтобы пользователь видел данные только по определенному массиву подразделений.
Рассмотрим ситуацию: нам необходимо ограничить видимость документов в форме списка (например, «Табель учета рабочего времени» или самописные документы по расчету выработки). Доступ должен предоставляться не по одному подразделению, а по списку, который мы получаем программно. При этом важно учитывать, что решение должно быть реализовано через расширение конфигурации, чтобы сохранить возможность типового обновления программы.
Проанализируем основные способы реализации такой фильтрации в Динамическом списке:
Компоновщике настроек.Фиксированных настроек для защиты от изменения пользователем.Текста запроса динамического списка.Это самый быстрый в реализации способ. Мы обращаемся к свойству Список.Отбор (где Список — это основной реквизит формы с типом ДинамическийСписок) и добавляем туда новый элемент. Разберем по шагам, как это сделать в процедуре ПриСозданииНаСервере.
Рассмотрим пример кода, который добавляет фильтр по массиву подразделений:
&НаСервере
Процедура Расш1_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
// 1. Получаем список подразделений (массив или список значений)
// В данном примере создадим его вручную, но на практике здесь может быть вызов функции
СписокРазрешенныхПодразделений = Новый СписокЗначений;
СписокРазрешенныхПодразделений.Добавить(Справочники.ПодразделенияОрганизаций.НайтиПоКоду("00001"));
СписокРазрешенныхПодразделений.Добавить(Справочники.ПодразделенияОрганизаций.НайтиПоКоду("00005"));
// 2. Ищем, нет ли уже установленного отбора по этому полю, чтобы не дублировать
ПолеОтбора = Новый ПолеКомпоновкиДанных("Подразделение");
// Удаляем старые отборы по подразделению, если они есть
Индекс = Список.Отбор.Элементы.Количество() - 1;
Пока Индекс >= 0 Цикл
Элемент = Список.Отбор.Элементы[Индекс];
Если ТипЗнч(Элемент) = Тип("ЭлементОтбораКомпоновкиДанных")
И Элемент.ЛевоеЗначение = ПолеОтбора Тогда
Список.Отбор.Элементы.Удалить(Элемент);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
// 3. Создаем новый элемент отбора
НовыйЭлемент = Список.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
НовыйЭлемент.ЛевоеЗначение = ПолеОтбора;
НовыйЭлемент.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке;
НовыйЭлемент.ПравоеЗначение = СписокРазрешенныхПодразделений;
НовыйЭлемент.Использование = Истина;
НовыйЭлемент.РежимОтображения = РежимОтображенияЭлементаНастройкиКомпоновкиДанных.Недоступный;
КонецПроцедуры
Важный момент: использование РежимОтображения.Недоступный позволяет скрыть этот отбор из окна пользовательских настроек, однако технически продвинутый пользователь все равно может попытаться сбросить настройки списка. Для более жесткого контроля перейдем к следующему методу.
Свойство ФиксированныеНастройки динамического списка предназначено именно для разработчика. Параметры, установленные здесь, накладываются поверх любых пользовательских настроек и не видны в стандартном интерфейсе настройки списка. Это критически важно для задач безопасности и разграничения прав.
Выясним, как программно задать фиксированный отбор:
&НаСервере
Процедура УстановитьФиксированныйОтбор()
ГруппаОтбора = Новый ГруппаЭлементовОтбораКомпоновкиДанных;
ГруппаОтбора.ТипГруппы = ТипГруппыЭлементовОтбораКомпоновкиДанных.ГруппаИ;
Элемент = ГруппаОтбора.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
Элемент.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Подразделение");
Элемент.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке;
Элемент.ПравоеЗначение = МассивПодразделенийИзОракл;
Элемент.Использование = Истина;
// Применяем настройки к списку
Список.КомпоновщикНастроек.ЗагрузитьФиксированныеНастройки(Новый НастройкиКомпоновкиДанных);
Список.КомпоновщикНастроек.ФиксированныеНастройки.Отбор.Элементы.Добавить(Элемент);
КонецПроцедуры
Проанализируем ситуацию: если в конфигурации ЗУП форма списка уже имеет какие-то предопределенные фильтры, загрузка пустых НастроекКомпоновкиДанных может их очистить. Поэтому действуйте аккуратно, при необходимости считывайте текущие фиксированные настройки перед модификацией.
Иногда требуется более сложная логика, например, если нужно соединить таблицу документов с какой-то другой таблицей прав доступа. В этом случае мы можем изменить сам SQL-подобный запрос динамического списка в расширении.
Разберем пример подмены текста запроса:
&НаСервере
Процедура Расш1_ПриСозданииНаСервереПеред(Отказ, СтандартнаяОбработка)
ТекстЗапроса = Список.ТекстЗапроса;
// Добавляем условие ГДЕ в конец запроса или модифицируем существующее
// Внимание: метод требует осторожности при работе со сложными вложенными запросами
НовоеУсловие = " ГДЕ ДокументТабель.Подразделение В (&РазрешенныеПодразделения)";
// Простая замена (пример)
Список.ТекстЗапроса = ТекстЗапроса + НовоеУсловие;
// Устанавливаем параметр запроса
Список.Параметры.УстановитьЗначениеПараметра("РазрешенныеПодразделения", ПолучитьСписокДоступаИзOracle());
КонецПроцедуры
Этот метод гарантирует, что данные будут отфильтрованы еще на уровне СУБД, что положительно сказывается на производительности при больших объемах данных.
В 1С:ЗУП справочник ПодразделенияОрганизаций является иерархическим. Если в списке разрешенных подразделений указано "Управление", пользователь ожидает увидеть документы и по "Бухгалтерии", которая входит в это управление.
При использовании ВидСравнения.ВСписке иерархия автоматически не учитывается. Чтобы решить эту проблему, проанализируем два пути:
ВидСравнения.ВСпискеИИерархии. Однако помните, что этот вид сравнения работает медленнее, так как системе приходится раскрывать дерево групп при каждом запросе.Если данные о правах доступа запрашиваются из внешней базы данных (Oracle), выполнение такого запроса при каждом открытии формы списка недопустимо — это вызовет серьезные задержки («фризы») интерфейса.
Выясним, как оптимизировать процесс:
ДоступныеПодразделенияПользователей.Пример получения данных из кэша:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ДоступныеПодразделения.Подразделение
|ИЗ
| РегистрСведений.ДоступныеПодразделенияПользователей КАК ДоступныеПодразделения
|ГДЕ
| ДоступныеПодразделения.Пользователь = &ТекущийПользователь";
Запрос.УстановитьПараметр("ТекущийПользователь", ПараметрыСеанса.ТекущийПользователь);
МассивПодразделений = Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Подразделение");
При написании кода в расширении для ЗУП, обязательно используйте аннотации &НаСервере и вызывайте ПродолжитьВызов(), если вы используете тип перехвата &Вместо. Однако для установки отборов лучше всего подходит аннотация &После для процедуры ПриСозданииНаСервере. Это гарантирует, что ваши настройки будут наложены после того, как типовая конфигурация завершит инициализацию формы и установит свои стандартные параметры (например, отбор по организации или периоду).
Подведем итоги: для надежного и скрытого от пользователя ограничения списка документов по нескольким подразделениям в 1С:ЗУП оптимально использовать ФиксированныеНастройки динамического списка в расширении конфигурации. В качестве источника данных для фильтра лучше использовать предварительно заполненный регистр-кэш, чтобы избежать прямых запросов к внешним системам в момент отрисовки интерфейса.