При работе с календарем рабочего времени возникают 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-запросы в функции.
Идея Календарь рабочего времени с учетом обеденного перерыва набрала немало "+", поэтому могу доработать, если реально кому надо.
Авторизуйтесь, чтобы написать комментарий