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

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

// Для процедуры ПриДобавленииСерверныхОповещений общего модуля ОбщегоНазначенияПереопределяемый.
//
// Параметры:
//  Имя - Строка - имя оповещения, подходящее для имени свойства структуры.
//
// Возвращаемое значение:
//  Структура:
//   * Имя - Строка - имя оповещения.
//
//   * ИмяМодуляОтправки - Строка - имя серверного общего модуля, например, "СоединенияИБ",
//                    содержащего экспортную процедуру ПриОтправкеСерверногоОповещения.
//                    Вызов процедуры выполняется в привилегированном режиме.
//                    Смотри пример в общем модуле СтандартныеПодсистемыСервер.
//                    Может быть не заполнено. В этом случае вызываться не будет,
//                    например, используется для отправки сообщений об изменении
//                    настроек функциональных опций в момент изменения их констант,
//                    вместо отслеживания изменений в регламентом задании.
//
//   * ИмяМодуляПолучения - Строка - имя клиентского общего модуля, например, "СоединенияИБКлиент",
//                    содержащего экспортную процедуру ПриПолученииСерверногоОповещения.
//                    Смотри пример в общем модуле СтандартныеПодсистемыКлиент.
//
//   * Параметры - Произвольный - любое сериализуемое значение, которое нужно передать
//                    в процедуру ПриОтправкеСерверногоОповещения в качестве параметров.
//                    Должно быть как можно меньшего размера в сериализованном виде.
//                    Например, описание метаданных и расширений сеанса, для вычисления
//                    добавленных и удаленных расширений и исправлений в регламентном
//                    задании при вызове ПриОтправкеСерверногоОповещения, и отправке
//                    оповещения с вычисленными отличиями соответствующим сеансам.
//
//   * ПериодПроверки - Число - количество секунд, через которое требуется
//                         вызывать ПриОтправкеСерверногоОповещения для проверки
//                         состояния сервера и отправки оповещения, если требуется.
//                         Начальное значение 20 минут.
//                         Если указано время меньше 1 мин, тогда будет 1 мин.
//                         Следует учитывать, что в модели сервиса реальный минимальный
//                         период может быть значительно больше и нестабильным: 5-10 минут.
//
//
Функция НовоеСерверноеОповещение(Имя) Экспорт
	
	Результат = Новый Структура;
	Результат.Вставить("Имя", Имя);
	Результат.Вставить("ИмяМодуляОтправки", "");
	Результат.Вставить("ИмяМодуляПолучения", "");
	Результат.Вставить("Параметры", Неопределено);
	Результат.Вставить("ПериодПроверки", 20*60);
	
	Возврат Результат;
	
КонецФункции

// Добавляет серверное оповещение в очередь для доставки на клиент.
// Оповещение доставляется через систему взаимодействия,
// либо забирается в рамках общего серверного вызова.
//
// Параметры:
//  ИмяОповещения - Строка - смотри НовоеСерверноеОповещение.Имя.
//  
//  Результат - Произвольный - произвольное сериализуемое значение,
//             которое будет отправлено в составе оповещения на клиент
//             (должны быть как можно меньшего размера, желательно не более 1 Кб).
//
//  Адресаты - Неопределено - все пользователи (и соответственно все сеансы).
//               Если указано незаполненное соответствие, тогда возврат.
//           - Соответствие из КлючИЗначение:
//              * Ключ - УникальныйИдентификатор - идентификатор пользователя ИБ.
//              * Значение - Массив из см. СерверныеОповещения.КлючСеанса
//
//  ОтправитьСразу - Булево - если Истина, попытаться сразу отправить сообщение
//               через систему взаимодействия перед добавлением в очередь.
//               Отправка сразу не допускается из обработчиков ПриОтправкеСерверногоОповещения.
//               Следует учесть (особенно при вызове в транзакции), что неудачное обращение
//               к системе взаимодействия может занимать (3-5 сек)*2, а удачное не менее (50 мс)*2.
//
Процедура ОтправитьСерверноеОповещение(ИмяОповещения, Результат, Адресаты, ОтправитьСразу = Ложь) Экспорт
	
	ОтправитьСерверноеОповещениеСИдентификаторомГруппы(ИмяОповещения, Результат, Адресаты, ОтправитьСразу);
	
КонецПроцедуры

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

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

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

// Возвращает строковый уникальный ключ сеанса,
// полученный из свойств сеанса НачалоСеанса и НомерСеанса.
//
// Параметры:
//  Сеанс - СеансИнформационнойБазы
//        - Неопределено - использовать текущий сеанс.
//
// Возвращаемое значение:
//  Строка - строка вида "<НачалоСеанса> <НомерСеанса>",
//    например, "2022.03.07 05:01:09 8342".
//
Функция КлючСеанса(Сеанс = Неопределено) Экспорт
	
	Если Сеанс = Неопределено Тогда
		Сеанс = ПолучитьТекущийСеансИнформационнойБазы();
	КонецЕсли;
	
	// АПК:1367-выкл - №763.1.1 Допустимо использовать произвольный формат,
	// так как не выводится пользователю и используется для технологических целей.
	Возврат Формат(Сеанс.НачалоСеанса, "ДФ='yyyy.MM.dd HH:mm:ss'") + " "
		+ Формат(Сеанс.НомерСеанса, "ЧН=0; ЧГ=");
	// АПК:1367-вкл.
	
КонецФункции

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

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

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

////////////////////////////////////////////////////////////////////////////////
// Обработчики событий подсистем конфигурации.

// См. ОбщегоНазначенияПереопределяемый.ПриДобавленииПараметровРаботыКлиентаПриЗапуске.
Процедура ПриДобавленииПараметровРаботыКлиентаПриЗапуске(Параметры) Экспорт
	
	Параметры.Вставить("СерверныеОповещения", ПараметрыСерверныхОповещенийЭтогоСеанса());
	
КонецПроцедуры

// См. ОчередьЗаданийПереопределяемый.ПриОпределенииПсевдонимовОбработчиков
Процедура ПриОпределенииПсевдонимовОбработчиков(СоответствиеИменПсевдонимам) Экспорт
	
	СоответствиеИменПсевдонимам.Вставить(
		Метаданные.РегламентныеЗадания.ОтправкаСерверныхОповещенийКлиентам.ИмяМетода);
	
КонецПроцедуры

// См. ВыгрузкаЗагрузкаДанныхПереопределяемый.ПриЗаполненииТиповИсключаемыхИзВыгрузкиЗагрузки.
Процедура ПриЗаполненииТиповИсключаемыхИзВыгрузкиЗагрузки(Типы) Экспорт
	
	Типы.Добавить(Метаданные.Константы.СостояниеОтправкиСерверныхОповещений);
	Типы.Добавить(Метаданные.РегистрыСведений.ПериодическиеСерверныеОповещения);
	Типы.Добавить(Метаданные.РегистрыСведений.ОтправленныеСерверныеОповещения);
	
КонецПроцедуры

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

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

////////////////////////////////////////////////////////////////////////////////
// Основные процедуры и функции.

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

