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


Ниже приведен разбор критических ошибок логики, антипаттернов и уязвимостей в представленном коде, а также исправленная и рефакторенная версия модулей.

---

### Анализ ошибок логики и антипаттернов

1. **Серверный вызов `ПоказатьВопрос` и вывод сообщений:**
* В процедуре `ПроизводственныйКалендарьПриИзмененииНаСервере` вызывался `Сообщить()`. Сообщения на сервере — антипаттерн, их лучше возвращать или выводить через `ВызватьИсключение` / `ОбщегоНазначенияКлиентСервер.СообщитьПользователю`.


2. **Работа с динамическими реквизитами формы (`ИзменитьРеквизиты`):**
* **Критическая ошибка:** При удалении сотрудника через `УдалитьКолонкуСотрудников` реквизит удалялся с формы, но динамический элемент формы (`Элементы.ДанныеОВремени`), созданный в `ДобавитьКолонкуСотрудника`, оставался «повисшим». Это приводит к обращению к несуществующему пути данных и падению платформы при перерисовке.


3. **Логика работы с коллекциями и массивами (`Сотрудники[0].Значение`):**
* В форме `РасписаниеРаботы` при `ПоОдномуСотруднику = Истина` происходило прямым ходом обращение к `Сотрудники[0]`. Если массив/список `Сотрудники` пустой — платформа выбрасывала исключение выхода за пределы массива.


4. **Запросы в цикле и неоптимальные операции со строками:**
* Удаление строк через `НайтиСтроки` в цикле `Для Каждого`: при удалении элементов коллекции во время итерации по найденным строкам индекс смещается, либо модифицируется коллекция. В коде применялся правильный шаблон удаления, но код был перегружен ручными циклами обращения `НомерСтроки = Количество() - 1`.
* Избыточный вызов `ОтрисоватьТаблицуДанныеОВремени()` при каждом действии, включая неоптимальную логику генерации таблицы дат на сервере.


5. **Использование контекстных вызовов без необходимости:**
* Функция `ВернутьУникальноеИмяКолонкиСотрудника` и работа с именами: в имени элемента использовались символы `С_` + `Код` + `УИД`. Если у сотрудника код содержит спецсимволы, имя реквизита становится недопустимым.


6. **Предупреждения и асинхронность:**
* В `ПередЗакрытием` не использовался режим работы с асинхронными вызовами стандартной библиотеки.



---

### Рефакторинг 1. Форма «РасписаниеРаботы»

