Загрузка данных


// Полная замена обработчика GetListIncidentsGet и новые локальные помощники.
// Внешние процедуры и функции исходного модуля сохранены как зависимости:
// ПолучитьДатуИзПараметра, Ошибка404, ЗаписатьЛоги, ПолучитьJSONОшибка,
// ЗаписатьСостояние, ЗаписатьСоставУслуги, ЗаписатьКатегорию.
// Минимальная версия платформы: 8.3.10.

// Возвращает список инцидентов по параметрам HTTP-запроса.
//
// Параметры:
//  ЗапросПарам - HTTPСервисЗапрос - запрос метода getListIncidents. Старый режим использует
//      startDate, endDate, status и author. Режим синхронизации использует changedSince,
//      необязательный limit (1..500) и продолжение только через cursor.
//
// Возвращаемое значение:
//  HTTPСервисОтвет - JSON-массив инцидентов. При наличии следующей страницы содержит
//      заголовок X-Next-Cursor.
Функция GetListIncidentsGet(ЗапросПарам) Экспорт
	Попытка
		Ответ = Новый HTTPСервисОтвет(200);
		
		ТекстОшибкиПараметров = "";
		Настройки = ПолучитьНастройкиЗапросаИнцидентов(ЗапросПарам, ТекстОшибкиПараметров);
		Если Настройки = Неопределено Тогда
			Возврат ПолучитьОтветОшибкиПараметров(ТекстОшибкиПараметров);
		КонецЕсли;
		
		// Старый сценарий сохраняет прежнюю проверку периода и ответы HTTP 404.
		Если Настройки.Режим = "legacy" И Не Настройки.ИзКурсора Тогда
			Настройки.ДатаНачала = ПолучитьДатуИзПараметра(Настройки.ДатаНачалаСтрока,, Ложь);
			Настройки.ДатаОкончания = ПолучитьДатуИзПараметра(Настройки.ДатаОкончанияСтрока, Истина, Ложь);
			
			Если Настройки.ДатаНачала = Неопределено Тогда
				Ошибка404(Ответ, "Неправильный формат или значение параметра startDate");
				ЗаписатьЛоги(404,
					СтрШаблон("Неправильный формат или значение параметра startDate = %1", Настройки.ДатаНачалаСтрока),
					"getIncidentsList");
				Возврат Ответ;
			ИначеЕсли Настройки.ДатаОкончания = Неопределено Тогда
				Ошибка404(Ответ, "Неправильный формат или значение параметра endDate");
				ЗаписатьЛоги(404,
					СтрШаблон("Неправильный формат или значение параметра endDate = %1", Настройки.ДатаОкончанияСтрока),
					"getIncidentsList");
				Возврат Ответ;
			ИначеЕсли Настройки.ДатаНачала > Настройки.ДатаОкончания Тогда
				Ошибка404(Ответ, "Дата начала периода больше даты конца периода");
				ЗаписатьЛоги(404,
					СтрШаблон("Дата начала периода больше даты конца периода startDate = %1, endDate = %2",
						Настройки.ДатаНачалаСтрока,
						Настройки.ДатаОкончанияСтрока),
					"getIncidentsList");
				Возврат Ответ;
			КонецЕсли;
		КонецЕсли;
		
		СтатусСтрокой = Настройки.СтатусСтрокой;
		АвторСтрокой = Настройки.АвторСтрокой;
		
		Если СтатусСтрокой = "Открытые" Или СтатусСтрокой = "Закрытые" Тогда
			Статусы = Новый Массив;
			Статусы.Добавить(Справочники.СостоянияИнцидентов.НайтиПоНаименованию("Закрыто"));
			Статусы.Добавить(Справочники.СостоянияИнцидентов.НайтиПоНаименованию("Завершено"));
		ИначеЕсли СтатусСтрокой = "Ожидают ответа" Тогда
			Статусы = Справочники.СостоянияИнцидентов.НайтиПоНаименованию("Ожидание");
		Иначе
			Статусы = "УбратьПараметр";
		КонецЕсли;
		
		Запрос = Новый Запрос;
		Запрос.Текст = ПолучитьТекстЗапросаИнцидентов(Настройки);
		
		Если Статусы = "УбратьПараметр" Тогда
			Запрос.Текст = СтрЗаменить(Запрос.Текст, "ДокИнцидент.Состояние В(&Состояние)", "Истина");
		ИначеЕсли СтатусСтрокой = "Открытые" Тогда
			Запрос.Текст = СтрЗаменить(Запрос.Текст, "ДокИнцидент.Состояние В(&Состояние)", "Не ДокИнцидент.Состояние В(&Состояние)");
			Запрос.УстановитьПараметр("Состояние", Статусы);
		Иначе
			Запрос.УстановитьПараметр("Состояние", Статусы);
		КонецЕсли;
		
		Если АвторСтрокой = Неопределено Тогда
			Запрос.Текст = СтрЗаменить(Запрос.Текст, "ДокИнцидент.КонтактноеЛицо.Наименование = &Автор", "Истина");
		Иначе
			Запрос.УстановитьПараметр("Автор", АвторСтрокой);
		КонецЕсли;
		
		Если Настройки.Режим = "changed" Тогда
			Запрос.УстановитьПараметр("ИзмененоПосле", Настройки.ДатаИзменения);
		Иначе
			Запрос.УстановитьПараметр("ДатаНачала", Настройки.ДатаНачала);
			Запрос.УстановитьПараметр("ДатаОкончания", Настройки.ДатаОкончания);
		КонецЕсли;
		
		Если Настройки.ЕстьГраница Тогда
			Запрос.УстановитьПараметр("ПоследняяДата", Настройки.ПоследняяДата);
			Запрос.УстановитьПараметр("ПоследняяСсылка", Настройки.ПоследняяСсылка);
		КонецЕсли;
		
		РезультатЗапроса = Запрос.Выполнить();
		ВыборкаДетальныеЗаписи = РезультатЗапроса.Выбрать();
		
		СписокКомментируемыхОценок = Новый СписокЗначений;
		СтрокаКомментарииКОценке = ИТ_ГлобальныйМодульСервер.ИТ_глЗначениеПеременной("ИТ_ТребоватьКомментарииКОценке");
		Для К = 1 По 5 Цикл
			Если Сред(СтрокаКомментарииКОценке, К, 1) = "1" Тогда
				СписокКомментируемыхОценок.Добавить(К);
			КонецЕсли;
		КонецЦикла;
		
		Запись = Новый ЗаписьJSON;
		Запись.УстановитьСтроку();
		Запись.ЗаписатьНачалоМассива();
		КоличествоЗаписанных = 0;
		ЕстьСледующаяСтраница = Ложь;
		ПоследняяДата = Неопределено;
		ПоследняяСсылка = Неопределено;
		
		Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
			Если Настройки.ИспользоватьПагинацию И КоличествоЗаписанных >= Настройки.Лимит Тогда
				ЕстьСледующаяСтраница = Истина;
				Прервать;
			КонецЕсли;
			
			Запись.ЗаписатьНачалоОбъекта();
			
			Запись.ЗаписатьИмяСвойства("Metadata");
			Запись.ЗаписатьЗначение("Document incident");
			
			Запись.ЗаписатьИмяСвойства("UID");
			Запись.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи.Ссылка.УникальныйИдентификатор()));
			
			Запись.ЗаписатьИмяСвойства("Number");
			Запись.ЗаписатьЗначение(ВыборкаДетальныеЗаписи.Номер);
			
			Запись.ЗаписатьИмяСвойства("Data");
			Запись.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи.Дата));
			
			Запись.ЗаписатьИмяСвойства("LastChanged");
			Запись.ЗаписатьЗначение(XMLСтрока(ВыборкаДетальныеЗаписи.ДатаПоследнегоИзменения) + "+03:00");
			
			Запись.ЗаписатьИмяСвойства("Author");
			Запись.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи.Инициатор));
			
			Запись.ЗаписатьИмяСвойства("Topic");
			Запись.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи.ТемаОбращения));
			
			Запись.ЗаписатьИмяСвойства("Description");
			Запись.ЗаписатьЗначение(ВыборкаДетальныеЗаписи.Описание);
			
			ЗаписатьСостояние(Запись,
				?(НЕ ВыборкаДетальныеЗаписи.СостояниеНаименование = null, ВыборкаДетальныеЗаписи.СостояниеНаименование, ""),
				?(НЕ ВыборкаДетальныеЗаписи.СостояниеНаименование = null, Строка(ВыборкаДетальныеЗаписи.Состояние.УникальныйИдентификатор()), ""),
				?(НЕ ВыборкаДетальныеЗаписи.СостояниеНаименование = null, ВыборкаДетальныеЗаписи.СостояниеЗакрыт, ЛОЖЬ));
			
			Запись.ЗаписатьИмяСвойства("TheRegulatoryClosureDate");
			Запись.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи.НормативнаяДатаЗакрытия));
			
			Запись.ЗаписатьИмяСвойства("TheActualClosingDate");
			Запись.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи.ФактическаяДатаЗакрытия));
			
			ЗаписатьСоставУслуги(Запись,
				?(НЕ ВыборкаДетальныеЗаписи.КомпонентаУслугиНаименование = null, ВыборкаДетальныеЗаписи.КомпонентаУслугиНаименование, ""),
				?(НЕ ВыборкаДетальныеЗаписи.КомпонентаУслугиНаименование = null, Строка(ВыборкаДетальныеЗаписи.КомпонентаУслуги.УникальныйИдентификатор()), ""),
				?(НЕ ВыборкаДетальныеЗаписи.КомпонентаУслугиНаименование = null, ВыборкаДетальныеЗаписи.УслугаНаименование, ""),
				?(НЕ ВыборкаДетальныеЗаписи.КомпонентаУслугиНаименование = null, Строка(ВыборкаДетальныеЗаписи.Услуга.УникальныйИдентификатор()), ""));
			
			ЗаписатьКатегорию(Запись,
				?(НЕ ВыборкаДетальныеЗаписи.КатегорияНаименование = null, ВыборкаДетальныеЗаписи.КатегорияНаименование, ""),
				?(НЕ ВыборкаДетальныеЗаписи.КатегорияНаименование = null, Строка(ВыборкаДетальныеЗаписи.Категория.УникальныйИдентификатор()), ""));
			
			Запись.ЗаписатьИмяСвойства("ThereAreFiles");
			Запись.ЗаписатьЗначение(ВыборкаДетальныеЗаписи.ЕстьФайлы);
			
			Запись.ЗаписатьИмяСвойства("Rating");
			Запись.ЗаписатьЗначение(Строка(ВыборкаДетальныеЗаписи.Оценка));
			
			Запись.ЗаписатьИмяСвойства("CommentToTheRating");
			Запись.ЗаписатьЗначение(ВыборкаДетальныеЗаписи.КомментарийОценка);
			
			СтруктураСостояний = ИТ_ПравилаПереходаСервер.ПолучитьСтруктуруСостояний(
				ВыборкаДетальныеЗаписи.Тип,
				ВыборкаДетальныеЗаписи.СценарийОбработки,
				"Обращение",
				ВыборкаДетальныеЗаписи.Состояние);
			
			Запись.ЗаписатьИмяСвойства("RequiresConfirmation");
			Запись.ЗаписатьЗначение(СтруктураСостояний.СвойстваТекСостояния.ТребуетПодтверждения);
			
			Запись.ЗаписатьИмяСвойства("RequiresEvaluation");
			Запись.ЗаписатьЗначение(СтруктураСостояний.СвойстваТекСостояния.ТребуетОценку);
			
			Запись.ЗаписатьИмяСвойства("RequiresCommentToRating");
			СтрокаОценок = "";
			Для Каждого ОценкаЭлемент Из СписокКомментируемыхОценок Цикл
				СтрокаОценок = СтрокаОценок + ОценкаЭлемент.Значение;
			КонецЦикла;
			Запись.ЗаписатьЗначение(СтрокаОценок);
			
			Запись.ЗаписатьКонецОбъекта();
			
			КоличествоЗаписанных = КоличествоЗаписанных + 1;
			Если Настройки.Режим = "changed" Тогда
				ПоследняяДата = ВыборкаДетальныеЗаписи.ДатаПоследнегоИзменения;
			Иначе
				ПоследняяДата = ВыборкаДетальныеЗаписи.Дата;
			КонецЕсли;
			ПоследняяСсылка = ВыборкаДетальныеЗаписи.Ссылка;
		КонецЦикла;
		
		Запись.ЗаписатьКонецМассива();
		Ответ.УстановитьТелоИзСтроки(Запись.Закрыть());
		
		Если ЕстьСледующаяСтраница Тогда
			СледующийКурсор = СформироватьКурсорИнцидентов(Настройки, ПоследняяДата, ПоследняяСсылка);
			Ответ.Заголовки.Вставить("X-Next-Cursor", СледующийКурсор);
		КонецЕсли;
	Исключение
		Ошибка = ОписаниеОшибки();
		Ответ = Новый HTTPСервисОтвет(200);
		Ответ.УстановитьТелоИзСтроки(ПолучитьJSONОшибка("Не удалось обработать документ"));
		Возврат Ответ;
	КонецПопытки;
	
	Возврат Ответ;