// Параметры:
//  ИмяОповещения  - см. ОтправитьСерверноеОповещение.ИмяОповещения
//  Результат      - см. ОтправитьСерверноеОповещение.Результат
//  Адресаты       - см. ОтправитьСерверноеОповещение.Адресаты
//  ОтправитьСразу - см. ОтправитьСерверноеОповещение.ОтправитьСразу
//
//  ДополнительныеПараметры - см. ДополнительныеПараметрыОтправки
//
Процедура ОтправитьСерверноеОповещениеСИдентификаторомГруппы(ИмяОповещения, Результат, Адресаты,
			ОтправитьСразу, ДополнительныеПараметрыОтправки = Неопределено) Экспорт
	
	Если Адресаты <> Неопределено И Не ЗначениеЗаполнено(Адресаты) Тогда
		Возврат;
	КонецЕсли;
	
	СодержимоеОповещения = НовоеСодержимоеОповещения();
	СодержимоеОповещения.ИмяОповещения = ИмяОповещения;
	СодержимоеОповещения.Результат     = Результат;
	СодержимоеОповещения.Адресаты      = Адресаты;
	
	Если ДлительныеОперации.ПропуститьОповещение(СодержимоеОповещения) Тогда
		Возврат;
	КонецЕсли;
	
	Если ОтправитьСразу
	   И СерверныеОповещенияСлужебныйПовтИсп.ЭтоСеансОтправкиСерверныхОповещенийКлиентам() Тогда
		
		ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'В процедуре %1 недопустимо указывать значение %2 для параметра %3
			           |при вызове из процедур %4.'"),
			"СерверныеОповещения.ОтправитьСерверноеОповещение",
			"Истина",
			"ОтправитьСразу",
			"ПриОтправкеСерверногоОповещения");
		ВызватьИсключение ТекстОшибки;
	КонецЕсли;
	
	ИдентификаторОповещения = НРег(Новый УникальныйИдентификатор);
	ДатаДобавления = ТекущаяДатаСеанса();
	ДатаДобавленияМиллисекунды = Миллисекунды(ДатаДобавления);
	ДополнительныеПараметры = ?(ДополнительныеПараметрыОтправки = Неопределено,
		ДополнительныеПараметрыОтправки(), ДополнительныеПараметрыОтправки);
	
	ОтсрочкаДоставки = ДополнительныеПараметры.ОтсрочкаДоставки;
	
	Если ДополнительныеПараметры.Заменить Тогда
		УдалитьПоследнееНедоставленноеОповещение(ДополнительныеПараметры.ИдентификаторГруппы,
			ДополнительныеПараметры.ВидОповещенияВГруппе, ОтсрочкаДоставки,
			ДатаДобавления, ДатаДобавленияМиллисекунды);
	КонецЕсли;
	
	Если Не ЗначениеЗаполнено(Адресаты)
	 Или Адресаты.Количество() > 27 Тогда
		ИдентификаторыАдресатов = "";
	Иначе
		Список = Новый Массив;
		Для Каждого КлючИЗначение Из Адресаты Цикл
			Список.Добавить(НРег(КлючИЗначение.Ключ));
		КонецЦикла;
		ИдентификаторыАдресатов = СтрСоединить(Список, Символы.ПС);
	КонецЕсли;
	
	НаборЗаписей = СлужебныйНаборЗаписей(РегистрыСведений.ОтправленныеСерверныеОповещения);
	НаборЗаписей.Отбор.ИдентификаторОповещения.Установить(ИдентификаторОповещения);
	НоваяЗапись = НаборЗаписей.Добавить();
	НоваяЗапись.ИдентификаторОповещения = ИдентификаторОповещения;
	НоваяЗапись.ДатаДобавления = ДатаДобавления;
	НоваяЗапись.ДатаДобавленияМиллисекунды = ДатаДобавленияМиллисекунды;
	НоваяЗапись.Адресаты = ИдентификаторыАдресатов;
	НоваяЗапись.СодержимоеОповещения = Новый ХранилищеЗначения(СодержимоеОповещения, Новый СжатиеДанных(9));
	НоваяЗапись.ИдентификаторГруппы = НРег(ДополнительныеПараметры.ИдентификаторГруппы);
	НоваяЗапись.ВидОповещенияВГруппе = НРег(ДополнительныеПараметры.ВидОповещенияВГруппе);
	
	Если Не ОбщегоНазначения.ДоступноИспользованиеРазделенныхДанных() Тогда
		НаборЗаписей.Отбор.ОбластьДанныхВспомогательныеДанные.Установить(0);
		НоваяЗапись.ОбластьДанныхВспомогательныеДанные = 0;
	КонецЕсли;
	
	УведомленияКлиентаДоступны = СерверныеОповещенияСлужебныйПовтИсп.УведомленияКлиентаДоступны();
	ЗапуститьДоставкуСОтсрочкой = Ложь;
	
	Если ЗначениеЗаполнено(ОтсрочкаДоставки) Тогда
		ЗапуститьДоставкуСОтсрочкой = Истина;
		
		Если ОтсрочкаДоставки > 5 Тогда
			НоваяЗапись.ОтсрочкаЗаписиВСистемуВзаимодействия = 5;
		Иначе
			НоваяЗапись.ОтсрочкаЗаписиВСистемуВзаимодействия = ОтсрочкаДоставки;
		КонецЕсли;
		
	ИначеЕсли ОтправитьСразу
	        И (УведомленияКлиентаДоступны
	           Или ТекущийПользовательЗарегистрированВСистемеВзаимодействия()) Тогда
		
		Если ОтправитьСообщениеСразу(ИдентификаторОповещения, ДатаДобавления, СодержимоеОповещения) Тогда
			НоваяЗапись.ДатаЗаписиВСистемуВзаимодействия = ТекущаяДатаСеанса();
			НоваяЗапись.ДатаЗаписиВСистемуВзаимодействияМиллисекунды = Миллисекунды();
		КонецЕсли;
		
	ИначеЕсли ОтправитьСразу Тогда
		НоваяЗапись.ОтсрочкаЗаписиВСистемуВзаимодействия = 0.1;
	КонецЕсли;
	
	Если ЗначениеЗаполнено(НоваяЗапись.ОтсрочкаЗаписиВСистемуВзаимодействия)
	   И Не УведомленияКлиентаДоступны
	   И Не СистемаВзаимодействийПодключена() Тогда
		
		НоваяЗапись.ОтсрочкаЗаписиВСистемуВзаимодействия = 0;
		ЗапуститьДоставкуСОтсрочкой = Ложь;
	КонецЕсли;
	
	Если Не ДополнительныеПараметры.Заменить
	   И УведомленияКлиентаДоступны
	   И Не ДополнительныеПараметры.ЗаписатьБезусловно
	   И ЗначениеЗаполнено(НоваяЗапись.ДатаЗаписиВСистемуВзаимодействия) Тогда
		
		Возврат;
	КонецЕсли;
	
	НаборЗаписей.Записать();
	
	Если ЗапуститьДоставкуСОтсрочкой Тогда
		Запущено = Ложь;
		ЗапуститьДоставкуСерверныхОповещенийСОтсрочкой(Запущено);
		Если Запущено И ЗначениеЗаполнено(ДополнительныеПараметры.СобытиеЖурналаПриОтсрочкеДоставки) Тогда
			Попытка
				ВызватьИсключение НСтр("ru = 'Стек вызовов:'");
			Исключение
				СтекВызовов = ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
			КонецПопытки;
			Комментарий = ДополнительныеПараметры.КомментарийЖурналаПриОтсрочкеДоставки;
			Комментарий = Комментарий + ?(ЗначениеЗаполнено(Комментарий), Символы.ПС, "") + СтекВызовов;
			ЗаписьЖурналаРегистрации(ДополнительныеПараметры.СобытиеЖурналаПриОтсрочкеДоставки,
				УровеньЖурналаРегистрации.Информация,,, Комментарий);
		КонецЕсли;
	КонецЕсли;
	
КонецПроцедуры

// Для процедуры ОтправитьСерверноеОповещениеСИдентификаторомГруппы.
Процедура УдалитьПоследнееНедоставленноеОповещение(ИдентификаторГруппы, ВидОповещенияВГруппе,
			ОтсрочкаДоставки, ДатаДобавления, ДатаДобавленияМиллисекунды)
	
	Запрос = Новый Запрос;
	Запрос.УстановитьПараметр("ИдентификаторГруппы",  НРег(ИдентификаторГруппы));
	Запрос.УстановитьПараметр("ВидОповещенияВГруппе", НРег(ВидОповещенияВГруппе));
	Запрос.Текст =
	"ВЫБРАТЬ ПЕРВЫЕ 1
	|	ОтправленныеСерверныеОповещения.ИдентификаторОповещения КАК ИдентификаторОповещения,
	|	ОтправленныеСерверныеОповещения.ДатаДобавления КАК ДатаДобавления,
	|	ОтправленныеСерверныеОповещения.ДатаДобавленияМиллисекунды КАК ДатаДобавленияМиллисекунды,
	|	ОтправленныеСерверныеОповещения.ОтсрочкаЗаписиВСистемуВзаимодействия КАК ОтсрочкаЗаписиВСистемуВзаимодействия,
	|	ОтправленныеСерверныеОповещения.ДатаЗаписиВСистемуВзаимодействия КАК ДатаЗаписиВСистемуВзаимодействия,
	|	ОтправленныеСерверныеОповещения.ДатаЗаписиВСистемуВзаимодействияМиллисекунды КАК ДатаЗаписиВСистемуВзаимодействияМиллисекунды
	|ИЗ
	|	РегистрСведений.ОтправленныеСерверныеОповещения КАК ОтправленныеСерверныеОповещения
	|ГДЕ
	|	ОтправленныеСерверныеОповещения.ИдентификаторГруппы = &ИдентификаторГруппы
	|	И ОтправленныеСерверныеОповещения.ВидОповещенияВГруппе = &ВидОповещенияВГруппе
	|
	|УПОРЯДОЧИТЬ ПО
	|	ОтправленныеСерверныеОповещения.ДатаДобавления УБЫВ,
	|	ОтправленныеСерверныеОповещения.ДатаДобавленияМиллисекунды УБЫВ";
	
	Выборка = Запрос.Выполнить().Выбрать();
	Если Не Выборка.Следующий() Тогда
		ОтсрочкаДоставки = 0;
		Возврат;
	КонецЕсли;
	
	Если ЗначениеЗаполнено(Выборка.ДатаЗаписиВСистемуВзаимодействия) Тогда
		Прошло = ДатаДобавления - Выборка.ДатаЗаписиВСистемуВзаимодействия
			+ (ДатаДобавленияМиллисекунды - Выборка.ДатаЗаписиВСистемуВзаимодействияМиллисекунды) / 1000;
	Иначе
		Прошло = ДатаДобавления - Выборка.ДатаДобавления
			+ (ДатаДобавленияМиллисекунды - Выборка.ДатаДобавленияМиллисекунды) / 1000;
	КонецЕсли;
	Прошло = Окр(Прошло, 1, РежимОкругления.Окр15как20);
	ОтсрочкаДоставки = ОтсрочкаДоставки - Прошло;
	
	Если ОтсрочкаДоставки < 0 Тогда
		ОтсрочкаДоставки = 0;
	КонецЕсли;
	
	Если ЗначениеЗаполнено(Выборка.ДатаЗаписиВСистемуВзаимодействия) Тогда
		Возврат;
	КонецЕсли;
	
	НаборЗаписей = СлужебныйНаборЗаписей(РегистрыСведений.ОтправленныеСерверныеОповещения);
	НаборЗаписей.Отбор.ИдентификаторОповещения.Установить(Выборка.ИдентификаторОповещения);
	НаборЗаписей.Записать();
	
