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

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

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

Ограничения вложенных запросов в 1С

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


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

Платформа выдаст ошибку или откажется понимать этот синтаксис. Выясним причину: язык запросов 1С не поддерживает использование подзапросов непосредственно в секции ВЫБРАТЬ. Это связано с тем, что платформа должна транслировать единый код на разные СУБД (PostgreSQL, MS SQL Server, Oracle и др.), используя универсальный механизм построения планов запросов. Конструкция вида SELECT t1.a, (SELECT COUNT(*) FROM t2 WHERE t1.id = t2.id) (коррелирующий подзапрос в списке выборки) в 1С недоступна.

Решение проблемы: используем ЛЕВОЕ СОЕДИНЕНИЕ

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

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

Посмотрим на пример правильного подхода:


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

Обратите внимание: мы считаем КОЛИЧЕСТВО(Календарь.ДатаКалендаря). Благодаря левому соединению, если праздников в периоде нет, агрегатная функция вернет 0, а не NULL, что избавит от необходимости использовать функцию ЕСТЬNULL() перед дальнейшими математическими операциями.

Альтернативное решение: типовые функции конфигураций

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

Например, в конфигурации УПП существует функция для получения дат по рабочему календарю. Хоть она и решает смежную задачу (сдвиг даты на N рабочих дней), рассмотрим ее код, чтобы понять общую логику работы с производственным календарем:


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

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

Важные методологические нюансы

Рассмотрим предметные особенности учета при подсчете праздников. В современных конфигурациях (например, ЗУП 3.1 или ERP) появилась поддержка региональных производственных календарей. Праздники теперь могут зависеть от региона сотрудника (к примеру, в Татарстане или Башкортостане есть свои нерабочие дни). Данные хранятся в регистре ДанныеПроизводственногоКалендаря, который имеет измерение с указанием конкретного календаря.

Также важно помнить статью 120 Трудового кодекса РФ: нерабочие праздничные дни, приходящиеся на период ежегодного основного или дополнительного оплачиваемого отпуска, в число календарных дней отпуска не включаются. Однако обычные выходные дни (суббота и воскресенье) из отпуска вычитать не нужно! Именно поэтому в условии нашего запроса мы жестко фильтруем виды дней на равенство значению Праздник, игнорируя обычные выходные.

← На главную