КонецФункции

// Возвращает параметры режима, фильтров и пагинации.
// Неопределено означает ожидаемую ошибку входных параметров с текстом в ТекстОшибки.
Функция ПолучитьНастройкиЗапросаИнцидентов(ЗапросПарам, ТекстОшибки)
	ТекстОшибки = "";
	ПараметрыЗапроса = ЗапросПарам.ПараметрыЗапроса;
	
	ЕстьКурсор = ЕстьПараметрHTTPЗапроса(ПараметрыЗапроса, "cursor");
	Если ЕстьКурсор Тогда
		СтрокаКурсора = ПараметрыЗапроса.Получить("cursor");
		Если СтрокаКурсора = Неопределено Или СтрокаКурсора = "" Тогда
			ТекстОшибки = "Параметр cursor не должен быть пустым";
			Возврат Неопределено;
		КонецЕсли;
		
		ИменаНесовместимыхПараметров = Новый Массив;
		ИменаНесовместимыхПараметров.Добавить("changedSince");
		ИменаНесовместимыхПараметров.Добавить("limit");
		ИменаНесовместимыхПараметров.Добавить("startDate");
		ИменаНесовместимыхПараметров.Добавить("endDate");
		ИменаНесовместимыхПараметров.Добавить("status");
		ИменаНесовместимыхПараметров.Добавить("author");
		Для Каждого ИмяПараметра Из ИменаНесовместимыхПараметров Цикл
			Если ЕстьПараметрHTTPЗапроса(ПараметрыЗапроса, ИмяПараметра) Тогда
				ТекстОшибки = "Параметр cursor нельзя передавать вместе с другими параметрами отбора";
				Возврат Неопределено;
			КонецЕсли;
		КонецЦикла;
		
		Возврат РазобратьКурсорИнцидентов(СтрокаКурсора, ТекстОшибки);
	КонецЕсли;
	
	ЕстьИзмененоПосле = ЕстьПараметрHTTPЗапроса(ПараметрыЗапроса, "changedSince");
	ЕстьЛимит = ЕстьПараметрHTTPЗапроса(ПараметрыЗапроса, "limit");
	
	Если ЕстьЛимит Тогда
		СтрокаЛимита = ПараметрыЗапроса.Получить("limit");
		Если СтрокаЛимита = Неопределено Или СтрокаЛимита = "" Тогда
			ТекстОшибки = "Параметр limit не должен быть пустым";
			Возврат Неопределено;
		КонецЕсли;
		Если Не ПолучитьЛимитСтраницы(СтрокаЛимита, Лимит) Тогда
			ТекстОшибки = "Параметр limit должен быть целым числом от 1 до 500";
			Возврат Неопределено;
		КонецЕсли;
	Иначе
		Лимит = 500;
	КонецЕсли;
	
	СтатусСтрокой = ПараметрыЗапроса.Получить("status");
	АвторСтрокой = ПараметрыЗапроса.Получить("author");
	
	Если ЕстьИзмененоПосле Тогда
		ИзмененоПослеСтрокой = ПараметрыЗапроса.Получить("changedSince");
		Если ИзмененоПослеСтрокой = Неопределено Или ИзмененоПослеСтрокой = "" Тогда
			ТекстОшибки = "Параметр changedSince не должен быть пустым";
			Возврат Неопределено;
		КонецЕсли;
		
		РезультатРазбораДаты = РазобратьДатуISOСЧасовымПоясом(ИзмененоПослеСтрокой, ТекстОшибки);
		Если РезультатРазбораДаты = Неопределено Тогда
			Возврат Неопределено;
		КонецЕсли;
		
		Настройки = Новый Структура;
		Настройки.Вставить("Режим", "changed");
		Настройки.Вставить("ИспользоватьПагинацию", Истина);
		Настройки.Вставить("Лимит", Лимит);
		Настройки.Вставить("ДатаИзменения", РезультатРазбораДаты.Дата);
		Настройки.Вставить("ДатаНачала", Неопределено);
		Настройки.Вставить("ДатаОкончания", Неопределено);
		Настройки.Вставить("ДатаНачалаСтрока", Неопределено);
		Настройки.Вставить("ДатаОкончанияСтрока", Неопределено);
		Настройки.Вставить("СтатусСтрокой", СтатусСтрокой);
		Настройки.Вставить("АвторСтрокой", АвторСтрокой);
		Настройки.Вставить("ЕстьГраница", Ложь);
		Настройки.Вставить("ПоследняяДата", Неопределено);
		Настройки.Вставить("ПоследняяСсылка", Неопределено);
		Настройки.Вставить("ИзКурсора", Ложь);
		Возврат Настройки;
	КонецЕсли;
	
	Настройки = Новый Структура;
	Настройки.Вставить("Режим", "legacy");
	Настройки.Вставить("ИспользоватьПагинацию", ЕстьЛимит);
	Настройки.Вставить("Лимит", Лимит);
	Настройки.Вставить("ДатаИзменения", Неопределено);
	Настройки.Вставить("ДатаНачала", Неопределено);
	Настройки.Вставить("ДатаОкончания", Неопределено);
	Настройки.Вставить("ДатаНачалаСтрока", ПараметрыЗапроса.Получить("startDate"));
	Настройки.Вставить("ДатаОкончанияСтрока", ПараметрыЗапроса.Получить("endDate"));
	Настройки.Вставить("СтатусСтрокой", СтатусСтрокой);
	Настройки.Вставить("АвторСтрокой", АвторСтрокой);
	Настройки.Вставить("ЕстьГраница", Ложь);
	Настройки.Вставить("ПоследняяДата", Неопределено);
	Настройки.Вставить("ПоследняяСсылка", Неопределено);
	Настройки.Вставить("ИзКурсора", Ложь);
	Возврат Настройки;