КонецПроцедуры

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

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

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

// Возвращаемое значение:
//  Структура:
//   * ИдентификаторОповещения - Строка
//   * Содержимое - см. НовоеСодержимоеОповещения
//   * ДатаДобавления - Дата
//   * ДатаДобавленияМиллисекунды - Число
//
Функция НовоеСерверноеОповещенияДляКлиента()
	
	Результат = Новый Структура;
	Результат.Вставить("ИдентификаторОповещения", "");
	Результат.Вставить("Содержимое", Неопределено);
	Результат.Вставить("ДатаДобавления", '00010101');
	Результат.Вставить("ДатаДобавленияМиллисекунды", 0);
	
	Возврат Результат;
	
КонецФункции

// Параметры:
//   Хранилище - Неопределено
//             - ХранилищеЗначения
//
// Возвращаемое значение:
//  Структура:
//   * ИмяОповещения - см. ОтправитьСерверноеОповещение.ИмяОповещения
//   * Результат     - см. ОтправитьСерверноеОповещение.Результат
//   * Адресаты      - см. ОтправитьСерверноеОповещение.Адресаты
//
Функция НовоеСодержимоеОповещения(Хранилище = Неопределено)
	
	Содержимое = Новый Структура;
	Содержимое.Вставить("ИмяОповещения", "");
	Содержимое.Вставить("Результат");
	Содержимое.Вставить("Адресаты", Новый Соответствие);
	
	Если ТипЗнч(Хранилище) <> Тип("ХранилищеЗначения") Тогда
		Возврат Содержимое;
	КонецЕсли;
	
	ТекущееСодержимое = Хранилище.Получить();
	Если ТипЗнч(ТекущееСодержимое) = Тип("Структура") Тогда
		ЗаполнитьЗначенияСвойств(Содержимое, ТекущееСодержимое);
	КонецЕсли;
	
	Возврат Содержимое;
	
КонецФункции

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

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

Процедура ДобавитьОсновнойПоказатель(Показатели, МоментНачала, ИмяПроцедуры,
			ДополнительныеРезультаты = Неопределено, Общий = Ложь)
	
	Если Показатели = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - МоментНачала;
	Если Не Общий И Не ЗначениеЗаполнено(Длительность) Тогда
		Возврат;
	КонецЕсли;
	
	Текст = Формат(Длительность / 1000, "ЧЦ=6; ЧДЦ=3; ЧН=000,000; ЧВН=") + " " + ИмяПроцедуры;
	
	Если Общий Тогда
		Показатели.Вставить(0, "    " + Текст);
		ДополнительныеРезультаты.Удалить(ИмяПараметраВложенныхПоказателей());
	Иначе
		Показатели.Добавить("      " + Текст);
	КонецЕсли;
	
	Если ДополнительныеРезультаты = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	ВложенныеПоказатели = ДополнительныеРезультаты.Получить(ИмяПараметраВложенныхПоказателей());
	Если ТипЗнч(ВложенныеПоказатели) <> Тип("Массив") Тогда
		Возврат;
	КонецЕсли;
	
	Для Каждого ВложенныйПоказатель Из ВложенныеПоказатели Цикл
		Показатели.Добавить("  " + ВложенныйПоказатель);
	КонецЦикла;
	ВложенныеПоказатели.Очистить();
	
КонецПроцедуры

Функция ИмяПараметраВложенныхПоказателей()
	Возврат "СтандартныеПодсистемы.БазоваяФункциональность.СерверныеОповещения.Показатели";
КонецФункции

Функция РегистрироватьПоказателиСерверныхОповещений()
	
	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);
	
	Возврат Константы.РегистрироватьПоказателиСерверныхОповещений.Получить();
	
КонецФункции

Процедура ПриИзмененииКонстантыРегистрироватьПоказателиСерверныхОповещений(Регистрировать) Экспорт
	
	ОтправитьСерверноеОповещение(
		"СтандартныеПодсистемы.БазоваяФункциональность.СерверныеОповещения.РегистрироватьПоказатели",
		Регистрировать, Неопределено, Истина);
	
КонецПроцедуры

