Added escalations of UI.

This commit is contained in:
Martin Edenhofer 2013-03-07 08:13:56 +01:00
parent 2abc1dd965
commit f3a51cf78f
12 changed files with 159 additions and 59 deletions

View file

@ -80,6 +80,7 @@ class App.Controller extends Spine.Controller
{ name: 'last_contact_customer', callback: @frontendTime },
{ name: 'first_response', callback: @frontendTime },
{ name: 'close_time', callback: @frontendTime },
{ name: 'escalation_time', callback: @frontendTime, subclass: 'escalation' },
{ name: 'article_count', },
]
shown_all_attributes = []
@ -104,40 +105,60 @@ class App.Controller extends Spine.Controller
return size
# human readable time
humanTime: (time) =>
humanTime: ( time, escalation ) =>
current = new Date()
created = new Date(time)
string = ''
diff = ( current - created ) / 1000
escalated = ''
if escalation
if diff > 0
escalated = '-'
if diff > -60 * 60
style = "class=\"label label-important\""
else
style = "class=\"label label-success\""
if diff.toString().match('-')
diff = diff.toString().replace('-', '')
diff = parseFloat(diff)
if diff >= 86400
unit = Math.round( ( diff / 86400 ) )
unit = Math.floor( ( diff / 86400 ) )
# if unit > 1
# return unit + ' ' + App.i18n.translateContent('days')
# else
# return unit + ' ' + App.i18n.translateContent('day')
string = unit + ' ' + App.i18n.translateInline('d')
if diff >= 3600
unit = Math.round( ( diff / 3600 ) % 24 )
unit = Math.floor( ( diff / 3600 ) % 24 )
# if unit > 1
# return unit + ' ' + App.i18n.translateContent('hours')
# else
# return unit + ' ' + App.i18n.translateContent('hour')
if string isnt ''
string = string + ' ' + unit + ' ' + App.i18n.translateInline('h')
if escalation
string = "<span #{style}>#{escalated}#{string}</b>"
return string
else
string = unit + ' ' + App.i18n.translateInline('h')
if diff <= 86400
unit = Math.round( ( diff / 60 ) % 60 )
unit = Math.floor( ( diff / 60 ) % 60 )
# if unit > 1
# return unit + ' ' + App.i18n.translateContent('minutes')
# else
# return unit + ' ' + App.i18n.translateContent('minute')
if string isnt ''
string = string + ' ' + unit + ' ' + App.i18n.translateInline('m')
if escalation
string = "<span #{style}>#{escalated}#{string}</b>"
return string
else
string = unit + ' ' + App.i18n.translateInline('m')
if escalation
string = "<span #{style}>#{escalated}#{string}</b>"
return string
userInfo: (data) =>
@ -162,18 +183,20 @@ class App.Controller extends Spine.Controller
@navigate '#login'
return false
frontendTime: (timestamp) ->
'<span class="humanTimeFromNow" data-time="' + timestamp + '">?</span>'
frontendTime: (timestamp, row = {}) ->
if !row['subclass']
row['subclass'] = ''
"<span class=\"humanTimeFromNow #{row.subclass}\" data-time=\"#{timestamp}\">?</span>"
frontendTimeUpdate: =>
update = =>
ui = @
$('.humanTimeFromNow').each( ->
# console.log('rewrite frontendTimeUpdate', this)
# console.log('rewrite frontendTimeUpdate', this, $(this).hasClass('escalation'))
timestamp = $(this).data('time')
time = ui.humanTime( timestamp )
time = ui.humanTime( timestamp, $(this).hasClass('escalation') )
$(this).attr( 'title', App.i18n.translateTimestamp(timestamp) )
$(this).text( time )
$(this).html( time )
)
@interval( update, 30000, 'frontendTimeUpdate' )
@ -346,7 +369,7 @@ class App.ControllerModal extends App.Controller
super(options)
modalShow: (params) =>
modalShow: (params) ->
defaults = {
backdrop: true,
keyboard: true,
@ -369,12 +392,12 @@ class App.ControllerModal extends App.Controller
$('.modal').remove();
)
modalHide: (e) =>
modalHide: (e) ->
if e
e.preventDefault()
@el.modal('hide')
submit: (e) =>
submit: (e) ->
e.preventDefault()
@log 'You need to implement your own "submit" method!'

View file

