При разработке систем учета задач, бизнес-процессов или напоминаний в 1С программисты часто сталкиваются с необходимостью построения отчетов по просроченным задачам за определенный период (например, за текущий месяц). При этом возникают две серьезные технические проблемы, которые на первый взгляд кажутся простыми, но требуют детального разбора:
РАЗНОСТЬДАТ считает календарные дни. Бизнесу же обычно требуется контролировать соблюдение сроков исключительно в рабочих днях, исключая выходные и государственные праздники.В этой статье мы подробно и по шагам разберем, как решить обе задачи с помощью эффективных запросов в 1С и механизмов Библиотеки стандартных подсистем (БСП).
Рассмотрим классическую ошибку начинающих разработчиков. Пытаясь выбрать задачи за октябрь, мы пишем следующее условие:
ГДЕ
Напоминания.Владелец.Дата МЕЖДУ &НачалоПериода И &КонецПериода
Такой подход полностью отсекает задачи, созданные, например, 30 сентября, даже если они активно решались в октябре и были просрочены именно в этом месяце. Чтобы отчет работал корректно, нам нужно применить математическое правило пересечения двух интервалов.
Пусть первый интервал — это наш отчетный период (выбранный месяц) с &НачалоПериода по &КонецПериода. Второй интервал — это время «жизни» задачи, от ДатаЗадачи до ДатаВыполнения.
Эти интервалы пересекаются тогда и только тогда, когда выполняется условие:
Дата начала задачи <= Конец отчетного периода И Дата окончания задачи >= Начало отчетного периода
Давайте проанализируем ситуацию, когда задача еще не выполнена. В этом случае у нее отсутствует фактическая дата завершения. В запросе мы должны обработать это условие, подставив вместо даты выполнения текущую дату или конец отчетного периода. На языке запросов 1С это условие будет выглядеть следующим образом:
ГДЕ
Напоминания.Владелец.Дата <= &КонецПериода
И ВЫБОР
КОГДА Напоминания.Выполнено
ТОГДА Напоминания.ДатаВыполнения
ИНАЧЕ &ТекущаяДата
КОНЕЦ >= &НачалоПериода
Благодаря такой конструкции в выборку попадут все задачи, которые были «активны» в выбранном месяце:
Использование функции РАЗНОСТЬДАТ(..., ..., ДЕНЬ) возвращает количество календарных дней. Если задача была создана в пятницу, а выполнена в следующий вторник, стандартная функция вернет 4 дня, хотя фактически на решение ушло всего 2 рабочих дня (понедельник и вторник).
Для точного расчета рабочих дней в запросах 1С используется левое соединение с производственным календарем. В типовых конфигурациях на платформе 1С:Предприятие 8 за это отвечает регистр сведений ДанныеПроизводственногоКалендаря.
Давайте разберем алгоритм подсчета рабочих дней по шагам:
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Календарь.Дата) для получения точного числа рабочих дней.Посмотрим на пример реализации данного алгоритма во временной таблице:
ВЫБРАТЬ
Напоминания.Владелец КАК Задача,
Напоминания.Владелец.Дата КАК ДатаЗадачи,
Напоминания.Выполнено КАК Выполнено,
Напоминания.ДатаВыполнения КАК ДатаВыполнения,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Календарь.Дата) КАК РабочихДнейВРаботе
ПОМЕСТИТЬ ВТ_ЗадачиСРабочимиДнями
ИЗ
Справочник.Напоминания КАК Напоминания
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДанныеПроизводственногоКалендаря КАК Календарь
ПО Календарь.ПроизводственныйКалендарь = &КалендарьРФ
И Календарь.ВидДня В (
ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий),
ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Предпраздничный)
)
// Интервал подсчета: от даты создания задачи до даты выполнения (или текущей даты)
И Календарь.Дата >= НАЧАЛОПЕРИОДА(Напоминания.Владелец.Дата, ДЕНЬ)
И Календарь.Дата <= НАЧАЛОПЕРИОДА(
ВЫБОР
КОГДА Напоминания.Выполнено ТОГДА Напоминания.ДатаВыполнения
ИНАЧЕ &ТекущаяДата
КОНЕЦ, ДЕНЬ)
ГДЕ
// Условие пересечения интервалов (задачи за выбранный период)
Напоминания.Владелец.Дата <= &КонецПериода
И ВЫБОР
КОГДА Напоминания.Выполнено ТОГДА Напоминания.ДатаВыполнения
ИНАЧЕ &ТекущаяДата
КОНЕЦ >= &НачалоПериода
СГРУППИРОВАТЬ ПО
Напоминания.Владелец,
Напоминания.Владелец.Дата,
Напоминания.Выполнено,
Напоминания.ДатаВыполнения
Важное примечание: Приведение дат к началу дня с помощью функции НАЧАЛОПЕРИОДА(..., ДЕНЬ) критически важно. Если этого не сделать, сравнение дат с временем (например, 15:30:00) с датами в календаре (которые всегда хранятся со временем 00:00:00) приведет к потере крайних дней в расчете.
Теперь объединим полученные знания и напишем финальный запрос, который решает задачу автора из темы форума. Нам нужно получить:
ВЫБРАТЬ
Задачи.Задача КАК Задача,
Задачи.ДатаЗадачи КАК ДатаЗадачи,
Задачи.Выполнено КАК Выполнено,
Задачи.ДатаВыполнения КАК ДатаВыполнения,
Задачи.РабочихДнейВРаботе КАК РабочихДнейВРаботе
ИЗ
ВТ_ЗадачиСРабочимиДнями КАК Задачи
ГДЕ
// Условие 1: Задача не выполнена (она считается просроченной, если открыта в отчетном периоде)
НЕ Задачи.Выполнено
// Условие 2: Задача выполнена, но процесс решения занял более 3 рабочих дней
ИЛИ (Задачи.Выполнено И Задачи.РабочихДнейВРаботе > 3)
УПОРЯДОЧИТЬ ПО
ДатаЗадачи
Этот комплексный запрос гарантирует, что ни одна переходящая задача не будет упущена, а расчет просрочки будет абсолютно точным и юридически корректным с точки зрения трудового законодательства.
Если вам не требуется упаковывать всю логику расчета исключительно в один тяжелый SQL-запрос (например, вы пишете обработку заполнения или формируете отчет программно), можно использовать готовые функции БСП из общего модуля КалендарныеГрафики. Это значительно упростит код и избавит от необходимости вручную прописывать левые соединения с производственным календарем.
Для расчета разницы дат в рабочих днях используем следующую функцию:
// Получаем количество рабочих дней между датами по производственному календарю РФ
КоличествоРабочихДней = КалендарныеГрафики.РазностьДатПоКалендарю(Справочники.ПроизводственныеКалендари.НайтиПоРеквизиту("Код", "РФ"), ДатаНачала, ДатаОкончания);
Если вам необходимо рассчитать точную дату дедлайна (прибавить 3 рабочих дня к дате создания задачи), используйте метод:
// Прибавляем 3 рабочих дня к дате создания задачи
ДатаДедлайна = КалендарныеГрафики.ПолучитьДатуПоКалендарю(Справочники.ПроизводственныеКалендари.НайтиПоРеквизиту("Код", "РФ"), ДатаСоздания, 3);
Если ТекущаяДата() > ДатаДедлайна Тогда
// Задача просрочена
КонецЕсли;
Использование готовых механизмов БСП повышает читаемость кода, упрощает его поддержку и минимизирует вероятность ошибок при изменении структуры метаданных в новых релизах платформы.