```bsl
&НаКлиенте
Перем ЗакрытиеРазрешено;   

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

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

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

	Если ПоОдномуСотруднику Тогда
		Элементы.НадписьСотрудников.Видимость = Ложь;  
		Элементы.НадписьСотрудника.Видимость = Истина;
		НадписьСотрудника = ?(Сотрудники.Количество() > 0, Сотрудники[0].Значение, ""); 	
	Иначе
		Элементы.НадписьСотрудников.Видимость = Истина;  
		Элементы.НадписьСотрудника.Видимость = Ложь;
		Элементы.НадписьСотрудников.Заголовок = NStr("ru = 'Задать единое расписание для сотрудников'"); 
				
		ТекстМассив = Новый Массив;
		Для Каждого ЭлементСписка Из Сотрудники Цикл
			ТекстМассив.Добавить(Строка(ЭлементСписка.Значение));
		КонецЦикла; 
		
		Элементы.НадписьСотрудников.Высота = 3;  
		Элементы.НадписьСотрудников.МаксимальнаяВысота = 3;
		Элементы.НадписьСотрудников.АвтоМаксимальнаяВысота = Ложь;
		НадписьСотрудников = СтрСоединить(ТекстМассив, Символы.ПС);
	КонецЕсли;
	
	УточнитьФорматПолейВремени();
	ЗагрузитьИнтервалы(); 	
	ЗаполнитьОбщееВремя(ЭтотОбъект);
	
КонецПроцедуры 

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

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

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

#Область ОбработчикиСобытийЭлементовФормы

&НаКлиенте
Процедура РасписаниеРаботыПриОкончанииРедактирования(Элемент, НоваяСтрока, ОтменаРедактирования)	
	Если ОтменаРедактирования Или Элемент.ТекущиеДанные = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	ТекущаяСтрока = Элемент.ТекущиеДанные;
	
	Если Не ПереходящиеСмены Тогда
		ВосстановитьПорядокСтрокКоллекцииПослеРедактирования(РасписаниеРаботы, "ВремяНачала", ТекущаяСтрока);
	КонецЕсли;  
	
	Если ТекущаяСтрока.ВремяОкончания = Дата(1, 1, 1, 0, 0, 0) Тогда  
		ТекущаяСтрока.ВремяОкончания = Дата(1, 1, 1, 23, 59, 0);	
	КонецЕсли;
	
	ЗаполнитьОбщееВремя(ЭтотОбъект); 
КонецПроцедуры

&НаКлиенте
Процедура РасписаниеРаботыПослеУдаления(Элемент)
	ЗаполнитьОбщееВремя(ЭтотОбъект);
КонецПроцедуры

&НаКлиенте
Процедура ДатаПриИзменении(Элемент)  
	Если Год(Дата) <> Год(МесяцГрафика) Или Месяц(Дата) <> Месяц(МесяцГрафика) Тогда 
		ТекстСообщения = СтрШаблон(
			NStr("ru = 'Выбранная дата не соответствует месяцу графика. Месяц графика: %1 года. Выберите дату из указанного месяца.'"),
			Формат(МесяцГрафика, "ДФ='ММММ гггг'")
		);
		ОбщегоНазначенияКлиент.СообщитьПользователю(ТекстСообщения);
	КонецЕсли;
	
	Дата = Дата(Год(МесяцГрафика), Месяц(МесяцГрафика), День(Дата));  
	ЗагрузитьИнтервалы();     
КонецПроцедуры

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

#Область КомандыФормы

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

&НаКлиенте
Процедура Отмена(Команда)
	ЗакрытьДаннуюФорму();	
КонецПроцедуры 

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

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

&НаКлиенте
Функция ИнтервалыКорректны()
	Отказ = Ложь;
	ОкончаниеДня = Неопределено;
	
	Для ИндексСтроки = 0 По РасписаниеРаботы.Количество() - 1 Цикл
		СтрокаРасписания = РасписаниеРаботы[ИндексСтроки]; 			
		
		Если СтрокаРасписания.ВремяНачала > СтрокаРасписания.ВремяОкончания 
			И ЗначениеЗаполнено(СтрокаРасписания.ВремяОкончания) Тогда 
			
			ОбщегоНазначенияКлиент.СообщитьПользователю(
				NStr("ru = 'Время начала больше времени окончания'"), ,
				СтрШаблон("РасписаниеРаботы[%1].ВремяОкончания", ИндексСтроки), ,
				Отказ);
		КонецЕсли;      
		
		Если СтрокаРасписания.ВремяНачала = СтрокаРасписания.ВремяОкончания Тогда
			ОбщегоНазначенияКлиент.СообщитьПользователю(
				NStr("ru = 'Длительность интервала не определена'"), ,
				СтрШаблон("РасписаниеРаботы[%1].ВремяОкончания", ИндексСтроки), ,
				Отказ);
		КонецЕсли;      
		
		Если ОкончаниеДня <> Неопределено Тогда
			Если ОкончаниеДня > СтрокаРасписания.ВремяНачала Или Не ЗначениеЗаполнено(ОкончаниеДня) Тогда
				ОбщегоНазначенияКлиент.СообщитьПользователю(
					NStr("ru = 'Обнаружены пересекающиеся интервалы'"), ,
					СтрШаблон("РасписаниеРаботы[%1].ВремяНачала", ИндексСтроки), ,
					Отказ);
			КонецЕсли;
		КонецЕсли;
		ОкончаниеДня = СтрокаРасписания.ВремяОкончания;
	КонецЦикла;  
	
	Возврат Не Отказ;	
КонецФункции

&НаКлиенте
Процедура ЗакрытьДаннуюФорму(Результат = Неопределено, ДополнительныеПараметры = Неопределено) Экспорт	
	Модифицированность = Ложь;
	ЗакрытиеРазрешено = Истина;
	ОповеститьОВыборе(Результат);
КонецПроцедуры

&НаКлиентеНаСервереБезКонтекста
Процедура ЗаполнитьОбщееВремя(Форма)
	Секунд = 0;
	
	Для Каждого Строка Из Форма.РасписаниеРаботы Цикл
		Если Строка.ВремяНачала = Строка.ВремяОкончания Тогда
			СекундИнтервала = 0;
		ИначеЕсли Строка.ВремяНачала < Строка.ВремяОкончания Тогда
			СекундИнтервала = Строка.ВремяОкончания - Строка.ВремяНачала;
		ИначеЕсли Не ЗначениеЗаполнено(Строка.ВремяОкончания) Тогда
			СекундИнтервала = КонецДня(Строка.ВремяОкончания) - Строка.ВремяНачала + 1;
		Иначе
			ОсновнаяЧасть = КонецДня(Строка.ВремяНачала) - Строка.ВремяНачала + 1;
			ПереходящаяЧасть = Строка.ВремяОкончания - НачалоДня(Строка.ВремяОкончания);
			СекундИнтервала = ОсновнаяЧасть + ПереходящаяЧасть;
		КонецЕсли;
		
		Секунд = Секунд + СекундИнтервала;
	КонецЦикла;
	
	Часов = Окр(Секунд / 3600, 1);
	Форма.Элементы.ОбщееВремя.Заголовок = СтрШаблон(NStr("ru = 'Всего: %1 ч.'"), Часов);
КонецПроцедуры

&НаСервере
Функция ДвенадцатиЧасовойФорматВремени()
	ПредставлениеВремени = Формат(ТекущаяДатаСеанса(), "ДЛФ=T");
	Возврат ВРег(Прав(ПредставлениеВремени, 1)) = "M";
КонецФункции  

&НаКлиенте
Процедура ВосстановитьПорядокСтрокКоллекцииПослеРедактирования(КоллекцияСтрок, ПолеУпорядочивания, ТекущаяСтрока) Экспорт
	Если КоллекцияСтрок.Количество() < 2 Тогда
		Возврат;
	КонецЕсли;
	
	Если Не ЗначениеЗаполнено(ТекущаяСтрока[ПолеУпорядочивания]) Тогда
		Возврат;
	КонецЕсли;
	
	ИндексИсходный = КоллекцияСтрок.Индекс(ТекущаяСтрока);
	ИндексРезультат = ИндексИсходный;
	
	Направление = 0;
	Если ИндексИсходный = 0 Тогда
		Направление = 1;
	ИначеЕсли ИндексИсходный = КоллекцияСтрок.Количество() - 1 Тогда
		Направление = -1;
	Иначе
		Если КоллекцияСтрок[ИндексИсходный][ПолеУпорядочивания] > КоллекцияСтрок[ИндексРезультат + 1][ПолеУпорядочивания] Тогда
			Направление = 1;
		ИначеЕсли КоллекцияСтрок[ИндексИсходный][ПолеУпорядочивания] < КоллекцияСтрок[ИндексРезультат - 1][ПолеУпорядочивания] Тогда
			Направление = -1;
		КонецЕсли;
	КонецЕсли;
	
	Если Направление = 0 Тогда
		Возврат;
	КонецЕсли;
	
	Если Направление = 1 Тогда
		Пока ИндексРезультат < КоллекцияСтрок.Количество() - 1 
			И КоллекцияСтрок[ИндексИсходный][ПолеУпорядочивания] > КоллекцияСтрок[ИндексРезультат + 1][ПолеУпорядочивания] Цикл
			ИндексРезультат = ИндексРезультат + 1;
		КонецЦикла;
	Иначе
		Пока ИндексРезультат > 0 
			И КоллекцияСтрок[ИндексИсходный][ПолеУпорядочивания] < КоллекцияСтрок[ИндексРезультат - 1][ПолеУпорядочивания] Цикл
			ИндексРезультат = ИндексРезультат - 1;
		КонецЦикла;
	КонецЕсли;
	
	Если ИндексИсходный <> ИндексРезультат Тогда
		КоллекцияСтрок.Сдвинуть(ИндексИсходный, ИндексРезультат - ИндексИсходный);
	КонецЕсли;
КонецПроцедуры

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

&НаСервере
Процедура УточнитьФорматПолейВремени()
	Есть12Часов = ДвенадцатиЧасовойФорматВремени();
	ФорматВремени = ?(Есть12Часов, NStr("ru = 'ДФ=''чч:мм вв''; ДП='"), NStr("ru = 'ДФ=ЧЧ:мм; ДП='"));
	ФорматВремениРедактирования = ?(Есть12Часов, NStr("ru = 'ДФ=''чч:мм вв'''"), NStr("ru = 'ДФ=ЧЧ:мм'"));
	
	Элементы.РасписаниеРаботыВремяНачала.Формат = ФорматВремени;
	Элементы.РасписаниеРаботыВремяНачала.ФорматРедактирования = ФорматВремениРедактирования;
	
	Элементы.РасписаниеРаботыВремяОкончания.Формат = ФорматВремени;
	Элементы.РасписаниеРаботыВремяОкончания.ФорматРедактирования = ФорматВремениРедактирования;
КонецПроцедуры

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

```

