Функция для расширенного использования календарей рабочего времени

14 2

При работе с календарем рабочего времени возникают 2 ситуации, которые не решаются "из коробки":

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

Предлагаемая функция позволяет решать эти ситуации.

 

Листинг функции UDL_GetRelativeDate( StartDate: Дата; Number: Целое число; NumberType: Целое число)

  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).

14
Авторизуйтесь, чтобы оценить материал.
1
Денис Архипов

а как быть с перерывом на обед? Не реализовывали такую возможность?

Александр Чугунов
а как быть с перерывом на обед? Не реализовывали такую возможность?

Денис, не вижу проблемы доработать решение:
1) добавить в табличную часть КРВ 4 реквизита (обед с, обед по, продолжительность до обеда, продолжительность после обеда)
2) изменить sql-запросы в функции.

Идея Календарь рабочего времени с учетом обеденного перерыва набрала немало "+", поэтому могу доработать, если реально кому надо.

Авторизуйтесь, чтобы написать комментарий