КонецФункции

Функция ПолучитьТекстЗапросаИнцидентов(Настройки)
	НачалоЗапроса = "ВЫБРАТЬ РАЗРЕШЕННЫЕ РАЗЛИЧНЫЕ";
	Если Настройки.ИспользоватьПагинацию Тогда
		// Лимит уже проверен как целое число 1..500; в ПЕРВЫЕ допустим только числовой литерал.
		НачалоЗапроса = НачалоЗапроса + " ПЕРВЫЕ " + Формат(Настройки.Лимит + 1, "ЧГ=0;ЧДЦ=0");
	КонецЕсли;
	
	ТекстЗапроса = НачалоЗапроса + "
	|	ДокИнцидент.Ссылка КАК Ссылка,
	|	ДокИнцидент.Номер КАК Номер,
	|	ДокИнцидент.Дата КАК Дата,
	|	ДокИнцидент.ДатаПоследнегоИзменения КАК ДатаПоследнегоИзменения,
	|	ВЫРАЗИТЬ(ДокИнцидент.Описание КАК СТРОКА(900)) КАК Описание,
	|	ДокИнцидент.Состояние.Наименование КАК СостояниеНаименование,
	|	ДокИнцидент.Состояние.Ссылка КАК Состояние,
	|	ДокИнцидент.НормативнаяДатаЗакрытия КАК НормативнаяДатаЗакрытия,
	|	ЕСТЬNULL(ПараметрыУровняСервиса.ФактическаяДатаЗакрытия, ДАТАВРЕМЯ(1, 1, 1)) КАК ФактическаяДатаЗакрытия,
	|	ВЫБОР
	|		КОГДА ПрикрепленныеФайлы.ИспользуемыйДокумент ЕСТЬ NULL
	|			ТОГДА ЛОЖЬ
	|		ИНАЧЕ ИСТИНА
	|	КОНЕЦ КАК ЕстьФайлы,
	|	ДокИнцидент.КомпонентаУслуги КАК КомпонентаУслуги,
	|	ДокИнцидент.КомпонентаУслуги.Наименование КАК КомпонентаУслугиНаименование,
	|	ДокИнцидент.Услуга КАК Услуга,
	|	ДокИнцидент.Услуга.Наименование КАК УслугаНаименование,
	|	ДокИнцидент.Категория КАК Категория,
	|	ДокИнцидент.Категория.Наименование КАК КатегорияНаименование,
	|	ДокИнцидент.Оценка КАК Оценка,
	|	ВЫРАЗИТЬ(ДокИнцидент.КомментарийОценка КАК СТРОКА(900)) КАК КомментарийОценка,
	|	ДокИнцидент.Тип КАК Тип,
	|	ДокИнцидент.СценарийОбработки КАК СценарийОбработки,
	|	ДокИнцидент.ТемаОбращения КАК ТемаОбращения,
	|	ДокИнцидент.Состояние.Закрыт КАК СостояниеЗакрыт,
	|	ДокИнцидент.КонтактноеЛицо КАК Инициатор
	|ИЗ
	|	Документ.Инцидент КАК ДокИнцидент
	|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ПараметрыУровняСервиса КАК ПараметрыУровняСервиса
	|		ПО (ПараметрыУровняСервиса.Инцидент = ДокИнцидент.Ссылка)
	|		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ПрикрепленныеФайлы КАК ПрикрепленныеФайлы
	|		ПО ((ВЫРАЗИТЬ(ПрикрепленныеФайлы.ИспользуемыйДокумент КАК Документ.Инцидент)) = ДокИнцидент.Ссылка)
	|ГДЕ
	|	%ОСНОВНОЙОТБОР%
	|	И ДокИнцидент.Состояние В(&Состояние)
	|	И ДокИнцидент.КонтактноеЛицо.Наименование = &Автор
	|%ГРАНИЦА%
	|УПОРЯДОЧИТЬ ПО
	|	%СОРТИРОВКА%";
	
	Если Настройки.Режим = "changed" Тогда
		ОсновнойОтбор = "ДокИнцидент.ДатаПоследнегоИзменения >= &ИзмененоПосле";
		Сортировка = "ДокИнцидент.ДатаПоследнегоИзменения, ДокИнцидент.Ссылка";
		Если Настройки.ЕстьГраница Тогда
			Граница = "И (ДокИнцидент.ДатаПоследнегоИзменения > &ПоследняяДата
			|ИЛИ ДокИнцидент.ДатаПоследнегоИзменения = &ПоследняяДата
			|	И ДокИнцидент.Ссылка > &ПоследняяСсылка)";
		Иначе
			Граница = "";
		КонецЕсли;
	Иначе
		ОсновнойОтбор = "ДокИнцидент.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания";
		Если Настройки.ИспользоватьПагинацию Тогда
			Сортировка = "ДокИнцидент.Дата УБЫВ, ДокИнцидент.Ссылка";
		Иначе
			Сортировка = "Дата УБЫВ";
		КонецЕсли;
		Если Настройки.ЕстьГраница Тогда
			Граница = "И (ДокИнцидент.Дата < &ПоследняяДата
			|ИЛИ ДокИнцидент.Дата = &ПоследняяДата
			|	И ДокИнцидент.Ссылка > &ПоследняяСсылка)";
		Иначе
			Граница = "";
		КонецЕсли;
	КонецЕсли;
	
	ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "%ОСНОВНОЙОТБОР%", ОсновнойОтбор);
	ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "%ГРАНИЦА%", Граница);
	ТекстЗапроса = СтрЗаменить(ТекстЗапроса, "%СОРТИРОВКА%", Сортировка);
	Возврат ТекстЗапроса;