---

### Рефакторинг 2. Основная форма управления графиками

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

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

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

#Область ОбработчикиСобытийЭлементовФормы

&НаКлиенте
Процедура МесяцРегистрацииСтрокойАвтоПодбор(Элемент, Текст, ДанныеВыбора, ПараметрыПолученияДанных, Ожидание, СтандартнаяОбработка)
	Если Не ПустаяСтрока(Текст) Тогда
		ДанныеВыбора = ДатаКакМесяцПодобратьДатуПоТексту(Текст);
		СтандартнаяОбработка = Ложь;
	КонецЕсли;
КонецПроцедуры 

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

&НаКлиенте
Процедура ПроизводственныйКалендарьНачалоВыбора(Элемент, ДанныеВыбора, ВыборДобавлением, СтандартнаяОбработка)
	СтандартнаяОбработка = Ложь;
	ГодВыбора = Год(Объект.МесяцГрафика);
	СписокКалендарей = ПолучитьСписокЗначенийКалендарей(ГодВыбора); 
	
	Если СписокКалендарей.Количество() = 0 Тогда
		ОбщегоНазначенияКлиент.СообщитьПользователю(СтрШаблон(NStr("ru = 'Отсутствует производственный календарь за %1 год.'"), Формат(ГодВыбора, "ЧГ=0")));	
		Возврат;
	КонецЕсли;
	
	ВыбранныйЭлемент = СписокКалендарей.ВыбратьЭлемент(СтрШаблон(NStr("ru = 'Выберите производственный календарь за %1'"), Формат(ГодВыбора, "ЧГ=0")));
	Если ВыбранныйЭлемент <> Неопределено Тогда
		Объект.ПроизводственныйКалендарь = ВыбранныйЭлемент.Значение;	
		ПроизводственныйКалендарьПриИзмененииНаКлиенте();
	КонецЕсли; 
КонецПроцедуры

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

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

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

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

&НаКлиенте
Процедура СотрудникиПередУдалением(Элемент, Отказ)
	СтрокаСотрудника = Элементы.Сотрудники.ТекущиеДанные;
	Если СтрокаСотрудника <> Неопределено Тогда
		УдалитьСотрудникаКомплексно(СтрокаСотрудника.Сотрудник);
	КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура СотрудникиПослеУдаления(Элемент)
	Если Объект.Сотрудники.Количество() = 0 Тогда
		Объект.ГрафикРаботыСотрудников.Очистить();
		Объект.СоответствиеКолонокСотрудников.Очистить();
	КонецЕсли;
	ОтрисоватьТаблицуДанныеОВремени();
КонецПроцедуры

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

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

#Область КомандыФормы

&НаКлиенте
Процедура ЗаполнитьПоПроизводственномуГрафику(Команда)
	ЗаполнитьПоПроизводственномуГрафикуНаСервере();   
	ОтрисоватьТаблицуДанныеОВремени();
КонецПроцедуры  

&НаКлиенте
Процедура ЗаполнитьПоПроизводственномуГрафикуСотрудника(Команда)  
	Если ЗначениеЗаполнено(ТекущийСотрудникТаблицыСотрудник) Тогда
		ЗаполнитьПоПроизводственномуГрафикуСотрудникаНаСервере(ТекущийСотрудникТаблицыСотрудник);
		ОтрисоватьТаблицуДанныеОВремени();        
	КонецЕсли;
КонецПроцедуры

&НаКлиенте
Процедура Подбор(Команда)
	Если Не ЗначениеЗаполнено(Объект.Подразделение) Тогда
		ОбщегоНазначенияКлиент.СообщитьПользователю(NStr("ru = 'Заполните подразделение'"));
		Возврат;
	КонецЕсли;
	
	ПараметрыФормы = Новый Структура("Подразделение", Объект.Подразделение);
	ОткрытьФорму("Справочник.Сотрудники.Форма.ФормаПодбора", ПараметрыФормы, Элементы.Сотрудники);
