В практике разработки на платформе 1С:Предприятие часто возникает задача определения даты предстоящего платежа или отгрузки, исходя из условий договора. Одной из самых коварных вариаций этой задачи является расчет даты с отсрочкой в рабочих днях, выполняемый непосредственно внутри запроса. Это критично для построения быстрых отчетов (например, по дебиторской задолженности), где недопустим расчет в цикле при обходе результата.
В рамках данной статьи мы разберем несколько проверенных методов реализации этой логики. Мы проанализируем ситуацию, когда необходимо учитывать только выходные дни (субботу и воскресенье), и выясним, как адаптировать решение под полноценный производственный календарь.
Это самый надежный и универсальный метод. Его суть заключается в том, что мы не пытаемся «вычислить» дату математически, а используем готовую таблицу-справочник. Рассмотрим подробнее, как это работает.
Представим, что у нас есть регистр сведений ПроизводственныйКалендарь. Для того чтобы расчет в запросе был мгновенным, нам нужно предварительно подготовить данные или использовать соединение. Основная идея — сопоставить каждой дате ее порядковый номер рабочего дня с начала времен.
Разберем по шагам алгоритм в запросе:
X.Проанализируем пример кода запроса, использующего временные таблицы для оптимизации:
ВЫБРАТЬ
Календарь.Дата КАК Дата,
АВТОНОМЕРЗАПИСИ() КАК НомерРабочегоДня
ПОМЕСТИТЬ ВТ_РабочиеДни
ИЗ
РегистрСведений.ПроизводственныйКалендарь КАК Календарь
ГДЕ
Календарь.ВидДня = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий)
ИНДЕКСИРОВАТЬ ПО
Дата,
НомерРабочегоДня;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Продажи.Ссылка КАК Документ,
Продажи.Дата КАК ДатаДокумента,
ВТ_Начало.НомерРабочегоДня + Продажи.СрокОтсрочки КАК ЦелевойНомер
ПОМЕСТИТЬ ВТ_РасчетНомеров
ИЗ
Документ.РеализацияТоваровУслуг КАК Продажи
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_РабочиеДни КАК ВТ_Начало
ПО Продажи.Дата = ВТ_Начало.Дата;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_Расчет.Документ,
ВТ_Конец.Дата КАК ДатаОплаты
ИЗ
ВТ_РасчетНомеров КАК ВТ_Расчет
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_РабочиеДни КАК ВТ_Конец
ПО ВТ_Расчет.ЦелевойНомер = ВТ_Конец.НомерРабочегоДня
Важный момент: Использование функции АВТОНОМЕРЗАПИСИ() или соединение таблицы саму с собой для генерации индекса позволяет превратить сложную календарную логику в простую арифметику.
Если задача ограничивается только отсечением выходных (суббота и воскресенье) и не требует учета праздничных дней, мы можем применить математическую формулу. Посмотрим на ситуацию: в каждой неделе 5 рабочих дней и 2 выходных. Значит, каждые полные 5 рабочих дней отсрочки превращаются в 7 календарных дней.
Рассмотрим формулу смещения. Пусть X — количество рабочих дней отсрочки.
Количество полных недель: Целое(X / 5).
Количество добавленных календарных дней: (Целое(X / 5) * 7) + (X % 5).
Однако здесь есть нюанс: если остаток дней (X % 5) при добавлении к начальной дате «перепрыгивает» через субботу или воскресенье, нужно добавить еще 2 дня.
В запросе 1С это реализуется через конструкцию ВЫБОР и функцию ДЕНЬНЕДЕЛИ. Разберем пример логики для отсрочки в 5 дней:
ВЫБРАТЬ
Продажи.Ссылка,
ВЫБОР
КОГДА ДЕНЬНЕДЕЛИ(Продажи.Дата) + 5 > 5
ТОГДА ДОБАВИТЬКДАТЕ(Продажи.Дата, ДЕНЬ, 7)
ИНАЧЕ ДОБАВИТЬКДАТЕ(Продажи.Дата, ДЕНЬ, 5)
КОНЕЦ КАК ДатаОплаты
ИЗ
Документ.РеализацияТоваровУслуг КАК Продажи
Для произвольного X формула становится значительно сложнее, так как требует учета того, на какой день недели выпал старт. Если X — переменная величина, лучше использовать Метод 1 или Метод 4.
Современные версии платформы 1С предоставляют мощный инструмент — оконные функции. Выясним, как они упрощают нашу жизнь. Нам больше не нужно строить громоздкие соединения для получения порядкового номера рабочего дня.
Мы можем использовать функцию ROW_NUMBER() непосредственно в запросе к календарю. Посмотрим на пример:
ВЫБРАТЬ
Календарь.Дата КАК Дата,
ПОРЯДКОВЫЙ_НОМЕР() ОБРАБОТКА (УПОРЯДОЧИТЬ ПО Календарь.Дата) КАК Индекс
ИЗ
РегистрСведений.ПроизводственныйКалендарь КАК Календарь
ГДЕ
Календарь.ВидДня = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий)
Этот подход идеологически похож на первый метод, но работает гораздо быстрее и читается проще. Мы получаем виртуальную таблицу, где каждый рабочий день имеет свой четкий индекс, и далее просто находим дату по индексу ИндексНачала + X.
Проанализируем ситуацию, когда у нас нет доступа к производственному календарю или он не заполнен, а расчет нужен «здесь и сейчас». Мы можем сгенерировать последовательность чисел (от 1 до, например, 30) и прибавить их к дате документа.
Рассмотрим по шагам:
ВТ_Числа.ВТ_Числа. Получаем для каждого документа набор строк с будущими датами: ДОБАВИТЬКДАТЕ(ДатаДок, ДЕНЬ, Число).ДЕНЬНЕДЕЛИ(БудущаяДата) равен 6 или 7.БудущуюДату, для которой количество рабочих дней, прошедших с даты документа, в точности равно X.Этот метод называется методом нарастающих итогов в запросе. Он довольно ресурсоемкий, поэтому его следует использовать осторожно на больших массивах данных.
Выполнение расчетов дат в запросах может существенно замедлить формирование отчета. Чтобы этого избежать, придерживайтесь следующих правил:
Дата. Это ускорит соединение в разы.X фиксирован для большинства клиентов, рассмотрите возможность добавления реквизита ДатаОплаты непосредственно в документ. Рассчитывайте его при записи документа на сервере, а в запросах просто считывайте готовое значение.ГрафикиРаботы вместо ПроизводственногоКалендаря.Подводя итог, можно сказать, что наиболее сбалансированным по производительности и сложности реализации является Метод 1 (индексация рабочего календаря). Он корректно обрабатывает и выходные, и праздники, и переносы дней, что невозможно реализовать чистой математикой без обращения к базе данных.