Moved to biz for working time calculations. Moved to new sla model.

This commit is contained in:
Martin Edenhofer 2015-09-10 21:09:50 +02:00
parent 049833fc02
commit a9e65611fe
15 changed files with 459 additions and 1185 deletions

View file

@ -36,6 +36,8 @@ gem 'mail', '~> 2.5.0'
gem 'mime-types' gem 'mime-types'
gem 'biz'
gem 'delayed_job_active_record' gem 'delayed_job_active_record'
gem 'daemons' gem 'daemons'

View file

@ -1,6 +1,7 @@
GEM GEM
remote: http://rubygems.org/ remote: http://rubygems.org/
specs: specs:
abstract_type (0.0.7)
actionmailer (4.1.13) actionmailer (4.1.13)
actionpack (= 4.1.13) actionpack (= 4.1.13)
actionview (= 4.1.13) actionview (= 4.1.13)
@ -39,11 +40,19 @@ GEM
autoprefixer-rails (6.0.2) autoprefixer-rails (6.0.2)
execjs execjs
json json
biz (1.3.0)
abstract_type (~> 0.0.0)
clavius (~> 1.0)
equalizer (~> 0.0.0)
memoizable (~> 0.4.0)
tzinfo
browser (1.0.1) browser (1.0.1)
buftok (0.2.0) buftok (0.2.0)
builder (3.2.2) builder (3.2.2)
childprocess (0.5.6) childprocess (0.5.6)
ffi (~> 1.0, >= 1.0.11) ffi (~> 1.0, >= 1.0.11)
clavius (1.0.1)
memoizable (~> 0.4.0)
coderay (1.1.0) coderay (1.1.0)
coffee-rails (4.1.0) coffee-rails (4.1.0)
coffee-script (>= 2.2.0) coffee-script (>= 2.2.0)
@ -262,6 +271,7 @@ PLATFORMS
DEPENDENCIES DEPENDENCIES
activerecord-session_store activerecord-session_store
autoprefixer-rails (>= 5.2) autoprefixer-rails (>= 5.2)
biz
browser browser
coffee-rails coffee-rails
coffee-script-source coffee-script-source

View file