КонецФункции

Функция СформироватьКурсорИнцидентов(Настройки, ПоследняяДата, ПоследняяСсылка)
	ЗаписьКурсора = Новый ЗаписьJSON;
	ЗаписьКурсора.УстановитьСтроку();
	ЗаписьКурсора.ЗаписатьНачалоОбъекта();
	
	ЗаписьКурсора.ЗаписатьИмяСвойства("v");
	ЗаписьКурсора.ЗаписатьЗначение(1);
	ЗаписьКурсора.ЗаписатьИмяСвойства("mode");
	ЗаписьКурсора.ЗаписатьЗначение(Настройки.Режим);
	
	ЗаписьКурсора.ЗаписатьИмяСвойства("changedSince");
	Если Настройки.Режим = "changed" Тогда
		ЗаписьКурсора.ЗаписатьЗначение(XMLСтрока(Настройки.ДатаИзменения) + "+03:00");
	Иначе
		ЗаписьКурсора.ЗаписатьЗначение(Неопределено);
	КонецЕсли;
	
	ЗаписьКурсора.ЗаписатьИмяСвойства("startDate");
	Если Настройки.Режим = "legacy" Тогда
		ЗаписьКурсора.ЗаписатьЗначение(XMLСтрока(Настройки.ДатаНачала));
	Иначе
		ЗаписьКурсора.ЗаписатьЗначение(Неопределено);
	КонецЕсли;
	
	ЗаписьКурсора.ЗаписатьИмяСвойства("endDate");
	Если Настройки.Режим = "legacy" Тогда
		ЗаписьКурсора.ЗаписатьЗначение(XMLСтрока(Настройки.ДатаОкончания));
	Иначе
		ЗаписьКурсора.ЗаписатьЗначение(Неопределено);
	КонецЕсли;
	
	ЗаписьКурсора.ЗаписатьИмяСвойства("status");
	ЗаписьКурсора.ЗаписатьЗначение(Настройки.СтатусСтрокой);
	ЗаписьКурсора.ЗаписатьИмяСвойства("author");
	ЗаписьКурсора.ЗаписатьЗначение(Настройки.АвторСтрокой);
	ЗаписьКурсора.ЗаписатьИмяСвойства("limit");
	ЗаписьКурсора.ЗаписатьЗначение(Настройки.Лимит);
	ЗаписьКурсора.ЗаписатьИмяСвойства("lastDate");
	ЗаписьКурсора.ЗаписатьЗначение(XMLСтрока(ПоследняяДата));
	ЗаписьКурсора.ЗаписатьИмяСвойства("lastUID");
	ЗаписьКурсора.ЗаписатьЗначение(Строка(ПоследняяСсылка.УникальныйИдентификатор()));
	
	ЗаписьКурсора.ЗаписатьКонецОбъекта();
	JSONКурсора = ЗаписьКурсора.Закрыть();
	ДвоичныеДанныеКурсора = ПолучитьДвоичныеДанныеИзСтроки(JSONКурсора, КодировкаТекста.UTF8);
	Base64 = ПолучитьBase64СтрокуИзДвоичныхДанных(ДвоичныеДанныеКурсора);
	Base64 = СтрЗаменить(Base64, Символы.ВК, "");
	Base64 = СтрЗаменить(Base64, Символы.ПС, "");
	Base64 = СтрЗаменить(Base64, "+", "-");
	Base64 = СтрЗаменить(Base64, "/", "_");
	Пока Прав(Base64, 1) = "=" Цикл
		Base64 = Лев(Base64, СтрДлина(Base64) - 1);
	КонецЦикла;
	Возврат Base64;
