При работе с календарем рабочего времени возникают 2 ситуации, которые не решаются "из коробки":
1) GetRelativeDate() не позволяет использовать отрицательные смещения. Пример, когда это может понадобиться: в типовых маршрутах есть срок, который задается пользователем, и необходимо за 4 рабочих часа до этого срока отправить кому-то уведомление.
2) Для различных категорий работников необходимо использовать свои календари рабочего времени и, соответственно, относительные даты для них рассчитывать по своим календарям.
Предлагаемая функция позволяет решать эти ситуации.
CheckParam("StartDate"; StartDate; "Date") CheckParam("Number"; Number; "Integer") CheckParam("NumberType"; NumberType; "Integer:0..2") CalRef = CreateReference('КРВ';; false) CalDDS = CalRef.DetailDataSet(1) if NumberType = dothours Minutes = Number * 60 else Minutes = Number endif //Сформировать общую часть запроса, содержащую таблицу с только рабочими днями. InitQuery = Format(" DECLARE @StartDate DateTime; SET @StartDate = '%2:s'; with WorkDays as ( select caldds.%1:s as [date], caldds.%3:s as [begin], caldds.%4:s as [end], caldds.%5:s as [length] from MBAnalit calendar join MBAnValR caldds on calendar.Analit = caldds.Analit where caldds.Date " & IfThen(Number < 0; '<='; '>=') & " cast(@StartDate as date) and calendar.Vid = %0:s and calendar.Sost = 'Д' and caldds.%3:s is not null) "; ArrayOf( CalRef.ComponentID ; CalDDS.Requisites('ISBDate').SQLFieldName ; FormatDate("yymd h:n:s"; StartDate) ; CalDDS.Requisites('ISBFloatNumber').SQLFieldName ; CalDDS.Requisites('ISBFloatNumber2').SQLFieldName ; CalDDS.Requisites('ISBFloatNumber3').SQLFieldName)) if NumberType = dotdays //Запрос для NumberType = dotdays Query = Format(" %0:s select convert(nvarchar, numbered.[date],104) + ' ' + convert(nvarchar, @StartDate,108) from (select WorkDays.[date] as [date] ,ROW_NUMBER() OVER(ORDER BY [date] " & IfThen(Number < 0; 'DESC'; 'ASC') & ") as rownumber from WorkDays) numbered where rownumber = %1:s + 1" ; ArrayOf(InitQuery; Abs(Number))) else //Запрос для NumberType = dothours или dotminutes Query = Format(" %0:s , ToMinus as --уже прошло с начала рабочего дня до @StartDate, в минутах (select case when [begin]*60 < DATEPART(minute, @StartDate) + 60*DATEPART(hour, @StartDate) and [end]*60 > DATEPART(minute, @StartDate) + 60*DATEPART(hour, @StartDate) then " & IfThen(Number < 0; '[end]*60 - DATEPART(minute, @StartDate) - 60*DATEPART(hour, @StartDate)'; 'DATEPART(minute, @StartDate) + 60*DATEPART(hour, @StartDate) - [begin]*60') & " when [begin]*60 " & IfThen(Number < 0; '>'; '<') & " DATEPART(minute, @StartDate) + 60*DATEPART(hour, @StartDate) and [end]*60 " & IfThen(Number < 0; '>='; '<=') & " DATEPART(minute, @StartDate) + 60*DATEPART(hour, @StartDate) then [length]*60 else 0 end as [Minutes] from WorkDays where [Date] = cast(@StartDate as date)) select top 1 DATEADD(minute, " & IfThen(Number < 0; 'WorkDays.[begin]*60 + '; 'WorkDays.[end]*60 - ') & " (withsum.[sum] - %1:s), WorkDays.[date]) from WorkDays WorkDays outer apply (select sum(tmp.[length]*60) - IsNull((select top 1 ToMinus.[Minutes] from ToMinus),0) as [sum] from WorkDays tmp where tmp.[date] " & IfThen(Number < 0; '>='; '<=') & " WorkDays.[date]) withsum where withsum.[sum] > %1:s order by WorkDays.[date] " & IfThen(Number < 0; 'DESC'; 'ASC'); ArrayOf(InitQuery; Abs(Minutes))) endif //EditText(Query) Result = SQL(Query) if not Assigned(Result) Raise(CreateException('ESBNotEnoughtWorkTimeCalendarData'; "Не хватает данных в календаре рабочего времени"; ecException)) endif
Для реализации собственных календарей можно сделать копию справочника КРВ, добавить туда дополнительный разрез (или указать какой-либо ведущий справочник, например, "Тип календаря"). В функции, соответственно, надо будет поменять:
1) в строке CalRef = CreateReference('КРВ') заменить КРВ на имя собственного справочника;
2) передавать в функцию дополнительный параметр "Разрез" и использовать его для дополнительной фильтрации при получении таблицы WorkDays. Для ведущего справочника "Тип календаря" в функцию передаем ИД типа календаря и добавляем условие фильтрации типа:
... where caldds.Date " & IfThen(Number < 0; '<='; '>=') & " cast(@StartDate as date) and calendar.Vid = %0:s and calendar.Sost = 'Д' and caldds.%3:s is not null and calendar.HighLvl = <ИД типа календаря>) ...
Пакет разработки (лицензия MIT): UDL_GetRelativeDate()
Функция выполняет один SQL-запрос, поэтому работает очень быстро.
Функция не поддерживает работу с типом смещения "Секунды" (dotSeconds).
а как быть с перерывом на обед? Не реализовывали такую возможность?
Денис, не вижу проблемы доработать решение:
1) добавить в табличную часть КРВ 4 реквизита (обед с, обед по, продолжительность до обеда, продолжительность после обеда)
2) изменить sql-запросы в функции.
Идея Календарь рабочего времени с учетом обеденного перерыва набрала немало "+", поэтому могу доработать, если реально кому надо.
Авторизуйтесь, чтобы написать комментарий