Процедура ПодготовитьСерверныеОповещения(СостояниеОтправки, МинимальныйПериодПоПользователям = Неопределено)
	
	УдалитьУстаревшиеОповещения();
	
	СостояниеОтправки.ДатаПоследнейПроверки = ТекущаяДатаСеанса();
	СостояниеОтправки.МинимальныйПериодПроверки = 60*20;
	
	АктивныеСеансыПоКлючам = Новый Соответствие;
	Сеансы = ПолучитьСеансыИнформационнойБазы();
	Для Каждого Сеанс Из Сеансы Цикл
		АктивныеСеансыПоКлючам.Вставить(КлючСеанса(Сеанс), Сеанс);
	КонецЦикла;
	
	ПериодическиеОповещения = ПериодическиеСерверныеОповещения(МинимальныйПериодПоПользователям,
		АктивныеСеансыПоКлючам);
	
	Для Каждого КлючИЗначение Из ПериодическиеОповещения Цикл
		ИмяОповещения = КлючИЗначение.Ключ;
		Оповещение    = КлючИЗначение.Значение;
		Если СостояниеОтправки.МинимальныйПериодПроверки > Оповещение.ПериодПроверки Тогда
			СостояниеОтправки.МинимальныйПериодПроверки = Оповещение.ПериодПроверки;
		КонецЕсли;
		ДатаПроверки = СостояниеОтправки.ДатыПроверкиПоИменамОповещений.Получить(ИмяОповещения);
		ТекущаяДатаСеанса = ТекущаяДатаСеанса();
		Если ТипЗнч(ДатаПроверки) = Тип("Дата")
		   И ТекущаяДатаСеанса < ДатаПроверки + Оповещение.ПериодПроверки Тогда
			Продолжить;
		КонецЕсли;
		СостояниеОтправки.ДатыПроверкиПоИменамОповещений.Вставить(ИмяОповещения, ТекущаяДатаСеанса);
		Если Метаданные.ОбщиеМодули.Найти(Оповещение.ИмяМодуляОтправки) = Неопределено Тогда
			Продолжить;
		КонецЕсли;
		МодульОтправки = ОбщегоНазначения.ОбщийМодуль(Оповещение.ИмяМодуляОтправки);
		Если МодульОтправки = ПользователиСлужебный Тогда
			ПараметрыПроцедуры = Новый Структура;
			ПараметрыПроцедуры.Вставить("ВариантыПараметров", КлючИЗначение.Значение.ВариантыПараметров);
			ПараметрыПроцедуры.Вставить("АктивныеСеансыПоКлючам", АктивныеСеансыПоКлючам);
		Иначе
			ПараметрыПроцедуры = КлючИЗначение.Значение.ВариантыПараметров;
		КонецЕсли;
		Попытка
			МодульОтправки.ПриОтправкеСерверногоОповещения(ИмяОповещения, ПараметрыПроцедуры);
		Исключение
			ИнформацияОбОшибке = ИнформацияОбОшибке();
			ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
				НСтр("ru = 'Процедура ""%1"" не выполнена по причине:
				           |%2'"),
				Оповещение.ИмяМодуляОтправки + ".ПриОтправкеСерверногоОповещения",
				ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке));
			ЗаписьЖурналаРегистрации(
				НСтр("ru = 'Серверные оповещения.Ошибка работы фонового задания'",
					ОбщегоНазначения.КодОсновногоЯзыка()),
				УровеньЖурналаРегистрации.Ошибка,,, ТекстОшибки);
		КонецПопытки;
	КонецЦикла;
	
	УточнитьМинимальныйПериодПроверки(СостояниеОтправки.МинимальныйПериодПроверки);
	
	РегистрироватьПоказатели = РегистрироватьПоказателиСерверныхОповещений();
	Если СостояниеОтправки.РегистрироватьПоказатели <> РегистрироватьПоказатели Тогда
		ОтправитьСерверноеОповещение(
			"СтандартныеПодсистемы.БазоваяФункциональность.СерверныеОповещения.РегистрироватьПоказатели",
			РегистрироватьПоказатели, Неопределено);
		СостояниеОтправки.РегистрироватьПоказатели = РегистрироватьПоказатели;
	КонецЕсли;
	
	СистемаВзаимодействийПодключена = СистемаВзаимодействийПодключена();
	Если СостояниеОтправки.СистемаВзаимодействийПодключена <> СистемаВзаимодействийПодключена Тогда
		ОтправитьСерверноеОповещение(
			"СтандартныеПодсистемы.БазоваяФункциональность.СерверныеОповещения.СистемаВзаимодействийПодключена",
			СистемаВзаимодействийПодключена, Неопределено);
		СостояниеОтправки.СистемаВзаимодействийПодключена = СистемаВзаимодействийПодключена;
	КонецЕсли;
	
	ОбновитьСостояниеОтправки(СостояниеОтправки,
		"ДатаПоследнейПроверки, ДатыПроверкиПоИменамОповещений, МинимальныйПериодПроверки,
		|СистемаВзаимодействийПодключена, РегистрироватьПоказатели");
	
КонецПроцедуры

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

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

// Параметры:
//  Оповещение - см. НовоеСерверноеОповещение
//
// Возвращаемое значение:
//  Структура:
//   * ИмяМодуляОтправки - Строка - смотри НовоеСерверноеОповещение.ИмяМодуляОтправки.
//   * Параметры         - Произвольный - смотри НовоеСерверноеОповещение.Параметры
//                           Если равен Неопределено, тогда свойства нет.
//
//   * ПериодПроверки    - Число - смотри НовоеСерверноеОповещение.ПериодПроверки.
//                           Если указано 20*60, тогда свойства нет.
//
Функция СохраняемоеСерверноеОповещение(Оповещение)
	
	Результат = Новый Структура;
	Результат.Вставить("ИмяМодуляОтправки", Оповещение.ИмяМодуляОтправки);
	
	Если Оповещение.Параметры <> Неопределено Тогда
		Результат.Вставить("Параметры", Оповещение.Параметры);
	КонецЕсли;
	
	Если Оповещение.ПериодПроверки <> 20*60 Тогда
		Результат.Вставить("ПериодПроверки", Оповещение.ПериодПроверки);
	КонецЕсли;
	
	Возврат Результат;
	
КонецФункции

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

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

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

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

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

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

Функция МаксимальныйПериодПовторенияДоставки()
	
	Возврат 120;
	
КонецФункции

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

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

Функция ВсеСеансыСпят()
	Возврат Ложь;
КонецФункции

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

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

// Параметры:
//  ХранилищеОповещений - ХранилищеЗначения
//
// Возвращаемое значение:
//  Соответствие из КлючИЗначение:
//   * Ключ     - Строка - смотри СерверныеОповещения.НовоеСерверноеОповещение.Имя
//   * Значение - см. СерверныеОповещения.НовоеСерверноеОповещение
//
Функция ПериодическиеСерверныеОповещенияСеанса(ХранилищеОповещений)
	
	Если ТипЗнч(ХранилищеОповещений) <> Тип("ХранилищеЗначения") Тогда
		Возврат Новый Соответствие;
	КонецЕсли;
	
	Оповещения = ХранилищеОповещений.Получить();
	Если ТипЗнч(Оповещения) <> Тип("Соответствие") Тогда
		Возврат Новый Соответствие;
	КонецЕсли;
	
	Возврат Оповещения;
	
КонецФункции

// Возвращаемое значение:
//  Структура:
//   * ДатаПоследнейПроверки - Дата
//   * МинимальныйПериодПроверки - Число
//   * ДатыПроверкиПоИменамОповещений - Соответствие из КлючИЗначение:
//       ** Ключ     - Строка - смотри НовоеСерверноеОповещение.Имя
//       ** Значение - Дата
//   * ДатаПоследнегоОповещения - Дата - дата последнего из отправляемых оповещений.
//   * ДатаОповещенияСОшибкой   - Дата - дата оповещения, на котором возникла ошибка.
//   * ДатыОповещенийСОшибкамиПоПользователям - Соответствие из КлючИЗначение:
//       ** Ключ     - УникальныйИдентификатор - идентификатор пользователя ИБ.
//       ** Значение - Дата - дата оповещения, на котором возникла ошибка.
//   * ДатыУспешнойОтправкиОповещенийПоПользователям - Соответствие из КлючИЗначение:
//       ** Ключ     - УникальныйИдентификатор - идентификатор пользователя ИБ.
//       ** Значение - Дата.
//   * ИдентификаторФоновогоЗадания - УникальныйИдентификатор
//   * ДатаПоследнейОчисткиСообщений - Дата
//   * СистемаВзаимодействийПодключена - Булево
//   * РегистрироватьПоказатели - Булево
//
Функция СостояниеОтправкиСерверныхОповещений()
	
	Состояние = Новый Структура;
	Состояние.Вставить("ДатаПоследнейПроверки", '00010101');
	Состояние.Вставить("МинимальныйПериодПроверки", 0);
	Состояние.Вставить("ДатыПроверкиПоИменамОповещений", Новый Соответствие);
	Состояние.Вставить("ДатаПоследнегоОповещения", '00010101');
	Состояние.Вставить("ДатаОповещенияСОшибкой", '00010101');
	Состояние.Вставить("ДатыОповещенийСОшибкамиПоПользователям", Новый Соответствие);
	Состояние.Вставить("ДатыУспешнойОтправкиОповещенийПоПользователям", Новый Соответствие);
	Состояние.Вставить("ИдентификаторФоновогоЗадания",
		ОбщегоНазначенияКлиентСервер.ПустойУникальныйИдентификатор());
	Состояние.Вставить("ДатаПоследнейОчисткиСообщений", '00010101');
	Состояние.Вставить("СистемаВзаимодействийПодключена", Ложь);
	Состояние.Вставить("РегистрироватьПоказатели", Ложь);
	
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	СостояниеОтправкиСерверныхОповещений.Значение КАК Значение
	|ИЗ
	|	Константа.СостояниеОтправкиСерверныхОповещений КАК СостояниеОтправкиСерверныхОповещений";
	Выборка = Запрос.Выполнить().Выбрать();
	Значение = ?(Выборка.Следующий(), Выборка.Значение, Неопределено);
	
	Если ТипЗнч(Значение) = Тип("ХранилищеЗначения") Тогда
		ТекущееСостояние = Значение.Получить();
		Если ТипЗнч(ТекущееСостояние) = Тип("Структура") Тогда
			Для Каждого КлючИЗначение Из ТекущееСостояние Цикл
				Если Состояние.Свойство(КлючИЗначение.Ключ)
				   И ТипЗнч(Состояние[КлючИЗначение.Ключ]) = ТипЗнч(КлючИЗначение.Значение) Тогда
					Состояние[КлючИЗначение.Ключ] = КлючИЗначение.Значение;
				КонецЕсли;
			КонецЦикла;
		КонецЕсли;
	КонецЕсли;
	
	Возврат Состояние;
	
КонецФункции

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

// Возвращаемое значение:
//   см. СостояниеОтправкиСерверныхОповещений
//
Функция СостояниеОтправкиПриЗапускеФоновогоЗадания()
	
	ТекущийСеанс = ПолучитьТекущийСеансИнформационнойБазы();
	Если ТекущийСеанс.ИмяПриложения <> "BackgroundJob" Тогда
		Возврат СостояниеОтправкиСерверныхОповещений();
	КонецЕсли;
	
	ТекущееФоновоеЗадание = ТекущийСеанс.ПолучитьФоновоеЗадание();
	Если ТекущееФоновоеЗадание = Неопределено Тогда
		Возврат СостояниеОтправкиСерверныхОповещений();
	КонецЕсли;
	
	СостояниеОтправки = Неопределено;
	
	Блокировка = Новый БлокировкаДанных;
	Блокировка.Добавить("Константа.СостояниеОтправкиСерверныхОповещений");
	НачатьТранзакцию();
	Попытка
		Блокировка.Заблокировать();
		СостояниеОтправки = СостояниеОтправкиСерверныхОповещений();
		Если ОтправкаСерверныхОповещенийКлиентамУжеВыполняется(СостояниеОтправки) Тогда
			СостояниеОтправки = Неопределено;
		Иначе
			СостояниеОтправки.ИдентификаторФоновогоЗадания = ТекущееФоновоеЗадание.УникальныйИдентификатор;
			МенеджерЗначения = СлужебныйМенеджерЗначения(Константы.СостояниеОтправкиСерверныхОповещений);
			МенеджерЗначения.Значение = Новый ХранилищеЗначения(СостояниеОтправки, Новый СжатиеДанных(9));
			МенеджерЗначения.Записать();
		КонецЕсли;
		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ИнформацияОбОшибке = ИнформацияОбОшибке();
		СостояниеОтправки = Неопределено;
		ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Не удалось установить константу %1 по причине:
			           |%2'"),
			"СостояниеОтправкиСерверныхОповещений",
			ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке));
		ЗаписьЖурналаРегистрации(
			НСтр("ru = 'Серверные оповещения.Ошибка запуска фонового задания'",
				ОбщегоНазначения.КодОсновногоЯзыка()),
			УровеньЖурналаРегистрации.Ошибка,,, ТекстОшибки);
	КонецПопытки;
	
	Возврат СостояниеОтправки;
	