КонецПроцедуры

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

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

&НаКлиенте
Процедура ПроизводственныйКалендарьПриИзмененииНаКлиенте()
	ЗагрузитьТаблицуПроизводственногоКалендаря();
	УправлениеДоступностьюЭлементов();
КонецПроцедуры

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

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

&НаКлиенте
Процедура ЗадатьВопросПриИзмененииДаты()  
	Обработчик = Новый ОписаниеОповещения("ОчиститьРасписаниеСотрудниковПослеЗакрытияВопроса", ЭтотОбъект);
	ТекстВопроса = NStr("ru = 'При изменении месяца будет утеряно нынешнее расписание сотрудников. Продолжить?'");
	ПоказатьВопрос(Обработчик, ТекстВопроса, РежимДиалогаВопрос.ДаНет);
КонецПроцедуры   

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

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

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

&НаКлиенте
Процедура МесяцРегистрацииСтрокойПриИзменении()   
	ОчиститьГрафикРаботыСотрудников(); 
	ЗаполнитьПроизводственныйКалендарь(Год(Объект.МесяцГрафика));	
	ПроизводственныйКалендарьПриИзмененииНаКлиенте();
КонецПроцедуры  

&НаКлиенте
Процедура ЗаполнитьПроизводственныйКалендарь(Год) 
	Объект.ПроизводственныйКалендарь = Неопределено; 
	ПроизводственныйГрафик.Очистить();
	Календарь = ПолучитьКалендарь(Год);
	Если Календарь <> Неопределено Тогда
		Объект.ПроизводственныйКалендарь = Календарь;		
	Иначе
		ОбщегоНазначенияКлиент.СообщитьПользователю(СтрШаблон(NStr("ru = 'Отсутствует производственный календарь за %1 год.'"), Формат(Год, "ЧГ=0")));	
	КонецЕсли;	
КонецПроцедуры  

&НаКлиенте
Процедура ПослеЗаписиДанныхФормыРасписаниеРаботы(Результат, ДопПараметры) Экспорт
	Если Результат <> Неопределено Тогда          
		ПослеЗаписиДанныхФормыРасписаниеРаботыНаСервере(Результат, ДопПараметры);	
	КонецЕсли;
	ОтрисоватьТаблицуДанныеОВремени();
КонецПроцедуры

&НаСервере
Процедура ПослеЗаписиДанныхФормыРасписаниеРаботыНаСервере(Результат, ДопПараметры)
	КоллекцияСотрудников = Результат.КоллекцияСотрудников;    
	ГрафикРаботыПоКонкретномуСотруднику = Результат.ГрафикРаботыПоКонкретномуСотруднику; 
	ДатаРедактирования = Результат.ДатаРедактирования;
	
	Для Индекс = 0 По КоллекцияСотрудников.Количество() - 1 Цикл 
		Отбор = Новый Структура("Сотрудник, Дата", КоллекцияСотрудников[Индекс].Значение, ДатаРедактирования);
		
		НайденныеСтроки = Объект.ГрафикРаботыСотрудников.НайтиСтроки(Отбор);
		Для Каждого Строка Из НайденныеСтроки Цикл
			Объект.ГрафикРаботыСотрудников.Удалить(Строка);
		КонецЦикла;	
	КонецЦикла;		
	
	Отбор = Новый Структура("Дата", ДатаРедактирования);
	НайденныеСтроки = ГрафикРаботыПоКонкретномуСотруднику.НайтиСтроки(Отбор);
	Для Каждого Строка Из НайденныеСтроки Цикл
		НоваяСтрока = Объект.ГрафикРаботыСотрудников.Добавить();
		ЗаполнитьЗначенияСвойств(НоваяСтрока, Строка);
	КонецЦикла;   
КонецПроцедуры   

&НаСервере
Процедура ОчиститьГрафикРаботыСотрудников()
	Объект.ГрафикРаботыСотрудников.Очистить();
	ОтрисоватьТаблицуДанныеОВремени();   	
КонецПроцедуры

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

&НаСервере
Процедура УдалитьСотрудникаКомплексно(Сотрудник)
	Если Не ЗначениеЗаполнено(Сотрудник) Тогда
		Возврат;
	КонецЕсли;
	
	ИмяКолонки = ВернутьУникальноеИмяКолонкиСотрудника(Сотрудник);
	
	// 1. Удаление элемента формы во избежание ошибки «Не найден путь к данным»
	ЭлементФормы = Элементы.Найти(ИмяКолонки);
	Если ЭлементФормы <> Неопределено Тогда
		Элементы.Удалить(ЭлементФормы);
	КонецЕсли;
	
	// 2. Удаление реквизита формы
	ИмяУдаляемогоРеквизита = "ДанныеОВремени." + ИмяКолонки;
	МассивУдаляемыхРеквизитов = Новый Массив;
	РекРодителя = ПолучитьРеквизиты("ДанныеОВремени");
	Для Каждого ТекРек Из РекРодителя Цикл    
		Если ТекРек.Имя = ИмяКолонки Тогда 
			МассивУдаляемыхРеквизитов.Добавить(ИмяУдаляемогоРеквизита);
			Прервать;
		КонецЕсли;
	КонецЦикла;
	
	Если МассивУдаляемыхРеквизитов.Количество() > 0 Тогда 		
		ИзменитьРеквизиты(, МассивУдаляемыхРеквизитов);		
	КонецЕсли;
	
	// 3. Безопасное удаление из ТЧ ГрафикРаботыСотрудников
	Индекс = Объект.ГрафикРаботыСотрудников.Количество() - 1;
	Пока Индекс >= 0 Цикл
		Если Объект.ГрафикРаботыСотрудников[Индекс].Сотрудник = Сотрудник Тогда
			Объект.ГрафикРаботыСотрудников.Удалить(Индекс);
		КонецЕсли;
		Индекс = Индекс - 1;
	КонецЦикла;
	
	// 4. Безопасное удаление из ТЧ СоответствиеКолонокСотрудников
	Индекс = Объект.СоответствиеКолонокСотрудников.Количество() - 1;
	Пока Индекс >= 0 Цикл
		Если Объект.СоответствиеКолонокСотрудников[Индекс].Сотрудник = Сотрудник Тогда
			Объект.СоответствиеКолонокСотрудников.Удалить(Индекс);
		КонецЕсли;
		Индекс = Индекс - 1;
	КонецЦикла;
