Как запретить редактирование группы в иерархическом справочнике 1С при наличии ответственного?

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

В процессе разработки бизнес-логики в системе «1С:Предприятие 8» программисты часто сталкиваются с задачами гибкого разграничения прав доступа внутри иерархических справочников (например, «Номенклатура», «Контрагенты» или «Подразделения»). Типичный сценарий: в справочнике создана структура папок, за каждую из которых отвечает определенный менеджер (реквизит Ответственный). Необходимо настроить систему таким образом, чтобы ответственный пользователь мог свободно создавать и редактировать элементы внутри «своей» группы и ее подгрупп, но при этом ему было строго запрещено изменять параметры самой родительской группы или переносить элементы в чужие ветки дерева.

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

Постановка задачи и логические коллизии

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

  1. Блокировка редактирования группы. Если текущий пользователь назначен ответственным в некоторой Группе 1, он может изменять любые подчиненные элементы и подчиненные группы (например, Группа 1.1), но саму Группу 1 редактировать не может.
  2. Запрет создания «выше» по иерархии. Пользователь, закрепленный за Группой 1.1, может создавать элементы только внутри нее и ниже. В родительской папке Группа 1 создавать элементы ему запрещено.
  3. Проблема изменения родителя (Drag-and-Drop). Пользователь не должен иметь возможности обойти систему прав, просто «перетащив» папку или элемент из разрешенной ему ветки в запрещенную (или наоборот).

Рассмотрим пошагово, как реализовать комплексную защиту от подобных действий.

Шаг 1. Надежная блокировка на уровне бизнес-логики (Модуль объекта)

Ограничение редактирования исключительно через свойства управляемой формы (например, установка ТолькоПросмотр = Истина) — это распространенная ошибка. Пользователь легко обойдет такой запрет с помощью стандартной обработки «Групповое изменение реквизитов», внешних обработок или при импорте данных через API и файлы обмена.

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

Откроем модуль объекта нашего справочника и добавим следующий алгоритм:


Процедура ПередЗаписью(Отказ)
    
    // Всегда пропускаем запись, если происходит загрузка данных (обмен)
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;

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

    // 2. Контроль иерархии при записи элементов и подгрупп
    ПроверитьПраваПоИерархии(Отказ);

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

Разберем, что происходит в этом коде. Функция ЭтоНовый() позволяет нам понять, создается ли группа впервые или пользователь пытается перезаписать уже существующую. Обращение к реквизиту через конструкцию Ссылка.Ответственный гарантирует, что мы считываем актуальное состояние объекта напрямую из базы данных, исключая подмену реквизитов на форме.

Шаг 2. Защита от несанкционированного изменения родителя

Теперь разберем более сложную уязвимость — смену родителя. Предположим, у нас есть две независимые ветки: Группа А (ответственный Иванов) и Группа Б (ответственный Петров). Иванов решает перенести свой элемент в Группу Б или забрать элемент Петрова к себе. Чтобы пресечь это, нам необходимо анализировать не только нового родителя, которого пользователь выбрал в интерфейсе, но и старого родителя, записанного в базе.

Давайте реализуем вспомогательную процедуру ПроверитьПраваПоИерархии в модуле объекта:


Процедура ПроверитьПраваПоИерархии(Отказ)

    // Если объект новый, то старого родителя у него нет
    СтарыйРодитель = ?(ЭтоНовый(), Справочники.Номенклатура.ПустаяСсылка(), Ссылка.Родитель);
    НовыйРодитель  = Родитель;

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

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

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

Шаг 3. Оптимальный поиск ответственного вверх по иерархии

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

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

Вместо этого мы применим эффективный запрос с использованием конструкции В ИЕРАРХИИ:


Функция ПользовательИмеетДоступКВетке(Знач ПроверяемыйРодитель, ТекущийПользователь) Экспорт

    Если Не ЗначениеЗаполнено(ПроверяемыйРодитель) Тогда
        // Если элемент находится в корне справочника, а текущий пользователь — рядовой менеджер,
        // то создавать элементы в корне ему запрещено.
        Возврат Ложь; 
    КонецЕсли;

    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ РАЗЛИЧНЫЕ
        |   Номенклатура.Ссылка КАК Ссылка
        |ИЗ
        |   Справочник.Номенклатура КАК Номенклатура
        |ГДЕ
        |   Номенклатура.Ссылка В ИЕРАРХИИ(&ПроверяемыйРодитель)
        |   И Номенклатура.ЭтоГруппа = ИСТИНА
        |   И Номенклатура.Ответственный = &ТекущийПользователь";

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

    РезультатЗапроса = Запрос.Выполнить();
    
    // Если запрос вернул хотя бы одну строку, значит, выше по иерархии 
    // найден родитель, за которого отвечает текущий пользователь.
    Возврат НЕ РезультатЗапроса.Пустой();

КонецФункции

Этот подход изящно решает обе проблемы автора темы:

Шаг 4. Настройка интерфейса управляемой формы (UX/UI)

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

Для этого заблокируем поля формы для редактирования еще в момент открытия. В модуле управляемой формы справочника найдем или создадим процедуру ПриЧтенииНаСервере:


&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)

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

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

Использование свойства формы ТолькоПросмотр = Истина автоматически делает все поля ввода недоступными для редактирования и отключает кнопки сохранения изменений, обеспечивая понятное и лаконичное поведение интерфейса.

Резюме и рекомендации для высоконагруженных систем

Рассмотренный нами метод является классическим стандартом разработки на платформе «1С:Предприятие 8». Тем не менее, если в вашей базе данных справочник содержит сотни тысяч элементов, а структура групп меняется очень часто, использование конструкции В ИЕРАРХИИ в транзакции записи может вызвать кратковременные блокировки таблиц базы данных.

Для оптимизации таких систем рекомендуется использовать механизмы БСП (Библиотеки Стандартных Подсистем). В частности, подсистема «Ограничение доступа на уровне записей» (RLS) решает проблему иерархических проверок за счет специального регистра сведений (например, НастройкиПравОбъектов), где хранится «развернутая» (плоская) таблица связей всех родителей и их потомков. Это позволяет заменить ресурсоемкую операцию В ИЕРАРХИИ на легкое и быстрое соединение с индексом регистра сведений.

← На главную