КонецФункции

Функция РазобратьКурсорИнцидентов(СтрокаКурсора, ТекстОшибки)
	ТекстОшибки = "Некорректный параметр cursor";
	Если ТипЗнч(СтрокаКурсора) <> Тип("Строка")
		Или СтрокаКурсора = ""
		Или СтрДлина(СтрокаКурсора) > 8192 Тогда
		Возврат Неопределено;
	КонецЕсли;
	
	ДопустимыеСимволы = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
	Для ИндексСимвола = 1 По СтрДлина(СтрокаКурсора) Цикл
		Если СтрНайти(ДопустимыеСимволы, Сред(СтрокаКурсора, ИндексСимвола, 1)) = 0 Тогда
			Возврат Неопределено;
		КонецЕсли;
	КонецЦикла;
	
	Попытка
		Base64 = СтрЗаменить(СтрокаКурсора, "-", "+");
		Base64 = СтрЗаменить(Base64, "_", "/");
		ОстатокДлины = СтрДлина(Base64) - Цел(СтрДлина(Base64) / 4) * 4;
		Если ОстатокДлины = 1 Тогда
			Возврат Неопределено;
		ИначеЕсли ОстатокДлины = 2 Тогда
			Base64 = Base64 + "==";
		ИначеЕсли ОстатокДлины = 3 Тогда
			Base64 = Base64 + "=";
		КонецЕсли;
		
		ДвоичныеДанныеКурсора = Base64Значение(Base64);
		JSONКурсора = ПолучитьСтрокуИзДвоичныхДанных(ДвоичныеДанныеКурсора, КодировкаТекста.UTF8);
		ЧтениеКурсора = Новый ЧтениеJSON;
		ЧтениеКурсора.УстановитьСтроку(JSONКурсора);
		ДанныеКурсора = ПрочитатьJSON(ЧтениеКурсора, Ложь);
	Исключение
		Возврат Неопределено;
	КонецПопытки;
	
	Если ТипЗнч(ДанныеКурсора) <> Тип("Структура") Или ДанныеКурсора.Количество() <> 10 Тогда
		Возврат Неопределено;
	КонецЕсли;
	
	Если Не ДанныеКурсора.Свойство("v", Версия)
		Или Не ДанныеКурсора.Свойство("mode", Режим)
		Или Не ДанныеКурсора.Свойство("changedSince", ИзмененоПослеСтрокой)
		Или Не ДанныеКурсора.Свойство("startDate", ДатаНачалаСтрокой)
		Или Не ДанныеКурсора.Свойство("endDate", ДатаОкончанияСтрокой)
		Или Не ДанныеКурсора.Свойство("status", СтатусСтрокой)
		Или Не ДанныеКурсора.Свойство("author", АвторСтрокой)
		Или Не ДанныеКурсора.Свойство("limit", Лимит)
		Или Не ДанныеКурсора.Свойство("lastDate", ПоследняяДатаСтрокой)
		Или Не ДанныеКурсора.Свойство("lastUID", ПоследнийUID) Тогда
		Возврат Неопределено;
	КонецЕсли;
	
	Если ТипЗнч(Версия) <> Тип("Число") Или Версия <> 1 Тогда
		Возврат Неопределено;
	КонецЕсли;
	Если ТипЗнч(Режим) <> Тип("Строка") Или (Режим <> "changed" И Режим <> "legacy") Тогда
		Возврат Неопределено;
	КонецЕсли;
	Если ТипЗнч(Лимит) <> Тип("Число") Или Лимит <> Цел(Лимит) Или Лимит < 1 Или Лимит > 500 Тогда
		Возврат Неопределено;
	КонецЕсли;
	Если Не ДопустимоеСтроковоеЗначениеКурсора(СтатусСтрокой)
		Или Не ДопустимоеСтроковоеЗначениеКурсора(АвторСтрокой) Тогда
		Возврат Неопределено;
	КонецЕсли;
	
	ПоследняяДата = ПрочитатьКаноническуюДатуКурсора(ПоследняяДатаСтрокой);
	Если ПоследняяДата = Неопределено Тогда
		Возврат Неопределено;
	КонецЕсли;
	
	Если ТипЗнч(ПоследнийUID) <> Тип("Строка") Тогда
		Возврат Неопределено;
	КонецЕсли;
	Попытка
		ИдентификаторПоследнейСсылки = Новый УникальныйИдентификатор(ПоследнийUID);
	Исключение
		Возврат Неопределено;
	КонецПопытки;
	Если НРег(Строка(ИдентификаторПоследнейСсылки)) <> НРег(ПоследнийUID) Тогда
		Возврат Неопределено;
	КонецЕсли;
	
	Если Режим = "changed" Тогда
		Если Не ПустоеЗначениеJSON(ДатаНачалаСтрокой)
			Или Не ПустоеЗначениеJSON(ДатаОкончанияСтрокой)
			Или ТипЗнч(ИзмененоПослеСтрокой) <> Тип("Строка") Тогда
			Возврат Неопределено;
		КонецЕсли;
		РезультатРазбораДаты = РазобратьДатуISOСЧасовымПоясом(ИзмененоПослеСтрокой, ТекстОшибкиДаты);
		Если РезультатРазбораДаты = Неопределено
			Или РезультатРазбораДаты.КаноническоеЗначение <> ИзмененоПослеСтрокой Тогда
			Возврат Неопределено;
		КонецЕсли;
		ДатаИзменения = РезультатРазбораДаты.Дата;
		ДатаНачала = Неопределено;
		ДатаОкончания = Неопределено;
	Иначе
		Если Не ПустоеЗначениеJSON(ИзмененоПослеСтрокой) Тогда
			Возврат Неопределено;
		КонецЕсли;
		ДатаНачала = ПрочитатьКаноническуюДатуКурсора(ДатаНачалаСтрокой);
		ДатаОкончания = ПрочитатьКаноническуюДатуКурсора(ДатаОкончанияСтрокой);
		Если ДатаНачала = Неопределено Или ДатаОкончания = Неопределено Или ДатаНачала > ДатаОкончания Тогда
			Возврат Неопределено;
		КонецЕсли;
		ДатаИзменения = Неопределено;
	КонецЕсли;
	
	Если ПустоеЗначениеJSON(СтатусСтрокой) Тогда
		СтатусСтрокой = Неопределено;
	КонецЕсли;
	Если ПустоеЗначениеJSON(АвторСтрокой) Тогда
		АвторСтрокой = Неопределено;
	КонецЕсли;
	
	Настройки = Новый Структура;
	Настройки.Вставить("Режим", Режим);
	Настройки.Вставить("ИспользоватьПагинацию", Истина);
	Настройки.Вставить("Лимит", Лимит);
	Настройки.Вставить("ДатаИзменения", ДатаИзменения);
	Настройки.Вставить("ДатаНачала", ДатаНачала);
	Настройки.Вставить("ДатаОкончания", ДатаОкончания);
	Настройки.Вставить("ДатаНачалаСтрока", ДатаНачалаСтрокой);
	Настройки.Вставить("ДатаОкончанияСтрока", ДатаОкончанияСтрокой);
	Настройки.Вставить("СтатусСтрокой", СтатусСтрокой);
	Настройки.Вставить("АвторСтрокой", АвторСтрокой);
	Настройки.Вставить("ЕстьГраница", Истина);
	Настройки.Вставить("ПоследняяДата", ПоследняяДата);
	Настройки.Вставить("ПоследняяСсылка", Документы.Инцидент.ПолучитьСсылку(ИдентификаторПоследнейСсылки));
	Настройки.Вставить("ИзКурсора", Истина);
	Возврат Настройки;