КонецПроцедуры

&НаСервере
Функция ВернутьФамилиюИИнициалы(Знач ФИО)  
	ФИО = СокрЛП(ФИО);
	МассивСлов = СтрРазделить(ФИО, " ", Ложь);
	Если МассивСлов.Количество() >= 3 Тогда
		Возврат СтрШаблон("%1 %2.%3.", МассивСлов[0], Лев(МассивСлов[1], 1), Лев(МассивСлов[2], 1)); 
	Иначе
		Возврат ФИО;
	КонецЕсли;  
КонецФункции

&НаСервере
Функция ВернутьУникальноеИмяКолонкиСотрудника(Сотрудник)  
	ГуидСтрокой = СтрЗаменить(Строка(Сотрудник.УникальныйИдентификатор()), "-", "_");
	Возврат СтрШаблон("С_Колонка_%1", ГуидСтрокой);
КонецФункции  

&НаСервере
Функция ПолучитьИмяКолонкиПоСотруднику(Сотрудник)
	Отбор = Новый Структура("Сотрудник", Сотрудник); 
	НайденныеСтроки = Объект.СоответствиеКолонокСотрудников.НайтиСтроки(Отбор);  
	Если НайденныеСтроки.Количество() > 0 Тогда
		Возврат НайденныеСтроки[0].ИмяКолонки;		
	КонецЕсли;
	Возврат ""; 
КонецФункции

&НаСервере
Функция ПолучитьСотрудникаПоИмениКолонки(ИмяКолонки)
	Отбор = Новый Структура("ИмяКолонки", ИмяКолонки); 
	НайденныеСтроки = Объект.СоответствиеКолонокСотрудников.НайтиСтроки(Отбор);
	Если НайденныеСтроки.Количество() > 0 Тогда
		Возврат НайденныеСтроки[0].Сотрудник;		
	КонецЕсли;
	Возврат Справочники.Сотрудники.ПустаяСсылка(); 
КонецФункции 

&НаСервере
Процедура ДобавитьСоответствиеИмениКолонкиИСотрудника(ИмяКолонки, Сотрудник)
	Отбор = Новый Структура("Сотрудник, ИмяКолонки", Сотрудник, ИмяКолонки); 
	НайденныеСтроки = Объект.СоответствиеКолонокСотрудников.НайтиСтроки(Отбор); 
	Если НайденныеСтроки.Количество() = 0 Тогда 
		НовоеСоответствие = Объект.СоответствиеКолонокСотрудников.Добавить();  
		НовоеСоответствие.ИмяКолонки = ИмяКолонки;
		НовоеСоответствие.Сотрудник = Сотрудник; 
	КонецЕсли;
КонецПроцедуры  

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

&НаСервере
Процедура ЗаполнитьДанныеОВремени() 
	Для Каждого СтрСотрудник Из Объект.Сотрудники Цикл  
		ДобавитьКолонкуСотрудника(СтрСотрудник.Сотрудник); 		
	КонецЦикла;                      	
КонецПроцедуры  

&НаСервере
Процедура ЗаполнитьПоПроизводственномуГрафикуНаСервере()         
	Если Объект.Сотрудники.Количество() = 0 Тогда
		Возврат;
	КонецЕсли;
	
	Объект.ГрафикРаботыСотрудников.Очистить();
	Для Каждого СтрокаСотрудник Из Объект.Сотрудники Цикл    
		Для Каждого СтрокаТЧ Из ПроизводственныйГрафик Цикл
			Если ЗначениеЗаполнено(СтрокаТЧ.ВремяНачала) И ЗначениеЗаполнено(СтрокаТЧ.ВремяОкончания) Тогда
				НоваяСтрока = Объект.ГрафикРаботыСотрудников.Добавить(); 
				НоваяСтрока.Сотрудник = СтрокаСотрудник.Сотрудник;
				НоваяСтрока.ВремяНачала = СтрокаТЧ.ВремяНачала;
				НоваяСтрока.ВремяОкончания = ?(СтрокаТЧ.ВремяОкончания = Дата(1, 1, 1, 0, 0, 0), Дата(1, 1, 1, 23, 59, 0), СтрокаТЧ.ВремяОкончания);
				НоваяСтрока.Дата = СтрокаТЧ.Дата;      
			КонецЕсли;
		КонецЦикла;
	КонецЦикла;	
КонецПроцедуры  

