Merge branch 'feature/reporting_time_zone_support' into develop

This commit is contained in:
Martin Edenhofer 2013-06-07 16:04:37 +02:00
commit cdde6d2e51
14 changed files with 755 additions and 295 deletions

4
.gitignore vendored
View file

@ -12,7 +12,9 @@
# Ignore all logfiles and tempfiles. # Ignore all logfiles and tempfiles.
/log/*.log /log/*.log
/tmp/* /tmp/websocket/*
/tmp/cache/*
/tmp/pids/*
# Ignore .project files # Ignore .project files
/.project /.project

View file

@ -157,7 +157,7 @@ class App.ControllerForm extends App.Controller
# set value # set value
if @params if @params
# check if we have a references # check if we have a references
parts = attribute.name.split '::' parts = attribute.name.split '::'
if parts[0] && parts[1] if parts[0] && parts[1]
@ -201,8 +201,8 @@ class App.ControllerForm extends App.Controller
# build options list # build options list
if _.isEmpty(attribute.options) if _.isEmpty(attribute.options)
attribute.options = [ attribute.options = [
{ name: 'active', value: true } { name: 'active', value: true }
{ name: 'inactive', value: false } { name: 'inactive', value: false }
] ]
# update boolean types # update boolean types
@ -221,6 +221,27 @@ class App.ControllerForm extends App.Controller
else if attribute.tag is 'select' else if attribute.tag is 'select'
item = $( App.view('generic/select')( attribute: attribute ) ) item = $( App.view('generic/select')( attribute: attribute ) )
# timezone
else if attribute.tag is 'timezone'
attribute.options = []
timezones = App.Config.get('timezones')
# build list based on config
for timezone_value, timezone_diff of timezones
if timezone_diff > 0
timezone_diff = '+' + timezone_diff
item =
name: "#{timezone_value} (GMT#{timezone_diff})"
value: timezone_value
attribute.options.push item
# finde selected item of list
for record in attribute.options
if record.value is attribute.value
record.selected = 'selected'
item = $( App.view('generic/select')( attribute: attribute ) )
# select # select
else if attribute.tag is 'input_select' else if attribute.tag is 'input_select'
item = $('<div class="input_select"></div>') item = $('<div class="input_select"></div>')
@ -374,7 +395,7 @@ class App.ControllerForm extends App.Controller
onRemoveTag: onRemoveTag onRemoveTag: onRemoveTag
) )
siteUpdate(true) siteUpdate(true)
# update box size # update box size
App.Event.bind 'ui:rerender:content', => App.Event.bind 'ui:rerender:content', =>
siteUpdate(true) siteUpdate(true)
@ -493,7 +514,7 @@ class App.ControllerForm extends App.Controller
name = 'owner_id' name = 'owner_id'
if key is 'customer_id' if key is 'customer_id'
display = 'Customer' display = 'Customer'
name = 'customer_id' name = 'customer_id'
attribute_config = { attribute_config = {
name: attribute.name + '::tickets.' + name name: attribute.name + '::tickets.' + name
display: display display: display
@ -674,7 +695,7 @@ class App.ControllerForm extends App.Controller
) )
# itemSub.append('<a href=\"#\" class=\"icon-minus\"></a>') # itemSub.append('<a href=\"#\" class=\"icon-minus\"></a>')
item.find('.ticket_attribute_item').append( itemSub ) item.find('.ticket_attribute_item').append( itemSub )
# list of shown attributes # list of shown attributes
show = [] show = []
@ -793,7 +814,7 @@ class App.ControllerForm extends App.Controller
selected: true selected: true
disable: false disable: false
}, },
# { # {
# value: 'tag' # value: 'tag'
# name: 'Tag' # name: 'Tag'

View file

@ -1,5 +1,5 @@
class App.Sla extends App.Model class App.Sla extends App.Model
@configure 'Sla', 'name', 'first_response_time', 'update_time', 'close_time', 'condition', 'data', 'active' @configure 'Sla', 'name', 'first_response_time', 'update_time', 'close_time', 'condition', 'timezone', 'data', 'active'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: 'api/slas' @url: 'api/slas'
@configure_attributes = [ @configure_attributes = [
@ -8,7 +8,8 @@ class App.Sla extends App.Model
{ name: 'update_time', display: 'Update Time', tag: 'input', type: 'text', limit: 100, null: true, 'class': 'span4', note: 'In minutes, only business times are counted.' }, { name: 'update_time', display: 'Update Time', tag: 'input', type: 'text', limit: 100, null: true, 'class': 'span4', note: 'In minutes, only business times are counted.' },
{ name: 'close_time', display: 'Solution Time', tag: 'input', type: 'text', limit: 100, null: true, 'class': 'span4', note: 'In minutes, only business times are counted.' }, { name: 'close_time', display: 'Solution Time', tag: 'input', type: 'text', limit: 100, null: true, 'class': 'span4', note: 'In minutes, only business times are counted.' },
{ name: 'condition', display: 'Conditions where SLA is used', tag: 'ticket_attribute_selection', null: true, class: 'span4' }, { name: 'condition', display: 'Conditions where SLA is used', tag: 'ticket_attribute_selection', null: true, class: 'span4' },
{ { name: 'timezone', display: 'Timezone', tag: 'timezone', null: true, class: 'span4' },
{
name: 'data' name: 'data'
display: 'Business Times' display: 'Business Times'
tag: 'working_hour' tag: 'working_hour'

View file

@ -196,12 +196,28 @@ class ApplicationController < ActionController::Base
end end
def config_frontend def config_frontend
# config # config
config = {} config = {}
Setting.select('name').where( :frontend => true ).each { |setting| Setting.select('name').where( :frontend => true ).each { |setting|
config[setting.name] = Setting.get(setting.name) config[setting.name] = Setting.get(setting.name)
} }
# get all time zones
config['timezones'] = {}
TZInfo::Timezone.all.each { |t|
# ignore the following time zones
next if t.name =~ /^GMT/
next if t.name =~ /^Etc/
next if t.name =~ /^MET/
next if t.name =~ /^MST/
next if t.name =~ /^ROC/
next if t.name =~ /^ROK/
diff = t.current_period.utc_total_offset / 60 /60
config['timezones'][ t.name ] = diff
}
return config return config
end end

View file

@ -504,7 +504,7 @@ class Ticket < ApplicationModel
when Symbol, String when Symbol, String
require "ticket/number/#{adapter_name.to_s.downcase}" require "ticket/number/#{adapter_name.to_s.downcase}"
adapter = Ticket::Number.const_get("#{adapter_name.to_s.capitalize}") adapter = Ticket::Number.const_get("#{adapter_name.to_s.capitalize}")
else else
raise "Missing number_adapter '#{adapter_name}'" raise "Missing number_adapter '#{adapter_name}'"
end end
return adapter return adapter
@ -557,10 +557,6 @@ class Ticket < ApplicationModel
end end
} }
# get and set calendar settings
if sla_selected
TimeCalculation.config( sla_selected.data )
end
return sla_selected return sla_selected
end end
@ -609,13 +605,13 @@ class Ticket < ApplicationModel
# first response # first response
if sla_selected.first_response_time if sla_selected.first_response_time
self.first_response_escal_date = TimeCalculation.dest_time( created_at, sla_selected.first_response_time ) self.first_response_escal_date = TimeCalculation.dest_time( created_at, sla_selected.first_response_time, sla_selected.data, sla_selected.timezone )
# set ticket escalation # set ticket escalation
self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.first_response_escal_date, self.first_response ) self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.first_response_escal_date, self.first_response )
end end
if self.first_response# && !self.first_response_in_min if self.first_response# && !self.first_response_in_min
self.first_response_in_min = TimeCalculation.business_time_diff( self.created_at, self.first_response ) self.first_response_in_min = TimeCalculation.business_time_diff( self.created_at, self.first_response, sla_selected.data, sla_selected.timezone )
end end
# set sla time # set sla time
if sla_selected.first_response_time && self.first_response_in_min if sla_selected.first_response_time && self.first_response_in_min
@ -629,13 +625,13 @@ class Ticket < ApplicationModel
last_update = self.created_at last_update = self.created_at
end end
if sla_selected.update_time if sla_selected.update_time
self.update_time_escal_date = TimeCalculation.dest_time( last_update, sla_selected.update_time ) self.update_time_escal_date = TimeCalculation.dest_time( last_update, sla_selected.update_time, sla_selected.data, sla_selected.timezone )
# set ticket escalation # set ticket escalation
self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.update_time_escal_date, false ) self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.update_time_escal_date, false )
end end
if self.last_contact_agent if self.last_contact_agent
self.update_time_in_min = TimeCalculation.business_time_diff( self.created_at, self.last_contact_agent ) self.update_time_in_min = TimeCalculation.business_time_diff( self.created_at, self.last_contact_agent, sla_selected.data, sla_selected.timezone )
end end
# set sla time # set sla time
@ -646,13 +642,13 @@ class Ticket < ApplicationModel
# close time # close time
if sla_selected.close_time if sla_selected.close_time
self.close_time_escal_date = TimeCalculation.dest_time( self.created_at, sla_selected.close_time ) self.close_time_escal_date = TimeCalculation.dest_time( self.created_at, sla_selected.close_time, sla_selected.data, sla_selected.timezone )
# set ticket escalation # set ticket escalation
self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.close_time_escal_date, self.close_time ) self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.close_time_escal_date, self.close_time )
end end
if self.close_time# && !self.close_time_in_min if self.close_time# && !self.close_time_in_min
self.close_time_in_min = TimeCalculation.business_time_diff( self.created_at, self.close_time ) self.close_time_in_min = TimeCalculation.business_time_diff( self.created_at, self.close_time, sla_selected.data, sla_selected.timezone )
end end
# set sla time # set sla time
if sla_selected.close_time && self.close_time_in_min if sla_selected.close_time && self.close_time_in_min
@ -704,4 +700,4 @@ class Ticket < ApplicationModel
class Number class Number
end end
end end

View file

@ -0,0 +1,9 @@
class SlaTimezone < ActiveRecord::Migration
def up
add_column :slas, :timezone, :string, :limit => 50, :null => true
end
def down
remove_column :slas, :timezone
end
end

View file

@ -1794,6 +1794,20 @@ Translation.create_if_not_exists( :locale => 'de', :source => "Add", :target =>
Translation.create_if_not_exists( :locale => 'de', :source => "Call Outbound", :target => "Anruf ausgehend" ) Translation.create_if_not_exists( :locale => 'de', :source => "Call Outbound", :target => "Anruf ausgehend" )
Translation.create_if_not_exists( :locale => 'de', :source => "Call Inbound", :target => "Anruf eingehend" ) Translation.create_if_not_exists( :locale => 'de', :source => "Call Inbound", :target => "Anruf eingehend" )
Translation.create_if_not_exists( :locale => 'de', :source => "Loading...", :target => "Laden..." ) Translation.create_if_not_exists( :locale => 'de', :source => "Loading...", :target => "Laden..." )
Translation.create_if_not_exists( :locale => 'de', :source => "Work Disposition", :target => "Arbeitsverteilung" )
Translation.create_if_not_exists( :locale => 'de', :source => "Timezone", :target => "Zeitzone" )
Translation.create_if_not_exists( :locale => 'de', :source => "Business Times", :target => "Arbeitszeiten" )
Translation.create_if_not_exists( :locale => 'de', :source => "Day", :target => "Day" )
Translation.create_if_not_exists( :locale => 'de', :source => "Days", :target => "Days" )
Translation.create_if_not_exists( :locale => 'de', :source => "Hour", :target => "Stunde" )
Translation.create_if_not_exists( :locale => 'de', :source => "Hours", :target => "Stunden" )
Translation.create_if_not_exists( :locale => 'de', :source => "New SLA", :target => "Neuer SLA" )
Translation.create_if_not_exists( :locale => 'de', :source => "Conditions where SLA is used", :target => "Bedingungen bei denen der SLA verwendet wird" )
Translation.create_if_not_exists( :locale => 'de', :source => "First Response Time", :target => "Reaktionszeit" )
Translation.create_if_not_exists( :locale => 'de', :source => "Update Time", :target => "Aktuallisierungszeit" )
Translation.create_if_not_exists( :locale => 'de', :source => "Solution Time", :target => "Lösungszeit" )
Translation.create_if_not_exists( :locale => 'de', :source => "Add Attribute", :target => "Attribut hinzufügen" )
Translation.create_if_not_exists( :locale => 'de', :source => "", :target => "" )
#Translation.create_if_not_exists( :locale => 'de', :source => "", :target => "" ) #Translation.create_if_not_exists( :locale => 'de', :source => "", :target => "" )

View file

@ -1,99 +0,0 @@
module BusinessTime
class BusinessMinutes
def initialize(minutes)
@minutes = minutes
end
def ago
Time.zone ? before(Time.zone.now) : before(Time.now)
end
def from_now
Time.zone ? after(Time.zone.now) : after(Time.now)
end
def after(time)
after_time = Time.roll_forward(time)
# Step through the minutes, skipping over non-business minutes
days = @minutes / 60 / 24
hours = ( @minutes - ( days * 60 * 24 ) ) / 60
minutes = @minutes - ( (days * 60 * 24 ) + (hours * 60) )
if @minutes > 60 * 12
end
local_sec = @minutes * 60
loop = true
while (loop == true) do
a = after_time
if local_sec >= 60 * 60
after_time = after_time + 1.hour
else
after_time = after_time + 1.minute
end
# Ignore minutes before opening and after closing
if (after_time > Time.end_of_workday(after_time))
after_time = after_time + off_minutes
if local_sec < 60 * 60
after_time = after_time - 60
else
after_time = after_time - 60 * 60
end
next
end
# Ignore weekends and holidays
while !Time.workday?(after_time)
after_time = Time.beginning_of_workday(after_time + 1.day)
a = after_time
end
diff = after_time - a
local_sec = local_sec - diff
if local_sec <= 0
loop = false
next
end
end
after_time
end
alias_method :since, :after
def before(time)
before_time = Time.roll_forward(time)
# Step through the hours, skipping over non-business hours
@minutes.times do
before_time = before_time - 1.minute
# Ignore hours before opening and after closing
if (before_time < Time.beginning_of_workday(before_time))
before_time = before_time - off_minutes
end
# Ignore weekends and holidays
while !Time.workday?(before_time)
before_time = before_time - 1.day
end
end
before_time
end
private
def off_minutes
return @gap if @gap
if Time.zone
gap_end = Time.zone.parse(BusinessTime::Config.beginning_of_workday)
gap_begin = (Time.zone.parse(BusinessTime::Config.end_of_workday)-1.day)
else
gap_end = Time.parse(BusinessTime::Config.beginning_of_workday)
gap_begin = (Time.parse(BusinessTime::Config.end_of_workday) - 1.day)
end
@gap = gap_end - gap_begin
end
end
end

View file

@ -1,11 +0,0 @@
# hook into fixnum so we can say things like:
# 5.business_minutes.from_now
# 4.business_minutes.before(some_date_time)
class Fixnum
include BusinessTime
def business_minutes
BusinessMinutes.new(self)
end
alias_method :business_minute, :business_minutes
end

View file

@ -1,130 +0,0 @@
# Add workday and weekday concepts to the Time class
class Time
class << self
# Gives the time at the end of the workday, assuming that this time falls on a
# workday.
# Note: It pretends that this day is a workday whether or not it really is a
# workday.
def end_of_workday(day)
format = "%B %d %Y #{BusinessTime::Config.end_of_workday}"
Time.zone ? Time.zone.parse(day.strftime(format)) :
Time.parse(day.strftime(format))
end
# Gives the time at the beginning of the workday, assuming that this time
# falls on a workday.
# Note: It pretends that this day is a workday whether or not it really is a
# workday.
def beginning_of_workday(day)
format = "%B %d %Y #{BusinessTime::Config.beginning_of_workday}"
Time.zone ? Time.zone.parse(day.strftime(format)) :
Time.parse(day.strftime(format))
end
# True if this time is on a workday (between 00:00:00 and 23:59:59), even if
# this time falls outside of normal business hours.
def workday?(day)
Time.weekday?(day) &&
!BusinessTime::Config.holidays.include?(day.to_date)
end
# True if this time falls on a weekday.
def weekday?(day)
BusinessTime::Config.weekdays.include? day.wday
end
def before_business_hours?(time)
time < beginning_of_workday(time)
end
def after_business_hours?(time)
time > end_of_workday(time)
end
# Rolls forward to the next beginning_of_workday
# when the time is outside of business hours
def roll_forward(time)
if (Time.before_business_hours?(time) || !Time.workday?(time))
next_business_time = Time.beginning_of_workday(time)
elsif Time.after_business_hours?(time + 1)
next_business_time = Time.beginning_of_workday(time) + 1.day
else
next_business_time = time.clone
end
while !Time.workday?(next_business_time)
next_business_time += 1.day
end
next_business_time
end
end
end
class Time
def business_time_until(to_time)
# Make sure that we will calculate time from A to B "clockwise"
direction = 1
if self < to_time
time_a = self
time_b = to_time
else
time_a = to_time
time_b = self
direction = -1
end
# Align both times to the closest business hours
time_a = Time::roll_forward(time_a)
time_b = Time::roll_forward(time_b)
# If same date, then calculate difference straight forward
# if time_a.to_date == time_b.to_date
# result = time_b - time_a
# return result *= direction
# end
# Both times are in different dates
result = Time.parse(time_a.strftime('%Y-%m-%d ') + BusinessTime::Config.end_of_workday) - time_a # First day
result += time_b - Time.parse(time_b.strftime('%Y-%m-%d ') + BusinessTime::Config.beginning_of_workday) # Last day
# All days in between
#puts "--- #{time_a}-#{time_b} - #{direction}"
# duration_of_working_day = Time.parse(BusinessTime::Config.end_of_workday) - Time.parse(BusinessTime::Config.beginning_of_workday)
# result += (time_a.to_date.business_days_until(time_b.to_date) - 1) * duration_of_working_day
result = 0
# All days in between
time_c = time_a
while time_c.to_i < time_b.to_i do
end_of_workday = Time.end_of_workday(time_c)
if !Time.workday?(time_c)
time_c = Time.beginning_of_workday(time_c) + 1.day
# puts 'VACATIONS! ' + time_c.to_s
end
if time_c.to_date == time_b.to_date
if end_of_workday < time_b
result += end_of_workday - time_c
break
else
result += time_b - time_c
break
end
else
result += end_of_workday - time_c
time_c = Time::roll_forward(end_of_workday)
end
result += 1 if end_of_workday.to_s =~ /23:59:59/
end
# Make sure that sign is correct
result *= direction
end
end

View file

@ -1,46 +1,295 @@
require 'business_time'
require 'business_time/business_minutes'
require 'business_time/core_ext/fixnum_minute'
require 'business_time/core_ext/time_fix'
module TimeCalculation module TimeCalculation
def self.config(config) def self.working_hours(start_time, config, timezone)
BusinessTime::Config.beginning_of_workday = config['beginning_of_workday'] time_diff = 0
BusinessTime::Config.end_of_workday = config['end_of_workday'] if timezone
days = [] begin
['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].each {|day| time_diff = Time.parse(start_time.to_s).in_time_zone(timezone).utc_offset
if config[day] rescue Exception => e
days.push day.downcase.to_sym puts "ERROR: Can't fine tomezone #{timezone}"
puts e.inspect
puts e.backtrace
end
end
beginning_of_workday = Time.parse("1977-10-27 #{config['beginning_of_workday']}")
end_of_workday = Time.parse("1977-10-27 #{config['end_of_workday']}") - 3600
config_ok = false
working_hours = {}
[:Mon, :Tue, :Wed, :Thu, :Fri, :Sat, :Sun].each {|day|
working_hours[day] = []
if config[day.to_s] == true || config[day.to_s] == day.to_s
config_ok = true
(0..23).each {|hour|
time = Time.parse("1977-10-27 #{hour}:00:00")
if time >= beginning_of_workday && time <= end_of_workday
working_hours[day].push true
else
working_hours[day].push nil
end
}
end end
} }
BusinessTime::Config.work_week = days
holidays = [] if !config_ok
if config['holidays'] raise "sla config is invalid! " + config.inspect
config['holidays'].each {|holiday| end
date = Date.parse( holiday )
holidays.push date.to_date # shift working hours / if needed
if time_diff && time_diff != 0
hours_to_shift = (time_diff / 3600 ).round
move_items = {
:Mon => [],
:Tue => [],
:Wed => [],
:Thu => [],
:Fri => [],
:Sat => [],
:Sun => [],
}
(1..hours_to_shift).each {|count|
working_hours.each {|day, value|
if working_hours[day]
to_move = working_hours[day].shift
if day == :Mon
move_items[:Tue].push to_move
elsif day == :Tue
move_items[:Wed].push to_move
elsif day == :Wed
move_items[:Thu].push to_move
elsif day == :Thu
move_items[:Fri].push to_move
elsif day == :Fri
move_items[:Sat].push to_move
elsif day == :Sat
move_items[:Sun].push to_move
elsif day == :Sun
move_items[:Mon].push to_move
end
end
}
}
move_items.each {|day, value|
value.each {|item|
working_hours[day].push item
}
} }
end end
BusinessTime::Config.holidays = holidays return working_hours
end end
def self.business_time_diff(start_time, end_time) def self.business_time_diff(start_time, end_time, config, timezone = '')
if start_time.class == String if start_time.class == String
start_time = Time.parse( start_time.to_s + 'UTC' ) start_time = Time.parse( start_time.to_s + 'UTC' )
end end
if end_time.class == String if end_time.class == String
end_time = Time.parse( end_time.to_s + 'UTC' ) end_time = Time.parse( end_time.to_s + 'UTC' )
end end
diff = start_time.business_time_until( end_time ) / 60
working_hours = self.working_hours(start_time, config, timezone)
week_day_map = {
1 => :Mon,
2 => :Tue,
3 => :Wed,
4 => :Thu,
5 => :Fri,
6 => :Sat,
0 => :Sun,
}
count = 0
calculation = true
first_loop = true
while calculation do
week_day = start_time.wday
day = start_time.day
month = start_time.month
year = start_time.year
hour = start_time.hour
# check if it's vacation day
if config
if config['holidays']
if config['holidays'].include?("#{year}-#{month}-#{day}")
# jump to next day
start_time = start_time.beginning_of_day + 86400
next
end
end
end
# check if it's countable day
if working_hours[ week_day_map[week_day] ].empty?
# jump to next day
start_time = start_time.beginning_of_day + 86400
next
end
# fillup to first full hour
if first_loop
diff = end_time - start_time
if diff > 59 * 60
diff = start_time - start_time.beginning_of_hour
end
start_time += diff
# check if it's countable hour
if working_hours[ week_day_map[week_day] ][ hour ]
count += diff
end
end
first_loop = false
# loop to next hour
(hour..23).each { |next_hour|
# check if end time is lower
if start_time >= end_time
calculation = false
break
end
# check if end_time is within this hour
diff = end_time - start_time
if diff > 59 * 60
diff = 3600
end
# keep it in current day
if next_hour == 23
start_time += diff-1
else
start_time += diff
end
# check if it's business hour and count
if working_hours[ week_day_map[week_day] ][ next_hour ]
count += diff
end
}
# loop to next day
start_time = start_time.beginning_of_day + 86400
end
diff = count / 60
diff.round diff.round
end end
def self.dest_time(start_time, diff_in_min) def self.dest_time(start_time, diff_in_min, config, timezone = '')
if start_time.class == String if start_time.class == String
start_time = Time.parse( start_time.to_s + ' UTC' ) start_time = Time.parse( start_time.to_s + ' UTC' )
end end
dest_time = diff_in_min.round.business_minute.after( start_time )
return dest_time # loop
working_hours = self.working_hours(start_time, config, timezone)
week_day_map = {
1 => :Mon,
2 => :Tue,
3 => :Wed,
4 => :Thu,
5 => :Fri,
6 => :Sat,
0 => :Sun,
}
count = diff_in_min * 60
calculation = true
first_loop = true
while calculation do
week_day = start_time.wday
day = start_time.day
month = start_time.month
year = start_time.year
hour = start_time.hour
#puts "start outer loop #{start_time}-#{week_day}-#{year}-#{month}-#{day}-#{hour}|c#{count}"
# check if it's vacation day
if config
if config['holidays']
if config['holidays'].include?("#{year}-#{month}-#{day}")
# jump to next day
start_time = start_time.beginning_of_day + 86400
next
end
end
end
# check if it's countable day
if working_hours[ week_day_map[week_day] ].empty?
# jump to next day
start_time = start_time.beginning_of_day + 86400
next
end
# fillup to first full hour
if first_loop
# get rest of this hour
diff = 3600 - (start_time - start_time.beginning_of_hour)
start_time += diff
# check if it's countable hour
if working_hours[ week_day_map[week_day] ][ hour ]
count -= diff
end
# start on next hour of we moved to next
if diff != 0
hour += 1
end
end
first_loop = false
# loop to next hour
(hour..23).each { |next_hour|
diff = 3600
# check if count positiv
if count <= 0
calculation = false
break
end
# check if it's business hour and count
if working_hours[ week_day_map[week_day] ][ next_hour ]
# check if count is within this hour
if count > 59 * 60
diff = 3600
else
diff = count
end
count -= diff
end
# keep it in current day
if next_hour == 23
start_time += diff-1
else
start_time += diff
end
}
# check if count positiv
if count <= 0
calculation = false
break
end
# loop to next day
start_time = start_time.beginning_of_day + 86400
end
return start_time
end end
end end

View file

@ -1,6 +1,6 @@
# encoding: utf-8 # encoding: utf-8
require 'test_helper' require 'test_helper'
class TicketTest < ActiveSupport::TestCase class TicketTest < ActiveSupport::TestCase
test 'ticket create' do test 'ticket create' do
ticket = Ticket.create( ticket = Ticket.create(
@ -195,7 +195,7 @@ class TicketTest < ActiveSupport::TestCase
assert_equal( ticket.close_time_escal_date.gmtime.to_s, '2013-03-21 12:30:00 UTC', 'ticket.close_time_escal_date verify 3' ) assert_equal( ticket.close_time_escal_date.gmtime.to_s, '2013-03-21 12:30:00 UTC', 'ticket.close_time_escal_date verify 3' )
assert_equal( ticket.close_time_in_min, nil, 'ticket.close_time_in_min verify 3' ) assert_equal( ticket.close_time_in_min, nil, 'ticket.close_time_in_min verify 3' )
assert_equal( ticket.close_time_diff_in_min, nil, 'ticket.close_time_diff_in_min verify 3' ) assert_equal( ticket.close_time_diff_in_min, nil, 'ticket.close_time_diff_in_min verify 3' )
# set first reponse over time # set first reponse over time
ticket.update_attributes( ticket.update_attributes(
:first_response => '2013-03-21 14:00:00 UTC', :first_response => '2013-03-21 14:00:00 UTC',
@ -214,7 +214,7 @@ class TicketTest < ActiveSupport::TestCase
assert_equal( ticket.close_time_escal_date.gmtime.to_s, '2013-03-21 12:30:00 UTC', 'ticket.close_time_escal_date verify 4' ) assert_equal( ticket.close_time_escal_date.gmtime.to_s, '2013-03-21 12:30:00 UTC', 'ticket.close_time_escal_date verify 4' )
assert_equal( ticket.close_time_in_min, nil, 'ticket.close_time_in_min verify 4' ) assert_equal( ticket.close_time_in_min, nil, 'ticket.close_time_in_min verify 4' )
assert_equal( ticket.close_time_diff_in_min, nil, 'ticket.close_time_diff_in_min verify 4' ) assert_equal( ticket.close_time_diff_in_min, nil, 'ticket.close_time_diff_in_min verify 4' )
# set update time in time # set update time in time
ticket.update_attributes( ticket.update_attributes(
@ -432,4 +432,143 @@ class TicketTest < ActiveSupport::TestCase
delete = sla.destroy delete = sla.destroy
assert( delete, "sla destroy" ) assert( delete, "sla destroy" )
end end
end
test 'ticket sla + timezone' do
# cleanup
delete = Sla.destroy_all
assert( delete, "sla destroy_all" )
delete = Ticket.destroy_all
assert( delete, "ticket destroy_all" )
ticket = Ticket.create(
:title => 'some title äöüß',
:group => Group.lookup( :name => 'Users'),
:customer_id => 2,
:ticket_state => Ticket::State.lookup( :name => 'new' ),
:ticket_priority => Ticket::Priority.lookup( :name => '2 normal' ),
:created_at => '2013-03-21 09:30:00 UTC',
:updated_at => '2013-03-21 09:30:00 UTC',
:updated_by_id => 1,
:created_by_id => 1,
)
assert( ticket, "ticket created" )
assert_equal( ticket.escalation_time, nil, 'ticket.escalation_time verify' )
# set sla's for timezone "Europe/Berlin" wintertime (+1), so UTC times are 8:00-17:00
sla = Sla.create(
:name => 'test sla 1',
:condition => {},
:data => {
"Mon"=>"Mon", "Tue"=>"Tue", "Wed"=>"Wed", "Thu"=>"Thu", "Fri"=>"Fri", "Sat"=>"Sat", "Sun"=>"Sun",
"beginning_of_workday" => "9:00",
"end_of_workday" => "18:00",
},
:timezone => 'Europe/Berlin',
:first_response_time => 120,
:update_time => 180,
:close_time => 240,
:active => true,
:updated_by_id => 1,
:created_by_id => 1,
)
ticket = Ticket.find(ticket.id)
assert_equal( ticket.escalation_time.gmtime.to_s, '2013-03-21 11:30:00 UTC', 'ticket.escalation_time verify 1' )
assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-03-21 11:30:00 UTC', 'ticket.first_response_escal_date verify 1' )
assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-03-21 12:30:00 UTC', 'ticket.update_time_escal_date verify 1' )
assert_equal( ticket.close_time_escal_date.gmtime.to_s, '2013-03-21 13:30:00 UTC', 'ticket.close_time_escal_date verify 1' )
delete = sla.destroy
assert( delete, "sla destroy" )
delete = ticket.destroy
assert( delete, "ticket destroy" )
ticket = Ticket.create(
:title => 'some title äöüß',
:group => Group.lookup( :name => 'Users'),
:customer_id => 2,
:ticket_state => Ticket::State.lookup( :name => 'new' ),
:ticket_priority => Ticket::Priority.lookup( :name => '2 normal' ),
:created_at => '2013-10-21 09:30:00 UTC',
:updated_at => '2013-10-21 09:30:00 UTC',
:updated_by_id => 1,
:created_by_id => 1,
)
assert( ticket, "ticket created" )
assert_equal( ticket.escalation_time, nil, 'ticket.escalation_time verify' )
# set sla's for timezone "Europe/Berlin" summertime (+2), so UTC times are 7:00-16:00
sla = Sla.create(
:name => 'test sla 1',
:condition => {},
:data => {
"Mon"=>"Mon", "Tue"=>"Tue", "Wed"=>"Wed", "Thu"=>"Thu", "Fri"=>"Fri", "Sat"=>"Sat", "Sun"=>"Sun",
"beginning_of_workday" => "9:00",
"end_of_workday" => "18:00",
},
:timezone => 'Europe/Berlin',
:first_response_time => 120,
:update_time => 180,
:close_time => 240,
:active => true,
:updated_by_id => 1,
:created_by_id => 1,
)
ticket = Ticket.find(ticket.id)
assert_equal( ticket.escalation_time.gmtime.to_s, '2013-10-21 11:30:00 UTC', 'ticket.escalation_time verify 1' )
assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-10-21 11:30:00 UTC', 'ticket.first_response_escal_date verify 1' )
assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-10-21 12:30:00 UTC', 'ticket.update_time_escal_date verify 1' )
assert_equal( ticket.close_time_escal_date.gmtime.to_s, '2013-10-21 13:30:00 UTC', 'ticket.close_time_escal_date verify 1' )
delete = ticket.destroy
assert( delete, "ticket destroy" )
delete = sla.destroy
assert( delete, "sla destroy" )
ticket = Ticket.create(
:title => 'some title äöüß',
:group => Group.lookup( :name => 'Users'),
:customer_id => 2,
:ticket_state => Ticket::State.lookup( :name => 'new' ),
:ticket_priority => Ticket::Priority.lookup( :name => '2 normal' ),
:created_at => '2013-10-21 06:30:00 UTC',
:updated_at => '2013-10-21 06:30:00 UTC',
:updated_by_id => 1,
:created_by_id => 1,
)
assert( ticket, "ticket created" )
assert_equal( ticket.escalation_time, nil, 'ticket.escalation_time verify' )
# set sla's for timezone "Europe/Berlin" summertime (+2), so UTC times are 7:00-16:00
sla = Sla.create(
:name => 'test sla 1',
:condition => {},
:data => {
"Mon"=>"Mon", "Tue"=>"Tue", "Wed"=>"Wed", "Thu"=>"Thu", "Fri"=>"Fri", "Sat"=>"Sat", "Sun"=>"Sun",
"beginning_of_workday" => "9:00",
"end_of_workday" => "18:00",
},
:timezone => 'Europe/Berlin',
:first_response_time => 120,
:update_time => 180,
:close_time => 240,
:active => true,
:updated_by_id => 1,
:created_by_id => 1,
)
ticket = Ticket.find(ticket.id)
assert_equal( ticket.escalation_time.gmtime.to_s, '2013-10-21 09:00:00 UTC', 'ticket.escalation_time verify 1' )
assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-10-21 09:00:00 UTC', 'ticket.first_response_escal_date verify 1' )
assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-10-21 10:00:00 UTC', 'ticket.update_time_escal_date verify 1' )
assert_equal( ticket.close_time_escal_date.gmtime.to_s, '2013-10-21 11:00:00 UTC', 'ticket.close_time_escal_date verify 1' )
delete = sla.destroy
assert( delete, "sla destroy" )
delete = ticket.destroy
assert( delete, "ticket destroy" )
end
end

View file

@ -73,6 +73,8 @@ class WorkingTimeTest < ActiveSupport::TestCase
], ],
}, },
}, },
# test 5
{ {
:start => '2013-02-28 17:00:00', :start => '2013-02-28 17:00:00',
:end => '2013-02-28 23:59:59', :end => '2013-02-28 23:59:59',
@ -87,10 +89,158 @@ class WorkingTimeTest < ActiveSupport::TestCase
'end_of_workday' => '6:00 pm', 'end_of_workday' => '6:00 pm',
}, },
}, },
# test 6
{
:start => '2013-02-28 17:00:00',
:end => '2013-03-08 23:59:59',
:diff => 3660,
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 7
{
:start => '2012-02-28 17:00:00',
:end => '2013-03-08 23:59:59',
:diff => 160860,
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 8
{
:start => '2013-02-28 17:01:00',
:end => '2013-02-28 18:10:59',
:diff => 61,
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 9
{
:start => '2013-02-28 18:01:00',
:end => '2013-02-28 18:10:59',
:diff => 0,
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 10 / summertime
{
:start => '2013-02-28 18:01:00',
:end => '2013-02-28 18:10:59',
:diff => 0,
:timezone => 'Europe/Berlin',
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 11 / summertime
{
:start => '2013-02-28 17:01:00',
:end => '2013-02-28 17:10:59',
:diff => 0,
:timezone => 'Europe/Berlin',
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 12 / wintertime
{
:start => '2013-08-29 17:01:00',
:end => '2013-08-29 17:10:59',
:diff => 0,
:timezone => 'Europe/Berlin',
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 13 / summertime
{
:start => '2013-02-28 16:01:00',
:end => '2013-02-28 16:10:59',
:diff => 10,
:timezone => 'Europe/Berlin',
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 14 / wintertime
{
:start => '2013-08-29 16:01:00',
:end => '2013-08-29 16:10:59',
:diff => 0,
:timezone => 'Europe/Berlin',
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
] ]
tests.each { |test| tests.each { |test|
TimeCalculation.config( test[:config] ) diff = TimeCalculation.business_time_diff( test[:start], test[:end], test[:config], test[:timezone] )
diff = TimeCalculation.business_time_diff( test[:start], test[:end] )
assert_equal( diff, test[:diff], 'diff' ) assert_equal( diff, test[:diff], 'diff' )
} }
end end
@ -178,6 +328,7 @@ class WorkingTimeTest < ActiveSupport::TestCase
}, },
}, },
# test 6 # test 6
{ {
:start => '2012-12-17 08:00:00', :start => '2012-12-17 08:00:00',
@ -241,10 +392,112 @@ class WorkingTimeTest < ActiveSupport::TestCase
'end_of_workday' => '6:00 pm', 'end_of_workday' => '6:00 pm',
}, },
}, },
# test 11 / summertime
{
:start => '2013-03-08 21:20:15',
:dest_time => '2013-03-11 09:00:00',
:diff => 120,
:timezone => 'Europe/Berlin',
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 12 / wintertime
{
:start => '2013-09-06 21:20:15',
:dest_time => '2013-09-09 08:00:00',
:diff => 120,
:timezone => 'Europe/Berlin',
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 13 / wintertime
{
:start => '2013-10-21 06:30:00',
:dest_time => '2013-10-21 09:00:00',
:diff => 120,
:timezone => 'Europe/Berlin',
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '9:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 14 / wintertime
{
:start => '2013-10-21 04:34:15',
:dest_time => '2013-10-21 09:00:00',
:diff => 120,
:timezone => 'Europe/Berlin',
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '9:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 15 / wintertime
{
:start => '2013-10-20 22:34:15',
:dest_time => '2013-10-21 09:00:00',
:diff => 120,
:timezone => 'Europe/Berlin',
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '9:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 16 / wintertime
{
:start => '2013-10-21 07:00:15',
:dest_time => '2013-10-21 09:00:00',
:diff => 120,
:timezone => 'Europe/Berlin',
:config => {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '9:00 am',
'end_of_workday' => '6:00 pm',
},
},
] ]
tests.each { |test| tests.each { |test|
TimeCalculation.config( test[:config] ) dest_time = TimeCalculation.dest_time( test[:start] + ' UTC', test[:diff], test[:config], test[:timezone] )
dest_time = TimeCalculation.dest_time( test[:start] + ' UTC', test[:diff] )
assert_equal( dest_time.gmtime, Time.parse( test[:dest_time] + ' UTC' ), "dest time - #{test[:dest_time].to_s}" ) assert_equal( dest_time.gmtime, Time.parse( test[:dest_time] + ' UTC' ), "dest time - #{test[:dest_time].to_s}" )
} }
end end

0
tmp/pids/README Normal file
View file