При работе с системой 1С:Предприятие 3.0 часто возникает необходимость тонкой настройки прав доступа, выходящей за рамки стандартных ролей. Рассмотрим задачу, когда пользователю требуется предоставить доступ только к одному конкретному документу, например, «Поступление товаров и услуг», и при этом разрешить изменение лишь определенных его реквизитов, таких как флаг «Оригинал получен» и поле «Комментарий», запретив изменение всего остального. Выясним, как добиться такого детального ограничения прав, используя современные подходы и методы разработки в 1С.
Прежде чем перейти к решению, проанализируем, почему стандартные инструменты 1С:Бухгалтерии 3.0 не позволяют реализовать столь узкие требования к правам доступа:
Row-Level Security, RLS) позволяет ограничивать доступ к данным на основе определенных условий (например, по организации, подразделению, ответственному). Теоретически, можно было бы попробовать настроить RLS так, чтобы конкретный пользователь видел только определенный документ. Однако для этого документ должен содержать реквизит, по которому мы могли бы однозначно идентифицировать «разрешенный» документ для этого пользователя. Настройка RLS требует изменения конфигурации (добавления шаблонов ограничений в роли или создания новых ролей с такими шаблонами) и может быть достаточно сложной, особенно если речь идет о единичном документе без явного признака. При этом RLS обычно используется для массовых ограничений, а не для одного-единственного документа. Важно отметить, что RLS не предназначен для разрешения изменения определенных реквизитов и запрета других – он контролирует видимость и доступность записи целиком.Таким образом, мы приходим к выводу, что типовыми методами, без доработки или использования расширений, добиться такого детального ограничения прав для одного документа и его реквизитов, скорее всего, не получится.
Наиболее гибкий и рекомендуемый способ решения этой задачи в 1С:Предприятие 3.0 – это создание нового расширения с собственной ролью и программными доработками.
Прежде всего, нам потребуется создать новое расширение конфигурации. Расширения позволяют вносить изменения в функциональность системы без снятия основной конфигурации с поддержки, что значительно упрощает процесс обновлений и сопровождения.
Для нашего пользователя мы создадим специальную роль, которая будет обладать минимально необходимыми правами. Это позволит нам контролировать доступ к документу на самом базовом уровне.
После этих действий пользователь сможет открывать и изменять любой документ «Поступление товаров и услуг». Однако нам нужно ограничить его только одним документом и только двумя реквизитами. Это потребует дополнительной программной работы.
Чтобы пользователь мог видеть только один конкретный документ, нам потребуется программно фильтровать доступ. Это можно сделать несколькими способами:
В расширении мы можем создать замещающую форму списка для документа «Поступление товаров и услуг». В модуле этой формы, в процедуре ПриСозданииНаСервере или ПриОткрытии, мы можем установить отбор для динамического списка:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
Если Пользователи.РольДоступна("ДоступКОдномуПоступлению") Тогда
ЭтаФорма.Элементы.Список.Отбор.Элементы.Очистить();
// Установим отбор по ссылке на нужный документ
// Предположим, что ссылка на разрешенный документ хранится где-то,
// например, в константе или параметре сеанса, или жестко задана.
// Для примера - ID документа
РазрешеннаяСсылка = СсылкаНаРазрешенныйДокумент(); // Ваша функция или константа
Если РазрешеннаяСсылка <> Неопределено Тогда
НовыйЭлементОтбора = ЭтаФорма.Элементы.Список.Отбор.Элементы.Добавить(ВидСравненияКомпоновкиДанных.Равно);
НовыйЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Ссылка");
НовыйЭлементОтбора.ПравоеЗначение = РазрешеннаяСсылка;
НовыйЭлементОтбора.Использование = Истина;
КонецЕсли;
// Запрещаем создание новых документов, если это необходимо
ЭтаФорма.Команды.Вставить("Создать", Новый Действие("ОтключитьДействие"), "Отключить");
ЭтаФорма.Команды.Удалить(ЭтаФорма.Команды.Найти("Создать"));
// ... или скрываем кнопку
ЭтаФорма.Элементы.Найти("Список_Создать").Видимость = Ложь;
КонецЕсли;
КонецПроцедуры
&НаСервере
Функция СсылкаНаРазрешенныйДокумент()
// Здесь должна быть логика получения ссылки на тот самый документ
// Например, из константы:
// Возврат Константы.РазрешенныйДокументДляПользователя.Получить();
// Или по номеру и дате:
// Возврат Документы.ПоступлениеТоваровУслуг.НайтиПоНомеру("000000001", Дата("20240101"));
// Или по УникальномуИдентификатору, если он известен
Возврат Документы.ПоступлениеТоваровУслуг.ПолучитьСсылку(Новый УникальныйИдентификатор("..."));
КонецФункции
Важно: Метод Пользователи.РольДоступна() требует, чтобы функция РольДоступна была доступна на клиенте. Возможно, потребуется перенести проверку роли на сервер или использовать параметр сеанса. Убедитесь, что пользователь не может создать новый документ, если это запрещено.
Можно использовать подписку на событие ОбработкаПолученияФормы для документа «Поступление товаров и услуг». В этой подписке мы можем проверить, какая ссылка передается для открытия формы. Если это не разрешенный документ для нашего пользователя с особой ролью, мы можем отменить открытие или перенаправить на сообщение об ошибке.
// В расширении создаем новый модуль менеджера документа "Поступление товаров и услуг"
// или создаем Подписку на событие "ОбработкаПолученияФормы"
Процедура ОбработкаПолученияФормыДокументаПоступлениеТоваровУслуг(Источник, Отказ, ИмяФормы, СтандартнаяОбработка) Экспорт
Если ТипЗнч(Источник) = Тип("ДокументСсылка.ПоступлениеТоваровУслуг")
И Пользователи.РольДоступна("ДоступКОдномуПоступлению") Тогда
РазрешеннаяСсылка = Константы.РазрешенныйДокументДляПользователя.Получить(); // Или другой способ получения ссылки
Если Источник <> РазрешеннаяСсылка Тогда
Отказ = Истина;
// СообщитьПользователю("Доступ к этому документу запрещен.");
// Или открыть форму с сообщением
ПоказатьОповещениеПользователя("Доступ запрещен", "Доступ к этому документу запрещен.");
КонецЕсли;
КонецЕсли;
КонецПроцедуры
При этом для корректной работы ПоказатьОповещениеПользователя может потребоваться перенос части кода на клиент в обернутую функцию или создание отдельной формы сообщения.
Это самый важный этап, где мы будем контролировать, какие реквизиты пользователь может изменять, а какие нет. Для этого нам потребуется заместить форму документа «Поступление товаров и услуг» в расширении и добавить код в её модуль.
В расширении найдите документ «Поступление товаров и услуг». Щелкните по нему правой кнопкой мыши и выберите «Добавить в расширение». Затем перейдите к его формам и для основной формы документа (например, ФормаДокумента) выберите «Добавить в расширение с замещением». Это позволит нам модифицировать её поведение.
Откройте модуль замещенной формы документа. Здесь мы будем добавлять код, который будет проверять роль текущего пользователя и, в зависимости от неё, изменять доступность элементов формы.
Пример кода в процедуре ПриСозданииНаСервере:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Вызываем стандартную обработку
СтандартнаяОбработка = Истина;
// Проверяем, является ли текущий пользователь членом нашей специальной роли
Если Пользователи.РольДоступна("ДоступКОдномуПоступлению") Тогда
// Получим ссылку на разрешенный документ (например, из константы)
РазрешеннаяСсылка = Константы.РазрешенныйДокументДляПользователя.Получить();
// Если открываемый документ не является разрешенным,
// то отменяем открытие или делаем его только для просмотра
Если Не ЭтотОбъект.Ссылка.Пустая() И ЭтотОбъект.Ссылка <> РазрешеннаяСсылка Тогда
// Отменяем открытие формы
Отказ = Истина;
ПоказатьОповещениеПользователяНаКлиенте("Доступ к этому документу запрещен.", "Доступ запрещен");
Возврат;
КонецЕсли;
// Заблокируем все реквизиты для изменения, кроме "ОригиналПолучен" и "Комментарий"
Для Каждого ЭлементФормы Из Элементы Цикл
// Исключаем системные элементы и элементы, которые мы хотим оставить доступными
Если ТипЗнч(ЭлементФормы) = Тип("ПолеФормы")
И ЭлементФормы.Имя <> "ОригиналПолучен"
И ЭлементФормы.Имя <> "Комментарий"
// Дополнительно можно исключить кнопки, таблицы и т.д.,
// которые не являются полями для ввода данных
И Не СтрНайти(ЭлементФормы.Имя, "Шапка") = 1 // Исключаем элементы шапки, если они не содержат поля
Тогда
ЭлементФормы.ТолькоПросмотр = Истина;
// Возможно, для табличных частей также потребуется перебрать их колонки
// Пример для табличной части:
// Если ЭлементФормы.Имя = "Товары" Тогда
// Для Каждого КолонкаТЧ Из ЭлементФормы.Колонки Цикл
// КолонкаТЧ.ТолькоПросмотр = Истина;
// КонецЦикла;
// КонецЕсли;
КонецЕсли;
КонецЦикла;
// Разрешим изменение нужных реквизитов (хотя они уже не заблокированы условием выше)
// Этот код просто для ясности, что именно эти поля должны быть доступны
Если Элементы.Есть("ОригиналПолучен") Тогда
Элементы.ОригиналПолучен.ТолькоПросмотр = Ложь;
КонецЕсли;
Если Элементы.Есть("Комментарий") Тогда
Элементы.Комментарий.ТолькоПросмотр = Ложь;
КонецЕсли;
// Можно также скрыть кнопки записи и проведения, оставив только запись,
// или переопределить их поведение
// Например, скрываем кнопку "Провести и закрыть"
Если Элементы.Есть("ПровестиИЗакрыть") Тогда
Элементы.ПровестиИЗакрыть.Видимость = Ложь;
КонецЕсли;
// И кнопку "Провести"
Если Элементы.Есть("Провести") Тогда
Элементы.Провести.Видимость = Ложь;
КонецЕсли;
// Или переопределить обработчик кнопки "Записать"
// КнопкаЗаписать.Действие = "НовоеДействиеЗаписать";
КонецЕсли;
КонецПроцедуры
// Вспомогательная процедура для отображения оповещения на клиенте
&НаКлиенте
Процедура ПоказатьОповещениеПользователяНаКлиенте(Текст, Заголовок)
ПоказатьОповещениеПользователя(Текст, Заголовок);
КонецПроцедуры
Примечание по Пользователи.РольДоступна(): Эта функция по умолчанию доступна на сервере. Если вам нужно выполнить проверку на клиенте, вызовите её через серверный вызов или установите параметр сеанса при входе пользователя.
Пример кода в процедуре ПередЗаписьюНаСервере:
Даже если мы заблокировали поля на форме, опытный пользователь может попытаться обойти эти ограничения, например, через стандартные обработчики или другие механизмы. Поэтому критически важно продублировать проверку на сервере в событии ПередЗаписьюНаСервере.
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ПараметрыЗаписи)
// Проверяем, является ли текущий пользователь членом нашей специальной роли
Если Пользователи.РольДоступна("ДоступКОдномуПоступлению") Тогда
// Получим ссылку на разрешенный документ
РазрешеннаяСсылка = Константы.РазрешенныйДокументДляПользователя.Получить();
// Если пытаемся записать не тот документ
Если Не ЭтотОбъект.Ссылка.Пустая() И ЭтотОбъект.Ссылка <> РазрешеннаяСсылка Тогда
Отказ = Истина;
Сообщить("Доступ к изменению этого документа запрещен!");
Возврат;
КонецЕсли;
// Загрузим объект из базы до изменения, чтобы сравнить
ОбъектДоЗаписи = ЭтотОбъект.Ссылка.ПолучитьОбъект();
Если ОбъектДоЗаписи <> Неопределено Тогда
// Проверяем, были ли изменены другие реквизиты, кроме разрешенных
// Создадим временные объекты для сравнения
ВременныйОбъект = Новый (ТипЗнч(ЭтотОбъект));
ЗаполнитьЗначенияСвойств(ВременныйОбъект, ОбъектДоЗаписи);
// Перенесем разрешенные изменения из текущего объекта во временный
ВременныйОбъект.ОригиналПолучен = ЭтотОбъект.ОригиналПолучен;
ВременныйОбъект.Комментарий = ЭтотОбъект.Комментарий;
// Сравниваем временный объект (с разрешенными изменениями) с текущим объектом
// Если есть отличия, значит были изменены и другие поля
Если Не СравнитьЗначенияОбъектов(ЭтотОбъект, ВременныйОбъект) Тогда
Отказ = Истина;
Сообщить("Вы можете изменять только поля 'Оригинал получен' и 'Комментарий'.");
Возврат;
КонецЕсли;
Иначе
// Если объект новый, то для пользователя с этой ролью нельзя создавать новые документы
Отказ = Истина;
Сообщить("Создание новых документов 'Поступление товаров и услуг' запрещено!");
Возврат;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
&НаСервере
// Вспомогательная функция для сравнения объектов
Функция СравнитьЗначенияОбъектов(Объект1, Объект2)
Для Каждого Свойство1 Из Объект1.Метаданные().Свойства() Цикл
Если Свойство1.Имя = "ОригиналПолучен" ИЛИ Свойство1.Имя = "Комментарий" Тогда
Продолжить; // Эти поля мы проверяем отдельно, или игнорируем здесь, если знаем, что они могли быть изменены
КонецЕсли;
Значение1 = Объект1[Свойство1.Имя];
Значение2 = Объект2[Свойство1.Имя];
Если ТипЗнч(Значение1) = Тип("ТаблицаЗначений") И ТипЗнч(Значение2) = Тип("ТаблицаЗначений") Тогда
// Для табличных частей потребуется более сложная логика сравнения
// В данном контексте мы просто считаем, что если табличные части разные, то это изменение
Если Значение1.Количество() <> Значение2.Количество() Тогда
Возврат Ложь;
КонецЕсли;
// TODO: Добавить сравнение строк табличных частей
ИначеЕсли Значение1 <> Значение2 Тогда
Возврат Ложь;
КонецЕсли;
КонецЦикла;
Возврат Истина;
КонецФункции
Важно: Функция СравнитьЗначенияОбъектов – это пример. Для полноценного сравнения объектов, особенно с табличными частями, потребуется более сложная и универсальная реализация, которая учитывает все типы реквизитов и вложенные структуры.
Для улучшения пользовательского опыта мы можем использовать условное оформление формы. В расширении в замещенной форме документа можно настроить условное оформление, которое будет блокировать или скрывать ненужные поля для нашей специальной роли. Это визуальный аспект, но он не заменяет серверную проверку.
ТекущийПользователь.РольДоступна("ДоступКОдномуПоступлению") (эта функция должна быть доступна на клиенте или использоваться с параметром сеанса).Этот способ работает на клиенте и делает поля визуально недоступными, но, как упоминалось, всегда дублируйте проверки на сервере.
После всех настроек обязательно тщательно протестируйте поведение системы под новым пользователем. Убедитесь, что он:
Подумайте о крайних случаях: что будет, если пользователь попытается получить доступ к документу через ссылки в отчетах или других механизмах? Возможно, потребуется расширить перехват или модифицировать соответствующие отчеты.
Решение задачи по ограничению прав доступа для одного документа и его реквизитов в 1С:Бухгалтерия 3.0 требует комплексного подхода с использованием расширений. Мы рассмотрели, как создать новую роль, заместить формы документов и добавить программный код для контроля доступа как на клиентской, так и на серверной стороне. Этот подход позволяет достичь необходимой гибкости в управлении правами, сохраняя при этом основную конфигурацию на поддержке, что является лучшей практикой в современных версиях 1С.
Помните, что каждый случай индивидуален, и предоставленные примеры кода могут потребовать адаптации под конкретные особенности вашей конфигурации и бизнес-процессов.