Added escalations of UI.
This commit is contained in:
parent
2abc1dd965
commit
f3a51cf78f
12 changed files with 159 additions and 59 deletions
|
@ -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!'
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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'
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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%' },
|
||||
]
|
|
@ -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>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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['tickets.ticket_priority_id'].include?( self.ticket_priority_id )
|
||||
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 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
|
||||
|
|
9
db/migrate/20130306224545_change_ticket_escalation2.rb
Normal file
9
db/migrate/20130306224545_change_ticket_escalation2.rb
Normal 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
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue