From 315a936bd4a1b931f13327649afe84a0b6325df8 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 22 Mar 2013 08:11:20 +0100 Subject: [PATCH] Improved sla calculation. Added Unit Tests. --- app/models/ticket.rb | 149 +++++++++++------- ...321124312_ticket_escalation_update_time.rb | 13 ++ test/unit/ticket_test.rb | 127 +++++++++++++++ 3 files changed, 233 insertions(+), 56 deletions(-) create mode 100644 db/migrate/20130321124312_ticket_escalation_update_time.rb create mode 100644 test/unit/ticket_test.rb diff --git a/app/models/ticket.rb b/app/models/ticket.rb index 6bacf891d..f00050bee 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -473,25 +473,13 @@ class Ticket < ApplicationModel } end - def escalation_calculation + def _escalation_calculation_get_sla - # set escalation off if ticket is already closed - ticket_state = Ticket::State.lookup( :id => self.ticket_state_id ) - ticket_state_type = Ticket::StateType.lookup( :id => ticket_state.state_type_id ) - ignore_escalation = ['removed', 'closed', 'merged', 'pending action'] - if ignore_escalation.include?(ticket_state_type.name) - self.escalation_time = nil -# self.first_response_escal_date = nil -# self.close_time_escal_date = nil - self.save - return true - end - - # get sla sla_selected = nil Sla.where( :active => true ).each {|sla| - if sla.condition - puts sla.condition.inspect + if !sla.condition || sla.condition.empty? + sla_selected = sla + elsif sla.condition hit = false map = [ [ 'tickets.ticket_priority_id', 'ticket_priority_id' ], @@ -515,6 +503,59 @@ class Ticket < ApplicationModel end } + # get and set calendar settings + if sla_selected + BusinessTime::Config.beginning_of_workday = sla_selected.data['beginning_of_workday'] + BusinessTime::Config.end_of_workday = sla_selected.data['end_of_workday'] + days = [] + ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].each {|day| + if sla_selected.data[day] + days.push day.downcase.to_sym + end + } + BusinessTime::Config.work_week = days + end + return sla_selected + end + + def _escalation_calculation_dest_time(start_time, diff_min) + start_time = Time.parse( start_time.to_s ) + dest_time = (diff_min / 60).round.business_hour.after( start_time ) + return dest_time + end + + def _escalation_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 + return escalation_time + end + + def _escalation_calculation_business_time_diff(start_time, end_time) + start_time = Time.parse(start_time.to_s) + end_time = Time.parse(end_time.to_s) + diff = start_time.business_time_until(end_time) / 60 + diff.round + end + + def escalation_calculation + + # set escalation off if ticket is already closed + ticket_state = Ticket::State.lookup( :id => self.ticket_state_id ) + ticket_state_type = Ticket::StateType.lookup( :id => ticket_state.state_type_id ) + ignore_escalation = ['removed', 'closed', 'merged', 'pending action'] + if ignore_escalation.include?( ticket_state_type.name ) + self.escalation_time = nil +# self.first_response_escal_date = nil +# self.close_time_escal_date = nil + self.save + return true + end + + # get sla for ticket + sla_selected = self._escalation_calculation_get_sla + # reset escalation if no sla is set if !sla_selected self.escalation_time = nil @@ -524,75 +565,71 @@ class Ticket < ApplicationModel return true end - # get calendar settings - BusinessTime::Config.beginning_of_workday = sla_selected.data['beginning_of_workday'] - BusinessTime::Config.end_of_workday = sla_selected.data['end_of_workday'] - days = [] - ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].each {|day| - if sla_selected.data[day] - days.push day.downcase.to_sym - end - } - BusinessTime::Config.work_week = days # puts sla_selected.inspect # puts days.inspect - self.escalation_time = nil self.first_response_escal_date = nil + self.update_time_escal_date = nil self.close_time_escal_date = nil # first response if sla_selected.first_response_time - created_at = Time.parse(self.created_at.to_s) - self.first_response_escal_date = (sla_selected.first_response_time / 60).round.business_hour.after( created_at ) + self.first_response_escal_date = self._escalation_calculation_dest_time( created_at, sla_selected.first_response_time ) # set ticket escalation - if !self.first_response && (!self.escalation_time || self.escalation_time > self.first_response_escal_date) - self.escalation_time = self.first_response_escal_date - end + 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 +# created_at = Time.parse(self.created_at.to_s) +# first_response_at = Time.parse(self.first_response.to_s) +# diff = created_at.business_time_until(first_response_at) / 60 +# self.first_response_in_min = diff.round + self.first_response_in_min = self._escalation_calculation_business_time_diff( self.created_at, self.first_response ) - if self.first_response && !self.first_response_in_min - created_at = Time.parse(self.created_at.to_s) - first_response_at = Time.parse(self.first_response.to_s) - diff = created_at.business_time_until(first_response_at) / 60 - self.first_response_in_min = diff.round end - - # set time over sla + # set sla time 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 -# # update time -# if sla_selected.close_time -# created_at = Time.parse(self.created_at.to_s) -# self.close_time_escal_date = (sla_selected.close_time / 60).round.business_hour.after( created_at ) + # update time + if sla_selected.update_time + last_update = self.last_contact_agent + if !last_update + last_update = self.created_at + end + self.update_time_escal_date = self._escalation_calculation_dest_time( last_update, sla_selected.update_time ) + + # set ticket escalation + self.escalation_time = self._escalation_calculation_higher_time( self.escalation_time, self.update_time_escal_date, false ) + end + +# if self.last_contact_agent && !self.update_time_in_min +# created_at = Time.parse(self.created_at.to_s) +# last_contact_agent = Time.parse(self.last_contact_agent.to_s) +# diff = created_at.business_time_until(closed_at) / 60 +# self.close_time_in_min = diff.round # end # close time if sla_selected.close_time - created_at = Time.parse(self.created_at.to_s) - self.close_time_escal_date = (sla_selected.close_time / 60).round.business_hour.after( created_at ) + self.close_time_escal_date = self._escalation_calculation_dest_time( self.created_at, sla_selected.close_time ) # set ticket escalation - if !self.close_time && (!self.escalation_time || self.escalation_time > self.close_time_escal_date) - self.escalation_time = self.close_time_escal_date - end + 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 +# created_at = Time.parse(self.created_at.to_s) +# closed_at = Time.parse(self.close_time.to_s) +# diff = created_at.business_time_until(closed_at) / 60 +# self.close_time_in_min = diff.round + self.close_time_in_min = self._escalation_calculation_business_time_diff( self.created_at, self.close_time ) - if self.close_time && !self.close_time_in_min - created_at = Time.parse(self.created_at.to_s) - closed_at = Time.parse(self.close_time.to_s) - diff = created_at.business_time_until(closed_at) / 60 - self.close_time_in_min = diff.round end - - # set time over sla + # set sla time if sla_selected.close_time && self.close_time_in_min self.close_time_diff_in_min = sla_selected.close_time - self.close_time_in_min end - self.save end diff --git a/db/migrate/20130321124312_ticket_escalation_update_time.rb b/db/migrate/20130321124312_ticket_escalation_update_time.rb new file mode 100644 index 000000000..2056e5f3e --- /dev/null +++ b/db/migrate/20130321124312_ticket_escalation_update_time.rb @@ -0,0 +1,13 @@ +class TicketEscalationUpdateTime < ActiveRecord::Migration + def up + add_column :tickets, :update_time_escal_date, :timestamp, :null => true + add_column :tickets, :updtate_time_sla_time, :timestamp, :null => true + add_column :tickets, :update_time_in_min, :integer, :null => true + add_column :tickets, :update_time_diff_in_min, :integer, :null => true + add_index :tickets, [:update_time_in_min] + add_index :tickets, [:update_time_diff_in_min] + end + + def down + end +end diff --git a/test/unit/ticket_test.rb b/test/unit/ticket_test.rb new file mode 100644 index 000000000..d49a3f4a0 --- /dev/null +++ b/test/unit/ticket_test.rb @@ -0,0 +1,127 @@ +# encoding: utf-8 +require 'test_helper' + +class TicketTest < ActiveSupport::TestCase + test 'ticket create' do + 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' ), + :updated_by_id => 1, + :created_by_id => 1, + ) + assert( ticket, "ticket created" ) + + assert_equal( ticket.title, 'some title äöüß', 'ticket.title verify' ) + assert_equal( ticket.group.name, 'Users', 'ticket.group verify' ) + assert_equal( ticket.ticket_state.name, 'new', 'ticket.state verify' ) + + delete = ticket.destroy + assert( delete, "ticket destroy" ) + end + + test 'ticket sla' do + 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' ) + + 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" => "8:00", + "end_of_workday" => "18:00", + }, + :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 1" ) + + sla = Sla.create( + :name => 'test sla 2', + :condition => { "tickets.ticket_priority_id" =>["1", "2", "3"] }, + :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 => 60, + :update_time => 120, + :close_time => 180, + :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 10:30:00 UTC', 'ticket.escalation_time verify 2' ) + assert_equal( ticket.first_response_escal_date.gmtime.to_s, '2013-03-21 10:30:00 UTC', 'ticket.first_response_escal_date verify 2' ) + assert_equal( ticket.first_response, nil, 'ticket.first_response verify 2' ) + assert_equal( ticket.first_response_in_min, nil, 'ticket.first_response_in_min verify 2' ) + assert_equal( ticket.first_response_diff_in_min, nil, 'ticket.first_response_diff_in_min verify 2' ) + assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-03-21 11:30:00 UTC', 'ticket.update_time_escal_date verify 2' ) + assert_equal( ticket.close_time_escal_date.gmtime.to_s, '2013-03-21 12:30:00 UTC', 'ticket.close_time_escal_date verify 2' ) + + ticket.update_attributes( +# :first_response_escal_date => '2013-03-26 09:30:00 UTC', + :first_response => '2013-03-21 10:00:00 UTC', + ) + ticket.escalation_calculation + 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.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.gmtime.to_s, '2013-03-21 10:00:00 UTC', 'ticket.first_response verify 3' ) + assert_equal( ticket.first_response_in_min, 30, 'ticket.first_response_in_min verify 3' ) + assert_equal( ticket.first_response_diff_in_min, 30, 'ticket.first_response_diff_in_min verify 3' ) + assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-03-21 11:30:00 UTC', 'ticket.update_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' ) + + ticket.update_attributes( +# :first_response_escal_date => '2013-03-26 09:30:00 UTC', + :first_response => '2013-03-21 14:00:00 UTC', + ) + ticket.escalation_calculation + 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.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.gmtime.to_s, '2013-03-21 14:00:00 UTC', 'ticket.first_response verify 4' ) + assert_equal( ticket.first_response_in_min, 270, 'ticket.first_response_in_min verify 4' ) + assert_equal( ticket.first_response_diff_in_min, -210, 'ticket.first_response_diff_in_min verify 4' ) + assert_equal( ticket.update_time_escal_date.gmtime.to_s, '2013-03-21 11:30:00 UTC', 'ticket.update_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' ) + + + + delete = sla.destroy + assert( delete, "sla destroy 2" ) + + delete = ticket.destroy + assert( delete, "ticket destroy" ) + delete = sla.destroy + assert( delete, "sla destroy" ) + end +end \ No newline at end of file