&НаСервере
Процедура ЗаполнитьПоПроизводственномуГрафикуСотрудникаНаСервере(Сотрудник)   
	Индекс = Объект.ГрафикРаботыСотрудников.Количество() - 1;
	Пока Индекс >= 0 Цикл
		Если Объект.ГрафикРаботыСотрудников[Индекс].Сотрудник = Сотрудник Тогда
			Объект.ГрафикРаботыСотрудников.Удалить(Индекс);
		КонецЕсли;
		Индекс = Индекс - 1;
	КонецЦикла;
	
	Для Каждого СтрокаТЧ Из ПроизводственныйГрафик Цикл
		Если ЗначениеЗаполнено(СтрокаТЧ.ВремяНачала) И ЗначениеЗаполнено(СтрокаТЧ.ВремяОкончания) Тогда
			НоваяСтрока = Объект.ГрафикРаботыСотрудников.Добавить(); 
			НоваяСтрока.Сотрудник = Сотрудник;
			НоваяСтрока.ВремяНачала = СтрокаТЧ.ВремяНачала;
			НоваяСтрока.ВремяОкончания = ?(СтрокаТЧ.ВремяОкончания = Дата(1, 1, 1, 0, 0, 0), Дата(1, 1, 1, 23, 59, 0), СтрокаТЧ.ВремяОкончания);
			НоваяСтрока.Дата = СтрокаТЧ.Дата;      
		КонецЕсли;
	КонецЦикла;
КонецПроцедуры

&НаСервере
Процедура ОтрисоватьТаблицуДанныеОВремени()     
	ТЗГрафикРаботыСотрудников = Объект.ГрафикРаботыСотрудников.Выгрузить();
	Если ТЗГрафикРаботыСотрудников.Количество() = 0 И Объект.Сотрудники.Количество() = 0 Тогда 
		ДанныеОВремени.Очистить();
		Возврат;
	КонецЕсли;   
	
	СохраненнаяДата = Неопределено;
	Если ТекущаяСтрокаТаблицыДанныеОВремени <> Неопределено
		И ТипЗнч(ТекущаяСтрокаТаблицыДанныеОВремени) = Тип("Структура")
		И ТекущаяСтрокаТаблицыДанныеОВремени.Свойство("Дата") Тогда
		СохраненнаяДата = ТекущаяСтрокаТаблицыДанныеОВремени.Дата;
	КонецЕсли;
	
	ТЗТаблицаДатМесяца = Новый ТаблицаЗначений;
	ТЗТаблицаДатМесяца.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата"));
	
	ДатаДляТаблицы = НачалоМесяца(Объект.МесяцГрафика);  
	ДатаСледующегоМесяца = НачалоМесяца(ДобавитьМесяц(ДатаДляТаблицы, 1));
	Пока ДатаДляТаблицы < ДатаСледующегоМесяца Цикл
		ТЗТаблицаДатМесяца.Добавить().Дата = ДатаДляТаблицы;
		ДатаДляТаблицы = ДатаДляТаблицы + 86400; // Прибавление 1 дня в секундах
	КонецЦикла; 
	
	ДанныеОВремени.Очистить(); 
	Запрос = Новый Запрос; 
	Запрос.Текст = 
	"ВЫБРАТЬ
	|	ТаблицаДатМесяца.Дата КАК Дата
	|ПОМЕСТИТЬ ВТТаблицаДатМесяца
	|ИЗ
	|	&ТаблицаДатМесяца КАК ТаблицаДатМесяца
	|;
	|
	|ВЫБРАТЬ
	|	ТаблицаСотрудников.Сотрудник КАК Сотрудник,
	|	ТаблицаСотрудников.ВремяНачала КАК ВремяНачала,
	|	ТаблицаСотрудников.ВремяОкончания КАК ВремяОкончания,
	|	ТаблицаСотрудников.Дата КАК Дата
	|ПОМЕСТИТЬ ВТТаблицаСотрудников
	|ИЗ
	|	&ТаблицаСотрудников КАК ТаблицаСотрудников
	|;
	|
	|ВЫБРАТЬ
	|	ВТТаблицаДатМесяца.Дата КАК Дата,
	|	ВТТаблицаСотрудников.Сотрудник КАК Сотрудник,
	|	ВТТаблицаСотрудников.ВремяНачала КАК ВремяНачала,
	|	ВТТаблицаСотрудников.ВремяОкончания КАК ВремяОкончания
	|ИЗ
	|	ВТТаблицаДатМесяца КАК ВТТаблицаДатМесяца
	|		ЛЕВОЕ СОЕДИНЕНИЕ ВТТаблицаСотрудников КАК ВТТаблицаСотрудников
	|		ПО ВТТаблицаДатМесяца.Дата = ВТТаблицаСотрудников.Дата
	|УПОРЯДОЧИТЬ ПО
	|	Дата,
	|	Сотрудник,
	|	ВремяНачала";
	
	Запрос.УстановитьПараметр("ТаблицаСотрудников", ТЗГрафикРаботыСотрудников); 
	Запрос.УстановитьПараметр("ТаблицаДатМесяца", ТЗТаблицаДатМесяца);
	
	РезультатЗапроса = Запрос.Выполнить();	
	Выборка = РезультатЗапроса.Выбрать();
	
	СтрокиПоДатам = Новый Соответствие;
	
	Пока Выборка.Следующий() Цикл
		ТекДата = Выборка.Дата; 
		ДниМесяцаСтр = Формат(ТекДата, "ДФ='dd ddd'");
		
		МассивСтрок = СтрокиПоДатам[ТекДата];
		Если МассивСтрок = Неопределено Тогда
			МассивСтрок = Новый Массив;
			НоваяСтрока = ДанныеОВремени.Добавить();
			НоваяСтрока.Дата = ТекДата;
			НоваяСтрока.ДниМесяца = ДниМесяцаСтр; 
			
			Отбор = Новый Структура("Дата", ТекДата);
			НайденныеСтроки = ПроизводственныйГрафик.НайтиСтроки(Отбор);
			НоваяСтрока.ВидДня = ?(НайденныеСтроки.Количество() > 0, НайденныеСтроки[0].ВидДня, Неопределено); 
			
			МассивСтрок.Добавить(НоваяСтрока);
			СтрокиПоДатам.Вставить(ТекДата, МассивСтрок);
		КонецЕсли;  
		
		Если Не ЗначениеЗаполнено(Выборка.Сотрудник) Тогда
			Продолжить;	
		КонецЕсли;
		
		Сотрудник = Выборка.Сотрудник;
		ИмяКолонки = ПолучитьИмяКолонкиПоСотруднику(Сотрудник);
		Если ПустаяСтрока(ИмяКолонки) Тогда
			Продолжить;
		КонецЕсли;
		
		Интервал = СтрШаблон("%1 - %2 (%3)", 
			?(ЗначениеЗаполнено(Выборка.ВремяНачала), Формат(Выборка.ВремяНачала, "ДФ=ЧЧ:мм"), "00:00"),
			?(ЗначениеЗаполнено(Выборка.ВремяОкончания), Формат(Выборка.ВремяОкончания, "ДФ=ЧЧ:мм"), "00:00"),
			ДниМесяцаСтр);
			
		СтрокаДляЗаписи = Неопределено;
		Для Каждого СтрокаДанных Из МассивСтрок Цикл
			Если ПустаяСтрока(СтрокаДанных[ИмяКолонки]) Тогда
				СтрокаДляЗаписи = СтрокаДанных;
				Прервать;
			КонецЕсли;
		КонецЦикла; 
		
		Если СтрокаДляЗаписи = Неопределено Тогда
			СтрокаДляЗаписи = ДанныеОВремени.Добавить(); 
			Отбор = Новый Структура("Дата", ТекДата);
			НайденныеСтроки = ПроизводственныйГрафик.НайтиСтроки(Отбор);
			
			СтрокаДляЗаписи.ВидДня = ?(НайденныеСтроки.Количество() > 0, НайденныеСтроки[0].ВидДня, Неопределено); 
			СтрокаДляЗаписи.Дата = ТекДата;
			МассивСтрок.Добавить(СтрокаДляЗаписи);
		КонецЕсли;
		
		СтрокаДляЗаписи[ИмяКолонки] = Интервал;
	КонецЦикла;	
	
	Если СохраненнаяДата <> Неопределено Тогда
		Для Каждого СтрокаДанных Из ДанныеОВремени Цикл 
			Если СохраненнаяДата = СтрокаДанных.Дата Тогда
				Элементы.ДанныеОВремени.ТекущаяСтрока = СтрокаДанных.ПолучитьИдентификатор();  
				Прервать;
			КонецЕсли;
		КонецЦикла;
	КонецЕсли;
	
	УстановитьУсловноеОформлениеДанныеОВремени();	