@ -2,3 +2,6 @@ class App.Calendar extends App.Model
@configure 'Calendar', 'name', 'timezone', 'default', 'business_hours', 'ical_url', 'public_holidays' @configure 'Calendar', 'name', 'timezone', 'default', 'business_hours', 'ical_url', 'public_holidays'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/calendars' @url: @apiPath + '/calendars'
displayName: ->
"#{@name} - #{@timezone}"

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', 'timezone', 'data', 'active', 'updated_at' @configure 'Sla', 'name', 'first_response_time', 'update_time', 'close_time', 'condition', 'calendar_id'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/slas' @url: @apiPath + '/slas'
@configure_attributes = [ @configure_attributes = [
@ -7,31 +7,8 @@ class App.Sla extends App.Model
{ name: 'first_response_time', display: 'First Response Time', tag: 'input', type: 'text', limit: 100, null: true, note: 'In minutes, only business times are counted.' }, { name: 'first_response_time', display: 'First Response Time', tag: 'input', type: 'text', limit: 100, null: true, note: 'In minutes, only business times are counted.' },
{ name: 'update_time', display: 'Update Time', tag: 'input', type: 'text', limit: 100, null: true, note: 'In minutes, only business times are counted.' }, { name: 'update_time', display: 'Update Time', tag: 'input', type: 'text', limit: 100, null: true, note: 'In minutes, only business times are counted.' },
{ name: 'close_time', display: 'Solution Time', tag: 'input', type: 'text', limit: 100, null: true, note: 'In minutes, only business times are counted.' }, { name: 'close_time', display: 'Solution Time', tag: 'input', type: 'text', limit: 100, null: true, note: 'In minutes, only business times are counted.' },
{ name: 'calendar_id', display: 'Calendar', tag: 'select', relation: 'Calendar', null: false },
{ name: 'condition', display: 'Conditions where SLA is used', tag: 'ticket_attribute_selection', null: true }, { name: 'condition', display: 'Conditions where SLA is used', tag: 'ticket_attribute_selection', null: true },
{ name: 'timezone', display: 'Timezone', tag: 'timezone', null: true },
{
name: 'data'
display: 'Business Times'
tag: 'working_hour'
default:
Mon: true
Tue: true
Wed: true
Thu: true
Fri: true
beginning_of_workday: '8:00'
end_of_workday: '18:00'
null: true
nulloption: true
translate: true
options:
customer: 'Customer'
state: 'State'
priority: 'Priority'
group: 'Group'
owner: 'Owner'
},
{ name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },

View file

@ -4,6 +4,10 @@ class Calendar < ApplicationModel
store :business_hours store :business_hours
store :public_holidays store :public_holidays
after_create :sync_default, :min_one_check
after_update :sync_default, :min_one_check
after_destroy :min_one_check
=begin =begin
get default calendar get default calendar
@ -100,11 +104,6 @@ returns
def self.timezones def self.timezones
list = {} list = {}
TZInfo::Timezone.all_country_zone_identifiers.each { |timezone| TZInfo::Timezone.all_country_zone_identifiers.each { |timezone|
# ignore the following time zones
#next if t.name =~ /^GMT/
#next if t.name =~ /^Etc/
#next if t.name !~ /\//
t = TZInfo::Timezone.get(timezone) t = TZInfo::Timezone.get(timezone)
diff = t.current_period.utc_total_offset / 60 / 60 diff = t.current_period.utc_total_offset / 60 / 60
list[ timezone ] = diff list[ timezone ] = diff
@ -194,4 +193,27 @@ returns
} }
events.sort.to_h events.sort.to_h
end end
private
# if changed calendar is default, set all others default to false
def sync_default
return if !default
Calendar.all.each {|calendar|
next if calendar.id == id
next if !calendar.default
calendar.default = false
calendar.save
}
end
# check if min one is set to default true
def min_one_check
Calendar.all.each {|calendar|
return if calendar.default
}
first = Calendar.first
first.default = true
first.save
end
end end

View file

@ -5,6 +5,8 @@ class Sla < ApplicationModel
store :data store :data
validates :name, presence: true validates :name, presence: true
belongs_to :calendar
after_create :escalation_calculation_rebuild after_create :escalation_calculation_rebuild
after_update :escalation_calculation_rebuild after_update :escalation_calculation_rebuild
after_destroy :escalation_calculation_rebuild after_destroy :escalation_calculation_rebuild

View file

@ -14,9 +14,9 @@ returns
=end =end
def self.rebuild_all def self.rebuild_all
state_list_open = Ticket::State.by_category( 'open' ) state_list_open = Ticket::State.by_category('open')
tickets = Ticket.where( state_id: state_list_open ) tickets = Ticket.where(state_id: state_list_open)
tickets.each(&:escalation_calculation) tickets.each(&:escalation_calculation)
end end
@ -37,117 +37,119 @@ returns
# set escalation off if ticket is already closed # set escalation off if ticket is already closed
state = Ticket::State.lookup( id: state_id ) state = Ticket::State.lookup( id: state_id )
escalation_disabled = false
if state.ignore_escalation? if state.ignore_escalation?
escalation_disabled = true
# nothing to change
return true if !escalation_time
self.escalation_time = nil
# self.first_response_escal_date = nil
# self.close_time_escal_date = nil
self.callback_loop = true
save
return true
end end
# get sla for ticket # get sla for ticket
sla_selected = escalation_calculation_get_sla calendar = nil
sla = escalation_calculation_get_sla
if sla
calendar = sla.calendar
end
# reset escalation if no sla is set # if no escalation is enabled
if !sla_selected if !sla
# nothing to change # nothing to change
return true if !escalation_time return true if !escalation_time
self.escalation_time = nil self.escalation_time = nil
# self.first_response_escal_date = nil
# self.close_time_escal_date = nil
self.callback_loop = true self.callback_loop = true
save save
return true return true
end end
# puts sla_selected.inspect # reset escalation attributes
# puts days.inspect
self.escalation_time = nil self.escalation_time = nil
self.first_response_escal_date = nil self.first_response_escal_date = nil
self.update_time_escal_date = nil self.update_time_escal_date = nil
self.close_time_escal_date = nil self.close_time_escal_date = nil
# first response biz = Biz::Schedule.new do |config|
if sla_selected.first_response_time config.hours = calendar.business_hours.symbolize_keys
#config.holidays = [Date.new(2014, 1, 1), Date.new(2014, 12, 25)]
# get escalation date without pending time config.time_zone = calendar.timezone
self.first_response_escal_date = TimeCalculation.dest_time( created_at, sla_selected.first_response_time, sla_selected.data, sla_selected.timezone )
# get pending time between created and first response escal. time
time_in_pending = escalation_suspend( created_at, first_response_escal_date, 'relative', sla_selected, sla_selected.first_response_time )
# get new escalation time (original escal_date + time_in_pending)
self.first_response_escal_date = TimeCalculation.dest_time( first_response_escal_date, time_in_pending.to_i, sla_selected.data, sla_selected.timezone )
# set ticket escalation
self.escalation_time = calculation_higher_time( escalation_time, first_response_escal_date, first_response )
end end
# fist response
# calculate first response escalation
if sla.first_response_time
self.first_response_escal_date = biz.time(sla.first_response_time, :minutes).after(created_at)
pending_time = pending_minutes(created_at, first_response_escal_date, biz)
if pending_time && pending_time > 0
self.first_response_escal_date = biz.time(pending_time, :minutes).after(first_response_escal_date)
end
end
# get response time in min
if first_response if first_response
self.first_response_in_min = pending_minutes(created_at, first_response, biz, 'business_minutes')
# get response time in min between created and first response else
self.first_response_in_min = escalation_suspend( created_at, first_response, 'real', sla_selected ) self.escalation_time = first_response_escal_date
end end
# set time to show if sla is raised ot in # set time to show if sla is raised or not
if sla_selected.first_response_time && first_response_in_min if sla.first_response_time && first_response_in_min
self.first_response_diff_in_min = sla_selected.first_response_time - first_response_in_min self.first_response_diff_in_min = sla.first_response_time - first_response_in_min
end end
# update time # update time
# calculate escalation
last_update = last_contact_agent last_update = last_contact_agent
if !last_update if !last_update
last_update = created_at last_update = created_at
end end
if sla_selected.update_time if sla.update_time
self.update_time_escal_date = TimeCalculation.dest_time( last_update, sla_selected.update_time, sla_selected.data, sla_selected.timezone ) self.update_time_escal_date = biz.time(sla.update_time, :minutes).after(last_update)
pending_time = pending_minutes(last_update, update_time_escal_date, biz)
# get pending time between created and update escal. time if pending_time && pending_time > 0
time_in_pending = escalation_suspend( last_update, update_time_escal_date, 'relative', sla_selected, sla_selected.update_time ) self.update_time_escal_date = biz.time(pending_time, :minutes).after(update_time_escal_date)
# get new escalation time (original escal_date + time_in_pending)
self.update_time_escal_date = TimeCalculation.dest_time( update_time_escal_date, time_in_pending.to_i, sla_selected.data, sla_selected.timezone )
# set ticket escalation
self.escalation_time = calculation_higher_time( escalation_time, update_time_escal_date, false )
end end
end
if (!self.escalation_time && self.update_time_escal_date) || self.update_time_escal_date < self.escalation_time
self.escalation_time = self.update_time_escal_date
end
# get update time in min
if last_contact_agent if last_contact_agent
self.update_time_in_min = TimeCalculation.business_time_diff( created_at, last_contact_agent, sla_selected.data, sla_selected.timezone ) self.update_time_in_min = pending_minutes(created_at, last_contact_agent, biz, 'business_minutes')
end end
# set sla time # set sla time
if sla_selected.update_time && update_time_in_min if sla.update_time && update_time_in_min
self.update_time_diff_in_min = sla_selected.update_time - update_time_in_min self.update_time_diff_in_min = sla.update_time - update_time_in_min
end end
# close time # close time
if sla_selected.close_time # calculate close time escalation
if sla.close_time
# get escalation date without pending time self.close_time_escal_date = biz.time(sla.close_time, :minutes).after(created_at)
self.close_time_escal_date = TimeCalculation.dest_time( created_at, sla_selected.close_time, sla_selected.data, sla_selected.timezone ) pending_time = pending_minutes(created_at, first_response_escal_date, biz)
if pending_time && pending_time > 0
# get pending time between created and close escal. time self.close_time_escal_date = biz.time(pending_time, :minutes).after(close_time_escal_date)
extended_escalation = escalation_suspend( created_at, close_time_escal_date, 'relative', sla_selected, sla_selected.close_time )
# get new escalation time (original escal_date + time_in_pending)
self.close_time_escal_date = TimeCalculation.dest_time( close_time_escal_date, extended_escalation.to_i, sla_selected.data, sla_selected.timezone )
# set ticket escalation
self.escalation_time = calculation_higher_time( escalation_time, close_time_escal_date, close_time )
end end
if close_time # && !self.close_time_in_min
self.close_time_in_min = escalation_suspend( created_at, close_time, 'real', sla_selected )
end end
# set sla time
if sla_selected.close_time && close_time_in_min # get close time in min
self.close_time_diff_in_min = sla_selected.close_time - close_time_in_min if close_time
self.close_time_in_min = pending_minutes(created_at, close_time, biz, 'business_minutes')
else
if (!self.escalation_time && self.close_time_escal_date) || self.close_time_escal_date < self.escalation_time
if !escalation_disabled
self.escalation_time = self.close_time_escal_date
end
end
end
# set time to show if sla is raised or not
if sla.close_time && close_time_in_min
self.close_time_diff_in_min = sla.close_time - close_time_in_min
end
if escalation_disabled
self.escalation_time = nil
end end
return if !self.changed? return if !self.changed?
@ -173,7 +175,7 @@ returns
sla_selected = nil sla_selected = nil
sla_list = Cache.get( 'SLA::List::Active' ) sla_list = Cache.get( 'SLA::List::Active' )
if sla_list.nil? if sla_list.nil?
sla_list = Sla.where( active: true ) sla_list = Sla.all
Cache.write( 'SLA::List::Active', sla_list, { expires_in: 1.hour } ) Cache.write( 'SLA::List::Active', sla_list, { expires_in: 1.hour } )
end end
sla_list.each {|sla| sla_list.each {|sla|
@ -208,24 +210,18 @@ returns
private private
#type could be: # get business minutes of pending time
# real - time without supsend state # type = business_minutes (pending time in business minutes)
# relative - only suspend time # type = non_business_minutes (pending time in non business minutes)
def pending_minutes(start_time, end_time, biz, type = 'non_business_minutes')
def escalation_suspend (start_time, end_time, type, sla_selected, sla_time = 0) working_time_in_min = 0
if type == 'relative' total_time_in_min = 0
end_time += sla_time * 60
end
total_time_without_pending = 0
total_time = 0
#get history for ticket
history_list = history_get
#loop through hist. changes and get time
last_state = nil last_state = nil
last_state_change = nil last_state_change = nil
last_state_is_pending = false last_state_is_pending = false
history_list.each { |history_item| pending_minutes = 0
history_get.each { |history_item|
# ignore if it isn't a state change # ignore if it isn't a state change
next if !history_item['attribute'] next if !history_item['attribute']
@ -256,14 +252,14 @@ returns
counted = false counted = false
end end
diff = escalation_time_diff( last_state_change, history_item['created_at'], sla_selected ) diff = biz.within(last_state_change, history_item['created_at']).in_minutes
if counted if counted
# puts "Diff count #{history_item['value_from']} -> #{history_item['value_to']} / #{last_state_change} -> #{history_item['created_at']}" # puts "Diff count #{history_item['value_from']} -> #{history_item['value_to']} / #{last_state_change} -> #{history_item['created_at']}"
total_time_without_pending = total_time_without_pending + diff working_time_in_min = working_time_in_min + diff
# else # else
# puts "Diff not count #{history_item['value_from']} -> #{history_item['value_to']} / #{last_state_change} -> #{history_item['created_at']}" # puts "Diff not count #{history_item['value_from']} -> #{history_item['value_to']} / #{last_state_change} -> #{history_item['created_at']}"
end end
total_time = total_time + diff total_time_in_min = total_time_in_min + diff
if history_item['value_to'] == 'pending reminder' if history_item['value_to'] == 'pending reminder'
last_state_is_pending = true last_state_is_pending = true
@ -278,45 +274,23 @@ returns
# if last state isnt pending, count rest # if last state isnt pending, count rest
if !last_state_is_pending && last_state_change && last_state_change < end_time if !last_state_is_pending && last_state_change && last_state_change < end_time
diff = escalation_time_diff( last_state_change, end_time, sla_selected ) diff = biz.within(last_state_change, end_time).in_minutes
# puts "Diff count last state was not pending #{diff.to_s} - #{last_state_change} - #{end_time}" working_time_in_min = working_time_in_min + diff
total_time_without_pending = total_time_without_pending + diff total_time_in_min = total_time_in_min + diff
total_time = total_time + diff
end end
# if we have not had any state change # if we have not had any state change
if !last_state_change if !last_state_change
diff = escalation_time_diff( start_time, end_time, sla_selected ) diff = biz.within(start_time, end_time).in_minutes
# puts 'Diff state has not changed ' + diff.to_s working_time_in_min = working_time_in_min + diff
total_time_without_pending = total_time_without_pending + diff total_time_in_min = total_time_in_min + diff
total_time = total_time + diff
end end
#return sum #puts "#{type}:working_time_in_min:#{working_time_in_min}|free_time:#{total_time_in_min - working_time_in_min}"
if type == 'real' if type == 'non_business_minutes'
return total_time_without_pending return total_time_in_min - working_time_in_min
elsif type == 'relative'
relative = total_time - total_time_without_pending
return relative
else
fail "ERROR: Unknown type #{type}"
end end
working_time_in_min
end end
def escalation_time_diff( start_time, end_time, sla_selected )
if sla_selected
diff = TimeCalculation.business_time_diff( start_time, end_time, sla_selected.data, sla_selected.timezone)
else
diff = TimeCalculation.business_time_diff( start_time, end_time )
end
diff
end
def calculation_higher_time(escalation_time, check_time, done_time)
return escalation_time if done_time
return check_time if !escalation_time
return escalation_time if !check_time
return check_time if escalation_time > check_time
escalation_time
end
end end

View file

@ -50,7 +50,7 @@ returns:
check if state is ignored for escalation check if state is ignored for escalation
state = Ticket::State.lookup( :name => 'state name' ) state = Ticket::State.lookup(name: 'state name')
result = state.ignore_escalation? result = state.ignore_escalation?
@ -61,8 +61,7 @@ returns:
=end =end
def ignore_escalation? def ignore_escalation?
ignore_escalation = %w(removed closed merged) return true if ignore_escalation
return true if ignore_escalation.include?( name )
false false
end end
end end

View file

@ -0,0 +1,8 @@
class UpdateSla < ActiveRecord::Migration
def up
add_column :slas, :calendar_id, :integer, null: false
remove_column :slas, :timezone
remove_column :slas, :data
remove_column :slas, :active
end
end

View file

@ -0,0 +1,12 @@
class UpdateTicketState < ActiveRecord::Migration
def up
add_column :ticket_states, :ignore_escalation, :boolean, null: false, default: false
Ticket::State.create_or_update( id: 3, name: 'pending reminder', state_type_id: Ticket::StateType.find_by(name: 'pending reminder').id, ignore_escalation: true )
Ticket::State.create_or_update( id: 4, name: 'closed', state_type_id: Ticket::StateType.find_by(name: 'closed').id, ignore_escalation: true )
Ticket::State.create_or_update( id: 5, name: 'merged', state_type_id: Ticket::StateType.find_by(name: 'merged').id, ignore_escalation: true )
Ticket::State.create_or_update( id: 6, name: 'removed', state_type_id: Ticket::StateType.find_by(name: 'removed').id, active: false, ignore_escalation: true )
Ticket::State.create_or_update( id: 7, name: 'pending close', state_type_id: Ticket::StateType.find_by(name: 'pending action').id, next_state_id: 4, ignore_escalation: true )
end
end

View file

@ -1527,13 +1527,13 @@ Ticket::StateType.create_if_not_exists( id: 5, name: 'closed' )
Ticket::StateType.create_if_not_exists( id: 6, name: 'merged' ) Ticket::StateType.create_if_not_exists( id: 6, name: 'merged' )
Ticket::StateType.create_if_not_exists( id: 7, name: 'removed' ) Ticket::StateType.create_if_not_exists( id: 7, name: 'removed' )
Ticket::State.create_if_not_exists( id: 1, name: 'new', state_type_id: Ticket::StateType.where(name: 'new').first.id ) Ticket::State.create_if_not_exists( id: 1, name: 'new', state_type_id: Ticket::StateType.find_by(name: 'new').id, )
Ticket::State.create_if_not_exists( id: 2, name: 'open', state_type_id: Ticket::StateType.where(name: 'open').first.id ) Ticket::State.create_if_not_exists( id: 2, name: 'open', state_type_id: Ticket::StateType.find_by(name: 'open').id )
Ticket::State.create_if_not_exists( id: 3, name: 'pending reminder', state_type_id: Ticket::StateType.where(name: 'pending reminder').first.id ) Ticket::State.create_if_not_exists( id: 3, name: 'pending reminder', state_type_id: Ticket::StateType.find_by(name: 'pending reminder').id, ignore_escalation: true )
Ticket::State.create_if_not_exists( id: 4, name: 'closed', state_type_id: Ticket::StateType.where(name: 'closed').first.id ) Ticket::State.create_if_not_exists( id: 4, name: 'closed', state_type_id: Ticket::StateType.find_by(name: 'closed').id, ignore_escalation: true )
Ticket::State.create_if_not_exists( id: 5, name: 'merged', state_type_id: Ticket::StateType.where(name: 'merged').first.id ) Ticket::State.create_if_not_exists( id: 5, name: 'merged', state_type_id: Ticket::StateType.find_by(name: 'merged').id, ignore_escalation: true )
Ticket::State.create_if_not_exists( id: 6, name: 'removed', state_type_id: Ticket::StateType.where(name: 'removed').first.id, active: false ) Ticket::State.create_if_not_exists( id: 6, name: 'removed', state_type_id: Ticket::StateType.find_by(name: 'removed').id, active: false, ignore_escalation: true )
Ticket::State.create_if_not_exists( id: 7, name: 'pending close', state_type_id: Ticket::StateType.where(name: 'pending action').first.id, next_state_id: 4 ) Ticket::State.create_if_not_exists( id: 7, name: 'pending close', state_type_id: Ticket::StateType.find_by(name: 'pending action').id, next_state_id: 4, ignore_escalation: true )
Ticket::Priority.create_if_not_exists( id: 1, name: '1 low' ) Ticket::Priority.create_if_not_exists( id: 1, name: '1 low' )
Ticket::Priority.create_if_not_exists( id: 2, name: '2 normal' ) Ticket::Priority.create_if_not_exists( id: 2, name: '2 normal' )

View file

@ -1,361 +0,0 @@
module TimeCalculation
=begin
put working hours matrix and timezone in function, returns UTC working hours matrix
working_hours_martix = TimeCalculation.working_hours('2013-10-27 20:00:15', working_hours_matrix, 'Europe/Berlin')
working_hours_martix = {
:Mon => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil],
:Tue => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil],
:Wed => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil],
:Thu => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil],
:Fri => [nil,nil,nil,nil,nil,nil,nil,nil,true,true,true,true,true,true,true,true,true,true,true,nil,nil,nil,nil,nil],
:Sat => [],
:Sun => [],
}
=end
def self.working_hours(start_time, config, timezone)
time_diff = 0
if timezone
begin
time_diff = Time.zone.parse(start_time.to_s).in_time_zone(timezone).utc_offset
rescue => e
Rails.logger.error "Can't fine tomezone #{timezone}"
Rails.logger.error e.inspect
Rails.logger.error e.backtrace
end
end
beginning_of_workday = Time.zone.parse("1977-10-27 #{config['beginning_of_workday']}")
end_of_workday = Time.zone.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] = []
next if !config[day.to_s]
if config[day.to_s] != true && config[day.to_s] != day.to_s
next
end
config_ok = true
(0..23).each {|hour|
time = Time.zone.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
}
}
if !config_ok
fail 'sla config is invalid! ' + config.inspect
end
# 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 {
working_hours.each {|day, value|
next if !value
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
}
}
move_items.each {|day, value|
value.each {|item|
working_hours[day].push item
}
}
end
working_hours
end
=begin
returns business hours in minutes between to dates
business_hours_in_min = Time.Calculation.business_time_diff(
'2013-10-27 14:00:15',
'2013-10-27 18:10:15',
working_hours_martix,
'Europe/Berlin',
)
=end
def self.business_time_diff(start_time, end_time, config = nil, timezone = '')
if start_time.class == String
start_time = Time.zone.parse( start_time.to_s + 'UTC' )
end
if end_time.class == String
end_time = Time.zone.parse( end_time.to_s + 'UTC' )
end
# if no config is given, just return calculation directly
if !config
return ((end_time - start_time) / 60 ).round
end
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
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 + 86_400
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 + 86_400
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 + 86_400
end
diff = count / 60
diff.round
end
=begin
returns destination date of start time plus X minutes
dest_time = Time.Calculation.dest_time(
'2013-10-27 14:00:15',
120,
working_hours_martix,
'Europe/Berlin',
)
=end
def self.dest_time(start_time, diff_in_min, config = nil, timezone = '')
if start_time.class == String
start_time = Time.zone.parse( start_time.to_s + ' UTC' )
end
return start_time if diff_in_min == 0
# if no config is given, just return calculation directly
if !config
return start_time + (diff_in_min * 60)
end
# 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
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 + 86_400
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 + 86_400
next
end
# fillup to first full hour
if first_loop
# get rest of this hour if diff_in_min in lower the one hour
diff_to_count = 3600
if diff_to_count > (diff_in_min * 60)
diff_to_count = diff_in_min * 60
end
diff = diff_to_count - (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 + 86_400
end
start_time
end
end

View file

@ -0,0 +1,93 @@
# encoding: utf-8
require 'test_helper'
class CalendarTest < ActiveSupport::TestCase
test 'default test' do
calendar1 = Calendar.create_or_update(
name: 'US 1',
timezone: 'America/Los_Angeles',
business_hours: {
mon: { '09:00' => '17:00' },
tue: { '09:00' => '17:00' },
wed: { '09:00' => '17:00' },
thu: { '09:00' => '17:00' },
fri: { '09:00' => '17:00' }
},
default: true,
ical_url: nil,
updated_by_id: 1,
created_by_id: 1,
)
calendar2 = Calendar.create_or_update(
name: 'US 2',
timezone: 'America/Los_Angeles',
business_hours: {
mon: { '09:00' => '17:00' },
tue: { '09:00' => '17:00' },
wed: { '09:00' => '17:00' },
thu: { '09:00' => '17:00' },
fri: { '09:00' => '17:00' }
},
default: false,
ical_url: nil,
updated_by_id: 1,
created_by_id: 1,
)
calendar3 = Calendar.create_or_update(
name: 'US 3',
timezone: 'America/Los_Angeles',
business_hours: {
mon: { '09:00' => '17:00' },
tue: { '09:00' => '17:00' },
wed: { '09:00' => '17:00' },
thu: { '09:00' => '17:00' },
fri: { '09:00' => '17:00' }
},
default: true,
ical_url: nil,
updated_by_id: 1,
created_by_id: 1,
)
calendar1 = Calendar.find_by(name: 'US 1')
calendar2 = Calendar.find_by(name: 'US 2')
calendar3 = Calendar.find_by(name: 'US 3')
assert_equal(false, calendar1.default)
assert_equal(false, calendar2.default)
assert_equal(true, calendar3.default)
calendar2.default = true
calendar2.save
calendar1 = Calendar.find_by(name: 'US 1')
calendar2 = Calendar.find_by(name: 'US 2')
calendar3 = Calendar.find_by(name: 'US 3')
assert_equal(false, calendar1.default)
assert_equal(true, calendar2.default)
assert_equal(false, calendar3.default)
calendar2.default = false
calendar2.save
calendar1 = Calendar.find_by(name: 'US 1')
calendar2 = Calendar.find_by(name: 'US 2')
calendar3 = Calendar.find_by(name: 'US 3')
assert_equal(true, calendar1.default)
assert_equal(false, calendar2.default)
assert_equal(false, calendar3.default)
calendar1.destroy
calendar2 = Calendar.find_by(name: 'US 2')
calendar3 = Calendar.find_by(name: 'US 3')
assert_equal(true, calendar2.default)
assert_equal(false, calendar3.default)
end
end

View file

@ -2,6 +2,7 @@
require 'test_helper' require 'test_helper'
class TicketSlaTest < ActiveSupport::TestCase class TicketSlaTest < ActiveSupport::TestCase
test 'ticket sla' do test 'ticket sla' do
# cleanup # cleanup
@ -24,18 +25,29 @@ class TicketSlaTest < ActiveSupport::TestCase
assert( ticket, 'ticket created' ) assert( ticket, 'ticket created' )
assert_equal( ticket.escalation_time, nil, 'ticket.escalation_time verify' ) assert_equal( ticket.escalation_time, nil, 'ticket.escalation_time verify' )
sla = Sla.create( calendar1 = Calendar.create_or_update(
name: 'EU 1',
timezone: 'Europe/Berlin',
business_hours: {
mon: { '09:00' => '17:00' },
tue: { '09:00' => '17:00' },
wed: { '09:00' => '17:00' },
thu: { '09:00' => '17:00' },
fri: { '09:00' => '17:00' }
},
default: true,
ical_url: nil,
updated_by_id: 1,
created_by_id: 1,
)
sla = Sla.create_or_update(
name: 'test sla 1', name: 'test sla 1',
condition: {}, condition: {},
data: {
'Mon' => 'Mon', 'Tue' => 'Tue', 'Wed' => 'Wed', 'Thu' => 'Thu', 'Fri' => 'Fri', 'Sat' => 'Sat', 'Sun' => 'Sun',
'beginning_of_workday' => '8:00',
'end_of_workday' => '18:00',
},
first_response_time: 120, first_response_time: 120,
update_time: 180, update_time: 180,
close_time: 240, close_time: 240,
active: true, calendar_id: calendar1.id,
updated_by_id: 1, updated_by_id: 1,
created_by_id: 1, created_by_id: 1,
) )
@ -47,18 +59,30 @@ class TicketSlaTest < ActiveSupport::TestCase
delete = sla.destroy delete = sla.destroy
assert( delete, 'sla destroy 1' ) assert( delete, 'sla destroy 1' )
sla = Sla.create( calendar2 = Calendar.create_or_update(
name: 'EU 2',
timezone: 'Europe/Berlin',
business_hours: {
mon: { '08:00' => '18:00' },
tue: { '08:00' => '18:00' },
wed: { '08:00' => '18:00' },
thu: { '08:00' => '18:00' },
fri: { '08:00' => '18:00' },
sat: { '08:00' => '18:00' },
sun: { '08:00' => '18:00' },
},
default: true,
ical_url: nil,
updated_by_id: 1,
created_by_id: 1,
)
sla = Sla.create_or_update(
name: 'test sla 2', name: 'test sla 2',
condition: { 'tickets.priority_id' => %w(1 2 3) }, condition: { 'tickets.priority_id' => %w(1 2 3) },
data: { calendar_id: calendar2.id,
'Mon' => 'Mon', 'Tue' => 'Tue', 'Wed' => 'Wed', 'Thu' => 'Thu', 'Fri' => 'Fri', 'Sat' => 'Sat', 'Sun' => 'Sun',
'beginning_of_workday' => '8:00',
'end_of_workday' => '18:00',
},
first_response_time: 60, first_response_time: 60,
update_time: 120, update_time: 120,
close_time: 180, close_time: 180,
active: true,
updated_by_id: 1, updated_by_id: 1,
created_by_id: 1, created_by_id: 1,
) )
@ -81,7 +105,6 @@ class TicketSlaTest < ActiveSupport::TestCase
ticket.update_attributes( ticket.update_attributes(
first_response: '2013-03-21 10:00:00 UTC', first_response: '2013-03-21 10:00:00 UTC',
) )
puts ticket.inspect
assert_equal( ticket.escalation_time.gmtime.to_s, '2013-03-21 11:30:00 UTC', 'ticket.escalation_time verify 3' ) assert_equal( ticket.escalation_time.gmtime.to_s, '2013-03-21 11:30:00 UTC', 'ticket.escalation_time verify 3' )
assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-03-21 10:30:00 UTC', 'ticket.first_response_escal_date verify 3' ) assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-03-21 10:30:00 UTC', 'ticket.first_response_escal_date verify 3' )
@ -101,7 +124,6 @@ class TicketSlaTest < ActiveSupport::TestCase
ticket.update_attributes( ticket.update_attributes(
first_response: '2013-03-21 14:00:00 UTC', first_response: '2013-03-21 14:00:00 UTC',
) )
puts ticket.inspect
assert_equal( ticket.escalation_time.gmtime.to_s, '2013-03-21 11:30:00 UTC', 'ticket.escalation_time verify 4' ) assert_equal( ticket.escalation_time.gmtime.to_s, '2013-03-21 11:30:00 UTC', 'ticket.escalation_time verify 4' )
assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-03-21 10:30:00 UTC', 'ticket.first_response_escal_date verify 4' ) assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-03-21 10:30:00 UTC', 'ticket.first_response_escal_date verify 4' )
@ -269,8 +291,8 @@ class TicketSlaTest < ActiveSupport::TestCase
type: Ticket::Article::Type.where(name: 'email').first, type: Ticket::Article::Type.where(name: 'email').first,
updated_by_id: 1, updated_by_id: 1,
created_by_id: 1, created_by_id: 1,
created_at: '2013-03-29 08:00:03 UTC', created_at: '2013-03-29 07:00:03 UTC',
updated_at: '2013-03-29 08:00:03 UTC', updated_at: '2013-03-29 07:00:03 UTC',
) )
ticket = Ticket.find(ticket.id) ticket = Ticket.find(ticket.id)
@ -355,20 +377,31 @@ class TicketSlaTest < ActiveSupport::TestCase
assert( ticket, 'ticket created' ) assert( ticket, 'ticket created' )
assert_equal( ticket.escalation_time, nil, 'ticket.escalation_time verify' ) 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 # set sla's for timezone "Europe/Berlin" wintertime (+1), so UTC times are 7:00-16:00
sla = Sla.create( calendar = Calendar.create_or_update(
name: 'test sla 1', name: 'EU 3',
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', timezone: 'Europe/Berlin',
business_hours: {
mon: { '08:00' => '17:00' },
tue: { '08:00' => '17:00' },
wed: { '08:00' => '17:00' },
thu: { '08:00' => '17:00' },
fri: { '08:00' => '17:00' },
sat: { '08:00' => '17:00' },
sun: { '08:00' => '17:00' },
},
default: true,
ical_url: nil,
updated_by_id: 1,
created_by_id: 1,
)
sla = Sla.create_or_update(
name: 'test sla 3',
condition: {},
calendar_id: calendar.id,
first_response_time: 120, first_response_time: 120,
update_time: 180, update_time: 180,
close_time: 240, close_time: 240,
active: true,
updated_by_id: 1, updated_by_id: 1,
created_by_id: 1, created_by_id: 1,
) )
@ -397,23 +430,35 @@ class TicketSlaTest < ActiveSupport::TestCase
assert( ticket, 'ticket created' ) assert( ticket, 'ticket created' )
assert_equal( ticket.escalation_time, nil, 'ticket.escalation_time verify' ) 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 # set sla's for timezone "Europe/Berlin" summertime (+2), so UTC times are 6:00-15:00
sla = Sla.create( calendar = Calendar.create_or_update(
name: 'test sla 1', name: 'EU 4',
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', timezone: 'Europe/Berlin',
first_response_time: 120, business_hours: {
update_time: 180, mon: { '08:00' => '17:00' },
close_time: 240, tue: { '08:00' => '17:00' },
active: true, wed: { '08:00' => '17:00' },
thu: { '08:00' => '17:00' },
fri: { '08:00' => '17:00' },
sat: { '08:00' => '17:00' },
sun: { '08:00' => '17:00' },
},
default: true,
ical_url: nil,
updated_by_id: 1, updated_by_id: 1,
created_by_id: 1, created_by_id: 1,
) )
sla = Sla.create_or_update(
name: 'test sla 4',
condition: {},
calendar_id: calendar.id,
first_response_time: 120,
update_time: 180,
close_time: 240,
updated_by_id: 1,
created_by_id: 1,
)
ticket = Ticket.find(ticket.id) 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.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.first_response_escal_date.gmtime.to_s, '2013-10-21 11:30:00 UTC', 'ticket.first_response_escal_date verify 1' )
@ -432,36 +477,30 @@ class TicketSlaTest < ActiveSupport::TestCase
customer_id: 2, customer_id: 2,
state: Ticket::State.lookup( name: 'new' ), state: Ticket::State.lookup( name: 'new' ),
priority: Ticket::Priority.lookup( name: '2 normal' ), priority: Ticket::Priority.lookup( name: '2 normal' ),
created_at: '2013-10-21 06:30:00 UTC', created_at: '2013-10-21 05:30:00 UTC',
updated_at: '2013-10-21 06:30:00 UTC', updated_at: '2013-10-21 05:30:00 UTC',
updated_by_id: 1, updated_by_id: 1,
created_by_id: 1, created_by_id: 1,
) )
assert( ticket, 'ticket created' ) assert( ticket, 'ticket created' )
assert_equal( ticket.escalation_time, nil, 'ticket.escalation_time verify' ) 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 # set sla's for timezone "Europe/Berlin" summertime (+2), so UTC times are 6:00-15:00
sla = Sla.create( sla = Sla.create_or_update(
name: 'test sla 1', name: 'test sla 5',
condition: {}, condition: {},
data: { calendar_id: calendar.id,
'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, first_response_time: 120,
update_time: 180, update_time: 180,
close_time: 240, close_time: 240,
active: true,
updated_by_id: 1, updated_by_id: 1,
created_by_id: 1, created_by_id: 1,
) )
ticket = Ticket.find(ticket.id) 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.escalation_time.gmtime.to_s, '2013-10-21 08: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.first_response_escal_date.gmtime.to_s, '2013-10-21 08: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.update_time_escal_date.gmtime.to_s, '2013-10-21 09: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' ) assert_equal( ticket.close_time_escal_date.gmtime.to_s, '2013-10-21 10:00:00 UTC', 'ticket.close_time_escal_date verify 1' )
delete = sla.destroy delete = sla.destroy
assert( delete, 'sla destroy' ) assert( delete, 'sla destroy' )
@ -545,22 +584,34 @@ class TicketSlaTest < ActiveSupport::TestCase
) )
# set sla's for timezone "Europe/Berlin" summertime (+2), so UTC times are 7:00-16:00 # set sla's for timezone "Europe/Berlin" summertime (+2), so UTC times are 7:00-16:00
sla = Sla.create( calendar = Calendar.create_or_update(
name: 'test sla 1', name: 'EU 5',
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', timezone: 'Europe/Berlin',
first_response_time: 120, business_hours: {
update_time: 180, mon: { '09:00' => '18:00' },
close_time: 250, tue: { '09:00' => '18:00' },
active: true, wed: { '09:00' => '18:00' },
thu: { '09:00' => '18:00' },
fri: { '09:00' => '18:00' },
sat: { '09:00' => '18:00' },
sun: { '09:00' => '18:00' },
},
default: true,
ical_url: nil,
updated_by_id: 1, updated_by_id: 1,
created_by_id: 1, created_by_id: 1,
) )
sla = Sla.create_or_update(
name: 'test sla 5',
condition: {},
calendar_id: calendar.id,
first_response_time: 120,
update_time: 180,
close_time: 250,
updated_by_id: 1,
created_by_id: 1,
)
ticket = Ticket.find(ticket.id) ticket = Ticket.find(ticket.id)
assert_equal( ticket.escalation_time.gmtime.to_s, '2013-06-04 13:30:00 UTC', 'ticket.escalation_time verify 1' ) assert_equal( ticket.escalation_time.gmtime.to_s, '2013-06-04 13:30:00 UTC', 'ticket.escalation_time verify 1' )
assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-06-04 11:30:00 UTC', 'ticket.first_response_escal_date verify 1' ) assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-06-04 11:30:00 UTC', 'ticket.first_response_escal_date verify 1' )
@ -608,25 +659,37 @@ class TicketSlaTest < ActiveSupport::TestCase
close_time: '2013-06-04 12:00:00 UTC', close_time: '2013-06-04 12:00:00 UTC',
) )
sla = Sla.create( calendar = Calendar.create_or_update(
name: 'test sla 1', name: 'EU 5',
condition: {}, timezone: 'Europe/Berlin',
data: { business_hours: {
'Mon' => 'Mon', 'Tue' => 'Tue', 'Wed' => 'Wed', 'Thu' => 'Thu', 'Fri' => 'Fri', 'Sat' => 'Sat', 'Sun' => 'Sun', mon: { '09:00' => '18:00' },
'beginning_of_workday' => '9:00', tue: { '09:00' => '18:00' },
'end_of_workday' => '18:00', wed: { '09:00' => '18:00' },
thu: { '09:00' => '18:00' },
fri: { '09:00' => '18:00' },
sat: { '09:00' => '18:00' },
sun: { '09:00' => '18:00' },
}, },
default: true,
ical_url: nil,
updated_by_id: 1,
created_by_id: 1,
)
sla = Sla.create_or_update(
name: 'test sla 5',
condition: {},
calendar_id: calendar.id,
first_response_time: 120, first_response_time: 120,
update_time: 180, update_time: 180,
close_time: 240, close_time: 240,
active: true,
updated_by_id: 1, updated_by_id: 1,
created_by_id: 1, created_by_id: 1,
) )
ticket = Ticket.find(ticket.id) ticket = Ticket.find(ticket.id)
assert_equal( ticket.escalation_time.gmtime.to_s, '2013-06-04 14:00:00 UTC', 'ticket.escalation_time verify 1' ) assert_equal( ticket.escalation_time, nil, 'ticket.escalation_time verify 1' )
assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-06-04 14:00:00 UTC', 'ticket.first_response_escal_date verify 1' ) assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-06-04 13:00:00 UTC', 'ticket.first_response_escal_date verify 1' )
assert_equal( ticket.first_response_in_min, nil, 'ticket.first_response_in_min verify 3' ) assert_equal( ticket.first_response_in_min, nil, 'ticket.first_response_in_min verify 3' )
assert_equal( ticket.first_response_diff_in_min, nil, 'ticket.first_response_diff_in_min verify 3' ) assert_equal( ticket.first_response_diff_in_min, nil, 'ticket.first_response_diff_in_min verify 3' )
assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-06-04 15:00:00 UTC', 'ticket.update_time_escal_date verify 1' ) assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-06-04 15:00:00 UTC', 'ticket.update_time_escal_date verify 1' )
@ -702,25 +765,37 @@ class TicketSlaTest < ActiveSupport::TestCase
close_time: '2013-06-04 12:00:00 UTC', close_time: '2013-06-04 12:00:00 UTC',
) )
sla = Sla.create( calendar = Calendar.create_or_update(
name: 'test sla 1', name: 'EU 5',
condition: {}, timezone: 'Europe/Berlin',
data: { business_hours: {
'Mon' => 'Mon', 'Tue' => 'Tue', 'Wed' => 'Wed', 'Thu' => 'Thu', 'Fri' => 'Fri', 'Sat' => 'Sat', 'Sun' => 'Sun', mon: { '09:00' => '18:00' },
'beginning_of_workday' => '9:00', tue: { '09:00' => '18:00' },
'end_of_workday' => '18:00', wed: { '09:00' => '18:00' },
thu: { '09:00' => '18:00' },
fri: { '09:00' => '18:00' },
sat: { '09:00' => '18:00' },
sun: { '09:00' => '18:00' },
}, },
default: true,
ical_url: nil,
updated_by_id: 1,
created_by_id: 1,
)
sla = Sla.create_or_update(
name: 'test sla 5',
condition: {},
calendar_id: calendar.id,
first_response_time: 120, first_response_time: 120,
update_time: 180, update_time: 180,
close_time: 240, close_time: 240,
active: true,
updated_by_id: 1, updated_by_id: 1,
created_by_id: 1, created_by_id: 1,
) )
ticket = Ticket.find(ticket.id) ticket = Ticket.find(ticket.id)
assert_equal( ticket.escalation_time.gmtime.to_s, '2013-06-04 13:30:00 UTC', 'ticket.escalation_time verify 1' ) assert_equal( ticket.escalation_time, nil, 'ticket.escalation_time verify 1' )
assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-06-04 13:30:00 UTC', 'ticket.first_response_escal_date verify 1' ) assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-06-04 12:30:00 UTC', 'ticket.first_response_escal_date verify 1' )
assert_equal( ticket.first_response_in_min, nil, 'ticket.first_response_in_min verify 3' ) assert_equal( ticket.first_response_in_min, nil, 'ticket.first_response_in_min verify 3' )
assert_equal( ticket.first_response_diff_in_min, nil, 'ticket.first_response_diff_in_min verify 3' ) assert_equal( ticket.first_response_diff_in_min, nil, 'ticket.first_response_diff_in_min verify 3' )
assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-06-04 14:30:00 UTC', 'ticket.update_time_escal_date verify 1' ) assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-06-04 14:30:00 UTC', 'ticket.update_time_escal_date verify 1' )
@ -812,25 +887,37 @@ class TicketSlaTest < ActiveSupport::TestCase
close_time: '2013-06-04 12:00:00 UTC', close_time: '2013-06-04 12:00:00 UTC',
) )
sla = Sla.create( calendar = Calendar.create_or_update(
name: 'test sla 1', name: 'EU 5',
condition: {}, timezone: 'Europe/Berlin',
data: { business_hours: {
'Mon' => 'Mon', 'Tue' => 'Tue', 'Wed' => 'Wed', 'Thu' => 'Thu', 'Fri' => 'Fri', 'Sat' => 'Sat', 'Sun' => 'Sun', mon: { '09:00' => '18:00' },
'beginning_of_workday' => '9:00', tue: { '09:00' => '18:00' },
'end_of_workday' => '18:00', wed: { '09:00' => '18:00' },
thu: { '09:00' => '18:00' },
fri: { '09:00' => '18:00' },
sat: { '09:00' => '18:00' },
sun: { '09:00' => '18:00' },
}, },
default: true,
ical_url: nil,
updated_by_id: 1,
created_by_id: 1,
)
sla = Sla.create_or_update(
name: 'test sla 5',
condition: {},
calendar_id: calendar.id,
first_response_time: 120, first_response_time: 120,
update_time: 180, update_time: 180,
close_time: 240, close_time: 240,
active: true,
updated_by_id: 1, updated_by_id: 1,
created_by_id: 1, created_by_id: 1,
) )
ticket = Ticket.find(ticket.id) ticket = Ticket.find(ticket.id)
assert_equal( ticket.escalation_time.gmtime.to_s, '2013-06-04 13:00:00 UTC', 'ticket.escalation_time verify 1' ) assert_equal( ticket.escalation_time, nil, 'ticket.escalation_time verify 1' )
assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-06-04 13:00:00 UTC', 'ticket.first_response_escal_date verify 1' ) assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-06-04 12:30:00 UTC', 'ticket.first_response_escal_date verify 1' )
assert_equal( ticket.first_response_in_min, nil, 'ticket.first_response_in_min verify 3' ) assert_equal( ticket.first_response_in_min, nil, 'ticket.first_response_in_min verify 3' )
assert_equal( ticket.first_response_diff_in_min, nil, 'ticket.first_response_diff_in_min verify 3' ) assert_equal( ticket.first_response_diff_in_min, nil, 'ticket.first_response_diff_in_min verify 3' )
assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-06-04 14:00:00 UTC', 'ticket.update_time_escal_date verify 1' ) assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-06-04 14:00:00 UTC', 'ticket.update_time_escal_date verify 1' )
@ -845,4 +932,5 @@ class TicketSlaTest < ActiveSupport::TestCase
assert( delete, 'ticket destroy' ) assert( delete, 'ticket destroy' )
end end
end end