КонецФункции

Функция ОтправкаСерверныхОповещенийКлиентамУжеВыполняется(СостояниеОтправки)
	
	ИсполняющееФоновоеЗадание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(
		СостояниеОтправки.ИдентификаторФоновогоЗадания);
	
	Возврат ИсполняющееФоновоеЗадание <> Неопределено
	      И ИсполняющееФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.Активно;
	
КонецФункции

Процедура УдалитьУстаревшиеОповещения()
	
	Запрос = Новый Запрос;
	Запрос.УстановитьПараметр("ГраницаУстаревания", ТекущаяДатаСеанса() - 60*60);
	Запрос.Текст =
	"ВЫБРАТЬ
	|	ОтправленныеСерверныеОповещения.ИдентификаторОповещения КАК ИдентификаторОповещения
	|ИЗ
	|	РегистрСведений.ОтправленныеСерверныеОповещения КАК ОтправленныеСерверныеОповещения
	|ГДЕ
	|	ОтправленныеСерверныеОповещения.ДатаДобавления < &ГраницаУстаревания
	|	И &Отбор
	|
	|УПОРЯДОЧИТЬ ПО
	|	ОтправленныеСерверныеОповещения.ДатаДобавления,
	|	ОтправленныеСерверныеОповещения.ДатаДобавленияМиллисекунды";
	
	ДоступноИспользованиеРазделенныхДанных = ОбщегоНазначения.ДоступноИспользованиеРазделенныхДанных();
	
	Запрос.Текст = СтрЗаменить(Запрос.Текст, "&Отбор",
		?(ДоступноИспользованиеРазделенныхДанных, "ИСТИНА",
			"ОтправленныеСерверныеОповещения.ОбластьДанныхВспомогательныеДанные = 0"));
	
	ПустойНаборЗаписей = СлужебныйНаборЗаписей(РегистрыСведений.ОтправленныеСерверныеОповещения);
	Если Не ДоступноИспользованиеРазделенныхДанных Тогда
		ПустойНаборЗаписей.Отбор.ОбластьДанныхВспомогательныеДанные.Установить(0);
	КонецЕсли;
	
	Выборка = Запрос.Выполнить().Выбрать();
	Пока Выборка.Следующий() Цикл
		ПустойНаборЗаписей.Отбор.ИдентификаторОповещения.Установить(Выборка.ИдентификаторОповещения);
		ПустойНаборЗаписей.Записать();
	КонецЦикла;
	
КонецПроцедуры

// Для процедуры ОтправитьСерверноеОповещениеСИдентификаторомГруппы.
Процедура ЗапуститьДоставкуСерверныхОповещенийСОтсрочкой(Запущено = Ложь)
	
	Если Не СерверныеОповещенияСлужебныйПовтИсп.УведомленияКлиентаДоступны()
	   И Не ТекущийПользовательЗарегистрированВСистемеВзаимодействия()
	 Или ОбщегоНазначения.ИнформационнаяБазаФайловая() // Запуск не имеет смысла, так как новое задание будет ждать задание длительной операции.
	 Или МонопольныйРежим() // Запуск недопустим, так изменения в сеансе будут заблокированы.
	 Или ОбновлениеИнформационнойБазы.НеобходимоОбновлениеИнформационнойБазы()
	 Или ДоставкаСерверныхОповещенийСОтсрочкойУжеВыполняется() Тогда
		Возврат;
	КонецЕсли;
	
	ТекущийСеанс = ПолучитьТекущийСеансИнформационнойБазы();
	НаименованиеЗадания =
		НСтр("ru = 'Автозапуск'", ОбщегоНазначения.КодОсновногоЯзыка()) + ": "
		+ НСтр("ru = 'Доставка серверных оповещений с отсрочкой'", ОбщегоНазначения.КодОсновногоЯзыка()) + " ("
		+ СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'из сеанса %1 от %2'", ОбщегоНазначения.КодОсновногоЯзыка()),
			Формат(ТекущийСеанс.НомерСеанса, "ЧГ="),
			Формат(ТекущийСеанс.НачалоСеанса, "ДЛФ=DT")) + ")";
	
	ФоновыеЗадания.Выполнить(ИмяМетодаЗаданияДоставкиСерверныхОповещенийСОтсрочкой(),,, НаименованиеЗадания);
	
	Запущено = Истина;
	
КонецПроцедуры

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

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

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

// Для процедуры ЗапуститьДоставкуСерверныхОповещенийСОтсрочкой
// и функции ДоставкаСерверныхОповещенийСОтсрочкойУжеВыполняется.
//
Функция ИмяМетодаЗаданияДоставкиСерверныхОповещенийСОтсрочкой()
	
	Возврат "СерверныеОповещения.ДоставкаСерверныхОповещенийСОтсрочкой";
	
КонецФункции

// Для процедуры ДоставкаСерверныхОповещенийСОтсрочкой.
Функция НедоставленныеОповещенияСОтсрочкой()
	
	СостояниеОтправки = СостояниеОтправкиСерверныхОповещений();
	ДатаПоследнегоОповещения = ?(ЗначениеЗаполнено(СостояниеОтправки.ДатаОповещенияСОшибкой),
		СостояниеОтправки.ДатаОповещенияСОшибкой, СостояниеОтправки.ДатаПоследнегоОповещения);
	
	Запрос = Новый Запрос;
	Запрос.УстановитьПараметр("ДатаПоследнегоОповещения", ДатаПоследнегоОповещения);
	Запрос.УстановитьПараметр("ПустаяДата", '00010101');
	Запрос.Текст =
	"ВЫБРАТЬ
	|	ОтправленныеСерверныеОповещения.ИдентификаторОповещения КАК ИдентификаторОповещения,
	|	ОтправленныеСерверныеОповещения.СодержимоеОповещения КАК СодержимоеОповещения,
	|	ОтправленныеСерверныеОповещения.ДатаДобавления КАК ДатаДобавления,
	|	ОтправленныеСерверныеОповещения.ОтсрочкаЗаписиВСистемуВзаимодействия КАК ОтсрочкаЗаписиВСистемуВзаимодействия
	|ИЗ
	|	РегистрСведений.ОтправленныеСерверныеОповещения КАК ОтправленныеСерверныеОповещения
	|ГДЕ
	|	ОтправленныеСерверныеОповещения.ДатаДобавления >= &ДатаПоследнегоОповещения
	|	И ОтправленныеСерверныеОповещения.ДатаЗаписиВСистемуВзаимодействия = &ПустаяДата
	|	И ОтправленныеСерверныеОповещения.ОтсрочкаЗаписиВСистемуВзаимодействия > 0
	|
	|УПОРЯДОЧИТЬ ПО
	|	ОтправленныеСерверныеОповещения.ДатаДобавления,
	|	ОтправленныеСерверныеОповещения.ДатаДобавленияМиллисекунды";
	
	Возврат Запрос.Выполнить().Выбрать();
	