КонецФункции

Функция РазобратьДатуISOСЧасовымПоясом(СтрокаДаты, ТекстОшибки)
	ТекстОшибки = "Параметр changedSince должен содержать дату и время ISO с часовым поясом";
	Если ТипЗнч(СтрокаДаты) <> Тип("Строка") Или СтрДлина(СтрокаДаты) < 20 Тогда
		Возврат Неопределено;
	КонецЕсли;
	
	ОсноваДаты = Лев(СтрокаДаты, 19);
	Если Сред(ОсноваДаты, 5, 1) <> "-"
		Или Сред(ОсноваДаты, 8, 1) <> "-"
		Или Сред(ОсноваДаты, 11, 1) <> "T"
		Или Сред(ОсноваДаты, 14, 1) <> ":"
		Или Сред(ОсноваДаты, 17, 1) <> ":"
		Или Не ВсеСимволыЦифры(Лев(ОсноваДаты, 4))
		Или Не ВсеСимволыЦифры(Сред(ОсноваДаты, 6, 2))
		Или Не ВсеСимволыЦифры(Сред(ОсноваДаты, 9, 2))
		Или Не ВсеСимволыЦифры(Сред(ОсноваДаты, 12, 2))
		Или Не ВсеСимволыЦифры(Сред(ОсноваДаты, 15, 2))
		Или Не ВсеСимволыЦифры(Сред(ОсноваДаты, 18, 2)) Тогда
		Возврат Неопределено;
	КонецЕсли;
	
	ПозицияПояса = 20;
	ЕстьНенулеваяДоляСекунды = Ложь;
	Если Сред(СтрокаДаты, ПозицияПояса, 1) = "." Тогда
		ПозицияПояса = ПозицияПояса + 1;
		НачалоДолиСекунды = ПозицияПояса;
		Пока ПозицияПояса <= СтрДлина(СтрокаДаты)
			И ВсеСимволыЦифры(Сред(СтрокаДаты, ПозицияПояса, 1)) Цикл
			Если Сред(СтрокаДаты, ПозицияПояса, 1) <> "0" Тогда
				ЕстьНенулеваяДоляСекунды = Истина;
			КонецЕсли;
			ПозицияПояса = ПозицияПояса + 1;
		КонецЦикла;
		Если ПозицияПояса = НачалоДолиСекунды Тогда
			Возврат Неопределено;
		КонецЕсли;
	КонецЕсли;
	
	ЧасовойПояс = Сред(СтрокаДаты, ПозицияПояса);
	Если ЧасовойПояс = "Z" Тогда
		ИдентификаторЧасовогоПояса = "GMT+0";
	ИначеЕсли СтрДлина(ЧасовойПояс) = 6
		И (Лев(ЧасовойПояс, 1) = "+" Или Лев(ЧасовойПояс, 1) = "-")
		И Сред(ЧасовойПояс, 4, 1) = ":"
		И ВсеСимволыЦифры(Сред(ЧасовойПояс, 2, 2))
		И ВсеСимволыЦифры(Сред(ЧасовойПояс, 5, 2)) Тогда
		ЧасыСмещения = Число(Сред(ЧасовойПояс, 2, 2));
		МинутыСмещения = Число(Сред(ЧасовойПояс, 5, 2));
		Если ЧасыСмещения > 14 Или МинутыСмещения > 59
			Или (ЧасыСмещения = 14 И МинутыСмещения <> 0) Тогда
			Возврат Неопределено;
		КонецЕсли;
		ИдентификаторЧасовогоПояса = "GMT" + ЧасовойПояс;
	Иначе
		Возврат Неопределено;
	КонецЕсли;
	
	Попытка
		ДатаБезЧасовогоПояса = XMLЗначение(Тип("Дата"), ОсноваДаты);
		Если XMLСтрока(ДатаБезЧасовогоПояса) <> ОсноваДаты Тогда
			Возврат Неопределено;
		КонецЕсли;
		ДатаUTC = УниверсальноеВремя(ДатаБезЧасовогоПояса, ИдентификаторЧасовогоПояса);
		ДатаМосква = МестноеВремя(ДатаUTC, "GMT+3");
		Если ЕстьНенулеваяДоляСекунды Тогда
			ДатаМосква = ДатаМосква + 1;
		КонецЕсли;
	Исключение
		Возврат Неопределено;
	КонецПопытки;
	
	Результат = Новый Структура;
	Результат.Вставить("Дата", ДатаМосква);
	Результат.Вставить("КаноническоеЗначение", XMLСтрока(ДатаМосква) + "+03:00");
	Возврат Результат;
