Как прибавить рабочие дни к дате с исключением выходных и праздников в 1С?

Программист 1С v8.3 (Обычные формы) 1С:Документооборот IT и автоматизация бизнеса
← На главную

При расчете сроков исполнения задач, регламентных сроков согласования документов или контроля исполнения поручений разработчики часто сталкиваются с необходимостью прибавить к дате определенное количество именно рабочих дней. Стандартная функция языка запросов ДОБАВИТЬКДАТЕ прибавляет календарные дни и не умеет учитывать график работы, праздники и переносы выходных.

Разберем, как правильно реализовать подобный расчет с помощью производственного календаря, рассмотрим типовой алгоритм выборки через запрос, изучим штатные механизмы БСП и обратим внимание на важные ограничения каждого подхода.

Решение 1. Определение даты через запрос к производственному календарю (метод первых N записей)

Рассмотрим ситуацию, когда в конфигурации присутствует производственный календарь (например, регистр сведений РегламентированныйПроизводственныйКалендарь или его аналог). Задача состоит в том, чтобы взять дату отсчета и получить дату, отстоящую от нее ровно на N рабочих дней вперед.

Проанализируем суть метода: нам необходимо выбрать из календаря дни, которые идут строго позже начальной даты, отфильтровать только рабочие и предпраздничные дни, отсортировать их по возрастанию и ограничить результат количеством прибавляемых дней. Последняя полученная дата выборки и будет искомым рабочим днем.

Посмотрим на практический пример формирования и выполнения такого запроса:


ЧислоДней = 2;
ДатаНачала = НачалоДня(ТекущаяДата());

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

Запрос.УстановитьПараметр("ДатаНач", ДатаНачала);

Выборка = Запрос.Выполнить().Выбрать();
ИскомаяДата = ДатаНачала;

Пока Выборка.Следующий() Цикл
	ИскомаяДата = Выборка.ДатаКалендаря;
КонецЦикла;

Разберем по шагам ключевые детали этого запроса:

  1. Конструкция ВЫБРАТЬ ПЕРВЫЕ: задает ограничение на количество строк. Мы подставляем количество добавляемых дней в текст запроса, чтобы получить ровно заданный пул рабочих смен.
  2. Фильтрация видов дней: обязательно учитываем как рабочие, так и предпраздничные дни (так как предпраздничные дни являются сокращенными, но рабочими). Использование конструкции ЗНАЧЕНИЕ(...) избавляет от необходимости передавать ссылки на перечисления через параметры запроса.
  3. Обязательная сортировка УПОРЯДОЧИТЬ ПО: без явного указания сортировки платформа 1С и СУБД не гарантируют хронологический порядок возвращаемых записей, что приведет к неверному определению конечной даты.

Ограничения и подводные камни метода «ВЫБРАТЬ ПЕРВЫЕ»

Выясним причину, почему данный метод нельзя назвать универсальным для всех сценариев:

  1. Невозможность массового расчета в одном запросе: конструкция ВЫБРАТЬ ПЕРВЫЕ работает только для одного значения смещения и одной начальной точки. Если требуется в запросе рассчитать даты сразу для таблицы из сотен документов, этот метод не подойдет.
  2. Границы календаря: если на будущий год календарь еще не заполнен администратором базы, запрос вернет меньше записей, чем запрошено, и в качестве результата будет ошибочно возвращен последний заполненный день текущего года без генерации исключения.
  3. Смещение на 0 дней или в прошлое: запрос с конструкцией ВЫБРАТЬ ПЕРВЫЕ 0 вызовет синтаксическую ошибку либо вернет пустую выборку. Для вычитания рабочих дней условие необходимо менять на Календарь.ДатаКалендаря < &ДатаНач, а упорядочивание делать по убыванию (УБЫВ).
  4. Потеря времени: производственный календарь оперирует сутками (датами без времени). Если исходная точка содержала время (например, 17.11 15:30), то прибавление дней по календарю вернет начало дня (21.11 00:00:00). Время необходимо сохранять и восстанавливать отдельно.

Решение 2. Использование штатных функций БСП и типовых механизмов

Проанализируем архитектуру конфигураций на базе Библиотеки стандартных подсистем (включая «1С:Документооборот» и «1С:ЗУП»). В них нет необходимости переносить устаревшие регистры из старых версий конфигураций. Графики работы и календари уже встроены в типовой функционал.

Для работы с датами по календарю в БСП предназначен общий модуль КалендарныеГрафики. В нем реализована функция ДатаПоКалендарю, которая берет на себя все проверки заполненности и расчет смещения.

Посмотрим на пример вызова типовой функции:


ГрафикРаботы = Справочники.ПроизводственныеКалендари.НайтиПоНаименованию("Российская Федерация");
ДатаОт = ТекущаяДата();
КоличествоДней = 2;

КонечнаяДата = КалендарныеГрафики.ДатаПоКалендарю(ГрафикРаботы, ДатаОт, КоличествоДней, Истина);

Рассмотрим подробнее преимущества использования типовой функции:

  1. Автоматический контроль заполнения: четвертый параметр ВызыватьИсключение = Истина гарантирует, что если календарь на требуемый период не заполнен, система выдаст информативное сообщение об ошибке, а не запишет неверную дату.
  2. Корректная обработка направления смещения: типовая подсистема корректно обрабатывает отрицательные числа (сдвиг назад) и нулевое смещение.
  3. Поддержка сменных графиков: первым параметром может передаваться не только производственный календарь, но и конкретный график работы организации или подразделения (справочник Календари).

Решение 3. Пакетный расчет в запросе методом сквозной нумерации

Если стоит задача рассчитать даты для большого массива документов в едином запросе без вызова функций в цикле, применяют метод нумерации рабочих дней.

Разберем логику построения такого алгоритма:

  1. Во временную таблицу помещаются только рабочие дни календаря за весь интересующий интервал.
  2. Каждому рабочему дню присваивается порядковый номер (порядковый номер рабочего дня от начала отсчета). Это делается соединением календаря самого с собой по условию Календарь1.Дата >= Календарь2.Дата с подсчетом количества строк через КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ...).
  3. Исходная таблица соединяется с календарем по дате документа для получения начального номера рабочего дня.
  4. Результирующая дата находится вторым соединением по условию: ЦелевойКалендарь.НомерРабочегоДня = НачальныйКалендарь.НомерРабочегоДня + ИсходныеДанные.ДнейКДобавлению.

Такой подход обеспечивает высокую скорость работы СУБД, так как исключает динамическое формирование текста запроса и построчную обработку в циклах встроенного языка.

← На главную