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