diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 79af2869f..9fcdd76e6 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -611,15 +611,27 @@ class Ticket < ApplicationModel # first response if 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 ) + + # get escalation date without pending time + self.first_response_escal_date = TimeCalculation.dest_time( self.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( self.created_at, self.first_response_escal_date, 'relative', sla_selected ) + + # get new escalation time (original escal_date + time_in_pending) + self.first_response_escal_date = TimeCalculation.dest_time( self.first_response_escal_date, time_in_pending.to_i, sla_selected.data, sla_selected.timezone ) # set ticket escalation self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.first_response_escal_date, self.first_response ) end if self.first_response# && !self.first_response_in_min - self.first_response_in_min = TimeCalculation.business_time_diff( self.created_at, self.first_response, sla_selected.data, sla_selected.timezone ) + + # get response time in min between created and first response + self.first_response_in_min = escalation_suspend( self.created_at, self.first_response, 'real', sla_selected ) + end - # set sla time + + # set time to show if sla is raised ot in if sla_selected.first_response_time && self.first_response_in_min self.first_response_diff_in_min = sla_selected.first_response_time - self.first_response_in_min end @@ -648,13 +660,21 @@ class Ticket < ApplicationModel # close time if sla_selected.close_time + + # get escalation date without pending time self.close_time_escal_date = TimeCalculation.dest_time( self.created_at, sla_selected.close_time, sla_selected.data, sla_selected.timezone ) + # get pending time between created and close escal. time + extended_escalation = escalation_suspend( self.created_at, self.close_time_escal_date, 'relative', sla_selected ) + + # get new escalation time (original escal_date + time_in_pending) + self.close_time_escal_date = TimeCalculation.dest_time( self.close_time_escal_date, extended_escalation.to_i, sla_selected.data, sla_selected.timezone ) + # set ticket escalation self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.close_time_escal_date, self.close_time ) end - if self.close_time# && !self.close_time_in_min - self.close_time_in_min = TimeCalculation.business_time_diff( self.created_at, self.close_time, sla_selected.data, sla_selected.timezone ) + if self.close_time # && !self.close_time_in_min + self.close_time_in_min = escalation_suspend( self.created_at, self.close_time, 'real', sla_selected ) end # set sla time if sla_selected.close_time && self.close_time_in_min @@ -699,9 +719,109 @@ class Ticket < ApplicationModel # delete history History.history_destroy( 'Ticket', self.id ) - # delete articles - self.articles.destroy_all - end + # delete articles + self.articles.destroy_all + end + + #type could be: + # real - time without supsend state + # relative - only suspend time + + def escalation_suspend (start_time, end_time, type, sla_selected) + total_time_without_pending = 0 + total_time = 0 + #get history for ticket + history_list = History.history_list( 'Ticket', self.id, 'Ticket' ) + + #loop through hist. changes and get time + last_state = nil + last_state_change = nil + last_state_is_pending = false + history_list.each { |history_item| + + # ignore if it isn't a state change + next if history_item['history_attribute'] != 'ticket_state' + + # ignore all older state changes after end_time + next if last_state_change && last_state_change > end_time + + # if created_at is later then end_time, use end_time as last time + if history_item['created_at'] > end_time + history_item['created_at'] = end_time + end + + # get initial state and time + if !last_state + last_state = history_item['value_from'] + last_state_change = start_time + end + + # use time if ticket got from e. g. open to pending + if history_item['value_from'] != 'pending' && history_item['value_to'] == 'pending' + diff = escalation_time_diff( last_state_change, history_item['created_at'], sla_selected ) + puts "Diff count !=pending -> ==pending #{diff.to_s} - #{last_state_change} - #{history_item['created_at']}" + total_time_without_pending = total_time_without_pending + diff + total_time = total_time + diff + last_state_is_pending = true + + # use time if ticket got from e. g. open to open + elsif history_item['value_from'] != 'pending' && history_item['value_to'] != 'pending' + diff = escalation_time_diff( last_state_change, history_item['created_at'], sla_selected ) + puts "Diff count !=pending -> !=pending #{diff.to_s} - #{last_state_change} - #{history_item['created_at']}" + total_time_without_pending = total_time_without_pending + diff + total_time = total_time + diff + last_state_is_pending = false + elsif history_item['value_from'] == 'pending' && history_item['value_to'] != 'pending' + diff = escalation_time_diff( last_state_change, history_item['created_at'], sla_selected ) + puts "Diff not count ==pending -> !=pending #{diff.to_s} - #{last_state_change} - #{history_item['created_at']}" + total_time = total_time + diff + last_state_is_pending = false + # no pending state, do not count + else + puts "Diff do not count #{history_item['value_from']}->#{history_item['value_to']} -> #{history_item['created_at']}" + last_state_is_pending = false + end + + # remember for next loop last state + last_state = history_item['value_to'] + last_state_change = history_item['created_at'] + } + + # if last state isnt pending, count rest + if !last_state_is_pending && last_state_change && last_state_change < end_time + diff = escalation_time_diff( last_state_change, end_time, sla_selected ) + puts "Diff count last state was not pending #{diff.to_s} - #{last_state_change} - #{end_time}" + total_time_without_pending = total_time_without_pending + diff + total_time = total_time + diff + end + + # if we have not had any state change + if !last_state_change + diff = escalation_time_diff( start_time, end_time, sla_selected ) + puts 'Diff state has not changed ' + diff.to_s + total_time_without_pending = total_time_without_pending + diff + total_time = total_time + diff + end + + #return sum + if (type == 'real') + return total_time_without_pending + elsif (type == 'relative') + relative = total_time - total_time_without_pending + return relative + else + raise "ERROR: Unknown type #{type}" + 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 class Number end diff --git a/lib/time_calculation.rb b/lib/time_calculation.rb index c09e0c9f4..290f0aad4 100644 --- a/lib/time_calculation.rb +++ b/lib/time_calculation.rb @@ -109,7 +109,7 @@ put working hours matrix and timezone in function, returns UTC working hours mat =end - def self.business_time_diff(start_time, end_time, config, timezone = '') + def self.business_time_diff(start_time, end_time, config = nil, timezone = '') if start_time.class == String start_time = Time.parse( start_time.to_s + 'UTC' ) end @@ -117,6 +117,12 @@ put working hours matrix and timezone in function, returns UTC working hours mat end_time = Time.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 = { @@ -225,11 +231,18 @@ put working hours matrix and timezone in function, returns UTC working hours mat =end - def self.dest_time(start_time, diff_in_min, config, timezone = '') + def self.dest_time(start_time, diff_in_min, config = nil, timezone = '') if start_time.class == String start_time = Time.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) @@ -277,8 +290,12 @@ put working hours matrix and timezone in function, returns UTC working hours mat # fillup to first full hour if first_loop - # get rest of this hour - diff = 3600 - (start_time - start_time.beginning_of_hour) + # 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 @@ -307,6 +324,7 @@ put working hours matrix and timezone in function, returns UTC working hours mat # 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 diff --git a/test/unit/ticket_test.rb b/test/unit/ticket_test.rb index 6f00d1d2f..fdea49fba 100644 --- a/test/unit/ticket_test.rb +++ b/test/unit/ticket_test.rb @@ -571,4 +571,122 @@ class TicketTest < ActiveSupport::TestCase end + test 'ticket escalation suspend' do + + + ticket = Ticket.create( + :title => 'some title äöüß3', + :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-06-04 09:00:00 UTC', + :updated_at => '2013-06-04 09:00:00 UTC', + :updated_by_id => 1, + :created_by_id => 1, + ) + assert( ticket, 'ticket created' ) + + # set ticket at 10:00 to pending + History.history_create( + :history_type => 'updated', + :history_object => 'Ticket', + :history_attribute => 'ticket_state', + :o_id => ticket.id, + :id_to => 3, + :id_from => 2, + :value_from => 'open', + :value_to => 'pending', + :created_by_id => 1, + :created_at => '2013-06-04 10:00:00', + :updated_at => '2013-06-04 10:00:00' + ) + + # set ticket at 10:30 to open + History.history_create( + :history_type => 'updated', + :history_object => 'Ticket', + :history_attribute => 'ticket_state', + :o_id => ticket.id, + :id_to => 2, + :id_from => 3, + :value_from => 'pending', + :value_to => 'open', + :created_by_id => 1, + :created_at => '2013-06-04 10:30:00', + :updated_at => '2013-06-04 10:30:00' + ) + + # set ticket from 11:00 to pending + #History.history_create( + # :history_type => 'updated', + # :history_object => 'Ticket', + # :history_attribute => 'ticket_state', + # :o_id => ticket.id, + # :id_to => 3, + # :id_from => 2, + # :value_from => 'open', + # :value_to => 'pending', + # :created_by_id => 1, + # :created_at => '2013-06-04 11:00:00', + # :updated_at => '2013-06-04 11:00:00' + #) + + # set first response in time + ticket.update_attributes( + :first_response => '2013-06-04 10:45:00 UTC', + ) + # set ticket from 11:30 to closed + History.history_create( + :history_type => 'updated', + :history_object => 'Ticket', + :history_attribute => 'ticket_state', + :o_id => ticket.id, + :id_to => 3, + :id_from => 2, + :value_from => 'open', + :value_to => 'closed', + :created_by_id => 1, + :created_at => '2013-06-04 12:00:00', + :updated_at => '2013-06-04 12:00:00' + ) + + ticket.update_attributes( + :close_time => '2013-06-04 12:00:00 UTC', + ) + + # 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-06-04 12:00:00 UTC', 'ticket.escalation_time verify 1' ) #check escal. time because first resp. is already done + 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_in_min, 75, 'ticket.first_response_in_min verify 3' ) + assert_equal( ticket.first_response_diff_in_min, 45, 'ticket.first_response_diff_in_min verify 3' ) + #assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-06-04 12:30:00 UTC', 'ticket.update_time_escal_date verify 1' ) + #assert_equal( ticket.close_time_escal_date.gmtime.to_s, '2013-06-04 13:30:00 UTC', 'ticket.close_time_escal_date verify 1' ) + assert_equal( ticket.close_time_in_min, 150, 'ticket.close_time_in_min verify 3' ) + assert_equal( ticket.close_time_diff_in_min, 90, 'ticket.close_time_diff_in_min# verify 3' ) + delete = sla.destroy + assert( delete, "sla destroy" ) + + delete = ticket.destroy + assert( delete, "ticket destroy" ) + + end + end diff --git a/test/unit/working_time_test.rb b/test/unit/working_time_test.rb index a7c01b512..f1d6fb1a5 100644 --- a/test/unit/working_time_test.rb +++ b/test/unit/working_time_test.rb @@ -238,6 +238,13 @@ class WorkingTimeTest < ActiveSupport::TestCase '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] ) @@ -495,6 +502,51 @@ class WorkingTimeTest < ActiveSupport::TestCase }, }, + # 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] )