КонецФункции

// Возвращаемое значение:
//  Структура:
//   * КлючСеанса - см. КлючСеанса
//   * ИдентификаторПользователяИБ - УникальныйИдентификатор - идентификатор текущего пользователя.
//   * ДатаПоследнегоОповещения - Дата
//   * Оповещения - см. ОбщегоНазначенияПереопределяемый.ПриДобавленииСерверныхОповещений.Оповещения
//   * МинимальныйПериод - Число - число секунд.
//   * СистемаВзаимодействийПодключена - Булево
//   * ИдентификаторЛичногоОбсуждения - Неопределено - обсуждение недоступно.
//                                    - ИдентификаторОбсужденияСистемыВзаимодействия - идентификатор
//                                        обсуждения "СерверныеОповещения <Идентификатор пользователя ИБ>".
//   * ИдентификаторОбщегоОбсуждения - Неопределено - обсуждение недоступно.
//                                   - ИдентификаторОбсужденияСистемыВзаимодействия - идентификатор
//                                        обсуждения "СерверныеОповещения".
//   * СеансАдминистратораСервиса - Булево
//
Функция ПараметрыСерверныхОповещенийЭтогоСеанса() Экспорт
	
	Оповещения = ДобавленныеОповещенияСеанса();
	
	Параметры = Новый Структура;
	Параметры.Вставить("КлючСеанса", КлючСеанса());
	Параметры.Вставить("ИдентификаторПользователяИБ",
		ПользователиИнформационнойБазы.ТекущийПользователь().УникальныйИдентификатор);
	Параметры.Вставить("ДатаПоследнегоОповещения", НачалоТекущегоСеансаВЧасовомПоясеТекущейДатыСеанса());
	Параметры.Вставить("Оповещения", Оповещения);
	Параметры.Вставить("МинимальныйПериод", 20*60);
	Параметры.Вставить("СистемаВзаимодействийПодключена", Ложь);
	Параметры.Вставить("ИдентификаторЛичногоОбсуждения", Неопределено);
	Параметры.Вставить("ИдентификаторОбщегоОбсуждения", Неопределено);
	Параметры.Вставить("СеансАдминистратораСервиса", СеансАдминистратораСервиса());
	Параметры.Вставить("РегистрироватьПоказатели", РегистрироватьПоказателиСерверныхОповещений());
	Параметры.Вставить("МинимальныйИнтервалПериодическойОтправкиДанных",
		МинимальныйИнтервалПериодическойОтправкиДанных());
	
	ПериодическиеОповещения = Новый Соответствие;
	Для Каждого КлючИЗначение Из Оповещения Цикл
		Оповещение = КлючИЗначение.Значение;
		Если Не ЗначениеЗаполнено(Оповещение.Имя)
		 Или КлючИЗначение.Ключ <> Оповещение.Имя Тогда
			ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
				НСтр("ru = 'В процедуре ""%1""
				           |не заполнено или некорректно заполнено имя оповещения
				           |%2 = ""%3""
				           |%4 = ""%5"".'"),
					"ОбщегоНазначенияПереопределяемый.ПриДобавленииСерверныхОповещений",
					"Ключ", КлючИЗначение.Ключ, "Оповещение.Имя", Оповещение.Имя);
			ВызватьИсключение ТекстОшибки;
		КонецЕсли;
		Если Не ЗначениеЗаполнено(Оповещение.ИмяМодуляПолучения) Тогда
			ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
				НСтр("ru = 'В процедуре ""%1""
				           |у оповещения ""%2""
				           |не заполнено свойство ""%3"".'"),
					"ОбщегоНазначенияПереопределяемый.ПриДобавленииСерверныхОповещений",
					Оповещение.Имя, "ИмяМодуляПолучения");
			ВызватьИсключение ТекстОшибки;
		КонецЕсли;
		Если Метаданные.ОбщиеМодули.Найти(Оповещение.ИмяМодуляПолучения) = Неопределено Тогда
			ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
				НСтр("ru = 'В процедуре ""%1""
				           |у оповещения ""%2""
				           |в свойстве ""%3"" указан несуществующий общий модуль
				           |""%4"".'"),
					"ОбщегоНазначенияПереопределяемый.ПриДобавленииСерверныхОповещений",
					Оповещение.Имя, "ИмяМодуляПолучения", Оповещение.ИмяМодуляПолучения);
			ВызватьИсключение ТекстОшибки;
		КонецЕсли;
		Если Не ЗначениеЗаполнено(Оповещение.ИмяМодуляОтправки) Тогда
			Продолжить;
		КонецЕсли;
		Если Метаданные.ОбщиеМодули.Найти(Оповещение.ИмяМодуляОтправки) = Неопределено Тогда
			ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
				НСтр("ru = 'В процедуре ""%1""
				           |у оповещения ""%2""
				           |в свойстве ""%3"" указан несуществующий общий модуль
				           |""%4"".'"),
					"ОбщегоНазначенияПереопределяемый.ПриДобавленииСерверныхОповещений",
					Оповещение.Имя, "ИмяМодуляОтправки", Оповещение.ИмяМодуляОтправки);
			ВызватьИсключение ТекстОшибки;
		КонецЕсли;
		Если Параметры.СеансАдминистратораСервиса Тогда
			Продолжить;
		КонецЕсли;
		ПериодическиеОповещения.Вставить(КлючИЗначение.Ключ, Оповещение);
		Если Параметры.МинимальныйПериод > Оповещение.ПериодПроверки Тогда
			Параметры.МинимальныйПериод = Оповещение.ПериодПроверки;
		КонецЕсли;
	КонецЦикла;
	
	Если Параметры.СеансАдминистратораСервиса Тогда
		Возврат Параметры;
	КонецЕсли;
	
	УточнитьМинимальныйПериодПроверки(Параметры.МинимальныйПериод);
	
	Если ЗначениеЗаполнено(ПериодическиеОповещения) Тогда
		УстановитьПривилегированныйРежим(Истина);
		НаборЗаписей = СлужебныйНаборЗаписей(РегистрыСведений.ПериодическиеСерверныеОповещения);
		НаборЗаписей.Отбор.КлючСеанса.Установить(Параметры.КлючСеанса);
		НоваяЗапись = НаборЗаписей.Добавить();
		НоваяЗапись.КлючСеанса = Параметры.КлючСеанса;
		НоваяЗапись.ИдентификаторПользователяИБ =
			ПользователиИнформационнойБазы.ТекущийПользователь().УникальныйИдентификатор;
		НоваяЗапись.Оповещения = Новый ХранилищеЗначения(СохраняемыеПериодическиеОповещения(ПериодическиеОповещения), Новый СжатиеДанных(9));
		НоваяЗапись.ДатаДобавления = ТекущаяДатаСеанса();
		НаборЗаписей.Записать();
		НастроитьЗаданиеОтправкаСерверныхОповещенийКлиентам(Истина, Параметры.МинимальныйПериод, Истина);
		УстановитьПривилегированныйРежим(Ложь);
	КонецЕсли;
	
	Для Каждого КлючИЗначение Из Оповещения Цикл
		КлючИЗначение.Значение.Параметры = Неопределено;
	КонецЦикла;
	
	Параметры.СистемаВзаимодействийПодключена = СистемаВзаимодействийПодключена();
	ЗаполнитьЗначенияСвойств(Параметры, ИдентификаторыОбсуждений());
	
	Возврат Параметры;
	
КонецФункции

Функция НачалоТекущегоСеансаВЧасовомПоясеТекущейДатыСеанса()
	
	// АПК:143-выкл - №643.2.1 Требуется ТекущаяДата сервера, а не ТекущаяДатаСеанса, так как
	// именно ТекущаяДата используется в свойстве НачалоСеанса объекта СеансИнформационнойБазы.
	СдвигВремени = ТекущаяДатаСеанса() - ТекущаяДата();
	// АПК:143-вкл.
	
	Возврат ПолучитьТекущийСеансИнформационнойБазы().НачалоСеанса + СдвигВремени;
	
