Предисловие
Бухгалтерия захотела отражать в 1С:УПП факт того, что она получила бумажные оригиналы документов. Желание в целом законное и очень даже правильное. Наличие такого признака у документов в программе позволяет отслеживать наличие первички и вовремя принимать меры к взысканию оной с контрагентов, которые её задерживают.
Первой мыслью было добавить нужным документам реквизит или категорию "Оригинал получен". Идея с заведением категории нравилась больше, т.к. одну категорию можно навесить сразу на все нужные виды документов, не нужно корёжить формы документов чтобы вывести на них реквизит (или править модули форм чтобы сделать это программно), при установке категории не нужно пересохранять (перепроводить) документ, что особенно актуально для закрытых периодов. Но было одно отягчающее обстоятельство - нужно чтобы этот признак могли установить только уполномоченные на то пользователи.
Задача
Обеспечить возможность разграничения прав пользователей для работы с категориями объектов в 1С: УПП.
Шаг 1. Добавляем регистр для хранения сведений о правах пользователей и код для работы с ним.
Вообще таких велосипедов, как регистр сведений, хранящий права пользователей, наделано уже великое множество. И каждый волен выбрать себе тот велосипед, который ему больше по душе. Для нашей задачи главное чтобы имелась в наличии экспортная функция общего модуля Функция ПолучитьРазрешенныеСвойстваИКатегории(Пользователь, СписокСвойствИКатегорий = Неопределено) Экспорт, которая будет возвращать массив категорий, которые Пользователь имеет право изменять (в параметре СписокСвойствИКатегорий передаётся интересующая нас категория или массив категорий; Неопределено в этом параметре означает что нас интересуют права на все категории). Если такая функция у вас уже есть - можно переходить к шагу 2.
Мой "велосипед" таков (в моём регистре помимо прав на изменение категорий можно также задавать права на изменение свойств объектов - это задел на будущее когда понадобится ещё разграничивать права пользователей на установку свойств объектов):
1. Непериодический независимый регистр сведений Префикс_ПраваДоступаДляДопСвойств ("Префикс" замените на свой префикс разработчика), имеющий
Измерения
- Субъект - тип {СправочникСсылка.Пользователи, СправочникСсылка.ГруппыПользователей}, Ведущее, Основной отбор;
- ОбъектДоступа - тип {СправочникСсылка.КатегорииОбъектов, ПланВидовХарактеристикСсылка.СвойстваОбъектов}, Ведущее, Основной отбор, Запрет незаполненных значений;
Ресурсы
- ДоступРазрешен - тип Булево.
Определение прав пользователя осуществляется следующим образом. Самым высоким приоритетом обладает право установленное для конкретного пользователя, более низким приоритетом обладает право установленное для группы, в которую входит данный пользователь, и, наконец, самым низким приоритетом обладает право установленное для всех пользователей (в качестве Субъекта указана предопределённая группа "Все пользователи" или Субъект не заполнен). В качестве права пользователя берётся право с максимальным приоритетом (пользователь->группа->все). Если право для пользователя не найдено, то считается что доступ пользователю запрещён.
В приведённом примере Пользователь1 не имеет прав на изменение категории "Категория1" (запрет задан явно для этого пользователя), хотя все остальные пользователи имеют право на изменение этой категории (право дано через группу "Все пользователи"). Пользователь1 имеет право на изменение категории "Категория3" (право дано всем пользователям, т.к. не указан Субъект) и изменение свойства "Торговая марка" (право задано явно для этого пользователя). Все пользователи группы Группа1 имеют право изменять категорию "Категория2".
2. Функция ПолучитьРазрешенныеСвойстваИКатегории() для получения массива категорий, которые пользователь имеет право изменять:
Функция ПолучитьРазрешенныеСвойстваИКатегории(Пользователь, СписокСвойствИКатегорий = Неопределено) Экспорт
//
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Пользователь", Пользователь);
Запрос.УстановитьПараметр("СписокСвойствИКатегорий", СписокСвойствИКатегорий);
ВсеСубъекты = Новый Массив; // через этих субъектов устанавливаются права вообще всем пользователям
ВсеСубъекты.Добавить(Неопределено);
ВсеСубъекты.Добавить(Справочники.Пользователи.ПустаяСсылка());
ВсеСубъекты.Добавить(Справочники.ГруппыПользователей.ПустаяСсылка());
ВсеСубъекты.Добавить(Справочники.ГруппыПользователей.ВсеПользователи);
Запрос.УстановитьПараметр("ВсеСубъекты", ВсеСубъекты);
Запрос.Текст =
"ВЫБРАТЬ
| ГруппыПользователейПользователиГруппы.Ссылка
|ПОМЕСТИТЬ ВТ_ГруппыПользователя
|ИЗ
| Справочник.ГруппыПользователей.ПользователиГруппы КАК ГруппыПользователейПользователиГруппы
|ГДЕ
| ГруппыПользователейПользователиГруппы.Пользователь = &Пользователь
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВЫБОР
| КОГДА ПраваДоступаДляДопСвойств.Субъект = &Пользователь
| ТОГДА 3
| КОГДА ПраваДоступаДляДопСвойств.Субъект ССЫЛКА Справочник.ГруппыПользователей
| И НЕ ПраваДоступаДляДопСвойств.Субъект В (&ВсеСубъекты)
| ТОГДА 2
| ИНАЧЕ 1
| КОНЕЦ КАК Приоритет,
| ПраваДоступаДляДопСвойств.ОбъектДоступа КАК ОбъектДоступа,
| ПраваДоступаДляДопСвойств.ДоступРазрешен КАК ДоступРазрешен
|ПОМЕСТИТЬ ВТ_ПраваДоступаСПриоритетами
|ИЗ
| РегистрСведений.Префикс_ПраваДоступаДляДопСвойств КАК ПраваДоступаДляДопСвойств
|ГДЕ
| (ПраваДоступаДляДопСвойств.Субъект = &Пользователь
| ИЛИ ПраваДоступаДляДопСвойств.Субъект В
| (ВЫБРАТЬ
| ВТ_ГруппыПользователя.Ссылка
| ИЗ
| ВТ_ГруппыПользователя)
| ИЛИ ПраваДоступаДляДопСвойств.Субъект В (&ВсеСубъекты))
| И &УсловиеСписокСвойствИКатегорий
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_ПраваДоступаСПриоритетами.ОбъектДоступа КАК ОбъектДоступа
|ИЗ
| ВТ_ПраваДоступаСПриоритетами КАК ВТ_ПраваДоступаСПриоритетами
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
| ВТ_ПраваДоступаСПриоритетами.ОбъектДоступа КАК ОбъектДоступа,
| МАКСИМУМ(ВТ_ПраваДоступаСПриоритетами.Приоритет) КАК Приоритет
| ИЗ
| ВТ_ПраваДоступаСПриоритетами КАК ВТ_ПраваДоступаСПриоритетами
|
| СГРУППИРОВАТЬ ПО
| ВТ_ПраваДоступаСПриоритетами.ОбъектДоступа) КАК ВложенныйЗапрос
| ПО ВТ_ПраваДоступаСПриоритетами.ОбъектДоступа = ВложенныйЗапрос.ОбъектДоступа
| И ВТ_ПраваДоступаСПриоритетами.Приоритет = ВложенныйЗапрос.Приоритет
|ГДЕ
| ВТ_ПраваДоступаСПриоритетами.ДоступРазрешен";
УсловиеСписокСвойствИКатегорий = ?(СписокСвойствИКатегорий = Неопределено,
"ИСТИНА",
"ПраваДоступаДляДопСвойств.ОбъектДоступа В (&СписокСвойствИКатегорий)");
Запрос.Текст = СтрЗаменить(Запрос.Текст, "&УсловиеСписокСвойствИКатегорий", УсловиеСписокСвойствИКатегорий);
Возврат Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("ОбъектДоступа");
КонецФункции
Шаг 2. Добавляем подписку на событие ПередЗаписью для регистра сведений КатегорииОбъектов.
Сам контроль прав будем осуществлять через подписку на событие ПередЗаписью для регистра сведений КатегорииОбъектов (Источник события РегистрСведенийНаборЗаписей.КатегорииОбъектов):
Обработчик события такой:
Процедура Префикс_ПередЗаписьюКатегорийОбъекта(Источник, Отказ, Замещение) Экспорт
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Заголовок = "Запись регистра сведений " + Источник.Метаданные().Имя + ":";
ОтборИсточника = Источник.Отбор;
Если НЕ ОтборИсточника.Категория.Использование Тогда
ТекстСообщения = "Не установлен отбор по измерению ""Категория""!";
ОбщегоНазначения.СообщитьОбОшибке(ТекстСообщения, Отказ, Заголовок);
Возврат;
КонецЕсли;
ТекПользователь = глЗначениеПеременной("глТекущийПользователь");
Категория = ОтборИсточника.Категория.Значение;
РазрешенныеКатегории = ПолучитьРазрешенныеСвойстваИКатегории(ТекПользователь, Категория);
Если РазрешенныеКатегории.Найти(Категория) = Неопределено Тогда
ТекстСообщения = "Недостаточно прав для изменения категории """ + Категория + """!";
ОбщегоНазначения.СообщитьОбОшибке(ТекстСообщения, Отказ, Заголовок);
Возврат;
КонецЕсли;
КонецПроцедуры
Шаг 3. Модифицируем штатную обработку КатегорииОбъектов.
К сожалению, совсем обойтись без изменения штатных объектов не удалось. Дело в том, что штатная обработка КатегорииОбъектов, которая вызывается из форм справочников и документов по соответствующей кнопке, записывает регистр сведений КатегорииОбъектов набором записей с отбором только по Объекту. Если в записываемом наборе записей будут категории, которые пользователь не имеет права изменять и одновременно с этим в нём будут категории, которые пользователь изменять может, то мы не сможем ни записать такой набор, ни отказаться от его записи. Да, конечно можно просмотреть записываемый набор записей, найти в нём те категории, которые пользователь изменять не имеет права, прочитать значения этих категорий из базы и если прочитанные из базы значения отличаются от значений в наборе записей, то подменить значения изменённых категорий в наборе на значения из базы, а пользователю сказать "Ай-яй-яй!", но по-моему это излишне усложнит алгоритм. Тем более что другие обработки (в частности, "Групповая обработка ..."), пишут наборы записей с полным отбором по Объект+Категория.
Для исправления ситуации в модуле обработки КатегорииОбъектов была модифицирована Функция ЗаписатьКатегорииОбъекта() Экспорт так, чтобы запись регистра сведений осуществлялась с отбором по Объект+Категория.
// Функция записывеет категории объекта в информационную базу.
//
// Параметры:
// Нет.
//
// Возвращаемое значение:
// Истина - если категории объекта были записаны, или их не требуется записывать
// Ложь - если категории объекта не удалось записать.
//
Функция ЗаписатьКатегорииОбъекта() Экспорт
// *** Разграничение прав пользователей на установку категорий объектов
//НаборЗаписейКатегорииОбъекта = РегистрыСведений.КатегорииОбъектов.СоздатьНаборЗаписей();
//Для каждого Строка Из КатегорииОбъекта Цикл
// Если Строка.Принадлежность Тогда
// Запись = НаборЗаписейКатегорииОбъекта.Добавить();
// Запись.Объект = ОбъектОтбораКатегорий;
// Запись.Категория = Строка.Категория;
// КонецЕсли;
//КонецЦикла;
//НаборЗаписейКатегорииОбъекта.Отбор.Объект.Установить(ОбъектОтбораКатегорий);
//Попытка
// НаборЗаписейКатегорииОбъекта.Записать();
//Исключение
// #Если Клиент Тогда
// Предупреждение("Не удалось записать категории объекта:" + Символы.ПС + ОписаниеОшибки());
// #КонецЕсли
// Возврат Ложь;
//КонецПопытки;
// ---------- заменено на:
НачатьТранзакцию();
// Прочитаем категории из БД
НаборЗаписейБД = РегистрыСведений.КатегорииОбъектов.СоздатьНаборЗаписей();
НаборЗаписейБД.Отбор.Объект.Установить(ОбъектОтбораКатегорий);
НаборЗаписейБД.Прочитать();
УстановленныеКатегорииБД = НаборЗаписейБД.ВыгрузитьКолонку("Категория");
// Получим разрешенные категории
ТекПользователь = глЗначениеПеременной("глТекущийПользователь");
РазрешенныеКатегории = Префикс_ПраваДоступаДляДопСвойств.ПолучитьРазрешенныеСвойстваИКатегории(ТекПользователь, КатегорииОбъекта.ВыгрузитьКолонку("Категория"));
Для каждого Строка Из КатегорииОбъекта Цикл
ПринадлежностьПоДаннымБД = (УстановленныеКатегорииБД.Найти(Строка.Категория) <> Неопределено);
Если ПринадлежностьПоДаннымБД = Строка.Принадлежность Тогда
// значение данной категории не изменилась
Продолжить;
ИначеЕсли РазрешенныеКатегории.Найти(Строка.Категория) = Неопределено Тогда
// у пользователя нет прав
ОтменитьТранзакцию();
ТекстСообщения = "Недостаточно прав для изменения категории """ + Строка.Категория + """!";
#Если Клиент Тогда
Предупреждение(ТекстСообщения);
#Иначе
Сообщить(ТекстСообщения, СтатусСообщения.Важное);
#КонецЕсли
Возврат Ложь;
КонецЕсли;
НаборЗаписейКатегорииОбъекта = РегистрыСведений.КатегорииОбъектов.СоздатьНаборЗаписей();
НаборЗаписейКатегорииОбъекта.Отбор.Объект.Установить(ОбъектОтбораКатегорий);
НаборЗаписейКатегорииОбъекта.Отбор.Категория.Установить(Строка.Категория);
Если Строка.Принадлежность Тогда
Запись = НаборЗаписейКатегорииОбъекта.Добавить();
Запись.Объект = ОбъектОтбораКатегорий;
Запись.Категория = Строка.Категория;
КонецЕсли;
Попытка
НаборЗаписейКатегорииОбъекта.Записать();
Исключение
ОтменитьТранзакцию();
Сообщить("Не удалось записать категории объекта:" + Символы.ПС + ОписаниеОшибки(), СтатусСообщения.Важное);
Возврат Ложь;
КонецПопытки;
КонецЦикла;
ЗафиксироватьТранзакцию();
Возврат Истина;
// Разграничение прав пользователей на установку категорий объектов ***
КонецФункции
Заключение
В прилагаемом архиве текстовые файлы с кодом процедур и функций, приведённых выше, а также модифицированная обработка КатегорииОбъектов с изменённиями в функции ЗаписатьКатегорииОбъекта(), описанными в разделе "Шаг 3".
Буду рад дельным замечаниям и предложениям по теме в комментариях (если кто знает как более просто и изящно решить задачу с добавлением признака "Оригинал получен" - обязательно поделитесь опытом).
Все права на код обработки КатегорииОбъектов принадлежат фирме 1С.
Весь остальной код - под GPLv3, а текст под CC-BY.
P.S. Возможно, обработку КатегорииОбъектов имеет смысл ещё немного допилить чтобы в табличную часть КатегорииОбъекта выводились только те категории, которые пользователь имеет право изменять (ну или выводить все, но строки с запрещёнными для изменения категориями делать недоступными для редактирования).