From 59876576c943e059f2b98a1cff76c566c88bdccc Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 16 Aug 2013 16:30:51 +0200 Subject: [PATCH] Added more docu, moved escalation to extra file. --- app/models/ticket.rb | 409 +++++++++----------------------- app/models/ticket/escalation.rb | 280 ++++++++++++++++++++++ 2 files changed, 390 insertions(+), 299 deletions(-) create mode 100644 app/models/ticket/escalation.rb diff --git a/app/models/ticket.rb b/app/models/ticket.rb index bd02c8859..b8ad95db8 100644 --- a/app/models/ticket.rb +++ b/app/models/ticket.rb @@ -22,6 +22,8 @@ class Ticket < ApplicationModel belongs_to :create_article_type, :class_name => 'Ticket::Article::Type' belongs_to :create_article_sender, :class_name => 'Ticket::Article::Sender' + include Ticket::Escalation + attr_accessor :callback_loop def agent_of_group @@ -116,6 +118,20 @@ class Ticket < ApplicationModel } end +=begin + +merge tickets + + result = Ticket.find(123).merge_to( + :ticket_id => 123, + ) + +returns + + result = true|false + +=end + def merge_to(data) # update articles @@ -153,9 +169,18 @@ class Ticket < ApplicationModel self.save end - # def self.agent - # Role.where( :name => ['Agent'], :active => true ).first.users.where( :active => true ).uniq() - # end +=begin + +build new subject with ticket number in there + + ticket = Ticket.find(123) + result = ticket.subject_build('some subject') + +returns + + result = "[Ticket#1234567] some subject" + +=end def subject_build (subject) @@ -179,6 +204,19 @@ class Ticket < ApplicationModel return "[#{ticket_hook}#{ticket_hook_divider}#{self.number}] " + subject end +=begin + +clean subject remove ticket number and other not needed chars + + ticket = Ticket.find(123) + result = ticket.subject_clean('[Ticket#1234567] some subject') + +returns + + result = "some subject" + +=end + def subject_clean (subject) ticket_hook = Setting.get('ticket_hook') ticket_hook_divider = Setting.get('ticket_hook_divider') @@ -205,9 +243,19 @@ class Ticket < ApplicationModel return subject end - # ticket.permission( - # :current_user => 123 - # ) +=begin + +check if user has access to ticket + + ticket = Ticket.find(123) + result = ticket.permission( :current_user => User.find(123) ) + +returns + + result = true|false + +=end + def permission (data) # check customer @@ -237,11 +285,22 @@ class Ticket < ApplicationModel return false end - # Ticket.search( - # :current_user => 123, - # :query => 'search something', - # :limit => 15, - # ) +=begin + +search tickets + + result = Ticket.search( + :current_user => User.find(123), + :query => 'search something', + :limit => 15, + ) + +returns + + result = [ticket_model1, ticket_model2] + +=end + def self.search (params) # get params @@ -282,9 +341,21 @@ class Ticket < ApplicationModel return tickets end - # Ticket.overview_list( - # :current_user => 123, - # ) + +=begin + +overview list + + result = Ticket.overview_list( + :current_user => User.find(123), + ) + +returns + + result = [overview1, overview2] + +=end + def self.overview_list (data) # get customer overviews @@ -304,10 +375,25 @@ class Ticket < ApplicationModel return overviews end - # Ticket.overview( - # :view => 'some_view_url', - # :current_user => OBJECT, - # ) +=begin + +search tickets + + result = Ticket.overview_list( + :current_user => User.find(123), + :view => 'some_view_url', + ) + +returns + + result = { + :tickets => tickets, # [ticket1, ticket2, ticket3] + :tickets_count => tickets_count, # count of tickets + :overview => overview_selected_raw, # overview attributes + } + +=end + def self.overview (data) overviews = self.overview_list(data) @@ -500,175 +586,6 @@ class Ticket < ApplicationModel return bind end - def self.escalation_calculation_rebuild - ticket_state_list_open = Ticket::State.by_category( 'open' ) - - tickets = Ticket.where( :ticket_state_id => ticket_state_list_open ) - tickets.each {|ticket| - ticket.escalation_calculation - } - end - - def _escalation_calculation_get_sla - - sla_selected = nil - sla_list = Cache.get( 'SLA::List::Active' ) - if sla_list == nil - sla_list = Sla.where( :active => true ).all - Cache.write( 'SLA::List::Active', sla_list, { :expires_in => 1.hour } ) - end - sla_list.each {|sla| - if !sla.condition || sla.condition.empty? - sla_selected = sla - elsif sla.condition - hit = false - map = [ - [ 'tickets.ticket_priority_id', 'ticket_priority_id' ], - [ 'tickets.group_id', 'group_id' ] - ] - map.each {|item| - if sla.condition[ item[0] ] - if sla.condition[ item[0] ].class == String - sla.condition[ item[0] ] = [ sla.condition[ item[0] ] ] - end - if sla.condition[ item[0] ].include?( self[ item[1] ].to_s ) - hit = true - else - hit = false - end - end - } - if hit - sla_selected = sla - end - end - } - - return sla_selected - 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 - - # set escalation off if ticket is already closed - ticket_state = Ticket::State.lookup( :id => self.ticket_state_id ) - if ticket_state.ignore_escalation? - self.escalation_time = nil - # self.first_response_escal_date = nil - # self.close_time_escal_date = nil - self.callback_loop = true - 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 - # self.first_response_escal_date = nil - # self.close_time_escal_date = nil - self.callback_loop = true - self.save - return true - end - - # 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 - - # 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, sla_selected.first_response_time ) - - # 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 - - # 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 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 - - - # update time - last_update = self.last_contact_agent - if !last_update - last_update = self.created_at - end - if sla_selected.update_time - self.update_time_escal_date = TimeCalculation.dest_time( last_update, sla_selected.update_time, sla_selected.data, sla_selected.timezone ) - - # get pending time between created and update escal. time - time_in_pending = escalation_suspend( last_update, self.update_time_escal_date, 'relative', sla_selected, sla_selected.update_time ) - - # get new escalation time (original escal_date + time_in_pending) - self.update_time_escal_date = TimeCalculation.dest_time( self.update_time_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.update_time_escal_date, false ) - end - if 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 - - # set sla time - if sla_selected.update_time && self.update_time_in_min - self.update_time_diff_in_min = sla_selected.update_time - self.update_time_in_min - end - - - # 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, sla_selected.close_time ) - - # 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 = 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 - self.close_time_diff_in_min = sla_selected.close_time - self.close_time_in_min - end - self.callback_loop = true - self.save - end - =begin list tickets by customer groupd in state categroie open and closed @@ -728,121 +645,15 @@ returns self.organization_id = customer.organization_id end end - - end + def destroy_dependencies - # delete history - History.remove( 'Ticket', self.id ) + # delete history + History.remove( 'Ticket', self.id ) - # 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, sla_time = 0) - if type == 'relative' - end_time += sla_time * 60 - end - total_time_without_pending = 0 - total_time = 0 - #get history for ticket - history_list = History.list( 'Ticket', self.id ) - - #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_id - history_attribute = History::Attribute.lookup( :id => history_item.history_attribute_id ); - next if history_attribute.name != 'ticket_state' - - # ignore all newer state before start_time - next if history_item.created_at < start_time - - # 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 - - # check if time need to be counted - counted = true - if history_item.value_from == 'pending' - counted = false - elsif history_item.value_from == 'close' - counted = false - end - - diff = escalation_time_diff( last_state_change, history_item.created_at, sla_selected ) - if counted - 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 - else - puts "Diff not count #{history_item.value_from} -> #{history_item.value_to} / #{last_state_change} -> #{history_item.created_at}" - end - total_time = total_time + diff - - if history_item.value_to == 'pending' - last_state_is_pending = true - else - 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 + # delete articles + self.articles.destroy_all + end end diff --git a/app/models/ticket/escalation.rb b/app/models/ticket/escalation.rb new file mode 100644 index 000000000..2a8fdbbbc --- /dev/null +++ b/app/models/ticket/escalation.rb @@ -0,0 +1,280 @@ +# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/ + +module Ticket::Escalation + + def self.escalation_calculation_rebuild + ticket_state_list_open = Ticket::State.by_category( 'open' ) + + tickets = Ticket.where( :ticket_state_id => ticket_state_list_open ) + tickets.each {|ticket| + ticket.escalation_calculation + } + end + + def escalation_calculation + + # set escalation off if ticket is already closed + ticket_state = Ticket::State.lookup( :id => self.ticket_state_id ) + if ticket_state.ignore_escalation? + self.escalation_time = nil + # self.first_response_escal_date = nil + # self.close_time_escal_date = nil + self.callback_loop = true + 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 + # self.first_response_escal_date = nil + # self.close_time_escal_date = nil + self.callback_loop = true + self.save + return true + end + + # 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 + + # 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, sla_selected.first_response_time ) + + # 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 + + # 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 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 + + + # update time + last_update = self.last_contact_agent + if !last_update + last_update = self.created_at + end + if sla_selected.update_time + self.update_time_escal_date = TimeCalculation.dest_time( last_update, sla_selected.update_time, sla_selected.data, sla_selected.timezone ) + + # get pending time between created and update escal. time + time_in_pending = escalation_suspend( last_update, self.update_time_escal_date, 'relative', sla_selected, sla_selected.update_time ) + + # get new escalation time (original escal_date + time_in_pending) + self.update_time_escal_date = TimeCalculation.dest_time( self.update_time_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.update_time_escal_date, false ) + end + if 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 + + # set sla time + if sla_selected.update_time && self.update_time_in_min + self.update_time_diff_in_min = sla_selected.update_time - self.update_time_in_min + end + + + # 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, sla_selected.close_time ) + + # 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 = 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 + self.close_time_diff_in_min = sla_selected.close_time - self.close_time_in_min + end + self.callback_loop = true + self.save + end + + private + + #type could be: + # real - time without supsend state + # relative - only suspend time + + def escalation_suspend (start_time, end_time, type, sla_selected, sla_time = 0) + if type == 'relative' + end_time += sla_time * 60 + end + total_time_without_pending = 0 + total_time = 0 + #get history for ticket + history_list = History.list( 'Ticket', self.id ) + + #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_id + history_attribute = History::Attribute.lookup( :id => history_item.history_attribute_id ); + next if history_attribute.name != 'ticket_state' + + # ignore all newer state before start_time + next if history_item.created_at < start_time + + # 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 + + # check if time need to be counted + counted = true + if history_item.value_from == 'pending' + counted = false + elsif history_item.value_from == 'close' + counted = false + end + + diff = escalation_time_diff( last_state_change, history_item.created_at, sla_selected ) + if counted + 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 + else + puts "Diff not count #{history_item.value_from} -> #{history_item.value_to} / #{last_state_change} -> #{history_item.created_at}" + end + total_time = total_time + diff + + if history_item.value_to == 'pending' + last_state_is_pending = true + else + 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 + + def _escalation_calculation_get_sla + + sla_selected = nil + sla_list = Cache.get( 'SLA::List::Active' ) + if sla_list == nil + sla_list = Sla.where( :active => true ).all + Cache.write( 'SLA::List::Active', sla_list, { :expires_in => 1.hour } ) + end + sla_list.each {|sla| + if !sla.condition || sla.condition.empty? + sla_selected = sla + elsif sla.condition + hit = false + map = [ + [ 'tickets.ticket_priority_id', 'ticket_priority_id' ], + [ 'tickets.group_id', 'group_id' ] + ] + map.each {|item| + if sla.condition[ item[0] ] + if sla.condition[ item[0] ].class == String + sla.condition[ item[0] ] = [ sla.condition[ item[0] ] ] + end + if sla.condition[ item[0] ].include?( self[ item[1] ].to_s ) + hit = true + else + hit = false + end + end + } + if hit + sla_selected = sla + end + end + } + + return sla_selected + 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 +end \ No newline at end of file