@ -179,19 +179,20 @@ class Settings extends App.ControllerModal
null: false,
translate: true
options: {
number: 'Number',
title: 'Title',
customer: 'Customer',
ticket_state: 'State',
ticket_priority: 'Priority',
group: 'Group',
owner: 'Owner',
created_at: 'Age',
last_contact: 'Last Contact',
last_contact_agent: 'Last Contact Agent',
last_contact_customer: 'Last Contact Customer',
first_response: 'First Response',
close_time: 'Close Time',
number: 'Number'
title: 'Title'
customer: 'Customer'
ticket_state: 'State'
ticket_priority: 'Priority'
group: 'Group'
owner: 'Owner'
created_at: 'Age'
last_contact: 'Last Contact'
last_contact_agent: 'Last Contact Agent'
last_contact_customer: 'Last Contact Customer'
first_response: 'First Response'
close_time: 'Close Time'
escalation_time: 'Escalation in'
article_count: 'Article Count'
},
class: 'medium',
@ -205,19 +206,20 @@ class Settings extends App.ControllerModal
null: false,
translate: true
options: {
number: 'Number',
title: 'Title',
customer: 'Customer',
ticket_state: 'State',
ticket_priority: 'Priority',
group: 'Group',
owner: 'Owner',
created_at: 'Age',
last_contact: 'Last Contact',
last_contact_agent: 'Last Contact Agent',
last_contact_customer: 'Last Contact Customer',
first_response: 'First Response',
close_time: 'Close Time',
number: 'Number'
title: 'Title'
customer: 'Customer'
ticket_state: 'State'
ticket_priority: 'Priority'
group: 'Group'
owner: 'Owner'
created_at: 'Age'
last_contact: 'Last Contact'
last_contact_agent: 'Last Contact Agent'
last_contact_customer: 'Last Contact Customer'
first_response: 'First Response'
close_time: 'Close Time'
escalation_time: 'Escalation in'
article_count: 'Article Count'
},
class: 'medium',

View file

@ -394,6 +394,7 @@ class Settings extends App.ControllerModal
last_contact_customer: 'Last Contact Customer'
first_response: 'First Response'
close_time: 'Close Time'
escalation_time: 'Escalation in'
article_count: 'Article Count'
class: 'medium'
},
@ -418,6 +419,7 @@ class Settings extends App.ControllerModal
last_contact_customer: 'Last Contact Customer'
first_response: 'First Response'
close_time: 'Close Time'
escalation_time: 'Escalation in'
article_count: 'Article Count'
class: 'medium'
},

View file

@ -26,7 +26,7 @@ class App extends Spine.Controller
# execute callback on content
if row.callback
return row.callback( item )
return row.callback( item, row )
# return raw data
item

View file

@ -4,9 +4,9 @@ class App.Sla extends Spine.Model
@url: 'api/slas'
@configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false, 'class': 'span4' },
{ name: 'first_response_time', display: 'First Resposne Time', tag: 'input', type: 'text', limit: 100, null: true, 'class': 'span4', note: 'In minutes, only business hours are counted.' },
{ name: 'update_time', display: 'Update Time', tag: 'input', type: 'text', limit: 100, null: true, 'class': 'span4', note: 'In minutes, only business hours are counted.' },
{ name: 'close_time', display: 'Solution Time', tag: 'input', type: 'text', limit: 100, null: true, 'class': 'span4', note: 'In minutes, only business hours are counted.' },
{ name: 'first_response_time', display: 'First Resposne Time', tag: 'input', type: 'text', limit: 100, null: true, 'class': 'span4', note: 'In minutes, only business times are counted.' },
{ name: 'update_time', display: 'Update Time', tag: 'input', type: 'text', limit: 100, null: true, 'class': 'span4', note: 'In minutes, only business times are counted.' },
{ name: 'close_time', display: 'Solution Time', tag: 'input', type: 'text', limit: 100, null: true, 'class': 'span4', note: 'In minutes, only business times are counted.' },
{ name: 'condition', display: 'Conditions where SLA is used', tag: 'ticket_attribute_selection', null: true, class: 'span4' },
{
name: 'data'

View file

@ -16,5 +16,6 @@ class App.Ticket extends App.Model
{ name: 'last_contact_customer', display: 'Last contact (Customer)', tag: 'time', null: true, style: 'width: 12%' },
{ name: 'first_response', display: 'First response', tag: 'time', null: true, style: 'width: 12%' },
{ name: 'close_time', display: 'Close time', tag: 'time', null: true, style: 'width: 12%' },
{ name: 'escalation_time', display: 'Escalation in', tag: 'time', null: true, style: 'width: 12%' },
{ name: 'article_count', display: 'Article#', style: 'width: 12%' },
]