КонецПроцедуры

&НаСервере
Процедура УстановитьУсловноеОформлениеДанныеОВремени()
	УсловноеОформление.Элементы.Очистить();
	
	// Выходные дни (Суббота, Воскресенье)
	ЭлементОформления = УсловноеОформление.Элементы.Добавить(); 
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ЦветФона", Новый Цвет(245, 251, 255)); 
	
	ЭлементОтбора = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ДанныеОВремени.ВидДня");
	ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.ВСписке;
	
	СписокВидовДней = Новый СписокЗначений;
	СписокВидовДней.Добавить(Перечисления.ВидыДнейПроизводственногоКалендаря.Суббота);	
	СписокВидовДней.Добавить(Перечисления.ВидыДнейПроизводственногоКалендаря.Воскресенье);
	ЭлементОтбора.ПравоеЗначение = СписокВидовДней;  
	ЭлементОтбора.Использование = Истина;
	
	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("ДанныеОВремени");   
	
	// Праздничные дни
	ЭлементОформления = УсловноеОформление.Элементы.Добавить(); 
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ЦветФона", Новый Цвет(255, 225, 232)); 
	
	ЭлементОтбора = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ДанныеОВремени.ВидДня");
	ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно; 
	ЭлементОтбора.ПравоеЗначение = Перечисления.ВидыДнейПроизводственногоКалендаря.Праздник;  
	ЭлементОтбора.Использование = Истина;
	
	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("ДанныеОВремени");   
	
	// Нерабочие дни
	ЭлементОформления = УсловноеОформление.Элементы.Добавить(); 
	ЭлементОформления.Оформление.УстановитьЗначениеПараметра("ЦветФона", Новый Цвет(240, 240, 240)); 
	
	ЭлементОтбора = ЭлементОформления.Отбор.Элементы.Добавить(Тип("ЭлементОтбораКомпоновкиДанных"));
	ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("ДанныеОВремени.ВидДня");
	ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно; 
	ЭлементОтбора.ПравоеЗначение = Перечисления.ВидыДнейПроизводственногоКалендаря.Нерабочий;  
	ЭлементОтбора.Использование = Истина;
	
	ПолеОформления = ЭлементОформления.Поля.Элементы.Добавить();
	ПолеОформления.Поле = Новый ПолеКомпоновкиДанных("ДанныеОВремени");
КонецПроцедуры

&НаСервере
Процедура ЗадатьФорматТЧДанныеОВремени()
	Элементы.ДанныеОВремени.РастягиватьПоВертикали = Истина;
	Элементы.ДанныеОВремени.РастягиватьПоГоризонтали = Истина;
	Элементы.ДанныеОВремени.Ширина = 100;   
	
	Подчиненные = Элементы.ДанныеОВремени.ПодчиненныеЭлементы;
	Для Каждый ЭлементКолонка Из Подчиненные Цикл 
		Если ТипЗнч(ЭлементКолонка) = Тип("ПолеФормы") Тогда
			ЭлементКолонка.ТолькоПросмотр = Истина; 
			ЭлементКолонка.РежимРедактирования = РежимРедактированияКолонки.Непосредственно;
			ЭлементКолонка.ГоризонтальноеПоложениеВШапке = ГоризонтальноеПоложениеЭлемента.Центр; 
			ЭлементКолонка.ГоризонтальноеПоложение = ГоризонтальноеПоложениеЭлемента.Центр;
		КонецЕсли;
	КонецЦикла; 
