Как в запросе 1С прибавить к дате рабочие дни с учетом производственного календаря?

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

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

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

Принцип расчета рабочих дней в запросе

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

Общая логика алгоритма строится следующим образом:

  1. Формируем список исходных дат и требуемых сроков отсрочки.
  2. Выбираем из календаря все рабочие дни в интервале от даты документа с запасом на величину отсрочки.
  3. Соединяем рабочие дни «сами на себя» по условию Дата1 <= Дата2, чтобы пронумеровать их или отобрать N-й по счету рабочий день.
  4. Отбираем дату, для которой количество предшествующих рабочих дней равно требуемому сроку отсрочки.

Вариант 1. Расчет через соединение уникальных пар «Дата + Отсрочка»

Рассмотрим рабочий и универсальный вариант запроса. Чтобы избежать тяжелых соединений на всей массе документов, сначала свернем исходные данные до уникальных пар Период и Отсрочка, рассчитаем для них даты оплаты и затем соединим с основной выборкой.

Посмотрим на пример текста запроса:


ВЫБРАТЬ
    ВТ_Документы.Период КАК Период,
    ВТ_Документы.ДнейОтсрочкиДолга КАК Отсрочка
ПОМЕСТИТЬ ИсходныеДаты
ИЗ
    ВТ_Документы КАК ВТ_Документы
ИНДЕКСИРОВАТЬ ПО
    Период,
    Отсрочка
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
    ИсходныеДаты.Период КАК Период,
    ИсходныеДаты.Отсрочка КАК Отсрочка,
    ДанныеКалендаря.ДатаКалендаря КАК РабочийДень
ПОМЕСТИТЬ СоединениеСРабочими
ИЗ
    РегистрСведений.РегламентированныйПроизводственныйКалендарь КАК ДанныеКалендаря
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ИсходныеДаты КАК ИсходныеДаты
        ПО ДанныеКалендаря.ДатаКалендаря >= ИсходныеДаты.Период
            И ДанныеКалендаря.ДатаКалендаря < ДОБАВИТЬКДАТЕ(ИсходныеДаты.Период, ДЕНЬ, ИсходныеДаты.Отсрочка * 2 + 15)
ГДЕ
    ДанныеКалендаря.ВидДня В (
        ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий),
        ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Предпраздничный)
    )
ИНДЕКСИРОВАТЬ ПО
    Период,
    Отсрочка
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    СоединениеСРабочими.Период КАК Период,
    СоединениеСРабочими.Отсрочка КАК Отсрочка,
    СоединениеСРабочими1.РабочийДень КАК ДатаОплаты
ПОМЕСТИТЬ ВТ_ДатыОплат
ИЗ
    СоединениеСРабочими КАК СоединениеСРабочими
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ СоединениеСРабочими КАК СоединениеСРабочими1
        ПО СоединениеСРабочими.Период = СоединениеСРабочими1.Период
            И СоединениеСРабочими.Отсрочка = СоединениеСРабочими1.Отсрочка
            И СоединениеСРабочими.РабочийДень <= СоединениеСРабочими1.РабочийДень
СГРУППИРОВАТЬ ПО
    СоединениеСРабочими.Период,
    СоединениеСРабочими.Отсрочка,
    СоединениеСРабочими1.РабочийДень
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ СоединениеСРабочими.РабочийДень) = СоединениеСРабочими.Отсрочка
ИНДЕКСИРОВАТЬ ПО
    Период,
    Отсрочка
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Документы.Ссылка КАК Документ,
    ВТ_Документы.Период КАК ДатаДокумента,
    ВТ_Документы.ДнейОтсрочкиДолга КАК Отсрочка,
    ВТ_ДатыОплат.ДатаОплаты КАК ДатаОплаты
ИЗ
    ВТ_Документы КАК ВТ_Документы
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ДатыОплат КАК ВТ_ДатыОплат
        ПО ВТ_Документы.Период = ВТ_ДатыОплат.Период
            И ВТ_Документы.ДнейОтсрочкиДолга = ВТ_ДатыОплат.Отсрочка

Важные детали и краевые условия

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

  1. Учет времени в дате документа: В производственном календаре даты хранятся без времени (начало дня, 00:00:00). Если дата документа содержит время (например, 14:30:00), прямое условие ДатаКалендаря >= ДатаДокумента отсечет текущий день, даже если он рабочий. Поэтому перед сравнением следует приводить дату к началу дня с помощью функции НАЧАЛОПЕРИОДА(Документ.Дата, ДЕНЬ).
  2. Отгрузки в выходные и праздничные дни: Если документ проведен в нерабочий день, важно определить логику: входит ли этот день в отсчет или отсчет начинается со следующего рабочего дня. Использование строгих или нестрогих неравенств в условиях соединения определяет поведение при таких сдвигах.
  3. Заполненность календаря: Если отсрочка переходит на следующий календарный год, производственный календарь на будущий период обязательно должен быть заполнен в базе, иначе запрос вернет NULL из-за отсутствия строк.

Альтернативный подход для отчетов на СКД

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

  1. В модуле отчета запросом получаем документы без расчета дат.
  2. Один раз загружаем рабочий календарь за нужный интервал в коллекцию (массив или ТаблицаЗначений).
  3. На встроенном языке 1С последовательным обходом вычисляем смещение даты для каждой строки документа за линейное время $O(N)$.
  4. Передаем готовую таблицу значений в схему компоновки данных через внешний набор данных.
← На главную