View file

@ -22,6 +22,10 @@
<%- @T( @ticket.ticket_state.name ) %> -
<%- @T( @ticket.ticket_priority.name ) %> -
<span class="humanTimeFromNow" data-time="<%- @ticket.created_at %>">?</span>
<% if @ticket.escalation_time: %>
- <%- @T('Escalation in') %>
<span class="humanTimeFromNow escalation" data-time="<%- @ticket.escalation_time %>">?</span>
<% end %>
</div>
</div>
</div>

View file

@ -11,8 +11,16 @@ class Observer::Ticket::UserTicketCounter < ActiveRecord::Observer
def user_ticket_counter_update(record)
return if !record.customer_id
ticket_state_list_open = Ticket::State.where( :state_type_id => Ticket::StateType.where( :name => ['new','open', 'pending reminder', 'pending action']) )
ticket_state_list_closed = Ticket::State.where( :state_type_id => Ticket::StateType.where( :name => ['closed'] ) )
ticket_state_list_open = Ticket::State.where(
:state_type_id => Ticket::StateType.where(
:name => ['new','open', 'pending reminder', 'pending action']
)
)
ticket_state_list_closed = Ticket::State.where(
:state_type_id => Ticket::StateType.where(
:name => ['closed']
)
)
tickets_open = Ticket.where( :customer_id => record.customer_id, :ticket_state_id => ticket_state_list_open ).count()
tickets_closed = Ticket.where( :customer_id => record.customer_id, :ticket_state_id => ticket_state_list_closed ).count()

View file

@ -2,4 +2,12 @@ class Sla < ApplicationModel
store :condition
store :data
validates :name, :presence => true
after_create :escalation_calculation_rebuild
after_update :escalation_calculation_rebuild
private
def escalation_calculation_rebuild
Ticket.escalation_calculation_rebuild
end
end

View file

@ -403,29 +403,60 @@ class Ticket < ApplicationModel
return adapter
end
def self.escalation_calculation_rebuild
ticket_state_list_open = Ticket::State.where(
:state_type_id => Ticket::StateType.where(
:name => ['new','open', 'pending reminder', 'pending action']
)
)
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 )
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
hit = false
if sla.condition['tickets.ticket_priority_id']
if sla.condition['tickets.ticket_priority_id'].class == String
sla.condition['tickets.ticket_priority_id'] = [ sla.condition['tickets.ticket_priority_id'].to_i ]
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 sla.condition['tickets.ticket_priority_id'].include?( self.ticket_priority_id )
hit = true
else
hit = false
end
end
}
if hit
sla_selected = sla
end
end
}
return if !sla_selected
# get calendar settings
@ -441,12 +472,19 @@ class Ticket < ApplicationModel
# puts sla_selected.inspect
# puts days.inspect
self.escalation_time = nil
self.first_response_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_sla_time =
#!self.first_response &&
# 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
end
if self.first_response && !self.first_response_in_min
@ -466,6 +504,11 @@ class Ticket < ApplicationModel
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 )
# 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
end
if self.close_time && !self.close_time_in_min

View file

@ -0,0 +1,9 @@
class ChangeTicketEscalation2 < ActiveRecord::Migration
def up
add_column :tickets, :escalation_time, :timestamp, :null => true
add_index :tickets, [:escalation_time]
end
def down
end
end

View file

@ -1298,13 +1298,13 @@ Overview.create_if_not_exists(
'tickets.ticket_state_id' => [1,2,3],
},
:order => {
:by => 'created_at',
:by => 'escalation_time',
:direction => 'ASC',
},
:view => {
:d => [ 'title', 'customer', 'ticket_state', 'group', 'owner', 'created_at' ],
:s => [ 'number', 'title', 'customer', 'ticket_state', 'ticket_priority', 'group', 'owner', 'created_at' ],
:m => [ 'number', 'title', 'customer', 'ticket_state', 'ticket_priority', 'group', 'owner', 'created_at' ],
:d => [ 'title', 'customer', 'ticket_state', 'group', 'owner', 'escalation_time' ],
:s => [ 'number', 'title', 'customer', 'ticket_state', 'ticket_priority', 'group', 'owner', 'escalation_time' ],
:m => [ 'number', 'title', 'customer', 'ticket_state', 'ticket_priority', 'group', 'owner', 'escalation_time' ],
:view_mode_default => 's',
},
:updated_by_id => 1,