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

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

#Область ПрограммныйИнтерфейс

#Область ДляВызоваИзДругихПодсистем

// Задать настройки формы отчета.
//
// Параметры:
//   Форма - ФормаКлиентскогоПриложения
//         - Неопределено
//   КлючВарианта - Строка
//                - Неопределено
//   Настройки - см. ОтчетыКлиентСервер.НастройкиОтчетаПоУмолчанию
//
Процедура ОпределитьНастройкиФормы(Форма, КлючВарианта, Настройки) Экспорт
	
	Настройки.События.ПередЗагрузкойНастроекВКомпоновщик = Истина;
	
КонецПроцедуры

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

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

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

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

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

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

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

Функция ИзмененияСоставов(Настройки)
	
	ИмяКолонкиСоединение = КонтрольРаботыПользователейСлужебный.ИмяКолонкиСоединение();
	
	Отбор = Новый Структура;
	
	СтатусыТранзакции = Новый Массив;
	СтатусыТранзакции.Добавить(СтатусТранзакцииЗаписиЖурналаРегистрации.Зафиксирована);
	СтатусыТранзакции.Добавить(СтатусТранзакцииЗаписиЖурналаРегистрации.НетТранзакции);
	Отбор.Вставить("СтатусТранзакции", СтатусыТранзакции);
	
	Период = ЗначениеПараметра(Настройки, "Период", Новый СтандартныйПериод);
	Если ЗначениеЗаполнено(Период.ДатаНачала) Тогда
		Отбор.Вставить("StartDate", Период.ДатаНачала);
	КонецЕсли;
	Если ЗначениеЗаполнено(Период.ДатаОкончания) Тогда
		Отбор.Вставить("EndDate", Период.ДатаОкончания);
	КонецЕсли;
	
	МодульУправлениеДоступомСлужебный = ОбщегоНазначения.ОбщийМодуль("УправлениеДоступомСлужебный");
	Отбор.Вставить("Событие",
		МодульУправлениеДоступомСлужебный.ИмяСобытияИзменениеРолейПрофилейДляЖурналаРегистрации());
	
	Автор = ЗначениеПараметра(Настройки, "Автор", Null);
	Если Автор <> Null Тогда
		Отбор.Вставить("Пользователь", Строка(Автор));
	КонецЕсли;
	
	ТипБулево     = Новый ОписаниеТипов("Булево");
	ТипДата       = Новый ОписаниеТипов("Дата");
	ТипЧисло      = Новый ОписаниеТипов("Число");
	ТипСтрока     = Новый ОписаниеТипов("Строка");
	ТипСтрока1    = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(1));
	ТипСтрока20   = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(20));
	ТипСтрока36   = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(36));
	ТипСтрока100  = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(100));
	ТипСтрока1000 = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(1000));
	
	ТипПрофиль = Новый ОписаниеТипов(ТипСтрока100, "СправочникСсылка.ПрофилиГруппДоступа");
	ТипРоль    = Новый ОписаниеТипов(ТипСтрока100, "СправочникСсылка.ИдентификаторыОбъектовМетаданных,
		|СправочникСсылка.ИдентификаторыОбъектовРасширений");
	
	Изменения = Новый ТаблицаЗначений;
	Колонки = Изменения.Колонки;
	Колонки.Добавить("Профиль",                      ТипПрофиль);
	Колонки.Добавить("ПредставлениеПрофиля",         ТипСтрока1000);
	Колонки.Добавить("ПометкаУдаленияПрофиля",       ТипБулево);
	Колонки.Добавить("НомерСобытия",                 ТипЧисло);
	Колонки.Добавить("Дата",                         ТипДата);
	Колонки.Добавить("Автор",                        ТипСтрока100);
	Колонки.Добавить("ИдентификаторАвтора",          ТипСтрока36);
	Колонки.Добавить("Приложение",                   ТипСтрока20);
	Колонки.Добавить("Компьютер",                    ТипСтрока);
	Колонки.Добавить("Сеанс",                        ТипЧисло);
	Колонки.Добавить(ИмяКолонкиСоединение,           ТипЧисло);
	Колонки.Добавить("Роль",                         ТипРоль);
	Колонки.Добавить("ИмяРоли",                      ТипСтрока1000);
	Колонки.Добавить("ПредставлениеРоли",            ТипСтрока1000);
	Колонки.Добавить("ПометкаУдаленияРоли",          ТипБулево);
	Колонки.Добавить("РольЕстьВМетаданных",          ТипБулево);
	Колонки.Добавить("ВидИзменения",                 ТипСтрока1);
	Колонки.Добавить("БылоПометкаУдаленияПрофиля",   ТипБулево);
	Колонки.Добавить("БылоИмяРоли",                  ТипСтрока1000);
	Колонки.Добавить("БылоПредставлениеРоли",        ТипСтрока1000);
	Колонки.Добавить("БылоПометкаУдаленияРоли",      ТипБулево);
	
	КолонкиЖурнала = "Событие,Дата,Пользователь,ИмяПользователя,
	|ИмяПриложения,Компьютер,Сеанс,Данные," + ИмяКолонкиСоединение;
	
	УстановитьПривилегированныйРежим(Истина);
	События = Новый ТаблицаЗначений;
	ВыгрузитьЖурналРегистрации(События, Отбор, КолонкиЖурнала);
	
	НомерСобытия = 0;
	ОтборДанных = Новый Структура;
	ОтборДанных.Вставить("Профили", ИдентификаторыЗначений(
		ЗначениеПараметра(Настройки, "Профиль", Неопределено)));
	ОтборДанных.Вставить("Роли", ИдентификаторыЗначений(
		ЗначениеПараметра(Настройки, "Роль", Неопределено)));
	
	Для Каждого Событие Из События Цикл
		Данные = РасширенныеДанныеИзмененияРолейПрофилей(Событие.Данные, ОтборДанных);
		Если Данные = Неопределено Тогда
			Продолжить;
		КонецЕсли;
		
		НомерСобытия = НомерСобытия + 1;
		
		СвойстваСобытия = Новый Структура;
		СвойстваСобытия.Вставить("НомерСобытия",        НомерСобытия);
		СвойстваСобытия.Вставить("Дата",                Событие.Дата);
		СвойстваСобытия.Вставить("Автор",               Событие.ИмяПользователя);
		СвойстваСобытия.Вставить("ИдентификаторАвтора", Событие.Пользователь);
		СвойстваСобытия.Вставить("Приложение",          Событие.ИмяПриложения);
		СвойстваСобытия.Вставить("Компьютер",           Событие.Компьютер);
		СвойстваСобытия.Вставить("Сеанс",               Событие.Сеанс);
		СвойстваСобытия.Вставить(ИмяКолонкиСоединение,  Событие.Соединение);
		
		Для Каждого ОписаниеИзменения Из Данные.ИзменениеРолей Цикл
			СвойстваПрофиля = Данные.ПредставлениеПрофилей.Найти(ОписаниеИзменения.Профиль, "Профиль");
			Если СвойстваПрофиля = Неопределено Тогда
				Продолжить;
			КонецЕсли;
			СвойстваРоли = Данные.ПредставлениеРолей.Найти(ОписаниеИзменения.Роль, "Роль");
			Если СвойстваРоли = Неопределено Тогда
				Продолжить;
			КонецЕсли;
			
			НоваяСтрока = Изменения.Добавить();
			ЗаполнитьЗначенияСвойств(НоваяСтрока, СвойстваСобытия);
			НоваяСтрока.Профиль                = ДесериализованнаяСсылка(ОписаниеИзменения.Профиль);
			НоваяСтрока.ПредставлениеПрофиля   = СвойстваПрофиля.Представление;
			НоваяСтрока.ПометкаУдаленияПрофиля = СвойстваПрофиля.ПометкаУдаления;
			НоваяСтрока.Роль                   = ДесериализованнаяСсылка(ОписаниеИзменения.Роль);
			НоваяСтрока.ИмяРоли                = СвойстваРоли.Имя;
			НоваяСтрока.ПредставлениеРоли      = ?(ЗначениеЗаполнено(СвойстваРоли.Синоним),
				СвойстваРоли.Синоним, СвойстваРоли.Имя);
			НоваяСтрока.ПометкаУдаленияРоли    = СвойстваРоли.ПометкаУдаления;
			НоваяСтрока.РольЕстьВМетаданных    = СвойстваРоли.ЕстьВМетаданных;
			НоваяСтрока.ВидИзменения           = ?(ОписаниеИзменения.ВидИзменения = "Удалено", "-",
				?(ОписаниеИзменения.ВидИзменения = "Добавлено", "+",
				?(ОписаниеИзменения.ВидИзменения = "Изменено", "*", "")));
			
			СтарыеЗначения = СвойстваПрофиля.СтарыеЗначенияСвойств;
			НоваяСтрока.БылоПометкаУдаленияПрофиля = ?(СтарыеЗначения.Свойство("ПометкаУдаления"),
				СтарыеЗначения.ПометкаУдаления, НоваяСтрока.ПометкаУдаленияПрофиля);
			
			СтарыеЗначения = СвойстваРоли.СтарыеЗначенияСвойств;
			НоваяСтрока.БылоИмяРоли = ?(СтарыеЗначения.Свойство("Имя"),
				СтарыеЗначения.Имя, НоваяСтрока.ИмяРоли);
			НоваяСтрока.БылоПредставлениеРоли = ?(СтарыеЗначения.Свойство("Синоним"),
				?(ЗначениеЗаполнено(СтарыеЗначения.Синоним), СтарыеЗначения.Синоним, НоваяСтрока.БылоИмяРоли),
				НоваяСтрока.ПредставлениеРоли);
			НоваяСтрока.БылоПометкаУдаленияРоли = ?(СтарыеЗначения.Свойство("ПометкаУдаления"),
				СтарыеЗначения.ПометкаУдаления, НоваяСтрока.ПометкаУдаленияРоли);
		КонецЦикла;
	КонецЦикла;
	
	Возврат Изменения;
	
КонецФункции

Функция ЗначениеПараметра(Настройки, ИмяПараметра, ЗначениеПоУмолчанию)
	
	Поле = Настройки.ПараметрыДанных.Элементы.Найти(ИмяПараметра);
	
	Если Поле <> Неопределено И Поле.Использование Тогда
		Возврат Поле.Значение;
	КонецЕсли;
	
	Возврат ЗначениеПоУмолчанию;
	
КонецФункции

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

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

Функция ХранимаяТаблица(Строки, Свойства)
	
	Если ТипЗнч(Строки) <> Тип("Массив") Тогда
		Возврат Неопределено;
	КонецЕсли;
	
	Результат = Новый ТаблицаЗначений;
	Для Каждого КлючИЗначение Из Свойства Цикл
		Типы = ОбщегоНазначенияКлиентСервер.ЗначениеВМассиве(ТипЗнч(КлючИЗначение.Значение));
		Результат.Колонки.Добавить(КлючИЗначение.Ключ, Новый ОписаниеТипов(Типы));
	КонецЦикла;
	
	Для Каждого Строка Из Строки Цикл
		Если ТипЗнч(Строка) <> Тип("Структура") Тогда
			Возврат Неопределено;
		КонецЕсли;
		НоваяСтрока = Результат.Добавить();
		Для Каждого КлючИЗначение Из Свойства Цикл
			Если Не Строка.Свойство(КлючИЗначение.Ключ)
			 Или ТипЗнч(Строка[КлючИЗначение.Ключ]) <> ТипЗнч(КлючИЗначение.Значение) Тогда
				Возврат Неопределено;
			КонецЕсли;
			НоваяСтрока[КлючИЗначение.Ключ] = Строка[КлючИЗначение.Ключ];
		КонецЦикла;
	КонецЦикла;
	
	Возврат Результат;
	
КонецФункции

Функция ДесериализованнаяСсылка(СериализованнаяСсылка)
	
	Если СериализованнаяСсылка = Неопределено Тогда
		Возврат Неопределено;
	КонецЕсли;
	
	Попытка
		Результат = ЗначениеИзСтрокиВнутр(СериализованнаяСсылка);
	Исключение
		Результат = Неопределено;
	КонецПопытки;
	
	Если Результат = Неопределено Тогда
		Возврат СериализованнаяСсылка;
	КонецЕсли;
	
	Возврат Результат;
	
КонецФункции

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

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