View file

@ -1,555 +0,0 @@
# encoding: utf-8
require 'test_helper'
require 'time_calculation'
class WorkingTimeTest < ActiveSupport::TestCase
test 'working time' do
tests = [
# test 1
{
start: '2012-12-17 08:00:00',
end: '2012-12-18 08:00:00',
diff: 600,
config: {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 2
{
start: '2012-12-17 08:00:00',
end: '2012-12-17 09:00:00',
diff: 60,
config: {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 3
{
start: '2012-12-17 08:00:00',
end: '2012-12-17 08:15:00',
diff: 15,
config: {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 4
{
start: '2012-12-23 08:00:00',
end: '2012-12-27 10:30:42',
diff: 151,
config: {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
'holidays' => [
'2012-12-24', '2012-12-25', '2012-12-26'
],
},
},
# test 5
{
start: '2013-02-28 17:00:00',
end: '2013-02-28 23:59:59',
diff: 60,
config: {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'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: 160_860,
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',
},
},
# test 15
{
start: '2013-08-29 16:01:00',
end: '2013-08-29 16:10:59',
diff: 10,
},
]
tests.each { |test|
diff = TimeCalculation.business_time_diff( test[:start], test[:end], test[:config], test[:timezone] )
assert_equal( diff, test[:diff], 'diff' )
}
end
test 'dest time' do
tests = [
# test 1
{
start: '2012-12-17 08:00:00',
dest_time: '2012-12-17 18:00:00',
diff: 600,
config: {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 2
{
start: '2012-12-17 08:00:00',
dest_time: '2012-12-18 08:30:00',
diff: 630,
config: {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 3
{
start: '2012-12-17 08:00:00',
dest_time: '2012-12-18 18:00:00',
diff: 1200,
config: {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 4
{
start: '2012-12-17 08:00:00',
dest_time: '2012-12-19 08:30:00',
diff: 1230,
config: {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 5
{
start: '2012-12-17 08:00:00',
dest_time: '2012-12-21 18:00:00',
diff: 3000,
config: {
'Mon' => true,
'Tue' => true,
'Wed' => true,
'Thu' => true,
'Fri' => true,
'beginning_of_workday' => '8:00 am',
'end_of_workday' => '6:00 pm',
},
},
# test 6
{
start: '2012-12-17 08:00:00',
dest_time: '2012-12-24 08:05:00',
diff: 3005,
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-12-17 08:00:00',
dest_time: '2012-12-31 08:05:00',
diff: 6005,
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: '2012-12-17 08:00:00',
dest_time: '2012-12-31 13:30:00',
diff: 6330,
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-04-12 21:20:15',
dest_time: '2013-04-15 10:00:00',
diff: 120,
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 7am-5pm
{
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 6am-4pm
{
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 - 7am-4pm
{
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 - 7am-4pm
{
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 - 7am-4pm
{
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 - 7am-4pm
{
start: '2013-10-21 07:00:15',
dest_time: '2013-10-21 09:00:15',
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 17
{
start: '2013-10-21 04:01:00',
dest_time: '2013-10-21 06:00:00',
diff: 119,
},
# test 18
{
start: '2013-10-21 04:01:00',
dest_time: '2013-10-21 04:01:00',
diff: 0,
},
# test 19
{
start: '2013-04-12 21:20:15',
dest_time: '2013-04-12 21:20:15',
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 20
{
start: '2013-04-12 11:20:15',
dest_time: '2013-04-12 11:21:15',
diff: 1,
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|
dest_time = TimeCalculation.dest_time( test[:start] + ' UTC', test[:diff], test[:config], test[:timezone] )
assert_equal( dest_time.gmtime, Time.zone.parse( test[:dest_time] + ' UTC' ), "dest time - #{test[:dest_time]}" )
}
end
end