КонецФункции

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

Процедура УточнитьМинимальныйПериодПроверки(МинимальныйПериодПроверки)
	
	НижняяГраница = ?(ОбщегоНазначения.РазделениеВключено(), 5*60, 60);
	
	Если МинимальныйПериодПроверки < НижняяГраница Тогда
		МинимальныйПериодПроверки = НижняяГраница;
	КонецЕсли;
	
	МинимальныйИнтервал = МинимальныйИнтервалПериодическойОтправкиДанных() * 60;
	
	Если МинимальныйПериодПроверки < МинимальныйИнтервал Тогда
		МинимальныйПериодПроверки = МинимальныйИнтервал;
	КонецЕсли;
	
КонецПроцедуры

// Когда система взаимодействия не используется, тогда
// при большом количестве сеансов (2000 и более) серверные вызовы
// один раз в минуту создают значительную нагрузку (33 вызова / секунду),
// которая при малом количестве ядер (меньше одного ядра на 10 сеансов)
// может стать критичной.
// Для снижения нагрузки можно переопределить интервал с 1 до 2 или даже 5,
// но это снизит и отзывчивость интерфейса пользователей на изменение состояния сервера.
//
// Возвращаемое значение:
//  Число - минимальное количество минут выполнения периодической отправки данных
//          для общего серверного вызова и выполнения проверок регламентным заданием.
//          Это значение переопределяет (повышает) значение указанное разработчиками.
//
Функция МинимальныйИнтервалПериодическойОтправкиДанных()
	Возврат 1;
КонецФункции

Процедура НастроитьЗаданиеОтправкаСерверныхОповещенийКлиентам(Включить, ПериодПовтора = 0, ПриЗапуске = Ложь)
	
	Попытка
		НастроитьЗаданиеОтправкаСерверныхОповещенийКлиентамБезПопытки(Включить, ПериодПовтора, ПриЗапуске);
	Исключение
		ИнформацияОбОшибке = ИнформацияОбОшибке();
		ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Не удалось настроить регламентное задание
			           |""%1"" по причине:
			           |%2'"),
			Метаданные.РегламентныеЗадания.ОтправкаСерверныхОповещенийКлиентам.Имя,
			ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке));
		ЗаписьЖурналаРегистрации(
			НСтр("ru = 'Серверные оповещения.Ошибка настройки регламентного задания'",
				ОбщегоНазначения.КодОсновногоЯзыка()),
			УровеньЖурналаРегистрации.Ошибка,,, ТекстОшибки);
	КонецПопытки;
	
КонецПроцедуры

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

Функция ИмяОповещенияВсеСеансыСпятЗаданиеОтключено()
	Возврат "СтандартныеПодсистемы.БазоваяФункциональность.СерверныеОповещения.ВсеСеансыСпятЗаданиеОтключено";
КонецФункции

Процедура УдалитьСерверноеОповещение(ИдентификаторОповещения)
	
	НаборЗаписей = СлужебныйНаборЗаписей(РегистрыСведений.ОтправленныеСерверныеОповещения);
	НаборЗаписей.Отбор.ИдентификаторОповещения.Установить(ИдентификаторОповещения);
	НаборЗаписей.Записать();
	
КонецПроцедуры

Процедура УстановитьИспользованиеЗаданияОтправкаСерверныхОповещенийКлиентам(Использование)
	
	Попытка
		УстановитьИспользованиеЗаданияОтправкаСерверныхОповещенийКлиентамБезПопытки(Использование);
	Исключение
		ИнформацияОбОшибке = ИнформацияОбОшибке();
		ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Не удалось изменить использование регламентного задания
			           |""%1"" по причине:
			           |%2'"),
			Метаданные.РегламентныеЗадания.ОтправкаСерверныхОповещенийКлиентам.Имя,
			ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке));
		ЗаписьЖурналаРегистрации(
			НСтр("ru = 'Серверные оповещения.Ошибка настройки регламентного задания'",
				ОбщегоНазначения.КодОсновногоЯзыка()),
			УровеньЖурналаРегистрации.Ошибка,,, ТекстОшибки);
	КонецПопытки;
	
КонецПроцедуры

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

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

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

// Параметры:
//  Служебный - Булево - если Истина и имя текущего пользователя пустое,
//     тогда создать служебного пользователя и установить регламентному заданию.
//
// Возвращаемое значение:
//  Булево
//
Функция ТекущийПользовательЗарегистрированВСистемеВзаимодействия(ИдентификаторПользователяСВ = Неопределено)
	
	Если Не СистемаВзаимодействийПодключена() Тогда
		Возврат Ложь;
	КонецЕсли;
	
	ПользовательИБ = ПользователиИнформационнойБазы.ТекущийПользователь();
	Если Не ЗначениеЗаполнено(ПользовательИБ.Имя)
	 Или Не ОбщегоНазначения.ПодсистемаСуществует("СтандартныеПодсистемы.Обсуждения")
	 Или СистемаВзаимодействияВременноНедоступна() Тогда
		Возврат Ложь;
	КонецЕсли;
	
	МодульОбсуждения = ОбщегоНазначения.ОбщийМодуль("Обсуждения");
	АвторизованныйПользователь = Пользователи.АвторизованныйПользователь();
	Попытка
		ИдентификаторПользователяСВ = МодульОбсуждения.ПользовательСистемыВзаимодействия(
			АвторизованныйПользователь, Истина);
	Исключение
		ИнформацияОбОшибке = ИнформацияОбОшибке();
		Если СистемаВзаимодействияВременноНедоступна(Истина) Тогда
			Возврат Ложь;
		КонецЕсли;
		ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Не удалось зарегистрировать текущего пользователя
			           |""%1 (%2)""
			           |в системе взаимодействия по причине:
			           |%3'"),
			ПользовательИБ.Имя,
			НРег(ПользовательИБ.УникальныйИдентификатор),
			ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке));
		ЗаписьЖурналаРегистрации(
			НСтр("ru = 'Серверные оповещения.Ошибка регистрации пользователя в системе взаимодействия'",
				ОбщегоНазначения.КодОсновногоЯзыка()),
			УровеньЖурналаРегистрацииОшибкиСВ(ИнформацияОбОшибке),,,
			ТекстОшибки);
		Возврат Ложь;
	КонецПопытки;
	
	Возврат Истина;
	
КонецФункции

Функция СистемаВзаимодействияВременноНедоступна(ПроверитьДоступность = Ложь)
	
	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);
	
	ИмяПараметра = "СтандартныеПодсистемы.БазоваяФункциональность.ДатаОшибкиДоступаКСистемеВзаимодействия";
	СтароеЗначение = СтандартныеПодсистемыСервер.ПараметрРаботыРасширения(ИмяПараметра, Истина);
	СтароеЗначение = ?(ТипЗнч(СтароеЗначение) = Тип("Дата"), СтароеЗначение, '00010101');
	
	Если ЗначениеЗаполнено(СтароеЗначение)
	   И СтароеЗначение + 60*5 > ТекущаяДатаСеанса()
	   И СтароеЗначение - 60 < ТекущаяДатаСеанса() Тогда
		Возврат Истина;
	КонецЕсли;
	
	Если Не ПроверитьДоступность Тогда
		Возврат Ложь;
	КонецЕсли;
	
	НовоеЗначение = '00010101';
	Попытка
		СистемаВзаимодействия.ПолучитьТипыВнешнихСистем();
	Исключение
		НовоеЗначение = ТекущаяДатаСеанса();
		ИнформацияОбОшибке = ИнформацияОбОшибке();
	КонецПопытки;
	
	Если Не ЗначениеЗаполнено(НовоеЗначение) Тогда
		Возврат Ложь;
	КонецЕсли;
	
	ТекущееЗначение = СтандартныеПодсистемыСервер.ПараметрРаботыРасширения(ИмяПараметра, Истина);
	ТекущееЗначение = ?(ТипЗнч(ТекущееЗначение) = Тип("Дата"), ТекущееЗначение, '00010101');
	Если СтароеЗначение <> ТекущееЗначение Тогда
		Возврат Истина;
	КонецЕсли;
	
	Блокировка = Новый БлокировкаДанных;
	ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ПараметрыРаботыВерсийРасширений");
	ЭлементБлокировки.УстановитьЗначение("ВерсияРасширений", Справочники.ВерсииРасширений.ПустаяСсылка());
	ЭлементБлокировки.УстановитьЗначение("ИмяПараметра", ИмяПараметра);
	
	НачатьТранзакцию();
	Попытка
		Блокировка.Заблокировать();
		ТекущееЗначение = СтандартныеПодсистемыСервер.ПараметрРаботыРасширения(ИмяПараметра, Истина);
		ТекущееЗначение = ?(ТипЗнч(ТекущееЗначение) = Тип("Дата"), ТекущееЗначение, '00010101');
		Если СтароеЗначение = ТекущееЗначение Тогда
			СтандартныеПодсистемыСервер.УстановитьПараметрРаботыРасширения(ИмяПараметра, НовоеЗначение, Истина);
		КонецЕсли;
		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;
	
	Если СтароеЗначение = ТекущееЗначение Тогда
		ЗаписьЖурналаРегистрации(
			НСтр("ru = 'Серверные оповещения.Система взаимодействия недоступна'",
				ОбщегоНазначения.КодОсновногоЯзыка()),
			УровеньЖурналаРегистрации.Предупреждение,,,
			ОбработкаОшибок.КраткоеПредставлениеОшибки(ИнформацияОбОшибке));
	КонецЕсли;
	
	Возврат Истина;
	
