///////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2024, ООО 1С-Софт
// Все права защищены. Эта программа и сопроводительные материалы предоставляются 
// в соответствии с условиями лицензии Attribution 4.0 International (CC BY 4.0)
// Текст лицензии доступен по ссылке:
// https://creativecommons.org/licenses/by/4.0/legalcode
///////////////////////////////////////////////////////////////////////////////////////////////////////

#Если Сервер Или ТолстыйКлиентОбычноеПриложение Или ВнешнееСоединение Тогда

#Область ОписаниеПеременных

Перем СтарыеЗначения; // Значения некоторых реквизитов и табличных частей группы доступа
                      // до ее изменения для использования в обработчике события ПриЗаписи.

#КонецОбласти

#Область ОбработчикиСобытий

Процедура ПередЗаписью(Отказ)
	
	// АПК:75-выкл проверка ОбменДанными.Загрузка должна быть после записи изменений в журнал.
	Если ЭтоГруппа Тогда
		Возврат;
	КонецЕсли;
	
	Если ПользователиСлужебныйПовтИсп.РегистрироватьИзмененияПравДоступа()
	 Или Не ОбменДанными.Загрузка Тогда
		
		Если ОбщегоНазначения.ИнформационнаяБазаФайловая() Тогда
			УправлениеДоступомСлужебный.ЗаблокироватьРегистрыПередЗаписьюОбъектаНастройкиДоступаВФайловойИБ();
		КонецЕсли;
		СтарыеЗначения = СтарыеЗначения();
	КонецЕсли;
	// АПК:75-вкл
	
	Если ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;
	
	Если УправлениеДоступомСлужебный.УпрощенныйИнтерфейсНастройкиПравДоступа()
		И ПоддерживаетсяНастройкаПравПользователейБТС() Тогда
		
		// Для УправлениеДоступомСлужебныйВМоделиСервиса.УчастникиПоПрофилю
		Блокировка = Новый БлокировкаДанных();
		ЭлементБлокировки = Блокировка.Добавить("Справочник.ПрофилиГруппДоступа");
		ЭлементБлокировки.УстановитьЗначение("Ссылка", Профиль);
		
		// АПК:1320-выкл - Попытка-Исключение не требуется, т.к. блокировка выполняется в обработчике ПередЗаписью
		Блокировка.Заблокировать(); 
		// АПК:1320-вкл
	КонецЕсли;

	РегистрыСведений.ПраваРолей.ПроверитьДанныеРегистра();
	
	Если Не Справочники.ВерсииРасширений.ВсеРасширенияПодключены() Тогда
		Справочники.ПрофилиГруппДоступа.ВосстановитьНесуществующиеВидыИЗначенияДоступа(СтарыеЗначения, ЭтотОбъект);
	КонецЕсли;
	
	// Удаление пустых участников группы доступа.
	Индекс = Пользователи.Количество() - 1;
	Пока Индекс >= 0 Цикл
		Если Не ЗначениеЗаполнено(Пользователи[Индекс].Пользователь) Тогда
			Пользователи.Удалить(Индекс);
		КонецЕсли;
		Индекс = Индекс - 1;
	КонецЦикла;
	
	Если Ссылка = УправлениеДоступом.ГруппаДоступаАдминистраторы() Тогда
		
		// Всегда предопределенный профиль Администратор.
		Профиль = УправлениеДоступом.ПрофильАдминистратор();
		
		// Не может быть персональной группой доступа.
		Пользователь = Неопределено;
		
		// Не может иметь обычного ответственного (только полноправные пользователи).
		Ответственный = Неопределено;
		
		// Изменение разрешено только полноправному пользователю.
		МодульПользователи = ОбщегоНазначения.ОбщийМодуль("Пользователи");
		Если Не МодульПользователи.ЭтоПолноправныйПользователь() Тогда
			ВызватьИсключение
				НСтр("ru = 'Предопределенную группу доступа Администраторы
				           |можно изменять, либо в привилегированном режиме,
				           |либо при наличии роли ""Полные права"".'");
		КонецЕсли;
		
		// Проверка наличия только пользователей.
		Для каждого ТекущаяСтрока Из Пользователи Цикл
			Если ТипЗнч(ТекущаяСтрока.Пользователь) <> Тип("СправочникСсылка.Пользователи") Тогда
				ВызватьИсключение
					НСтр("ru = 'Предопределенная группа доступа Администраторы
					           |может содержать только пользователей.
					           |
					           |Группы пользователей, внешние пользователи и
					           |группы внешних пользователей недопустимы.'");
			КонецЕсли;
		КонецЦикла;
		
	// Нельзя устанавливать предопределенный профиль Администратор произвольной группе доступа.
	ИначеЕсли Профиль = УправлениеДоступом.ПрофильАдминистратор() Тогда
		ВызватьИсключение
			НСтр("ru = 'Предопределенный профиль Администратор может быть только
			           |у предопределенной группы доступа Администраторы.'");
	КонецЕсли;
	
	// Автоматическая установка реквизитов для персональной группы доступа.
	Если ЗначениеЗаполнено(Пользователь) Тогда
		Родитель = Справочники.ГруппыДоступа.РодительПерсональныхГруппДоступа();
	Иначе
		Пользователь = Неопределено;
		Если Родитель = Справочники.ГруппыДоступа.РодительПерсональныхГруппДоступа(Истина) Тогда
			Родитель = Неопределено;
		КонецЕсли;
	КонецЕсли;
	
	// При снятии пометки удаления с группы доступа выполняется
	// снятие пометки удаления с профиля этой группы доступа.
	Если Не ПометкаУдаления И СтарыеЗначения.ПометкаУдаления = Истина
	   И Профиль = СтарыеЗначения.Профиль Тогда
		
		НачатьТранзакцию();
		Попытка
			Блокировка = Новый БлокировкаДанных;
			ЭлементБлокировки = Блокировка.Добавить("Справочник.ПрофилиГруппДоступа");
			ЭлементБлокировки.УстановитьЗначение("Ссылка", Профиль);
			Блокировка.Заблокировать();
			
			ПометкаУдаленияПрофиля = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(Профиль, "ПометкаУдаления");
			ПометкаУдаленияПрофиля = ?(ТипЗнч(ПометкаУдаленияПрофиля) = Тип("Булево"), ПометкаУдаленияПрофиля, Ложь);
			Если ПометкаУдаленияПрофиля Тогда
				УправлениеДоступом.ПроверитьИзменениеРазрешено(Профиль);
				ЗаблокироватьДанныеДляРедактирования(Профиль);
				УстановитьПривилегированныйРежим(Истина);
				ПрофильОбъект = Профиль.ПолучитьОбъект();
				ПрофильОбъект.ДополнительныеСвойства.Вставить("СнятиеПометкиУдаленияПрофиляПриСнятииПометкиУдаленияГруппыДоступа", Ссылка);
				ПрофильОбъект.ПометкаУдаления = Ложь;
				ПрофильОбъект.Записать();
				УстановитьПривилегированныйРежим(Ложь);
			КонецЕсли;
			
			ЗафиксироватьТранзакцию();
		Исключение
			ОтменитьТранзакцию();
			ВызватьИсключение;
		КонецПопытки;
	КонецЕсли;
	
КонецПроцедуры

// Обновляет:
// - роли добавленных, оставшихся и удаленных пользователей;
// - РегистрСведений.ТаблицыГруппДоступа;
// - РегистрСведений.ЗначенияГруппДоступа.
//
Процедура ПриЗаписи(Отказ)
	
	// АПК:75-выкл проверка ОбменДанными.Загрузка должна быть после записи изменений в журнал.
	Если ЭтоГруппа Тогда
		ОтправитьСообщениеИзмененияГруппыДоступа();		
		Возврат;
	КонецЕсли;
	
	Если ПользователиСлужебныйПовтИсп.РегистрироватьИзмененияПравДоступа() Тогда
		УстановитьОтключениеБезопасногоРежима(Истина);
		УстановитьПривилегированныйРежим(Истина);
		
		Справочники.ГруппыДоступа.ЗарегистрироватьИзменениеУчастниковГруппДоступа(ЭтотОбъект, СтарыеЗначения);
		Справочники.ГруппыДоступа.ЗарегистрироватьИзменениеРазрешенныхЗначений(ЭтотОбъект, СтарыеЗначения);
		
		УстановитьПривилегированныйРежим(Ложь);
		УстановитьОтключениеБезопасногоРежима(Ложь);
	КонецЕсли;
	// АПК:75-вкл
	
	Если ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;
	
	Если Не ДополнительныеСвойства.Свойство("НеОбновлятьРолиПользователей") Тогда
		ОбновитьРолиПользователейПриИзмененииГруппыДоступа();
	КонецЕсли;
	
	НаличиеУчастников = Пользователи.Количество() <> 0;
	СтароеНаличиеУчастников = СтарыеЗначения.Ссылка = Ссылка И Не СтарыеЗначения.Пользователи.Пустой();
	
	Если Профиль           <> СтарыеЗначения.Профиль
	 Или ПометкаУдаления   <> СтарыеЗначения.ПометкаУдаления
	 Или НаличиеУчастников <> СтароеНаличиеУчастников Тогда
		
		РегистрыСведений.ТаблицыГруппДоступа.ОбновитьДанныеРегистра(Ссылка);
	КонецЕсли;
	
	Если УправлениеДоступомСлужебный.ОграничиватьДоступНаУровнеЗаписейУниверсально() Тогда
		СоставИзмененийРолей = Справочники.ГруппыДоступа.РолиДляОбновленияПрав(СтарыеЗначения, ЭтотОбъект);
		УправлениеДоступомСлужебный.ЗапланироватьОбновлениеДоступаПриИзмененииПрофиляГруппыДоступа(
			"ГруппаДоступаПриИзмененииПрофиля", СоставИзмененийРолей, Профиль <> СтарыеЗначения.Профиль);
	КонецЕсли;
	
	Если Справочники.ГруппыДоступа.ИзменилисьВидыИлиЗначенияДоступа(СтарыеЗначения, ЭтотОбъект)
	 Или ПометкаУдаления   <> СтарыеЗначения.ПометкаУдаления
	 Или НаличиеУчастников <> СтароеНаличиеУчастников Тогда
		
		РегистрыСведений.ЗначенияГруппДоступа.ОбновитьДанныеРегистра(Ссылка);
	КонецЕсли;
	
	Если УправлениеДоступомСлужебный.ОграничиватьДоступНаУровнеЗаписейУниверсально() Тогда
		ТипыИзмененныхУчастников = ТипыИзмененныхУчастников(Пользователи, СтарыеЗначения.Пользователи,
			ПометкаУдаления <> СтарыеЗначения.ПометкаУдаления);
		УправлениеДоступомСлужебный.ЗапланироватьОбновлениеНаборовГруппДоступа(
			"ГруппыДоступаПриИзмененииУчастников",
			ТипыИзмененныхУчастников.Пользователи,
			ТипыИзмененныхУчастников.ВнешниеПользователи);
		
		Если Не ПометкаУдаления Тогда
			УправлениеДоступомСлужебный.ЗапланироватьОбновлениеДоступаПриИзмененииУчастниковГруппыДоступа(Ссылка,
				ТипыИзмененныхУчастников);
		КонецЕсли;
		Если ПометкаУдаления <> СтарыеЗначения.ПометкаУдаления Тогда
			УправлениеДоступомСлужебный.ОбновитьГруппыДоступаРазрешенногоКлючаДоступа(Ссылка);
		КонецЕсли;
	КонецЕсли;
	
	ОтправитьСообщениеИзмененияГруппыДоступа();
	
КонецПроцедуры

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

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

Процедура ПередУдалением(Отказ)
	
	Если ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;
	
	ОтправитьСообщениеИзмененияГруппыДоступа(Истина);

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

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

// Значения некоторых реквизитов и табличных частей группы доступа
// до ее изменения для использования в обработчике события ПриЗаписи.
// 
// Возвращаемое значение:
//  Структура:
//     * Ссылка - СправочникСсылка.ГруппыДоступа
//     * ПометкаУдаления - Булево
//     * Профиль - СправочникСсылка.ПрофилиГруппДоступа
//     * ПометкаУдаленияПрофиля - Булево
//     * Пользователи - РезультатЗапроса
//     * ВидыДоступа - РезультатЗапроса
//     * ЗначенияДоступа - РезультатЗапроса
//
Функция СтарыеЗначения()
	
	Результат = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Ссылка,
		"Ссылка, Профиль, ПометкаУдаления, Пользователи, ВидыДоступа, ЗначенияДоступа");
	
	Результат.Вставить("ПометкаУдаленияПрофиля", Неопределено);
	
	Если ЗначениеЗаполнено(Результат.Профиль) Тогда
		Результат.ПометкаУдаленияПрофиля = ОбщегоНазначения.ЗначениеРеквизитаОбъекта(
			Результат.Профиль, "ПометкаУдаления");
	КонецЕсли;
	
	Возврат Результат;
	
КонецФункции

// Параметры:
//  НовыеУчастники - СправочникТабличнаяЧасть.ГруппыДоступа.Пользователи
//  СтарыеУчастники - РезультатЗапроса
//                  - Неопределено
// Возвращаемое значение:
//  Структура:
//    * ВнешниеПользователи - Булево
//    * Пользователи - Булево
//
Функция ТипыИзмененныхУчастников(НовыеУчастники, СтарыеУчастники, ИзмениласьПометкаУдаления)
	
	ТипыИзмененныхУчастников = Новый Структура;
	ТипыИзмененныхУчастников.Вставить("Пользователи", Ложь);
	ТипыИзмененныхУчастников.Вставить("ВнешниеПользователи", Ложь);
	
	ВсеУчастники = НовыеУчастники.Выгрузить(, "Пользователь");
	ВсеУчастники.Колонки.Добавить("ВидИзмененияСтроки", Новый ОписаниеТипов("Число"));
	ВсеУчастники.ЗаполнитьЗначения(1, "ВидИзмененияСтроки");
	Если СтарыеУчастники <> Неопределено Тогда
		Выборка = СтарыеУчастники.Выбрать();
		Пока Выборка.Следующий() Цикл
			НоваяСтрока = ВсеУчастники.Добавить();
			НоваяСтрока.Пользователь = Выборка.Пользователь;
			НоваяСтрока.ВидИзмененияСтроки = -1;
		КонецЦикла;
	КонецЕсли;
	ВсеУчастники.Свернуть("Пользователь", "ВидИзмененияСтроки");
	Для Каждого Строка Из ВсеУчастники Цикл
		Если Строка.ВидИзмененияСтроки = 0
		   И Не ИзмениласьПометкаУдаления Тогда
			Продолжить;
		КонецЕсли;
		Если ТипЗнч(Строка.Пользователь) = Тип("СправочникСсылка.Пользователи")
		 Или ТипЗнч(Строка.Пользователь) = Тип("СправочникСсылка.ГруппыПользователей") Тогда
			ТипыИзмененныхУчастников.Пользователи = Истина;
		КонецЕсли;
		Если ТипЗнч(Строка.Пользователь) = Тип("СправочникСсылка.ВнешниеПользователи")
		 Или ТипЗнч(Строка.Пользователь) = Тип("СправочникСсылка.ГруппыВнешнихПользователей") Тогда
			ТипыИзмененныхУчастников.ВнешниеПользователи = Истина;
		КонецЕсли;
	КонецЦикла;
	
	Возврат ТипыИзмененныхУчастников;
	
КонецФункции

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

Функция ПоддерживаетсяУправлениеДоступомВМоделиСервиса()
	
	Возврат ОбщегоНазначения.ПодсистемаСуществует(
		"СтандартныеПодсистемы.РаботаВМоделиСервиса.УправлениеДоступомВМоделиСервиса");
	
КонецФункции

Функция ПоддерживаетсяНастройкаПравПользователейБТС()
	
	Если Не ПоддерживаетсяУправлениеДоступомВМоделиСервиса() Тогда
		Возврат Ложь;
	КонецЕсли;
	
	МодульУправлениеДоступомСлужебныйВМоделиСервиса = ОбщегоНазначения.ОбщийМодуль(
		"УправлениеДоступомСлужебныйВМоделиСервиса");
	
	Возврат МодульУправлениеДоступомСлужебныйВМоделиСервиса.ПоддерживаетсяНастройкаПравПользователейБТС();
	
КонецФункции

#КонецОбласти

#Иначе
ВызватьИсключение НСтр("ru = 'Недопустимый вызов объекта на клиенте.'");
#КонецЕсли