При разработке механизмов расчета плановых дат (например, времени реакции на заявку или срока исполнения задач) разработчики часто используют данные регистра сведений КалендарныеГрафики. Однако при добавлении определенного количества рабочих дней можно столкнуться с ситуацией, когда итоговое поле принимает значение NULL, хотя календарь заполнен на весь текущий год.
Рассмотрим подробно, почему возникает эта ошибка, проанализируем структуру запроса и разберем способы корректного расчета дат с учетом графиков работы.
Выясним причину такого поведения запроса. Типичная схема расчета даты по производственному календарю включает следующие шаги:
КоличествоДнейВГрафикеСНачалаГода.Проблема кроется в жестком ограничении по году при соединении таблиц:
ПО (ГОД(ВложенныйЗапрос.ДатаСоздания) = Вт_Календарь.Год)
И (ВложенныйЗапрос.КоличествоДнейВГрафикеСНачалаГода + ВложенныйЗапрос.ВремяРеагирования = Вт_Календарь.КоличествоДнейВГрафикеСНачалаГода)
Если сумма КоличествоДнейВГрафикеСНачалаГода + ВремяРеагирования превышает максимальное число рабочих дней в текущем году (например, при переносе даты с конца декабря на январь следующего года), запрос пытается найти в графике текущего года день с порядковым номером 366 или выше. Так как таких записей в пределах одного года не существует, левое соединение возвращает NULL.
Кроме того, в новом календарном году значение поля КоличествоДнейВГрафикеСНачалаГода сбрасывается и снова начинается с 1. Прямое прибавление дней без учета сброса нумерации в новом году не позволит найти правильную дату даже при снятии ограничения по полю Год.
Проанализируем базовый вариант решения в запросе. Если расчет требуется выполнять строго в пределах текущего года, необходимо определить максимальное количество дней в графике за год и добавить проверку выхода за границы периода.
Разберем пример запроса с определением признака ВРамкахГода:
ВЫБРАТЬ
ВложенныйЗапрос.Запрос КАК Запрос,
ВложенныйЗапрос.Услуга КАК Услуга,
ВложенныйЗапрос.ПлановоеВремя КАК ПлановоеВремя,
ВложенныйЗапрос.ДатаСоздания КАК ДатаСоздания,
ВложенныйЗапрос.ВремяРеагирования КАК ВремяРеагирования,
Вт_Календарь.КоличествоДнейВГрафикеСНачалаГода КАК КоличествоДнейВГрафикеСНачалаГода,
МаксимальноеЧислоДнейГрВгоду.МаксКоличествоДнейВГодуИзГрафика КАК МаксКоличествоДнейВГодуИзГрафика,
ВЫБОР
КОГДА Вт_Календарь.КоличествоДнейВГрафикеСНачалаГода + ВложенныйЗапрос.ВремяРеагирования <= МаксимальноеЧислоДнейГрВгоду.МаксКоличествоДнейВГодуИзГрафика
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК ВРамкахГода
ИЗ
(ВЫБРАТЬ
itilprofЗапросы.Ссылка КАК Запрос,
&Услуга КАК Услуга,
&ПлановоеВремяЗагрузкиСпециалиста КАК ПлановоеВремя,
itilprofЗапросы.ДатаСоздания КАК ДатаСоздания,
ВЫБОР
КОГДА НЕ &Услуга ЕСТЬ NULL
ТОГДА ВЫБОР
КОГДА НЕ &ПлановоеВремяЗагрузкиСпециалиста = 0
ТОГДА &ПлановоеВремяЗагрузкиСпециалиста
ИНАЧЕ 1
КОНЕЦ
ИНАЧЕ 1
КОНЕЦ КАК ВремяРеагирования
ИЗ
Документ.itilprofЗапросы КАК itilprofЗапросы
ГДЕ
itilprofЗапросы.Ссылка = &Ссылка) КАК ВложенныйЗапрос
ЛЕВОЕ СОЕДИНЕНИЕ Вт_Календарь КАК Вт_Календарь
ПО (ГОД(ВложенныйЗапрос.ДатаСоздания) = Вт_Календарь.Год)
И (НАЧАЛОПЕРИОДА(ВложенныйЗапрос.ДатаСоздания, ДЕНЬ) = Вт_Календарь.ДатаГрафика)
ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
МАКСИМУМ(Вт_Календарь.КоличествоДнейВГрафикеСНачалаГода) КАК МаксКоличествоДнейВГодуИзГрафика,
Вт_Календарь.Год КАК Год
ИЗ
Вт_Календарь КАК Вт_Календарь
СГРУППИРОВАТЬ ПО
Вт_Календарь.Год) КАК МаксимальноеЧислоДнейГрВгоду
ПО (ГОД(ВложенныйЗапрос.ДатаСоздания) = МаксимальноеЧислоДнейГрВгоду.Год)
Этот подход позволяет избежать некорректных данных и явно обработать ситуации, когда сдвиг даты выходит за пределы заполненного года.
Если необходимо корректно получать дату при переходе на следующий год исключительно средствами языка запросов 1С, рекомендуется использовать алгоритм со сквозной нумерацией:
АВТОНОМЕРЗАПИСИ() или соединение таблицы «самой с собой» по условию ДатаГрафика >= ДатаГрафика для формирования единого сквозного порядкового номера дня.СквознойНомерНачала + ВремяРеагирования = СквознойНомерЦелевойДаты.Обратите внимание на конструкцию прибавления времени дня. Вместо тройного вызова функции ДОБАВИТЬКДАТЕ для часов, минут и секунд:
ДОБАВИТЬКДАТЕ(ДОБАВИТЬКДАТЕ(ДОБАВИТЬКДАТЕ(ДатаГрафика, ЧАС, ЧАС(ДатаСоздания)), МИНУТА, МИНУТА(ДатаСоздания)), СЕКУНДА, СЕКУНДА(ДатаСоздания))
Эффективнее рассчитывать разницу в секундах от начала дня с помощью РАЗНОСТЬДАТ и прибавлять её за одну операцию:
ДОБАВИТЬКДАТЕ(Вт_Календарь.ДатаГрафика, СЕКУНДА, РАЗНОСТЬДАТ(НАЧАЛОПЕРИОДА(ВложенныйЗапрос.ДатаСоздания, ДЕНЬ), ВложенныйЗапрос.ДатаСоздания, СЕКУНДА))
Этот метод делает код запроса более читаемым и снижает нагрузку на СУБД.
При работе в конфигурациях на базе Библиотеки Стандартных Подсистем (БСП) перенос дат с учетом графиков, сменности, предпраздничных дней и переносов праздников лучше выполнять программно во встроенном языке.
Для этого в общем модуле КалендарныеГрафики предусмотрены готовые функции, которые автоматически учитывают переход через границы годов и праздничные дни. Запросом выбираются первичные данные, после чего в цикле вызывается программный расчет даты, что полностью исключает ошибки типа NULL.