КонецФункции

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

Функция ПолучитьЛимитСтраницы(СтрокаЛимита, Лимит)
	Лимит = 0;
	Если ТипЗнч(СтрокаЛимита) <> Тип("Строка")
		Или СтрокаЛимита = ""
		Или Не ВсеСимволыЦифры(СтрокаЛимита) Тогда
		Возврат Ложь;
	КонецЕсли;
	Попытка
		Лимит = Число(СтрокаЛимита);
	Исключение
		Возврат Ложь;
	КонецПопытки;
	Возврат Лимит >= 1 И Лимит <= 500 И Лимит = Цел(Лимит);
КонецФункции

Функция ЕстьПараметрHTTPЗапроса(ПараметрыЗапроса, ИмяПараметра)
	Для Каждого ПараметрЗапроса Из ПараметрыЗапроса Цикл
		Если ПараметрЗапроса.Ключ = ИмяПараметра Тогда
			Возврат Истина;
		КонецЕсли;
	КонецЦикла;
	Возврат Ложь;
КонецФункции

Функция ВсеСимволыЦифры(Значение)
	Если Значение = "" Тогда
		Возврат Ложь;
	КонецЕсли;
	Для ИндексСимвола = 1 По СтрДлина(Значение) Цикл
		Если СтрНайти("0123456789", Сред(Значение, ИндексСимвола, 1)) = 0 Тогда
			Возврат Ложь;
		КонецЕсли;
	КонецЦикла;
	Возврат Истина;
КонецФункции

Функция ДопустимоеСтроковоеЗначениеКурсора(Значение)
	Возврат ПустоеЗначениеJSON(Значение) Или ТипЗнч(Значение) = Тип("Строка");
КонецФункции

Функция ПустоеЗначениеJSON(Значение)
	Возврат Значение = Неопределено Или Значение = Null;
КонецФункции

Функция ПолучитьОтветОшибкиПараметров(ТекстОшибки)
	Ответ = Новый HTTPСервисОтвет(400);
	Ответ.УстановитьТелоИзСтроки(ПолучитьJSONОшибка(ТекстОшибки));
	Возврат Ответ;
КонецФункции