КонецФункции

Функция СлужебныйПользовательИБ()
	
	Свойства = Пользователи.НовоеОписаниеПользователяИБ(, Истина);
	Свойства.Имя = ИмяСлужебногоПользователя();
	СсылкаНового = Справочники.Пользователи.ПолучитьСсылку(
		Новый УникальныйИдентификатор("ea3b6bcf-d6d8-11ee-886a-b06ebfbf08c7"));
	
	Возврат Пользователи.СлужебныйПользовательИБ(Свойства, СсылкаНового);
	
КонецФункции

Функция ИмяСлужебногоПользователя()
	
	Возврат "ОтправкаСерверныхОповещений";
	
КонецФункции

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

Функция ИспользоватьСистемуВзаимодействияВФайловойИБ()
	
	Возврат Ложь;
	
КонецФункции

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

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

Функция ИдентификаторЛичногоОбсуждения(ИдентификаторПользователяИБ = Неопределено,
			ИдентификаторПользователяСВ = Неопределено)
	
	УстановитьПривилегированныйРежим(Истина);
	Если ИдентификаторПользователяИБ = Неопределено Тогда
		УникальныйИдентификатор = ПользователиИнформационнойБазы.ТекущийПользователь().УникальныйИдентификатор;
	Иначе
		УникальныйИдентификатор = ИдентификаторПользователяИБ;
	КонецЕсли;
	КлючЛичногоОбсуждения = "СерверныеОповещения" + "_" + НРег(УникальныйИдентификатор);
	
	Попытка
		ОбновлятьУчастниковОбсуждения = ИдентификаторПользователяСВ <> Неопределено;
		НомерПопытки = 1;
		Создано = Ложь;
		Пока Истина Цикл
			Обсуждение = СистемаВзаимодействия.ПолучитьОбсуждение(КлючЛичногоОбсуждения);
			Если Обсуждение <> Неопределено Тогда
				Прервать;
			КонецЕсли;
			Если ИдентификаторПользователяСВ = Неопределено Тогда
				Если ИдентификаторПользователяИБ = Неопределено Тогда
					ИдентификаторПользователяСВ = СистемаВзаимодействия.ИдентификаторТекущегоПользователя();
				Иначе
					Попытка
						ИдентификаторПользователяСВ = СистемаВзаимодействия.ПолучитьИдентификаторПользователя(УникальныйИдентификатор);
					Исключение
						ИдентификаторПользователяСВ = СистемаВзаимодействия.ИдентификаторТекущегоПользователя();
					КонецПопытки;
				КонецЕсли;
			КонецЕсли;
			НовоеОбсуждение = СистемаВзаимодействия.СоздатьОбсуждение();
			НовоеОбсуждение.Отображаемое = Ложь;
			НовоеОбсуждение.Ключ = КлючЛичногоОбсуждения;
			НовоеОбсуждение.Участники.Добавить(ИдентификаторПользователяСВ);
			Попытка
				НовоеОбсуждение.Записать();
				Обсуждение = НовоеОбсуждение;
				Создано = Истина;
				Прервать;
			Исключение
				НомерПопытки = НомерПопытки + 1;
				Если НомерПопытки > 10 Тогда
					ВызватьИсключение;
				КонецЕсли;
			КонецПопытки;
		КонецЦикла;
		Если Не Создано
		   И (Обсуждение.Отображаемое
		      Или ОбновлятьУчастниковОбсуждения
		        И (Обсуждение.Участники.Количество() <> 1
		           Или Не Обсуждение.Участники.Содержит(ИдентификаторПользователяСВ))) Тогда
			Обсуждение.Отображаемое = Ложь;
			Если ОбновлятьУчастниковОбсуждения Тогда
				Обсуждение.Участники.Очистить();
				Обсуждение.Участники.Добавить(ИдентификаторПользователяСВ);
			КонецЕсли;
			Обсуждение.Записать();
		КонецЕсли;
	Исключение
		ИнформацияОбОшибке = ИнформацияОбОшибке();
		ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Не удалось создать личное обсуждение для пользователя ""%1 (%2)"" по причине:
			           |%3'"),
			ПользователиИнформационнойБазы.ТекущийПользователь().Имя,
			НРег(ИдентификаторПользователяИБ),
			ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке));
		ЗаписьЖурналаРегистрации(
			НСтр("ru = 'Серверные оповещения.Ошибка создания личного обсуждения'",
				ОбщегоНазначения.КодОсновногоЯзыка()),
			УровеньЖурналаРегистрацииОшибкиСВ(ИнформацияОбОшибке),,, ТекстОшибки);
		Возврат Неопределено;
	КонецПопытки;
	УстановитьПривилегированныйРежим(Ложь);
	
	Возврат Обсуждение.Идентификатор;
	
КонецФункции

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

// Отправляет сообщение с произвольными данными с клиента на сервер.
//
// Выбрасывает исключение, если не удалось отправить сообщение.
//
// Параметры: 
//   Данные - см. НовыеДанныеСообщения
//   ИдентификаторОбсуждения - ИдентификаторОбсужденияСистемыВзаимодействия
//
// Возвращаемое значение:
//  Булево - Истина, если успешно записано в систему взаимодействия.
//
Функция ОтправитьСообщение(Данные, ИдентификаторОбсуждения)
	
	ИмяПроцедуры = ДлительныеОперации.ПолноеИмяПрикладнойПроцедурыДлительнойОперации();
	Текст = Данные.ИмяОповещения + "
		|" + ?(ЗначениеЗаполнено(Данные.ИдентификаторОповещения), Данные.ИдентификаторОповещения, "-") + "
		|" + ?(ЗначениеЗаполнено(ИмяПроцедуры), ИмяПроцедуры, "-");
	
	Попытка
		НовоеСообщение = СистемаВзаимодействия.СоздатьСообщение(ИдентификаторОбсуждения);
		НовоеСообщение.Данные = Данные;
		НовоеСообщение.Дата   = ТекущаяДатаСеанса();
		НовоеСообщение.Текст  = Новый ФорматированнаяСтрока(Текст);
		НовоеСообщение.Записать();
	Исключение
		ИнформацияОбОшибке = ИнформацияОбОшибке();
		ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'Не удалось отправить сообщение в обсуждение %1 по причине:
			           |%2'"),
			НРег(ИдентификаторОбсуждения),
			ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке));
		ЗаписьЖурналаРегистрации(
			НСтр("ru = 'Серверные оповещения.Ошибка отправки сообщения'",
				ОбщегоНазначения.КодОсновногоЯзыка()),
			УровеньЖурналаРегистрацииОшибкиСВ(ИнформацияОбОшибке),,, ТекстОшибки);
		Возврат Ложь;
	КонецПопытки;
	
	Возврат Истина;
	
КонецФункции

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

// Позволяет подключиться расширением для журналирования отправляемых уведомлений.
Процедура ОтправитьУведомлениеКлиента(КлючУведомления, Данные, НомераСеансов)
	МенеджерУведомленийКлиента().ОтправитьУведомление(КлючУведомления, Данные, НомераСеансов);
КонецПроцедуры

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

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

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

// Уведомления клиента

Функция МенеджерУведомленийКлиента()
	
	// АПК:488-выкл Поддержка новых возможностей платформы (исполняемый код безопасен)
	Возврат Вычислить("УведомленияКлиента")
	// АПК:488-вкл
	
КонецФункции

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