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