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