В процессе разработки и настройки прав доступа в системе «1С:Предприятие» программисты часто сталкиваются с задачей: нужно разрешить пользователю редактировать только табличную часть документа (или справочника), но при этом запретить изменение реквизитов шапки (самого объекта). На первый взгляд кажется, что для этого достаточно настроить роли, однако стандартный механизм прав выдает ошибку или блокирует элементы интерфейса (например, кнопку выбора/открытия в полях табличной части).
В этой статье мы подробно разберем, почему платформа накладывает такое ограничение на уровне архитектуры, как на самом деле работает флаг «Независимые права подчиненных объектов» и какие существуют практические способы решения этой задачи.
Выясним причину такого поведения системы. В реляционных СУБД (будь то MS SQL Server, PostgreSQL или IBM DB2) табличная часть документа или справочника физически хранится в виде отдельной таблицы, которая связана с основной таблицей объекта по уникальному идентификатору (ссылке).
Однако в объектной модели «1С:Предприятие» табличная часть не является самостоятельным объектом базы данных. Она не может существовать и быть записана в базу в отрыве от своего владельца. Когда пользователь или программный код вносит изменения в строки табличной части и пытается их сохранить, платформа всегда выполняет транзакционную запись всего объекта целиком через вызов метода Записать().
В этот момент происходят следующие процессы:
ПередЗаписью и ПриЗаписи в модуле самого объекта.Поскольку физически выполняется полная запись основного объекта, СУБД и внутренний механизм безопасности платформы требуют обязательного наличия права «Изменение» (Update) на сам объект-родитель. Если у роли пользователя этого права нет, транзакция записи будет отклонена еще до попытки модификации табличной части.
Проанализируем предназначение этого свойства в настройках роли. Этот флаг часто вводит начинающих разработчиков в заблуждение, заставляя думать, что с его помощью можно решить нашу задачу. Однако логика его работы строго однонаправленная:
Таким образом, настроить такое разграничение прав исключительно галочками в дереве метаданных роли невозможно. Рассмотрим практические варианты решения этой проблемы.
Разберем по шагам наиболее популярный и надежный способ реализации. Суть его заключается в том, что мы даем пользователю полноценную роль с правами на «Изменение» самого объекта, но программно пресекаем любые попытки изменить реквизиты шапки как на уровне интерфейса, так и при записи.
Шаг 1. Ограничение доступности элементов на форме.
В модуле формы документа (или справочника) в обработчике события ПриСозданииНаСервере мы анализируем роли пользователя и принудительно отключаем доступность реквизитов шапки:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем, относится ли пользователь к категории с ограниченными правами
Если РольДоступна("РедактированиеТолькоТабличнойЧасти") Тогда
// Блокируем изменение реквизитов шапки на форме
Элементы.Организация.Доступность = Ложь;
Элементы.Контрагент.Доступность = Ложь;
Элементы.Договор.Доступность = Ложь;
Элементы.Дата.Доступность = Ложь;
// При этом сама табличная часть остается полностью доступной для редактирования
Элементы.Товары.ТолькоПросмотр = Ложь;
КонецЕсли;
КонецПроцедуры
Шаг 2. Контроль на уровне базы данных (Защита от обхода формы).
Продвинутый пользователь может попытаться обойти ограничения интерфейса (например, запустить групповую обработку реквизитов или внешнюю обработку). Чтобы этого не произошло, необходимо реализовать проверку непосредственно перед записью объекта в базу данных. Лучше всего использовать подписку на событие ПередЗаписью для нужного типа документа:
Процедура ПередЗаписьюДокументаПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
// Если документ новый, то проверять изменения старых значений нет смысла
Если Источник.ЭтоНовый() Тогда
Возврат;
КонецЕсли;
// Если у пользователя есть полные права администратора, пропускаем проверку
Если РольДоступна("ПолныеПрава") Тогда
Возврат;
КонецЕсли;
// Получаем текущие сохраненные значения реквизитов шапки из базы данных по ссылке
СсылкаНаОбъект = Источник.Ссылка;
СтарыеДанные = Новый Структура("Организация, Контрагент, Договор, Дата");
ЗаполнитьЗначенияСвойств(СтарыеДанные, СсылкаНаОбъект);
// Сравниваем старые значения в БД с новыми значениями в записываемом объекте
Если Источник.Организация <> СтарыеДанные.Организация
Или Источник.Контрагент <> СтарыеДанные.Контрагент
Или Источник.Договор <> СтарыеДанные.Договор
Или Источник.Дата <> СтарыеДанные.Дата Тогда
// Если обнаружено изменение контролируемых реквизитов шапки, отменяем транзакцию
ВызватьИсключение "Ошибка доступа! Вам разрешено редактировать только табличную часть этого документа.";
Отказ = Истина;
КонецЕсли;
КонецПроцедуры
Этот подход гарантирует 100% безопасность данных, при этом табличная часть остается полностью интерактивной для пользователя.
Рассмотрим ситуацию с точки зрения правильного проектирования архитектуры базы данных. Если бизнес-логика вашей системы предполагает, что разные группы пользователей должны независимо и в разное время редактировать данные одной сущности (например, менеджер создает документ, а кладовщик проставляет отметки о сборке в строках), то хранение этих данных в табличной части может быть архитектурной ошибкой.
Решение:
СведенияОСборкеЗаказов).ДокументСсылка (тип: Ссылка на наш документ) и КлючСтроки (числовой идентификатор строки).КоличествоСобрано, СтатусСтроки, Исполнитель).Пользователи смогут свободно изменять данные строк, при этом платформа будет записывать данные напрямую в таблицу регистра сведений, вообще не затрагивая и не блокируя основной документ.
Посмотрим на пример, когда пользователю нужно лишь изредка вносить изменения в табличную часть закрытого для него документа (например, ставить галочку «Проверено аудитором» или изменять статус конкретной строки).
В этом случае мы оставляем документ полностью в режиме «Только чтение» для пользователя на уровне ролей. На форму документа выводим специальную команду (кнопку) «Обновить строки».
При нажатии на эту кнопку управление передается на сервер в модуль, где включен флаг Привилегированный (или вызывается метод УстановитьПривилегированныйРежим(Истина)):
&НаСервереБезКонтекста
Процедура ВнестиИзмененияПривилегированно(СсылкаНаДокумент, НомерСтроки, НовыйСтатус)
// Включаем привилегированный режим, игнорируя права текущего пользователя
УстановитьПривилегированныйРежим(Истина);
ДокументОбъект = СсылкаНаДокумент.ПолучитьОбъект();
// Находим нужную строку и вносим изменения
Если ДокументОбъект.Товары.Количество() >= НомерСтроки Тогда
СтрокаТЧ = ДокументОбъект.Товары[НомерСтроки - 1];
СтрокаТЧ.Статус = НовыйСтатус;
// Записываем объект без проверки прав текущего сеанса
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);
КонецЕсли;
КонецПроцедуры
Такой подход позволяет точечно управлять записью данных, сохраняя строгие ограничения на весь остальной объект.
Подводя итоги, отметим: выбор конкретного варианта зависит от сложности вашей конфигурации и требований к производительности. Для большинства стандартных задач идеально подходит Вариант А, так как он сохраняет привычную пользователю работу в форме документа без кардинальной перестройки структуры метаданных.