КонецПроцедуры

&НаСервере
Функция ПолучитьКалендарь(Год)
	МассивКалендарей = ПолучитьМассивКалендарей(Год); 
	Возврат ?(МассивКалендарей.Количество() > 0, МассивКалендарей[0], Неопределено);
КонецФункции 

&НаСервере
Функция ПолучитьСписокЗначенийКалендарей(Год)
	СписокКалендарей = Новый СписокЗначений; 
	СписокКалендарей.ЗагрузитьЗначения(ПолучитьМассивКалендарей(Год)); 
	Возврат СписокКалендарей; 
КонецФункции

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

&НаСервере
Функция ДатаКакМесяцПодобратьДатуПоТексту(Текст, ДатаПоТексту = Неопределено)
	СписокВозврата = Новый СписокЗначений;
	ТекущийГод = Год(ТекущаяДатаСеанса());
	
	Если ПустаяСтрока(Текст) Тогда
		ДатаПоТексту = Дата(1, 1, 1);
		Возврат СписокВозврата;
	КонецЕсли;
	
	Подстроки = СтрРазделить(Текст, ".,-/\ ", Ложь);
	
	Если Подстроки.Количество() = 1 Тогда
		Если СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке(Текст) Тогда
			МесяцЧислом = Число(Текст);
			Если МесяцЧислом >= 1 И МесяцЧислом <= 12 Тогда
				ДатаПоТексту = Дата(ТекущийГод, МесяцЧислом, 1);
				ФорматДаты = ?(СтрДлина(Текст) = 1, "ДФ='М/гг'", "ДФ='ММ/гг'");
				СписокВозврата.Добавить(Формат(ДатаПоТексту, ФорматДаты));
			КонецЕсли;                
		Иначе
			СписокМесяцев = СписокМесяцевПоСтроке(Текст);
			Для Каждого Месяц Из СписокМесяцев Цикл
				ДатаПоТексту = Дата(ТекущийГод, Месяц, 1);
				СписокВозврата.Добавить(Формат(ДатаПоТексту, "ДФ='ММММ гггг'"));
			КонецЦикла;
		КонецЕсли;
		
	ИначеЕсли Подстроки.Количество() = 2 Тогда
		Если Не СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке(Подстроки[1]) Тогда
			Возврат СписокВозврата;
		КонецЕсли;
		
		ГодЧислом = Число(Подстроки[1]);
		ДлинаГода = СтрДлина(Подстроки[1]);
		
		Если ГодЧислом > 3000 Тогда
			Возврат СписокВозврата;
		КонецЕсли;
		
		Если ДлинаГода <= 1 Тогда
			ГодЧислом = Число(Лев(Формат(ТекущийГод, "ЧГ="), 3) + Подстроки[1]);
		ИначеЕсли ДлинаГода = 2 Тогда
			ГодЧислом = Число(Лев(Формат(ТекущийГод, "ЧГ="), 2) + Подстроки[1]);
		ИначеЕсли ДлинаГода = 3 Тогда
			ГодЧислом = Число(Лев(Формат(ТекущийГод, "ЧГ="), 1) + Подстроки[1]);
		КонецЕсли;                    
		
		Если ЗначениеЗаполнено(Подстроки[0]) И СтроковыеФункцииКлиентСервер.ТолькоЦифрыВСтроке(Подстроки[0]) Тогда
			МесяцЧислом = Число(Подстроки[0]);
			Если МесяцЧислом >= 1 И МесяцЧислом <= 12 Тогда
				ДатаПоТексту = Дата(ГодЧислом, МесяцЧислом, 1);
				СписокВозврата.Добавить(Формат(ДатаПоТексту, "ДФ='ММММ гггг'"));
			КонецЕсли;                
		Иначе
			СписокМесяцев = СписокМесяцевПоСтроке(Подстроки[0]);
			Для Каждого Месяц Из СписокМесяцев Цикл
				ДатаПоТексту = Дата(ГодЧислом, Месяц, 1);
				СписокВозврата.Добавить(Формат(ДатаПоТексту, "ДФ='ММММ гггг'"));
			КонецЦикла;
		КонецЕсли;
	КонецЕсли;
	
	Возврат СписокВозврата;
КонецФункции    

&НаСервере
Функция СписокМесяцевПоСтроке(Текст)
	СписокМесяцев = Новый СписокЗначений;
	МесяцыВозврата = Новый Массив;
	
	Для Счетчик = 1 По 12 Цикл
		СписокМесяцев.Добавить(Счетчик, Формат(Дата(2000, Счетчик, 1), "ДФ='ММММ'"));
		СписокМесяцев.Добавить(Счетчик, Формат(Дата(2000, Счетчик, 1), "ДФ='МММ'"));
	КонецЦикла;
	
	ИскомыйТекст = ВРег(СокрЛП(Текст));
	УникальныеЗначения = Новый Соответствие;
	
	Для Каждого ЭлементСписка Из СписокМесяцев Цикл
		Если Лев(ВРег(ЭлементСписка.Представление), СтрДлина(ИскомыйТекст)) = ИскомыйТекст Тогда
			УникальныеЗначения.Вставить(ЭлементСписка.Значение, Истина);
		КонецЕсли;
	КонецЦикла;
	
	Для Каждого КлючИЗначение Из УникальныеЗначения Цикл
		МесяцыВозврата.Добавить(КлючИЗначение.Ключ);
	КонецЦикла;
	
	Возврат МесяцыВозврата;
КонецФункции

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

```