Как в запросе 1С выбрать задачи, переходящие между периодами, и рассчитать просрочку в рабочих днях?

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

При разработке систем учета задач, бизнес-процессов или напоминаний в 1С программисты часто сталкиваются с необходимостью построения отчетов по просроченным задачам за определенный период (например, за текущий месяц). При этом возникают две серьезные технические проблемы, которые на первый взгляд кажутся простыми, но требуют детального разбора:

  1. Проблема «переходящих» задач (пересечение интервалов): как правильно отобрать задачи, которые были начаты в прошлом месяце, а завершены в текущем (или еще выполняются), не потеряв их при жесткой фильтрации по дате создания?
  2. Расчет просрочки в рабочих днях: стандартная функция РАЗНОСТЬДАТ считает календарные дни. Бизнесу же обычно требуется контролировать соблюдение сроков исключительно в рабочих днях, исключая выходные и государственные праздники.

В этой статье мы подробно и по шагам разберем, как решить обе задачи с помощью эффективных запросов в 1С и механизмов Библиотеки стандартных подсистем (БСП).

Часть 1. Логика пересечения интервалов для переходящих задач

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


ГДЕ
    Напоминания.Владелец.Дата МЕЖДУ &НачалоПериода И &КонецПериода

Такой подход полностью отсекает задачи, созданные, например, 30 сентября, даже если они активно решались в октябре и были просрочены именно в этом месяце. Чтобы отчет работал корректно, нам нужно применить математическое правило пересечения двух интервалов.

Пусть первый интервал — это наш отчетный период (выбранный месяц) с &НачалоПериода по &КонецПериода. Второй интервал — это время «жизни» задачи, от ДатаЗадачи до ДатаВыполнения.

Эти интервалы пересекаются тогда и только тогда, когда выполняется условие:

Дата начала задачи <= Конец отчетного периода И Дата окончания задачи >= Начало отчетного периода

Давайте проанализируем ситуацию, когда задача еще не выполнена. В этом случае у нее отсутствует фактическая дата завершения. В запросе мы должны обработать это условие, подставив вместо даты выполнения текущую дату или конец отчетного периода. На языке запросов 1С это условие будет выглядеть следующим образом:


ГДЕ
    Напоминания.Владелец.Дата <= &КонецПериода
    И ВЫБОР 
        КОГДА Напоминания.Выполнено 
            ТОГДА Напоминания.ДатаВыполнения 
        ИНАЧЕ &ТекущаяДата 
    КОНЕЦ >= &НачалоПериода

Благодаря такой конструкции в выборку попадут все задачи, которые были «активны» в выбранном месяце:

Часть 2. Расчет разницы в рабочих днях внутри запроса

Использование функции РАЗНОСТЬДАТ(..., ..., ДЕНЬ) возвращает количество календарных дней. Если задача была создана в пятницу, а выполнена в следующий вторник, стандартная функция вернет 4 дня, хотя фактически на решение ушло всего 2 рабочих дня (понедельник и вторник).

Для точного расчета рабочих дней в запросах 1С используется левое соединение с производственным календарем. В типовых конфигурациях на платформе 1С:Предприятие 8 за это отвечает регистр сведений ДанныеПроизводственногоКалендаря.

Давайте разберем алгоритм подсчета рабочих дней по шагам:

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

Посмотрим на пример реализации данного алгоритма во временной таблице:


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

СГРУППИРОВАТЬ ПО
    Напоминания.Владелец,
    Напоминания.Владелец.Дата,
    Напоминания.Выполнено,
    Напоминания.ДатаВыполнения

Важное примечание: Приведение дат к началу дня с помощью функции НАЧАЛОПЕРИОДА(..., ДЕНЬ) критически важно. Если этого не сделать, сравнение дат с временем (например, 15:30:00) с датами в календаре (которые всегда хранятся со временем 00:00:00) приведет к потере крайних дней в расчете.

Часть 3. Сборка итогового запроса для вывода просроченных задач

Теперь объединим полученные знания и напишем финальный запрос, который решает задачу автора из темы форума. Нам нужно получить:

  1. Все невыполненные задачи за выбранный период.
  2. Задачи, срок выполнения которых превысил 3 рабочих дня с момента создания (включая переходящие задачи прошлого месяца).

ВЫБРАТЬ
    Задачи.Задача КАК Задача,
    Задачи.ДатаЗадачи КАК ДатаЗадачи,
    Задачи.Выполнено КАК Выполнено,
    Задачи.ДатаВыполнения КАК ДатаВыполнения,
    Задачи.РабочихДнейВРаботе КАК РабочихДнейВРаботе
ИЗ
    ВТ_ЗадачиСРабочимиДнями КАК Задачи
ГДЕ
    // Условие 1: Задача не выполнена (она считается просроченной, если открыта в отчетном периоде)
    НЕ Задачи.Выполнено
    
    // Условие 2: Задача выполнена, но процесс решения занял более 3 рабочих дней
    ИЛИ (Задачи.Выполнено И Задачи.РабочихДнейВРаботе > 3)
УПОРЯДОЧИТЬ ПО
    ДатаЗадачи

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

Часть 4. Альтернативное решение с использованием программного кода БСП

Если вам не требуется упаковывать всю логику расчета исключительно в один тяжелый SQL-запрос (например, вы пишете обработку заполнения или формируете отчет программно), можно использовать готовые функции БСП из общего модуля КалендарныеГрафики. Это значительно упростит код и избавит от необходимости вручную прописывать левые соединения с производственным календарем.

Для расчета разницы дат в рабочих днях используем следующую функцию:


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

Если вам необходимо рассчитать точную дату дедлайна (прибавить 3 рабочих дня к дате создания задачи), используйте метод:


// Прибавляем 3 рабочих дня к дате создания задачи
ДатаДедлайна = КалендарныеГрафики.ПолучитьДатуПоКалендарю(Справочники.ПроизводственныеКалендари.НайтиПоРеквизиту("Код", "РФ"), ДатаСоздания, 3);

Если ТекущаяДата() > ДатаДедлайна Тогда
    // Задача просрочена
КонецЕсли;

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

← На главную