Added initial version of Out Of Office functionality.
This commit is contained in:
parent
8958a3ac63
commit
20a8e40484
27 changed files with 882 additions and 56 deletions
|
@ -344,7 +344,10 @@ class App.Controller extends Spine.Controller
|
|||
title: ->
|
||||
userId = $(@).data('id')
|
||||
user = App.User.find(userId)
|
||||
App.Utils.htmlEscape(user.displayName())
|
||||
headline = App.Utils.htmlEscape(user.displayName())
|
||||
if user.isOutOfOffice()
|
||||
headline += " (#{App.Utils.htmlEscape(user.outOfOfficeText())})"
|
||||
headline
|
||||
content: ->
|
||||
userId = $(@).data('id')
|
||||
user = App.User.fullLocal(userId)
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
class Index extends App.ControllerSubContent
|
||||
requiredPermission: 'user_preferences.out_of_office+ticket.agent'
|
||||
header: 'Out of Office'
|
||||
events:
|
||||
'submit form': 'submit'
|
||||
'click .js-disabled': 'disable'
|
||||
'click .js-enable': 'enable'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
@render()
|
||||
|
||||
render: =>
|
||||
user = @Session.get()
|
||||
if !@localData
|
||||
@localData =
|
||||
out_of_office: user.out_of_office
|
||||
out_of_office_start_at: user.out_of_office_start_at
|
||||
out_of_office_end_at: user.out_of_office_end_at
|
||||
out_of_office_replacement_id: user.out_of_office_replacement_id
|
||||
out_of_office_replacement_id_completion: user.preferences.out_of_office_replacement_id_completion
|
||||
out_of_office_text: user.preferences.out_of_office_text
|
||||
form = $(App.view('profile/out_of_office')(
|
||||
user: user
|
||||
localData: @localData
|
||||
placeholder: App.User.outOfOfficeTextPlaceholder()
|
||||
))
|
||||
|
||||
dateStart = new App.ControllerForm(
|
||||
model:
|
||||
configure_attributes:
|
||||
[
|
||||
name: 'out_of_office_start_at'
|
||||
display: ''
|
||||
tag: 'date'
|
||||
past: false
|
||||
future: true
|
||||
null: false
|
||||
]
|
||||
noFieldset: true
|
||||
params: @localData
|
||||
)
|
||||
form.find('.js-startDate').html(dateStart.form)
|
||||
|
||||
dateEnd = new App.ControllerForm(
|
||||
model:
|
||||
configure_attributes:
|
||||
[
|
||||
name: 'out_of_office_end_at'
|
||||
display: ''
|
||||
tag: 'date'
|
||||
past: false
|
||||
future: true
|
||||
null: false
|
||||
]
|
||||
noFieldset: true
|
||||
params: @localData
|
||||
)
|
||||
form.find('.js-endDate').html(dateEnd.form)
|
||||
|
||||
agentList = new App.ControllerForm(
|
||||
model:
|
||||
configure_attributes:
|
||||
[
|
||||
name: 'out_of_office_replacement_id'
|
||||
display: ''
|
||||
relation: 'User'
|
||||
tag: 'user_autocompletion'
|
||||
autocapitalize: false
|
||||
multiple: false
|
||||
limit: 30
|
||||
minLengt: 2
|
||||
placeholder: 'Enter Person or Organization/Company'
|
||||
null: false
|
||||
translate: false
|
||||
disableCreateObject: true
|
||||
value: @localData
|
||||
]
|
||||
noFieldset: true
|
||||
params: @localData
|
||||
)
|
||||
form.find('.js-recipientDropdown').html(agentList.form)
|
||||
if @localData.out_of_office is true
|
||||
form.find('.js-disabled').removeClass('is-disabled')
|
||||
#form.find('.js-enable').addClass('is-disabled')
|
||||
else
|
||||
form.find('.js-disabled').addClass('is-disabled')
|
||||
#form.find('.js-enable').removeClass('is-disabled')
|
||||
@html(form)
|
||||
|
||||
enable: (e) =>
|
||||
e.preventDefault()
|
||||
params = @formParam(e.target)
|
||||
params.out_of_office = true
|
||||
@store(e, params)
|
||||
|
||||
disable: (e) =>
|
||||
e.preventDefault()
|
||||
params = @formParam(e.target)
|
||||
params.out_of_office = false
|
||||
@store(e, params)
|
||||
|
||||
submit: (e, params) =>
|
||||
e.preventDefault()
|
||||
params = @formParam(e.target)
|
||||
@store(e, params)
|
||||
|
||||
store: (e, params) =>
|
||||
@formDisable(e)
|
||||
for key, value of params
|
||||
@localData[key] = value
|
||||
App.Ajax.request(
|
||||
id: 'user_out_of_office'
|
||||
type: 'PUT'
|
||||
url: "#{@apiPath}/users/out_of_office"
|
||||
data: JSON.stringify(params)
|
||||
processData: true
|
||||
success: @success
|
||||
error: @error
|
||||
)
|
||||
|
||||
success: (data) =>
|
||||
if data.message is 'ok'
|
||||
@render()
|
||||
@notify(
|
||||
type: 'success'
|
||||
msg: App.i18n.translateContent('Successfully!')
|
||||
timeout: 1000
|
||||
)
|
||||
else
|
||||
if data.notice
|
||||
@notify
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent(data.notice[0], data.notice[1])
|
||||
removeAll: true
|
||||
else
|
||||
@notify
|
||||
type: 'error'
|
||||
msg: 'Please contact your administrator.'
|
||||
removeAll: true
|
||||
@formEnable( @$('form') )
|
||||
|
||||
error: (xhr, status, error) =>
|
||||
@formEnable( @$('form') )
|
||||
|
||||
# do not close window if request is aborted
|
||||
return if status is 'abort'
|
||||
data = JSON.parse(xhr.responseText)
|
||||
|
||||
# show error message
|
||||
if xhr.status is 401 || error is 'Unauthorized'
|
||||
message = '» ' + App.i18n.translateInline('Unauthorized') + ' «'
|
||||
else if xhr.status is 404 || error is 'Not Found'
|
||||
message = '» ' + App.i18n.translateInline('Not Found') + ' «'
|
||||
else if data.error
|
||||
message = App.i18n.translateInline(data.error)
|
||||
else
|
||||
message = '» ' + App.i18n.translateInline('Error') + ' «'
|
||||
@notify
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent(message)
|
||||
removeAll: true
|
||||
|
||||
App.Config.set('OutOfOffice', { prio: 2800, name: 'Out of Office', parent: '#profile', target: '#profile/out_of_office', permission: ['user_preferences.out_of_office+ticket.agent'], controller: Index }, 'NavBarProfile')
|
|
@ -20,7 +20,7 @@ class App.UiElement.postmaster_set
|
|||
name: 'Customer'
|
||||
relation: 'User'
|
||||
tag: 'user_autocompletion'
|
||||
disableCreateUser: true
|
||||
disableCreateObject: true
|
||||
}
|
||||
{
|
||||
value: 'group_id'
|
||||
|
@ -32,7 +32,7 @@ class App.UiElement.postmaster_set
|
|||
name: 'Owner'
|
||||
relation: 'User'
|
||||
tag: 'user_autocompletion'
|
||||
disableCreateUser: true
|
||||
disableCreateObject: true
|
||||
}
|
||||
]
|
||||
article:
|
||||
|
|
|
@ -2,5 +2,5 @@
|
|||
class App.UiElement.user_autocompletion_search
|
||||
@render: (attributeOrig, params = {}) ->
|
||||
attribute = _.clone(attributeOrig)
|
||||
attribute.disableCreateUser = true
|
||||
attribute.disableCreateObject = true
|
||||
new App.UserOrganizationAutocompletion(attribute: attribute, params: params).element()
|
||||
|
|
|
@ -1499,7 +1499,7 @@ class InputsRef extends App.ControllerContent
|
|||
null: false
|
||||
relation: 'User'
|
||||
autocapitalize: false
|
||||
disableCreateUser: true
|
||||
disableCreateObject: true
|
||||
multiple: true
|
||||
|
||||
@$('.userOrganizationAutocompletePlaceholder').replaceWith( userOrganizationAutocomplete.element() )
|
||||
|
|
|
@ -6,7 +6,7 @@ class App.TicketCustomer extends App.ControllerModal
|
|||
|
||||
content: ->
|
||||
configure_attributes = [
|
||||
{ name: 'customer_id', display: 'Customer', tag: 'user_autocompletion', null: false, placeholder: 'Enter Person or Organization/Company', minLengt: 2, disableCreateUser: true },
|
||||
{ name: 'customer_id', display: 'Customer', tag: 'user_autocompletion', null: false, placeholder: 'Enter Person or Organization/Company', minLengt: 2, disableCreateObject: true },
|
||||
]
|
||||
controller = new App.ControllerForm(
|
||||
model:
|
||||
|
|
|
@ -7,6 +7,12 @@ class App.WidgetAvatar extends App.ObserverController
|
|||
email: true
|
||||
image: true
|
||||
vip: true
|
||||
out_of_office: true,
|
||||
out_of_office_start_at: true,
|
||||
out_of_office_end_at: true,
|
||||
out_of_office_replacement_id: true,
|
||||
active: true
|
||||
|
||||
globalRerender: false
|
||||
|
||||
render: (user) =>
|
||||
|
|
|
@ -8,6 +8,7 @@ class App.Overview extends App.Model
|
|||
{ name: 'role_ids', display: 'Available for Role', tag: 'column_select', multiple: true, null: false, relation: 'Role', translate: true },
|
||||
{ name: 'user_ids', display: 'Available for User', tag: 'column_select', multiple: true, null: true, relation: 'User', sortBy: 'firstname' },
|
||||
{ name: 'organization_shared', display: 'Only available for Users with shared Organization', tag: 'select', options: { true: 'yes', false: 'no' }, default: false, null: true },
|
||||
{ name: 'out_of_office', display: 'Only available for Users which are replacements for other users.', tag: 'select', options: { true: 'yes', false: 'no' }, default: false, null: true },
|
||||
{ name: 'condition', display: 'Conditions for shown Tickets', tag: 'ticket_selector', null: false },
|
||||
{ name: 'prio', display: 'Prio', readonly: 1 },
|
||||
{
|
||||
|
@ -72,4 +73,4 @@ Sie können auch individuelle Übersichten für einzelne Agenten oder agenten Gr
|
|||
'''
|
||||
|
||||
uiUrl: ->
|
||||
'#ticket/view/' + @link
|
||||
"#ticket/view/#{@link}"
|
||||
|
|
|
@ -53,8 +53,14 @@ class App.User extends App.Model
|
|||
cssClass += ' ' if cssClass
|
||||
cssClass += "size-#{ size }"
|
||||
|
||||
if @active is false
|
||||
cssClass += ' avatar--inactive'
|
||||
|
||||
if @isOutOfOffice()
|
||||
cssClass += ' avatar--vacation'
|
||||
|
||||
if placement
|
||||
placement = " data-placement='#{ placement }'"
|
||||
placement = " data-placement='#{placement}'"
|
||||
|
||||
if !avatar
|
||||
if type is 'personal'
|
||||
|
@ -104,6 +110,19 @@ class App.User extends App.Model
|
|||
vip: vip
|
||||
url: @imageUrl()
|
||||
|
||||
isOutOfOffice: ->
|
||||
return false if @out_of_office isnt true
|
||||
start_time = @out_of_office_start_at
|
||||
return false if !start_time
|
||||
end_time = @out_of_office_end_at
|
||||
return false if !end_time
|
||||
start_time = new Date(Date.parse(start_time))
|
||||
end_time = new Date(Date.parse(end_time))
|
||||
now = new Date((new Date).toDateString())
|
||||
if start_time <= now && end_time >= now
|
||||
return true
|
||||
false
|
||||
|
||||
imageUrl: ->
|
||||
return if !@image
|
||||
# set image url
|
||||
|
@ -237,3 +256,16 @@ class App.User extends App.Model
|
|||
break
|
||||
return access if access
|
||||
false
|
||||
|
||||
@outOfOfficeTextPlaceholder: ->
|
||||
today = new Date()
|
||||
outOfOfficeText = 'Christmas holiday'
|
||||
if today.getMonth() < 3
|
||||
outOfOfficeText = 'Easter holiday'
|
||||
else if today.getMonth() < 9
|
||||
outOfOfficeText = 'Summer holiday'
|
||||
outOfOfficeText
|
||||
|
||||
outOfOfficeText: ->
|
||||
return @preferences.out_of_office_text if !_.isEmpty(@preferences.out_of_office_text)
|
||||
App.User.outOfOfficeTextPlaceholder()
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<div class="page-header">
|
||||
<div class="page-header-title"><h1><%- @T('Out of Office') %></h1></div>
|
||||
</div>
|
||||
|
||||
<form class="action">
|
||||
<div class="action-flow action-flow--row">
|
||||
<div class="action-row">
|
||||
<div class="action-flow action-flow--noWrap">
|
||||
<h2><span class="action-form-status">
|
||||
<% if @localData.out_of_office is true: %>
|
||||
<%- @Icon('status', 'ok inline') %>
|
||||
<% else: %>
|
||||
<%- @Icon('status', 'error inline') %>
|
||||
<% end %></span> <input id="out_of_office_reason" name="out_of_office_text" class="form-control form-control--inline" type="text" placeholder="<%- @Ti('e. g.') %> <%- @Ti(@placeholder) %>" value="<% if !_.isEmpty(@localData.out_of_office_text): %><%= @localData.out_of_office_text %><% end %>"></h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-block action-block--flex">
|
||||
<div class="label"><%- @T('From') %></div>
|
||||
<div class="form-group js-startDate"></div>
|
||||
</div>
|
||||
<div class="action-block action-block--flex">
|
||||
<div class="label"><%- @T('Till') %></div>
|
||||
<div class="form-group js-endDate"></div>
|
||||
</div>
|
||||
<div class="action-row">
|
||||
<label for="out_of_office_replacement"><%- @T('Replacement') %></label>
|
||||
<div class="dropdown js-recipientDropdown"></div>
|
||||
</div>
|
||||
<div class="action-controls">
|
||||
<div class="btn btn--danger js-disabled"><%- @Ti('Disable') %></div>
|
||||
<div class="btn btn--create js-enable"><%- @Ti('Enable') %></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -3695,6 +3695,16 @@ footer {
|
|||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
&--vacation {
|
||||
filter: grayscale(70%);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&--unique {
|
||||
background-image: image_url("/assets/images/avatar-bg.png");
|
||||
background-size: 300px 226px;
|
||||
|
@ -7816,6 +7826,10 @@ output {
|
|||
|
||||
h2 {
|
||||
margin-bottom: 0;
|
||||
|
||||
.action-form-status .icon {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.action-block,
|
||||
|
|
|
@ -538,7 +538,7 @@ Response:
|
|||
}
|
||||
|
||||
Test:
|
||||
curl http://localhost/api/v1/users/email_verify.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"token": "SoMeToKeN"}'
|
||||
curl http://localhost/api/v1/users/email_verify -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"token": "SoMeToKeN"}'
|
||||
|
||||
=end
|
||||
|
||||
|
@ -567,7 +567,7 @@ Response:
|
|||
}
|
||||
|
||||
Test:
|
||||
curl http://localhost/api/v1/users/email_verify_send.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"email": "some_email@example.com"}'
|
||||
curl http://localhost/api/v1/users/email_verify_send -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"email": "some_email@example.com"}'
|
||||
|
||||
=end
|
||||
|
||||
|
@ -626,7 +626,7 @@ Response:
|
|||
}
|
||||
|
||||
Test:
|
||||
curl http://localhost/api/v1/users/password_reset.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"username": "some_username"}'
|
||||
curl http://localhost/api/v1/users/password_reset -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"username": "some_username"}'
|
||||
|
||||
=end
|
||||
|
||||
|
@ -678,7 +678,7 @@ Response:
|
|||
}
|
||||
|
||||
Test:
|
||||
curl http://localhost/api/v1/users/password_reset_verify.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"token": "SoMeToKeN", "password" "new_password"}'
|
||||
curl http://localhost/api/v1/users/password_reset_verify -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"token": "SoMeToKeN", "password" "new_password"}'
|
||||
|
||||
=end
|
||||
|
||||
|
@ -734,7 +734,7 @@ Response:
|
|||
}
|
||||
|
||||
Test:
|
||||
curl http://localhost/api/v1/users/password_change.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"password_old": "password_old", "password_new": "password_new"}'
|
||||
curl http://localhost/api/v1/users/password_change -v -u #{login}:#{password} -H "Content-Type: application/json" -X POST -d '{"password_old": "password_old", "password_new": "password_new"}'
|
||||
|
||||
=end
|
||||
|
||||
|
@ -781,7 +781,7 @@ curl http://localhost/api/v1/users/password_change.json -v -u #{login}:#{passwor
|
|||
=begin
|
||||
|
||||
Resource:
|
||||
PUT /api/v1/users/preferences.json
|
||||
PUT /api/v1/users/preferences
|
||||
|
||||
Payload:
|
||||
{
|
||||
|
@ -795,7 +795,7 @@ Response:
|
|||
}
|
||||
|
||||
Test:
|
||||
curl http://localhost/api/v1/users/preferences.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X PUT -d '{"language": "de", "notifications": true}'
|
||||
curl http://localhost/api/v1/users/preferences -v -u #{login}:#{password} -H "Content-Type: application/json" -X PUT -d '{"language": "de", "notifications": true}'
|
||||
|
||||
=end
|
||||
|
||||
|
@ -808,7 +808,7 @@ curl http://localhost/api/v1/users/preferences.json -v -u #{login}:#{password} -
|
|||
params[:user].each { |key, value|
|
||||
user.preferences[key.to_sym] = value
|
||||
}
|
||||
user.save
|
||||
user.save!
|
||||
end
|
||||
end
|
||||
render json: { message: 'ok' }, status: :ok
|
||||
|
@ -817,7 +817,47 @@ curl http://localhost/api/v1/users/preferences.json -v -u #{login}:#{password} -
|
|||
=begin
|
||||
|
||||
Resource:
|
||||
DELETE /api/v1/users/account.json
|
||||
PUT /api/v1/users/out_of_office
|
||||
|
||||
Payload:
|
||||
{
|
||||
"out_of_office": true,
|
||||
"out_of_office_start_at": true,
|
||||
"out_of_office_end_at": true,
|
||||
"out_of_office_replacement_id": 123,
|
||||
"out_of_office_text": 'honeymoon'
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
:message => 'ok'
|
||||
}
|
||||
|
||||
Test:
|
||||
curl http://localhost/api/v1/users/out_of_office -v -u #{login}:#{password} -H "Content-Type: application/json" -X PUT -d '{"out_of_office": true, "out_of_office_replacement_id": 123}'
|
||||
|
||||
=end
|
||||
|
||||
def out_of_office
|
||||
raise Exceptions::UnprocessableEntity, 'No current user!' if !current_user
|
||||
user = User.find(current_user.id)
|
||||
user.with_lock do
|
||||
user.assign_attributes(
|
||||
out_of_office: params[:out_of_office],
|
||||
out_of_office_start_at: params[:out_of_office_start_at],
|
||||
out_of_office_end_at: params[:out_of_office_end_at],
|
||||
out_of_office_replacement_id: params[:out_of_office_replacement_id],
|
||||
)
|
||||
user.preferences[:out_of_office_text] = params[:out_of_office_text]
|
||||
user.save!
|
||||
end
|
||||
render json: { message: 'ok' }, status: :ok
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
Resource:
|
||||
DELETE /api/v1/users/account
|
||||
|
||||
Payload:
|
||||
{
|
||||
|
@ -831,7 +871,7 @@ Response:
|
|||
}
|
||||
|
||||
Test:
|
||||
curl http://localhost/api/v1/users/account.json -v -u #{login}:#{password} -H "Content-Type: application/json" -X PUT -d '{"provider": "twitter", "uid": 581482342942}'
|
||||
curl http://localhost/api/v1/users/account -v -u #{login}:#{password} -H "Content-Type: application/json" -X PUT -d '{"provider": "twitter", "uid": 581482342942}'
|
||||
|
||||
=end
|
||||
|
||||
|
|
|
@ -19,11 +19,11 @@ returns
|
|||
# get customer overviews
|
||||
role_ids = User.joins(:roles).where(users: { id: current_user.id, active: true }, roles: { active: true }).pluck('roles.id')
|
||||
if current_user.permissions?('ticket.customer')
|
||||
overviews = if current_user.organization_id && current_user.organization.shared
|
||||
Overview.joins(:roles).where(overviews_roles: { role_id: role_ids }, overviews: { active: true }).distinct('overview.id').order(:prio)
|
||||
else
|
||||
Overview.joins(:roles).where(overviews_roles: { role_id: role_ids }, overviews: { active: true, organization_shared: false }).distinct('overview.id').order(:prio)
|
||||
end
|
||||
overview_filter = { active: true, organization_shared: false }
|
||||
if current_user.organization_id && current_user.organization.shared
|
||||
overview_filter.delete(:organization_shared)
|
||||
end
|
||||
overviews = Overview.joins(:roles).where(overviews_roles: { role_id: role_ids }, overviews: overview_filter).distinct('overview.id').order(:prio)
|
||||
overviews_list = []
|
||||
overviews.each { |overview|
|
||||
user_ids = overview.user_ids
|
||||
|
@ -35,7 +35,12 @@ returns
|
|||
|
||||
# get agent overviews
|
||||
return [] if !current_user.permissions?('ticket.agent')
|
||||
overviews = Overview.joins(:roles).where(overviews_roles: { role_id: role_ids }, overviews: { active: true }).distinct('overview.id').order(:prio)
|
||||
overview_filter = { active: true }
|
||||
overview_filter_not = { out_of_office: true }
|
||||
if User.where('out_of_office = ? AND out_of_office_start_at <= ? AND out_of_office_end_at >= ? AND out_of_office_replacement_id = ? AND active = ?', true, Time.zone.today, Time.zone.today, current_user.id, true).count.positive?
|
||||
overview_filter_not = {}
|
||||
end
|
||||
overviews = Overview.joins(:roles).where(overviews_roles: { role_id: role_ids }, overviews: overview_filter).where.not(overview_filter_not).distinct('overview.id').order(:prio)
|
||||
overviews_list = []
|
||||
overviews.each { |overview|
|
||||
user_ids = overview.user_ids
|
||||
|
|
|
@ -26,12 +26,11 @@ class Transaction::Notification
|
|||
|
||||
# return if we run import mode
|
||||
return if Setting.get('import_mode')
|
||||
|
||||
return if @item[:object] != 'Ticket'
|
||||
|
||||
return if @params[:disable_notification]
|
||||
|
||||
ticket = Ticket.find(@item[:object_id])
|
||||
ticket = Ticket.find_by(id: @item[:object_id])
|
||||
return if !ticket
|
||||
if @item[:article_id]
|
||||
article = Ticket::Article.find(@item[:article_id])
|
||||
|
||||
|
@ -47,20 +46,41 @@ class Transaction::Notification
|
|||
|
||||
# find recipients
|
||||
recipients_and_channels = []
|
||||
recipients_reason = {}
|
||||
|
||||
# loop through all users
|
||||
possible_recipients = User.group_access(ticket.group_id, 'full').sort_by(&:login)
|
||||
if ticket.owner_id == 1
|
||||
|
||||
# apply owner
|
||||
if ticket.owner_id != 1
|
||||
possible_recipients.push ticket.owner
|
||||
recipients_reason[ticket.owner_id] = 'are assigned'
|
||||
end
|
||||
|
||||
# apply out of office agents
|
||||
possible_recipients_additions = Set.new
|
||||
possible_recipients.each { |user|
|
||||
recursive_ooo_replacements(
|
||||
user: user,
|
||||
replacements: possible_recipients_additions,
|
||||
reasons: recipients_reason,
|
||||
)
|
||||
}
|
||||
|
||||
if possible_recipients_additions.present?
|
||||
# join unique entries
|
||||
possible_recipients = possible_recipients | possible_recipients_additions.to_a
|
||||
end
|
||||
|
||||
already_checked_recipient_ids = {}
|
||||
possible_recipients.each { |user|
|
||||
result = NotificationFactory::Mailer.notification_settings(user, ticket, @item[:type])
|
||||
next if !result
|
||||
next if already_checked_recipient_ids[result[:user].id]
|
||||
already_checked_recipient_ids[result[:user].id] = true
|
||||
next if already_checked_recipient_ids[user.id]
|
||||
already_checked_recipient_ids[user.id] = true
|
||||
recipients_and_channels.push result
|
||||
next if recipients_reason[user.id]
|
||||
recipients_reason[user.id] = 'are in group'
|
||||
}
|
||||
|
||||
# send notifications
|
||||
|
@ -76,7 +96,7 @@ class Transaction::Notification
|
|||
end
|
||||
|
||||
# ignore inactive users
|
||||
next if !user.active
|
||||
next if !user.active?
|
||||
|
||||
# ignore if no changes has been done
|
||||
changes = human_changes(user, ticket)
|
||||
|
@ -180,6 +200,7 @@ class Transaction::Notification
|
|||
recipient: user,
|
||||
current_user: current_user,
|
||||
changes: changes,
|
||||
reason: recipients_reason[user.id],
|
||||
},
|
||||
message_id: "<notification.#{DateTime.current.to_s(:number)}.#{ticket.id}.#{user.id}.#{rand(999_999)}@#{Setting.get('fqdn')}>",
|
||||
references: ticket.get_references,
|
||||
|
@ -296,4 +317,27 @@ class Transaction::Notification
|
|||
changes
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def recursive_ooo_replacements(user:, replacements:, reasons:, level: 0)
|
||||
if level == 10
|
||||
Rails.logger.warn("Found more than 10 replacement levels for agent #{user}.")
|
||||
return
|
||||
end
|
||||
|
||||
replacement = user.out_of_office_agent
|
||||
return if !replacement
|
||||
# return for already found, added and checked users
|
||||
# to prevent re-doing complete lookup paths
|
||||
return if !replacements.add?(replacement)
|
||||
reasons[replacement.id] = 'are the out-of-office replacement of the owner'
|
||||
|
||||
recursive_ooo_replacements(
|
||||
user: replacement,
|
||||
replacements: replacements,
|
||||
reasons: reasons,
|
||||
level: level + 1
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -39,8 +39,8 @@ class User < ApplicationModel
|
|||
include User::SearchIndex
|
||||
|
||||
before_validation :check_name, :check_email, :check_login, :ensure_uniq_email, :ensure_password, :ensure_roles, :ensure_identifier
|
||||
before_create :check_preferences_default, :validate_roles, :domain_based_assignment, :set_locale
|
||||
before_update :check_preferences_default, :validate_roles, :reset_login_failed
|
||||
before_create :check_preferences_default, :validate_roles, :validate_ooo, :domain_based_assignment, :set_locale
|
||||
before_update :check_preferences_default, :validate_roles, :validate_ooo, :reset_login_failed
|
||||
after_create :avatar_for_email_check
|
||||
after_update :avatar_for_email_check
|
||||
after_destroy :avatar_destroy, :user_device_destroy
|
||||
|
@ -160,6 +160,45 @@ returns
|
|||
|
||||
=begin
|
||||
|
||||
check if user is in role
|
||||
|
||||
user = User.find(123)
|
||||
result = user.out_of_office?
|
||||
|
||||
returns
|
||||
|
||||
result = true|false
|
||||
|
||||
=end
|
||||
|
||||
def out_of_office?
|
||||
return false if out_of_office != true
|
||||
return false if out_of_office_start_at.blank?
|
||||
return false if out_of_office_end_at.blank?
|
||||
Time.zone.today.between?(out_of_office_start_at, out_of_office_end_at)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
check if user is in role
|
||||
|
||||
user = User.find(123)
|
||||
result = user.out_of_office_agent
|
||||
|
||||
returns
|
||||
|
||||
result = user_model
|
||||
|
||||
=end
|
||||
|
||||
def out_of_office_agent
|
||||
return if !out_of_office?
|
||||
return if out_of_office_replacement_id.blank?
|
||||
User.find_by(id: out_of_office_replacement_id)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
get users activity stream
|
||||
|
||||
user = User.find(123)
|
||||
|
@ -922,6 +961,15 @@ returns
|
|||
true
|
||||
end
|
||||
|
||||
def validate_ooo
|
||||
return true if out_of_office != true
|
||||
raise Exceptions::UnprocessableEntity, 'out of office start is required' if out_of_office_start_at.blank?
|
||||
raise Exceptions::UnprocessableEntity, 'out of office end is required' if out_of_office_end_at.blank?
|
||||
raise Exceptions::UnprocessableEntity, 'out of office end is before start' if out_of_office_start_at > out_of_office_end_at
|
||||
raise Exceptions::UnprocessableEntity, 'out of office replacement user is required' if out_of_office_replacement_id.blank?
|
||||
raise Exceptions::UnprocessableEntity, 'out of office no such replacement user' if !User.find_by(id: out_of_office_replacement_id)
|
||||
true
|
||||
end
|
||||
=begin
|
||||
|
||||
checks if the current user is the last one
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
font-size: 16px;
|
||||
}
|
||||
.footer {
|
||||
font-size: 10px;
|
||||
color: #aaaaaa;
|
||||
border-top-style:solid;
|
||||
border-top-width:1px;
|
||||
|
@ -40,6 +41,13 @@
|
|||
|
||||
<% if @objects[:standalone] != true %>
|
||||
<div class="footer">
|
||||
<a href="<%= c 'http_type' %>://<%= c 'fqdn' %>/#profile/notifications"><%= t 'Manage your notifications settings' %></a> <% if !c('organization').empty? %>| <%= c 'organization' %><% end %>
|
||||
<% if @objects[:reason] %>
|
||||
<% reason = t('You are receiving this because you "%s".') %>
|
||||
<% reason_item = t (@objects[:reason]) %>
|
||||
<% reason.gsub!('%s', reason_item) %>
|
||||
<%= reason %> |
|
||||
<% end %>
|
||||
<a href="<%= c 'http_type' %>://<%= c 'fqdn' %>/#profile/notifications"><%= t 'Manage your notifications settings' %></a>
|
||||
<% if c('organization').present? %>| <%= c 'organization' %><% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
|
|
@ -8,6 +8,7 @@ Zammad::Application.routes.draw do
|
|||
match api_path + '/users/password_reset_verify', to: 'users#password_reset_verify', via: :post
|
||||
match api_path + '/users/password_change', to: 'users#password_change', via: :post
|
||||
match api_path + '/users/preferences', to: 'users#preferences', via: :put
|
||||
match api_path + '/users/out_of_office', to: 'users#out_of_office', via: :put
|
||||
match api_path + '/users/account', to: 'users#account_remove', via: :delete
|
||||
|
||||
match api_path + '/users/avatar', to: 'users#avatar_new', via: :post
|
||||
|
|
|
@ -40,6 +40,10 @@ class CreateBase < ActiveRecord::Migration
|
|||
t.timestamp :last_login, limit: 3, null: true
|
||||
t.string :source, limit: 200, null: true
|
||||
t.integer :login_failed, null: false, default: 0
|
||||
t.boolean :out_of_office, null: false, default: false
|
||||
t.date :out_of_office_start_at, null: true
|
||||
t.date :out_of_office_end_at, null: true
|
||||
t.integer :out_of_office_replacement_id, null: true
|
||||
t.string :preferences, limit: 8000, null: true
|
||||
t.integer :updated_by_id, null: false
|
||||
t.integer :created_by_id, null: false
|
||||
|
@ -54,10 +58,13 @@ class CreateBase < ActiveRecord::Migration
|
|||
add_index :users, [:phone]
|
||||
add_index :users, [:fax]
|
||||
add_index :users, [:mobile]
|
||||
add_index :users, [:out_of_office, :out_of_office_start_at, :out_of_office_end_at], name: 'index_out_of_office'
|
||||
add_index :users, [:out_of_office_replacement_id]
|
||||
add_index :users, [:source]
|
||||
add_index :users, [:created_by_id]
|
||||
add_foreign_key :users, :users, column: :created_by_id
|
||||
add_foreign_key :users, :users, column: :updated_by_id
|
||||
add_foreign_key :users, :users, column: :out_of_office_replacement_id
|
||||
|
||||
create_table :signatures do |t|
|
||||
t.string :name, limit: 100, null: false
|
||||
|
|
|
@ -238,6 +238,7 @@ class CreateTicket < ActiveRecord::Migration
|
|||
t.column :order, :string, limit: 2500, null: false
|
||||
t.column :group_by, :string, limit: 250, null: true
|
||||
t.column :organization_shared, :boolean, null: false, default: false
|
||||
t.column :out_of_office, :boolean, null: false, default: false
|
||||
t.column :view, :string, limit: 1000, null: false
|
||||
t.column :active, :boolean, null: false, default: true
|
||||
t.column :updated_by_id, :integer, null: false
|
||||
|
|
53
db/migrate/20170826000001_out_of_office.rb
Normal file
53
db/migrate/20170826000001_out_of_office.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
class OutOfOffice < ActiveRecord::Migration
|
||||
def up
|
||||
|
||||
# return if it's a new setup
|
||||
return if !Setting.find_by(name: 'system_init_done')
|
||||
|
||||
add_column :overviews, :out_of_office, :boolean, null: false, default: false
|
||||
Overview.reset_column_information
|
||||
|
||||
role_ids = Role.with_permissions(['ticket.agent']).map(&:id)
|
||||
overview_role = Role.find_by(name: 'Agent')
|
||||
Overview.create_if_not_exists(
|
||||
name: 'My replacement Tickets',
|
||||
link: 'my_replacement_tickets',
|
||||
prio: 1080,
|
||||
role_ids: role_ids,
|
||||
out_of_office: true,
|
||||
condition: {
|
||||
'ticket.state_id' => {
|
||||
operator: 'is',
|
||||
value: Ticket::State.by_category(:open).pluck(:id),
|
||||
},
|
||||
#'ticket.out_of_office_replacement_id' => {
|
||||
# operator: 'is',
|
||||
# pre_condition: 'current_user.organization_id',
|
||||
#},
|
||||
},
|
||||
order: {
|
||||
by: 'created_at',
|
||||
direction: 'DESC',
|
||||
},
|
||||
view: {
|
||||
d: %w(title customer group owner escalation_at),
|
||||
s: %w(title customer group owner escalation_at),
|
||||
m: %w(number title customer group owner escalation_at),
|
||||
view_mode_default: 's',
|
||||
},
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
|
||||
add_column :users, :out_of_office, :boolean, null: false, default: false
|
||||
add_column :users, :out_of_office_start_at, :date, null: true
|
||||
add_column :users, :out_of_office_end_at, :date, null: true
|
||||
add_column :users, :out_of_office_replacement_id, :integer, null: true
|
||||
|
||||
add_index :users, [:out_of_office, :out_of_office_start_at, :out_of_office_end_at], name: 'index_out_of_office'
|
||||
add_index :users, [:out_of_office_replacement_id]
|
||||
add_foreign_key :users, :users, column: :out_of_office_replacement_id
|
||||
|
||||
Cache.clear
|
||||
end
|
||||
end
|
|
@ -160,6 +160,34 @@ Overview.create_if_not_exists(
|
|||
},
|
||||
)
|
||||
|
||||
Overview.create_if_not_exists(
|
||||
name: 'My replacement Tickets',
|
||||
link: 'my_replacement_tickets',
|
||||
prio: 1080,
|
||||
role_ids: [overview_role.id],
|
||||
out_of_office: true,
|
||||
condition: {
|
||||
'ticket.state_id' => {
|
||||
operator: 'is',
|
||||
value: Ticket::State.by_category(:open).pluck(:id),
|
||||
},
|
||||
#'ticket.out_of_office_replacement_id' => {
|
||||
# operator: 'is',
|
||||
# pre_condition: 'current_user.organization_id',
|
||||
#},
|
||||
},
|
||||
order: {
|
||||
by: 'created_at',
|
||||
direction: 'DESC',
|
||||
},
|
||||
view: {
|
||||
d: %w(title customer group owner escalation_at),
|
||||
s: %w(title customer group owner escalation_at),
|
||||
m: %w(number title customer group owner escalation_at),
|
||||
view_mode_default: 's',
|
||||
},
|
||||
)
|
||||
|
||||
overview_role = Role.find_by(name: 'Customer')
|
||||
Overview.create_if_not_exists(
|
||||
name: 'My Tickets',
|
||||
|
|
|
@ -306,7 +306,7 @@ Permission.create_if_not_exists(
|
|||
)
|
||||
Permission.create_if_not_exists(
|
||||
name: 'ticket.customer',
|
||||
note: 'Access to Customer Tickets based on current_user.id and current_user.organization_id',
|
||||
note: 'Access to Customer Tickets based on current_user and organization',
|
||||
preferences: {
|
||||
not: ['ticket.agent'],
|
||||
},
|
||||
|
|
|
@ -35,27 +35,47 @@ returns
|
|||
matrix = user.preferences['notification_config']['matrix']
|
||||
return if !matrix
|
||||
|
||||
# check if group is in selecd groups
|
||||
if ticket.owner_id != user.id
|
||||
owned_by_nobody = false
|
||||
owned_by_me = false
|
||||
if ticket.owner_id == 1
|
||||
owned_by_nobody = true
|
||||
elsif ticket.owner_id == user.id
|
||||
owned_by_me = true
|
||||
else
|
||||
# check the replacement chain of max 10
|
||||
# if the current user is in it
|
||||
check_for = ticket.owner
|
||||
10.times do
|
||||
replacement = check_for.out_of_office_agent
|
||||
break if !replacement
|
||||
|
||||
check_for = replacement
|
||||
next if replacement.id != user.id
|
||||
|
||||
owned_by_me = true
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# check if group is in selected groups
|
||||
if !owned_by_me
|
||||
selected_group_ids = user.preferences['notification_config']['group_ids']
|
||||
if selected_group_ids
|
||||
if selected_group_ids.class == Array
|
||||
hit = nil
|
||||
if selected_group_ids.empty?
|
||||
hit = true
|
||||
elsif selected_group_ids[0] == '-' && selected_group_ids.count == 1
|
||||
hit = true
|
||||
else
|
||||
hit = false
|
||||
selected_group_ids.each { |selected_group_id|
|
||||
if selected_group_id.to_s == ticket.group_id.to_s
|
||||
hit = true
|
||||
break
|
||||
end
|
||||
}
|
||||
end
|
||||
return if !hit
|
||||
if selected_group_ids.is_a?(Array)
|
||||
hit = nil
|
||||
if selected_group_ids.empty?
|
||||
hit = true
|
||||
elsif selected_group_ids[0] == '-' && selected_group_ids.count == 1
|
||||
hit = true
|
||||
else
|
||||
hit = false
|
||||
selected_group_ids.each { |selected_group_id|
|
||||
if selected_group_id.to_s == ticket.group_id.to_s
|
||||
hit = true
|
||||
break
|
||||
end
|
||||
}
|
||||
end
|
||||
return if !hit # no group access
|
||||
end
|
||||
end
|
||||
return if !matrix[type]
|
||||
|
@ -64,13 +84,13 @@ returns
|
|||
return if !data['criteria']
|
||||
channels = data['channel']
|
||||
return if !channels
|
||||
if data['criteria']['owned_by_me'] && ticket.owner_id == user.id
|
||||
if data['criteria']['owned_by_me'] && owned_by_me
|
||||
return {
|
||||
user: user,
|
||||
channels: channels
|
||||
}
|
||||
end
|
||||
if data['criteria']['owned_by_nobody'] && ticket.owner_id == 1
|
||||
if data['criteria']['owned_by_nobody'] && owned_by_nobody
|
||||
return {
|
||||
user: user,
|
||||
channels: channels
|
||||
|
|
|
@ -26,6 +26,34 @@ RSpec.describe User do
|
|||
end
|
||||
end
|
||||
|
||||
context '#out_of_office_agent' do
|
||||
|
||||
it 'responds to out_of_office_agent' do
|
||||
user = create(:user)
|
||||
expect(user).to respond_to(:out_of_office_agent)
|
||||
end
|
||||
|
||||
context 'replacement' do
|
||||
|
||||
it 'finds assigned' do
|
||||
user_replacement = create(:user)
|
||||
|
||||
user_ooo = create(:user,
|
||||
out_of_office: true,
|
||||
out_of_office_start_at: Time.zone.yesterday,
|
||||
out_of_office_end_at: Time.zone.tomorrow,
|
||||
out_of_office_replacement_id: user_replacement.id,)
|
||||
|
||||
expect(user_ooo.out_of_office_agent).to eq user_replacement
|
||||
end
|
||||
|
||||
it 'finds none for available users' do
|
||||
user = create(:user)
|
||||
expect(user.out_of_office_agent).to be nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#max_login_failed?' do
|
||||
|
||||
it 'responds to max_login_failed?' do
|
||||
|
|
|
@ -180,6 +180,10 @@ class ZendeskImportTest < ActiveSupport::TestCase
|
|||
last_login
|
||||
source
|
||||
login_failed
|
||||
out_of_office
|
||||
out_of_office_start_at
|
||||
out_of_office_end_at
|
||||
out_of_office_replacement_id
|
||||
preferences
|
||||
updated_by_id
|
||||
created_by_id
|
||||
|
|
|
@ -56,6 +56,7 @@ class TicketNotificationTest < ActiveSupport::TestCase
|
|||
lastname: 'Agent1',
|
||||
email: 'ticket-notification-agent1@example.com',
|
||||
password: 'agentpw',
|
||||
out_of_office: false,
|
||||
active: true,
|
||||
roles: roles,
|
||||
groups: groups,
|
||||
|
@ -71,6 +72,7 @@ class TicketNotificationTest < ActiveSupport::TestCase
|
|||
lastname: 'Agent2',
|
||||
email: 'ticket-notification-agent2@example.com',
|
||||
password: 'agentpw',
|
||||
out_of_office: false,
|
||||
active: true,
|
||||
roles: roles,
|
||||
groups: groups,
|
||||
|
@ -80,6 +82,38 @@ class TicketNotificationTest < ActiveSupport::TestCase
|
|||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
@agent3 = User.create_or_update(
|
||||
login: 'ticket-notification-agent3@example.com',
|
||||
firstname: 'Notification',
|
||||
lastname: 'Agent3',
|
||||
email: 'ticket-notification-agent3@example.com',
|
||||
password: 'agentpw',
|
||||
out_of_office: false,
|
||||
active: true,
|
||||
roles: roles,
|
||||
groups: groups,
|
||||
preferences: {
|
||||
locale: 'de-de',
|
||||
},
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
@agent4 = User.create_or_update(
|
||||
login: 'ticket-notification-agent4@example.com',
|
||||
firstname: 'Notification',
|
||||
lastname: 'Agent4',
|
||||
email: 'ticket-notification-agent4@example.com',
|
||||
password: 'agentpw',
|
||||
out_of_office: false,
|
||||
active: true,
|
||||
roles: roles,
|
||||
groups: groups,
|
||||
preferences: {
|
||||
locale: 'de-de',
|
||||
},
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
Group.create_if_not_exists(
|
||||
name: 'WithoutAccess',
|
||||
note: 'Test for notification check.',
|
||||
|
@ -944,6 +978,126 @@ class TicketNotificationTest < ActiveSupport::TestCase
|
|||
|
||||
end
|
||||
|
||||
test 'ticket notification - out of office' do
|
||||
|
||||
# create ticket in group
|
||||
ticket1 = Ticket.create!(
|
||||
title: 'some notification test out of office',
|
||||
group: Group.lookup(name: 'TicketNotificationTest'),
|
||||
customer: @customer,
|
||||
owner_id: @agent2.id,
|
||||
#state: Ticket::State.lookup(name: 'new'),
|
||||
#priority: Ticket::Priority.lookup(name: '2 normal'),
|
||||
updated_by_id: @customer.id,
|
||||
created_by_id: @customer.id,
|
||||
)
|
||||
Ticket::Article.create!(
|
||||
ticket_id: ticket1.id,
|
||||
from: 'some_sender@example.com',
|
||||
to: 'some_recipient@example.com',
|
||||
subject: 'some subject',
|
||||
message_id: 'some@id',
|
||||
body: 'some message',
|
||||
internal: false,
|
||||
sender: Ticket::Article::Sender.where(name: 'Customer').first,
|
||||
type: Ticket::Article::Type.where(name: 'email').first,
|
||||
updated_by_id: @customer.id,
|
||||
created_by_id: @customer.id,
|
||||
)
|
||||
assert(ticket1, 'ticket created - ticket notification simple')
|
||||
|
||||
# execute object transaction
|
||||
Observer::Transaction.commit
|
||||
Scheduler.worker(true)
|
||||
|
||||
# verify notifications to @agent1 + @agent2
|
||||
assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket1, @agent1, 'email'), ticket1.id)
|
||||
assert_equal(1, NotificationFactory::Mailer.already_sent?(ticket1, @agent2, 'email'), ticket1.id)
|
||||
assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket1, @agent3, 'email'), ticket1.id)
|
||||
assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket1, @agent4, 'email'), ticket1.id)
|
||||
|
||||
@agent2.out_of_office = true
|
||||
@agent2.preferences[:out_of_office_text] = 'at the doctor'
|
||||
@agent2.out_of_office_replacement_id = @agent3.id
|
||||
@agent2.out_of_office_start_at = Time.zone.today - 2.days
|
||||
@agent2.out_of_office_end_at = Time.zone.today + 2.days
|
||||
@agent2.save!
|
||||
|
||||
# create ticket in group
|
||||
ticket2 = Ticket.create!(
|
||||
title: 'some notification test out of office',
|
||||
group: Group.lookup(name: 'TicketNotificationTest'),
|
||||
customer: @customer,
|
||||
owner_id: @agent2.id,
|
||||
#state: Ticket::State.lookup(name: 'new'),
|
||||
#priority: Ticket::Priority.lookup(name: '2 normal'),
|
||||
updated_by_id: @customer.id,
|
||||
created_by_id: @customer.id,
|
||||
)
|
||||
Ticket::Article.create!(
|
||||
ticket_id: ticket2.id,
|
||||
from: 'some_sender@example.com',
|
||||
to: 'some_recipient@example.com',
|
||||
subject: 'some subject',
|
||||
message_id: 'some@id',
|
||||
body: 'some message',
|
||||
internal: false,
|
||||
sender: Ticket::Article::Sender.where(name: 'Customer').first,
|
||||
type: Ticket::Article::Type.where(name: 'email').first,
|
||||
updated_by_id: @customer.id,
|
||||
created_by_id: @customer.id,
|
||||
)
|
||||
assert(ticket2, 'ticket created - ticket notification simple')
|
||||
|
||||
# execute object transaction
|
||||
Observer::Transaction.commit
|
||||
Scheduler.worker(true)
|
||||
|
||||
# verify notifications to @agent1 + @agent2
|
||||
assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket2, @agent1, 'email'), ticket2.id)
|
||||
assert_equal(1, NotificationFactory::Mailer.already_sent?(ticket2, @agent2, 'email'), ticket2.id)
|
||||
assert_equal(1, NotificationFactory::Mailer.already_sent?(ticket2, @agent3, 'email'), ticket2.id)
|
||||
assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket2, @agent4, 'email'), ticket2.id)
|
||||
|
||||
# update ticket attributes
|
||||
ticket2.title = "#{ticket2.title} - #2"
|
||||
ticket2.priority = Ticket::Priority.lookup(name: '3 high')
|
||||
ticket2.save!
|
||||
|
||||
# execute object transaction
|
||||
Observer::Transaction.commit
|
||||
Scheduler.worker(true)
|
||||
|
||||
# verify notifications to @agent1 + @agent2
|
||||
assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket2, @agent1, 'email'), ticket2.id)
|
||||
assert_equal(2, NotificationFactory::Mailer.already_sent?(ticket2, @agent2, 'email'), ticket2.id)
|
||||
assert_equal(2, NotificationFactory::Mailer.already_sent?(ticket2, @agent3, 'email'), ticket2.id)
|
||||
assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket2, @agent4, 'email'), ticket2.id)
|
||||
|
||||
@agent3.out_of_office = true
|
||||
@agent3.preferences[:out_of_office_text] = 'at the doctor'
|
||||
@agent3.out_of_office_replacement_id = @agent4.id
|
||||
@agent3.out_of_office_start_at = Time.zone.today - 2.days
|
||||
@agent3.out_of_office_end_at = Time.zone.today + 2.days
|
||||
@agent3.save!
|
||||
|
||||
# update ticket attributes
|
||||
ticket2.title = "#{ticket2.title} - #3"
|
||||
ticket2.priority = Ticket::Priority.lookup(name: '3 high')
|
||||
ticket2.save!
|
||||
|
||||
# execute object transaction
|
||||
Observer::Transaction.commit
|
||||
Scheduler.worker(true)
|
||||
|
||||
# verify notifications to @agent1 + @agent2
|
||||
assert_equal(0, NotificationFactory::Mailer.already_sent?(ticket2, @agent1, 'email'), ticket2.id)
|
||||
assert_equal(3, NotificationFactory::Mailer.already_sent?(ticket2, @agent2, 'email'), ticket2.id)
|
||||
assert_equal(3, NotificationFactory::Mailer.already_sent?(ticket2, @agent3, 'email'), ticket2.id)
|
||||
assert_equal(1, NotificationFactory::Mailer.already_sent?(ticket2, @agent4, 'email'), ticket2.id)
|
||||
|
||||
end
|
||||
|
||||
test 'ticket notification template' do
|
||||
|
||||
# create ticket in group
|
||||
|
|
131
test/unit/user_out_of_office_test.rb
Normal file
131
test/unit/user_out_of_office_test.rb
Normal file
|
@ -0,0 +1,131 @@
|
|||
require 'test_helper'
|
||||
|
||||
class UserOutOfOfficeTest < ActiveSupport::TestCase
|
||||
setup do
|
||||
|
||||
UserInfo.current_user_id = 1
|
||||
|
||||
groups = Group.all
|
||||
roles = Role.where(name: 'Agent')
|
||||
@agent1 = User.create_or_update(
|
||||
login: 'user-out_of_office-agent1@example.com',
|
||||
firstname: 'UserOutOfOffice',
|
||||
lastname: 'Agent1',
|
||||
email: 'user-out_of_office-agent1@example.com',
|
||||
password: 'agentpw',
|
||||
active: true,
|
||||
roles: roles,
|
||||
groups: groups,
|
||||
)
|
||||
@agent2 = User.create_or_update(
|
||||
login: 'user-out_of_office-agent2@example.com',
|
||||
firstname: 'UserOutOfOffice',
|
||||
lastname: 'Agent2',
|
||||
email: 'user-out_of_office-agent2@example.com',
|
||||
password: 'agentpw',
|
||||
active: true,
|
||||
roles: roles,
|
||||
groups: groups,
|
||||
)
|
||||
@agent3 = User.create_or_update(
|
||||
login: 'user-out_of_office-agent3@example.com',
|
||||
firstname: 'UserOutOfOffice',
|
||||
lastname: 'Agent3',
|
||||
email: 'user-out_of_office-agent3@example.com',
|
||||
password: 'agentpw',
|
||||
active: true,
|
||||
roles: roles,
|
||||
groups: groups,
|
||||
)
|
||||
end
|
||||
|
||||
test 'check out_of_office?' do
|
||||
|
||||
# check
|
||||
assert_not(@agent1.out_of_office?)
|
||||
assert_not(@agent2.out_of_office?)
|
||||
assert_not(@agent3.out_of_office?)
|
||||
|
||||
assert_raises(Exceptions::UnprocessableEntity) {
|
||||
@agent1.out_of_office = true
|
||||
@agent1.out_of_office_start_at = Time.zone.now + 2.days
|
||||
@agent1.out_of_office_end_at = Time.zone.now
|
||||
@agent1.save!
|
||||
}
|
||||
|
||||
assert_raises(Exceptions::UnprocessableEntity) {
|
||||
@agent1.out_of_office = true
|
||||
@agent1.out_of_office_start_at = Time.zone.now
|
||||
@agent1.out_of_office_end_at = Time.zone.now - 2.days
|
||||
@agent1.save!
|
||||
}
|
||||
|
||||
assert_raises(Exceptions::UnprocessableEntity) {
|
||||
@agent1.out_of_office = true
|
||||
@agent1.out_of_office_start_at = nil
|
||||
@agent1.out_of_office_end_at = Time.zone.now
|
||||
@agent1.save!
|
||||
}
|
||||
|
||||
assert_raises(Exceptions::UnprocessableEntity) {
|
||||
@agent1.out_of_office = true
|
||||
@agent1.out_of_office_start_at = Time.zone.now
|
||||
@agent1.out_of_office_end_at = nil
|
||||
@agent1.save!
|
||||
}
|
||||
|
||||
@agent1.out_of_office = false
|
||||
@agent1.out_of_office_start_at = Time.zone.now + 2.days
|
||||
@agent1.out_of_office_end_at = Time.zone.now
|
||||
@agent1.save!
|
||||
|
||||
assert_not(@agent1.out_of_office?)
|
||||
|
||||
assert_raises(Exceptions::UnprocessableEntity) {
|
||||
@agent1.out_of_office = true
|
||||
@agent1.out_of_office_start_at = Time.zone.now + 2.days
|
||||
@agent1.out_of_office_end_at = Time.zone.now + 4.days
|
||||
@agent1.save!
|
||||
}
|
||||
assert_raises(Exceptions::UnprocessableEntity) {
|
||||
@agent1.out_of_office_replacement_id = 999_999_999_999 # not existing
|
||||
@agent1.save!
|
||||
}
|
||||
@agent1.out_of_office_replacement_id = @agent2.id
|
||||
@agent1.save!
|
||||
|
||||
assert_not(@agent1.out_of_office?)
|
||||
|
||||
travel 2.days
|
||||
|
||||
assert(@agent1.out_of_office?)
|
||||
|
||||
travel 1.day
|
||||
|
||||
assert(@agent1.out_of_office?)
|
||||
|
||||
travel 1.day
|
||||
|
||||
assert(@agent1.out_of_office?)
|
||||
|
||||
travel 1.day
|
||||
|
||||
assert_not(@agent1.out_of_office?)
|
||||
|
||||
assert_not(@agent1.out_of_office_agent)
|
||||
|
||||
assert_not(@agent2.out_of_office_agent)
|
||||
|
||||
@agent2.out_of_office = true
|
||||
@agent2.out_of_office_start_at = Time.zone.now
|
||||
@agent2.out_of_office_end_at = Time.zone.now + 4.days
|
||||
@agent2.out_of_office_replacement_id = @agent3.id
|
||||
@agent2.save!
|
||||
|
||||
assert(@agent2.out_of_office?)
|
||||
|
||||
assert_equal(@agent2.out_of_office_agent.id, @agent3.id)
|
||||
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in a new issue