Implemented CTI ticket create screen popup on answering call.
This commit is contained in:
parent
9503ff20ce
commit
7faa4c310d
31 changed files with 1840 additions and 463 deletions
|
@ -57,7 +57,7 @@ License: MIT license
|
|||
highlight.pack.js
|
||||
Source: https://highlightjs.org
|
||||
Copyright: 2006 Ivan Sagalaev (https://github.com/isagalaev)
|
||||
License: BSD License
|
||||
License: BSD License (BSD-3-Clause)
|
||||
-----------------------------------------------------------------------------
|
||||
jquery.flot.js
|
||||
Source: https://github.com/dnschnur/flot
|
||||
|
@ -74,7 +74,7 @@ jquery.fineuploader-3.0.js
|
|||
Source: http://github.com/Valums-File-Uploader/file-uploader
|
||||
Copyright: 2010 Andrew Valums <andrew(at)valums.com>
|
||||
2012 Ray Nicholus <fineuploader(at)garstasio.com>
|
||||
License: MIT license, GNU GPL 2 or later, GNU LGPL 2 or later
|
||||
License: GNU GPL 2 or later, GNU LGPL 2 or later
|
||||
-----------------------------------------------------------------------------
|
||||
jquery.noty.js
|
||||
Source: https://github.com/needim/noty/
|
||||
|
@ -171,7 +171,7 @@ License: MIT license
|
|||
Font Awesome icon font
|
||||
Source: http://fontawesome.io/
|
||||
Copyright: Font Awesome by Dave Gandy - http://fontawesome.io
|
||||
License: SIL OFL 1.1
|
||||
License: MIT License
|
||||
-----------------------------------------------------------------------------
|
||||
Simple line icons font
|
||||
Source: https://github.com/thesabbir/simple-line-icons
|
||||
|
|
|
@ -26,8 +26,10 @@ class Form extends App.Controller
|
|||
'submit form': 'update'
|
||||
'click .js-inboundBlockCallerId .js-add': 'addInboundBlockCallerId'
|
||||
'click .js-outboundRouting .js-add': 'addOutboundRouting'
|
||||
'click .js-notifyMap .js-addMap': 'addNotifyMap'
|
||||
'click .js-inboundBlockCallerId .js-remove': 'removeInboundBlockCallerId'
|
||||
'click .js-outboundRouting .js-remove': 'removeOutboundRouting'
|
||||
'click .js-notifyMap .js-removeMap': 'removeNotifyMap'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
@ -43,6 +45,8 @@ class Form extends App.Controller
|
|||
config.inbound = {}
|
||||
if !config.inbound.block_caller_ids
|
||||
config.inbound.block_caller_ids = []
|
||||
if !config.notify_map
|
||||
config.notify_map = []
|
||||
config
|
||||
|
||||
setConfig: (value) ->
|
||||
|
@ -56,6 +60,32 @@ class Form extends App.Controller
|
|||
cti_token: App.Setting.get('cti_token')
|
||||
)
|
||||
|
||||
# placeholder
|
||||
configure_attributes = [
|
||||
{ name: 'user_ids', display: '', tag: 'column_select', multiple: true, null: true, relation: 'User', sortBy: 'firstname' },
|
||||
]
|
||||
new App.ControllerForm(
|
||||
el: @$('.js-userSelectorBlank')
|
||||
model:
|
||||
configure_attributes: configure_attributes,
|
||||
params:
|
||||
user_ids: []
|
||||
autofocus: false
|
||||
)
|
||||
|
||||
for row in @config.notify_map
|
||||
configure_attributes = [
|
||||
{ name: 'user_ids', display: '', tag: 'column_select', multiple: true, null: true, relation: 'User', sortBy: 'firstname' },
|
||||
]
|
||||
new App.ControllerForm(
|
||||
el: @$("[name=queue][value='#{row.queue}']").closest('tr').find('.js-userSelector')
|
||||
model:
|
||||
configure_attributes: configure_attributes,
|
||||
params:
|
||||
user_ids: row.user_ids
|
||||
autofocus: false
|
||||
)
|
||||
|
||||
updateCurrentConfig: =>
|
||||
config = @config
|
||||
cleanupInput = @cleanupInput
|
||||
|
@ -88,6 +118,17 @@ class Form extends App.Controller
|
|||
}
|
||||
)
|
||||
|
||||
# notify map
|
||||
config.notify_map = []
|
||||
@$('.js-notifyMap .js-row').each(->
|
||||
queue = $(@).find('input[name="queue"]').val()
|
||||
user_ids = $(@).find('select[name="user_ids"]').val()
|
||||
config.notify_map.push {
|
||||
queue: cleanupInput(queue)
|
||||
user_ids: user_ids
|
||||
}
|
||||
)
|
||||
|
||||
@config = config
|
||||
|
||||
update: (e) =>
|
||||
|
@ -127,6 +168,41 @@ class Form extends App.Controller
|
|||
}
|
||||
@render()
|
||||
|
||||
addNotifyMap: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
element = $(e.currentTarget).closest('tr')
|
||||
queue = @cleanupInput(element.find('input[name="queue"]').val())
|
||||
user_ids = element.find('select[name="user_ids"]').val()
|
||||
if _.isEmpty(queue)
|
||||
@notify(
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('A queue is required!')
|
||||
timeout: 6000
|
||||
)
|
||||
return
|
||||
if _.isEmpty(user_ids)
|
||||
@notify(
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('A user is required!')
|
||||
timeout: 6000
|
||||
)
|
||||
return
|
||||
|
||||
for row in @config.notify_map
|
||||
if row.queue is queue
|
||||
@notify(
|
||||
type: 'error'
|
||||
msg: App.i18n.translateContent('Queue already exists!')
|
||||
timeout: 6000
|
||||
)
|
||||
return
|
||||
@config.notify_map.push {
|
||||
queue: queue
|
||||
user_ids: user_ids
|
||||
}
|
||||
@render()
|
||||
|
||||
removeInboundBlockCallerId: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
|
@ -141,6 +217,13 @@ class Form extends App.Controller
|
|||
element.remove()
|
||||
@updateCurrentConfig()
|
||||
|
||||
removeNotifyMap: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
element = $(e.currentTarget).closest('tr')
|
||||
element.remove()
|
||||
@updateCurrentConfig()
|
||||
|
||||
class State
|
||||
@current: ->
|
||||
App.Setting.get('cti_integration')
|
||||
|
|
|
@ -25,9 +25,11 @@ class Form extends App.Controller
|
|||
events:
|
||||
'submit form': 'update'
|
||||
'click .js-inboundBlockCallerId .js-add': 'addInboundBlockCallerId'
|
||||
'click .js-outboundRouting .js-add': 'addOutboundRouting'
|
||||
'click .js-inboundBlockCallerId .js-remove': 'removeInboundBlockCallerId'
|
||||
'click .js-outboundRouting .js-add': 'addOutboundRouting'
|
||||
'click .js-outboundRouting .js-remove': 'removeOutboundRouting'
|
||||
'click .js-userDeviceMap .js-add': 'addUserDeviceMap'
|
||||
'click .js-userDeviceMap .js-remove': 'removeUserDeviceMap'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
@ -43,6 +45,8 @@ class Form extends App.Controller
|
|||
config.inbound = {}
|
||||
if !config.inbound.block_caller_ids
|
||||
config.inbound.block_caller_ids = []
|
||||
if !config.user_device_map
|
||||
config.user_device_map = []
|
||||
config
|
||||
|
||||
setConfig: (value) ->
|
||||
|
@ -60,7 +64,7 @@ class Form extends App.Controller
|
|||
config = @config
|
||||
cleanupInput = @cleanupInput
|
||||
|
||||
config.api_token = @$('input[name=api_token]').val()
|
||||
config.api_token = cleanupInput(@$('input[name=api_token]').val())
|
||||
|
||||
# default caller_id
|
||||
default_caller_id = @$('input[name=default_caller_id]').val()
|
||||
|
@ -90,6 +94,17 @@ class Form extends App.Controller
|
|||
}
|
||||
)
|
||||
|
||||
# user device map
|
||||
config.user_device_map = []
|
||||
@$('.js-userDeviceMap .js-row').each(->
|
||||
device_id = $(@).find('input[name="device_id"]').val()
|
||||
user_id = $(@).find('input[name="user_id"]').val()
|
||||
config.user_device_map.push {
|
||||
device_id: device_id
|
||||
user_id: user_id
|
||||
}
|
||||
)
|
||||
|
||||
@config = config
|
||||
|
||||
update: (e) =>
|
||||
|
@ -114,6 +129,13 @@ class Form extends App.Controller
|
|||
}
|
||||
@render()
|
||||
|
||||
removeInboundBlockCallerId: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
element = $(e.currentTarget).closest('tr')
|
||||
element.remove()
|
||||
@updateCurrentConfig()
|
||||
|
||||
addOutboundRouting: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
|
@ -129,14 +151,27 @@ class Form extends App.Controller
|
|||
}
|
||||
@render()
|
||||
|
||||
removeInboundBlockCallerId: (e) =>
|
||||
removeOutboundRouting: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
element = $(e.currentTarget).closest('tr')
|
||||
element.remove()
|
||||
@updateCurrentConfig()
|
||||
|
||||
removeOutboundRouting: (e) =>
|
||||
addUserDeviceMap: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
element = $(e.currentTarget).closest('tr')
|
||||
user_id = @cleanupInput(element.find('input[name="user_id"]').val())
|
||||
device_id = @cleanupInput(element.find('input[name="device_id"]').val())
|
||||
return if _.isEmpty(user_id) || _.isEmpty(device_id)
|
||||
@config.user_device_map.push {
|
||||
user_id: user_id
|
||||
device_id: device_id
|
||||
}
|
||||
@render()
|
||||
|
||||
removeUserDeviceMap: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
element = $(e.currentTarget).closest('tr')
|
||||
|
|
|
@ -28,6 +28,8 @@ class Form extends App.Controller
|
|||
'click .js-outboundRouting .js-add': 'addOutboundRouting'
|
||||
'click .js-inboundBlockCallerId .js-remove': 'removeInboundBlockCallerId'
|
||||
'click .js-outboundRouting .js-remove': 'removeOutboundRouting'
|
||||
'click .js-userRemoteMap .js-add': 'addUserRemoteMap'
|
||||
'click .js-userRemoteMap .js-remove': 'removeUserRemoteMap'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
@ -43,6 +45,8 @@ class Form extends App.Controller
|
|||
config.inbound = {}
|
||||
if !config.inbound.block_caller_ids
|
||||
config.inbound.block_caller_ids = []
|
||||
if !config.user_remote_map
|
||||
config.user_remote_map = []
|
||||
config
|
||||
|
||||
setConfig: (value) ->
|
||||
|
@ -59,6 +63,9 @@ class Form extends App.Controller
|
|||
config = @config
|
||||
cleanupInput = @cleanupInput
|
||||
|
||||
config.api_user = cleanupInput(@$('input[name=api_user]').val())
|
||||
config.api_password = cleanupInput(@$('input[name=api_password]').val())
|
||||
|
||||
# default caller_id
|
||||
default_caller_id = @$('input[name=default_caller_id]').val()
|
||||
config.outbound.default_caller_id = cleanupInput(default_caller_id)
|
||||
|
@ -87,6 +94,17 @@ class Form extends App.Controller
|
|||
}
|
||||
)
|
||||
|
||||
# user device map
|
||||
config.user_remote_map = []
|
||||
@$('.js-userRemoteMap .js-row').each(->
|
||||
remote_user_id = $(@).find('input[name="remote_user_id"]').val()
|
||||
user_id = $(@).find('input[name="user_id"]').val()
|
||||
config.user_remote_map.push {
|
||||
remote_user_id: remote_user_id
|
||||
user_id: user_id
|
||||
}
|
||||
)
|
||||
|
||||
@config = config
|
||||
|
||||
update: (e) =>
|
||||
|
@ -140,6 +158,26 @@ class Form extends App.Controller
|
|||
element.remove()
|
||||
@updateCurrentConfig()
|
||||
|
||||
addUserRemoteMap: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
element = $(e.currentTarget).closest('tr')
|
||||
user_id = @cleanupInput(element.find('input[name="user_id"]').val())
|
||||
remote_user_id = @cleanupInput(element.find('input[name="remote_user_id"]').val())
|
||||
return if _.isEmpty(user_id) || _.isEmpty(remote_user_id)
|
||||
@config.user_remote_map.push {
|
||||
user_id: user_id
|
||||
remote_user_id: remote_user_id
|
||||
}
|
||||
@render()
|
||||
|
||||
removeUserRemoteMap: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
element = $(e.currentTarget).closest('tr')
|
||||
element.remove()
|
||||
@updateCurrentConfig()
|
||||
|
||||
class State
|
||||
@current: ->
|
||||
App.Setting.get('sipgate_integration')
|
||||
|
|
|
@ -199,7 +199,7 @@ class App.TicketCreate extends App.Controller
|
|||
|
||||
if _.isEmpty(params.ticket_id) && _.isEmpty(params.article_id)
|
||||
if !_.isEmpty(params.customer_id)
|
||||
@renderQueue(options: { customer_id: params.customer_id })
|
||||
@renderQueue(options: params)
|
||||
return
|
||||
@renderQueue()
|
||||
return
|
||||
|
|
|
@ -6,7 +6,7 @@ class App.CTI extends App.Controller
|
|||
'.js-callerLog': 'callerLog'
|
||||
events:
|
||||
'click .js-check': 'done'
|
||||
'click .js-userNew': 'userNew'
|
||||
'click .js-newUser': 'newUser'
|
||||
list: []
|
||||
backends: []
|
||||
meta:
|
||||
|
@ -32,9 +32,30 @@ class App.CTI extends App.Controller
|
|||
return if data.state isnt 'newCall'
|
||||
return if data.direction isnt 'in'
|
||||
return if @switch() isnt true
|
||||
@notify(data)
|
||||
if !document.hasFocus()
|
||||
@notify(data)
|
||||
'cti_event'
|
||||
)
|
||||
@bind('menu:render', (data) =>
|
||||
return if @switch() isnt true
|
||||
localHtml = ''
|
||||
for item in @ringingCalls()
|
||||
localHtml += App.view('navigation/menu_cti_ringing')(
|
||||
item: item
|
||||
)
|
||||
$('.js-phoneMenuItem').after(localHtml)
|
||||
$('.call-widget').find('.js-newUser').bind('click', (e) =>
|
||||
@newUser(e)
|
||||
)
|
||||
$('.call-widget').find('.js-newTicket').bind('click', (e) =>
|
||||
user = undefined
|
||||
user_id = $(e.currentTarget).data('user-id')
|
||||
if user_id
|
||||
user = App.User.find(user_id)
|
||||
console.log('user_id', user_id, user)
|
||||
@newTicket(user)
|
||||
)
|
||||
)
|
||||
@bind('auth', (data) =>
|
||||
@meta.counter = 0
|
||||
)
|
||||
|
@ -57,6 +78,13 @@ class App.CTI extends App.Controller
|
|||
@initSpoolSent = true
|
||||
)
|
||||
|
||||
ringingCalls: =>
|
||||
ringing = []
|
||||
for row in @list
|
||||
if row.state is 'newCall' && row.done is false
|
||||
ringing.push row
|
||||
ringing
|
||||
|
||||
# fetch data, render view
|
||||
load: ->
|
||||
@ajax(
|
||||
|
@ -148,8 +176,18 @@ class App.CTI extends App.Controller
|
|||
item.disabled = false
|
||||
|
||||
@removePopovers()
|
||||
@callerLog.html(App.view('cti/caller_log')(list: @list))
|
||||
@renderPopovers()
|
||||
|
||||
list = $(App.view('cti/caller_log')(list: @list))
|
||||
list.find('.js-avatar').each( ->
|
||||
$element = $(@)
|
||||
new WidgetAvatar(
|
||||
el: $element
|
||||
object_id: $element.attr('data-id')
|
||||
level: $element.attr('data-level')
|
||||
size: 40
|
||||
)
|
||||
)
|
||||
@callerLog.html(list)
|
||||
|
||||
@updateNavMenu()
|
||||
|
||||
|
@ -163,9 +201,15 @@ class App.CTI extends App.Controller
|
|||
data: JSON.stringify(done: done)
|
||||
)
|
||||
|
||||
userNew: (e) ->
|
||||
newTicket: (user) =>
|
||||
if user
|
||||
@navigate("ticket/create/customer/#{user.id}")
|
||||
return
|
||||
@navigate('ticket/create')
|
||||
|
||||
newUser: (e) ->
|
||||
e.preventDefault()
|
||||
phone = $(e.currentTarget).text()
|
||||
phone = $(e.currentTarget).data('phone')
|
||||
new App.ControllerGenericNew(
|
||||
pageData:
|
||||
title: 'Users'
|
||||
|
@ -176,7 +220,7 @@ class App.CTI extends App.Controller
|
|||
genericObject: 'User'
|
||||
item:
|
||||
phone: phone
|
||||
container: @el.closest('.content')
|
||||
#container: @el.closest('.content')
|
||||
callback: @ticketNew
|
||||
)
|
||||
|
||||
|
@ -221,6 +265,33 @@ class App.CTI extends App.Controller
|
|||
currentPosition: =>
|
||||
@$('.main').scrollTop()
|
||||
|
||||
class WidgetAvatar extends App.ObserverController
|
||||
@extend App.PopoverProvidable
|
||||
@registerPopovers 'User'
|
||||
|
||||
model: 'User'
|
||||
observe:
|
||||
login: true
|
||||
firstname: true
|
||||
lastname: true
|
||||
organization_id: true
|
||||
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) =>
|
||||
classes = ['user-popover', 'u-textTruncate']
|
||||
classes.push('is-inactive') if !user.active
|
||||
@html(App.view('cti/caller_log_avatar')(user: user, classes: classes, level: @level))
|
||||
@renderPopovers()
|
||||
|
||||
class CTIRouter extends App.ControllerPermanent
|
||||
requiredPermission: 'cti.agent'
|
||||
constructor: (params) ->
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
class Widget extends App.Controller
|
||||
serverRestarted: false
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
App.Event.bind(
|
||||
'remote_task'
|
||||
(data) =>
|
||||
console.log('remote_task', data)
|
||||
App.TaskManager.execute(data)
|
||||
@navigate(data.url)
|
||||
'remote_task'
|
||||
)
|
||||
|
||||
App.Config.set('remote_task', Widget, 'Widgets')
|
|
@ -200,6 +200,20 @@ App.ViewHelpers =
|
|||
return true if contentType.match(/image\/(png|jpg|jpeg|gif)/i)
|
||||
false
|
||||
|
||||
unique_avatar: (seed, text, size = 40) ->
|
||||
baseSize = 40
|
||||
width = 300 * size/baseSize
|
||||
height = 226 * size/baseSize
|
||||
|
||||
rng = new Math.seedrandom(seed)
|
||||
x = rng() * (width - size)
|
||||
y = rng() * (height - size)
|
||||
|
||||
return App.view('avatar_unique')
|
||||
x: x
|
||||
y: y
|
||||
initials: text
|
||||
|
||||
# icon with modifier based on visibility state
|
||||
# params: className, iconset, addStateClass
|
||||
iconWithModifier: (item, params) ->
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
</thead>
|
||||
<tbody>
|
||||
<% for item in @list: %>
|
||||
<tr <% if item.done: %>class="is-grayed-out"<% end %> data-id="<%- item.id %>">
|
||||
<td class="table-checkbox" style="vertical-align: middle">
|
||||
<label class="checkbox-replacement<% if item.disabled is true: %> is-disabled<% end %>">
|
||||
<tr class="u-center<% if item.done: %> is-grayed-out<% end %>" data-id="<%- item.id %>">
|
||||
<td class="table-checkbox u-positionOrigin">
|
||||
<label class="checkbox-replacement checkbox-replacement--fullscreen<% if item.disabled is true: %> is-disabled<% end %>">
|
||||
<input type="checkbox" class="js-check"<% if item.done: %> checked<% end %><% if item.disabled is true: %> disabled<% end %>>
|
||||
<%- @Icon('checkbox', 'icon-unchecked') %>
|
||||
<%- @Icon('checkbox-checked', 'icon-checked') %>
|
||||
|
@ -26,31 +26,42 @@
|
|||
<% if item.preferences.from && !_.isEmpty(item.preferences.from): %>
|
||||
<% for caller_id in item.preferences.from: %>
|
||||
<% if caller_id.user_id && App.User.exists(caller_id.user_id): %>
|
||||
<% if shown: %><div class="spacer"></div><% end %>
|
||||
<div class="user-card">
|
||||
<div class="js-avatar" data-id="<%- caller_id.user_id %>" data-level="<%= caller_id.level %>"></div>
|
||||
<a class="text-muted" href="<%- App.Utils.phoneify(item.from_pretty) %>"><%= item.from_pretty %></a>
|
||||
</div>
|
||||
<% shown = true %>
|
||||
<% user = App.User.fullLocal(caller_id.user_id) %>
|
||||
<% classes = ['user-popover'] %>
|
||||
<% classes.push('is-inactive') if !user.active %>
|
||||
<% if caller_id.level isnt 'known': %><%- @T('maybe') %> <% end %>
|
||||
<span class="<%= classes.join(' ') %>" data-id="<%- user.id %>"><%= user.displayNameLong() %></span><br>
|
||||
<% else if !_.isEmpty(caller_id.comment): %>
|
||||
<% shown = true %>
|
||||
<%- @T('maybe') %> <%= caller_id.comment %><br>
|
||||
<div class="user-card">
|
||||
<%- @unique_avatar(caller_id.comment, caller_id.comment.split(" ").map((name) -> name[0]).join("")) %>
|
||||
<%- @T('maybe') %>: <%= caller_id.comment %><br>
|
||||
<a class="text-muted" href="<%- App.Utils.phoneify(item.from_pretty) %>"><%= item.from_pretty %></a>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if !shown && !_.isEmpty(item.from_comment): %>
|
||||
<% shown = true %>
|
||||
<%= item.from_comment %>
|
||||
<br>
|
||||
<div class="user-card">
|
||||
<%- @unique_avatar(item.from_comment, item.from_comment.split(" ").map((name) -> name[0]).join("")) %>
|
||||
<%= item.from_comment %><br>
|
||||
<a class="text-muted" href="<%- App.Utils.phoneify(item.from_pretty) %>"><%= item.from_pretty %></a>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if shown: %>
|
||||
<small><%= item.from_pretty %></small>
|
||||
<% else: %>
|
||||
<% if !shown: %>
|
||||
<div class="user-card">
|
||||
<%- @unique_avatar(item.from_pretty || item.from, '??') %>
|
||||
<% if !_.isEmpty(item.from_pretty): %>
|
||||
<span class="js-userNew u-clickable" href="#"><%= item.from_pretty %></span>
|
||||
<a class="inherit-color" href="<%- App.Utils.phoneify(item.from_pretty) %>"><%= item.from_pretty %></a>
|
||||
<% if item.direction is 'in': %>
|
||||
<div class="btn btn--text btn--create no-padding js-newUser" href="#" data-phone="<%= item.from_pretty %>"><%- @Icon('plus-small') %> <span><%- @T('Create User') %></div>
|
||||
<% end %>
|
||||
<% else: %>
|
||||
<span><%= item.from %></span>
|
||||
<a class="inherit-color" href="<%- App.Utils.phoneify(item.from) %>"><%= item.from %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</td>
|
||||
<td>
|
||||
|
@ -59,41 +70,53 @@
|
|||
<% for caller_id in item.preferences.to: %>
|
||||
<% if caller_id.user_id && App.User.exists(caller_id.user_id): %>
|
||||
<% shown = true %>
|
||||
<% user = App.User.fullLocal(caller_id.user_id) %>
|
||||
<% classes = ['user-popover'] %>
|
||||
<% classes.push('is-inactive') if !user.active %>
|
||||
<% if caller_id.level isnt 'known': %><%- @T('maybe') %> <% end %>
|
||||
<span class="<%= classes.join(' ') %>" data-id="<%- user.id %>"><%= user.displayNameLong() %></span><br>
|
||||
<div class="user-card">
|
||||
<div class="js-avatar" data-id="<%- caller_id.user_id %>" data-level="<%= caller_id.level %>"></div>
|
||||
<a class="text-muted" href="<%- App.Utils.phoneify(item.to_pretty) %>"><%= item.to_pretty %></a>
|
||||
</div>
|
||||
<% else if !_.isEmpty(caller_id.comment): %>
|
||||
<% shown = true %>
|
||||
<%- @T('maybe') %> <%= caller_id.comment %><br>
|
||||
<div class="user-card">
|
||||
<%- @unique_avatar(caller_id.comment, caller_id.comment.split(" ").map((name) -> name[0]).join("")) %>
|
||||
<%- @T('maybe') %>: <%= caller_id.comment %><br>
|
||||
<a class="text-muted" href="<%- App.Utils.phoneify(item.to_pretty) %>"><%= item.to_pretty %></a>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if !shown && !_.isEmpty(item.to_comment): %>
|
||||
<% shown = true %>
|
||||
<%= item.to_comment %>
|
||||
<br>
|
||||
<div class="user-card">
|
||||
<%- @unique_avatar(item.to_comment, item.to_comment.split(" ").map((name) -> name[0]).join("")) %>
|
||||
<%= item.to_comment %><br>
|
||||
<a class="text-muted" href="<%- App.Utils.phoneify(item.to_pretty) %>"><%= item.to_pretty %></a>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if shown: %>
|
||||
<small><%= item.to_pretty %></small>
|
||||
<% else: %>
|
||||
<% if !shown: %>
|
||||
<div class="user-card">
|
||||
<% if item.direction isnt 'in': %>
|
||||
<%- @unique_avatar(item.to_pretty || item.to, '??') %>
|
||||
<% end %>
|
||||
<% if !_.isEmpty(item.to_pretty): %>
|
||||
<%= item.to_pretty %>
|
||||
<a class="inherit-color" href="<%- App.Utils.phoneify(item.to_pretty) %>"><%= item.to_pretty %></a>
|
||||
<% if item.direction is 'out': %>
|
||||
<div class="btn btn--text btn--create no-padding js-newUser" href="#" data-phone="<%= item.to_pretty %>"><%- @Icon('plus-small') %> <span><%- @T('Create User') %></div>
|
||||
<% end %>
|
||||
<% else: %>
|
||||
<%= item.to %>
|
||||
<a class="inherit-color" href="<%- App.Utils.phoneify(item.to) %>"><%= item.to %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</td>
|
||||
<!--<td style="vertical-align: middle"><%= item.queue %></td>-->
|
||||
<td style="vertical-align: middle">
|
||||
<td>
|
||||
<% if item.state_human: %>
|
||||
<%- @Icon('status', "#{item.status_class} inline") %> <%- @T(item.state_human) %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td style="vertical-align: middle"><%= @time_duration(item.duration_waiting_time) %></td>
|
||||
<td style="vertical-align: middle"><%= @time_duration(item.duration_talking_time) %></td>
|
||||
<td style="vertical-align: middle"><%- @humanTime(item.created_at) %></td>
|
||||
<td><%= @time_duration(item.duration_waiting_time) %></td>
|
||||
<td><%= @time_duration(item.duration_talking_time) %></td>
|
||||
<td><%- @humanTime(item.created_at) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
<%- @user.avatar() %>
|
||||
<div class="<%= @classes.join(' ') %>" data-id="<%- @user.id %>"><% if @level isnt 'known': %><%- @T('maybe') %>: <% end %><%= @user.displayNameLong() %></div>
|
|
@ -93,5 +93,32 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<h2><%- @T('Notify Map') %></h2>
|
||||
|
||||
<p><%- @T('Notify certain users by matching caller id.') %><%- @T('If no mapping is defined, all user get notified about any event.') %>
|
||||
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list js-notifyMap" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="30%"><%- @T('Destination caller id or queue') %>
|
||||
<th width="60%"><%- @T('Agents') %>
|
||||
<th width="10%"><%- @T('Action') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for row in @config.notify_map: %>
|
||||
<tr class="js-row">
|
||||
<td class="settings-list-control-cell"><input name="queue" value="<%= row.queue %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-control-cell js-userSelector">
|
||||
<td class="settings-list-row-control"><div class="btn btn--text js-removeMap"><%- @Icon('trash') %> <%- @T('Remove') %></div>
|
||||
<% end %>
|
||||
<tr>
|
||||
<td class="settings-list-control-cell"><input name="queue" value="" placeholder="4930609854189" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-control-cell js-userSelectorBlank">
|
||||
<td class="settings-list-row-control"><div class="btn btn--text btn--create js-addMap"><%- @Icon('plus-small') %> <%- @T('Add') %></div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn--primary js-submit"><%- @T('Save') %></button>
|
||||
</form>
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<h2>Placetel <%- @T('Settings') %></h2>
|
||||
|
||||
<p><%- @T('You need to configure the Zammad endpoints in the %s', 'Placetel') %>:<p>
|
||||
<p><%- @T('You need to configure the Zammad endpoints in the %s web interface', 'Placetel') %>:<p>
|
||||
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
|
@ -109,5 +109,32 @@
|
|||
</table>
|
||||
</div>
|
||||
-->
|
||||
<h2><%- @T('User assignment to telephones') %></h2>
|
||||
|
||||
<p><%- @T('User assignment to telephones to be able to offer extended like open new ticket screen on answering a call.') %>
|
||||
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list js-userDeviceMap" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="45%"><%- @T('Placetel') %>/<%- @T('Device') %>
|
||||
<th width="45%"><%- @T('Zammad') %>/<%- @T('User') %>
|
||||
<th width="10%"><%- @T('Action') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for row in @config.user_device_map: %>
|
||||
<tr class="js-row">
|
||||
<td class="settings-list-control-cell"><input name="device_id" value="<%= row.device_id %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-control-cell"><input name="user_id" value="<%= row.user_id %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-row-control"><div class="btn btn--text js-remove"><%- @Icon('trash') %> <%- @T('Remove') %></div>
|
||||
<% end %>
|
||||
<tr>
|
||||
<td class="settings-list-control-cell"><input name="device_id" value="" placeholder="e. g. 777042617425@fpbx.de" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-control-cell"><input name="user_id" value="" placeholder="<%- @Ti('e. g. user@example.com') %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-row-control"><div class="btn btn--text btn--create js-add"><%- @Icon('plus-small') %> <%- @T('Add') %></div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn--primary js-submit"><%- @T('Save') %></button>
|
||||
</form>
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
<h2>sipgate.io <%- @T('Settings') %></h2>
|
||||
|
||||
<p><%- @T('You need to configure the Zammad endpoints in the Sipgate web interface') %>:<p>
|
||||
<p><%- @T('You need to configure the Zammad endpoints in the %s web interface', 'Sipgate') %>:<p>
|
||||
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
|
@ -96,5 +96,51 @@
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<p><%- @T('In order for Zammad to access %s, a %s API user and password must be stored here', 'Sipgate', 'Sipgate') %>:<p>
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="20%"><%- @T('Type') %>
|
||||
<th width="80%"><%- @T('Content') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('API User') %>
|
||||
<td class="settings-list-control-cell"><input type="input" class="form-control form-control--small js-select" value="<%= @config.api_user %>" name="api_user" placeholder="someuser@example.com">
|
||||
<tr>
|
||||
<td class="settings-list-row-control"><%- @T('API Password') %>
|
||||
<td class="settings-list-control-cell"><input type="password" class="form-control form-control--small js-select" value="<%= @config.api_password %>" name="api_password" placeholder="">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2><%- @T('User assignment to Sipgate users') %></h2>
|
||||
|
||||
<p><%- @T('User assignment to Sipgate users to be able to offer extended like open new ticket screen on answering a call.') %>
|
||||
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list js-userRemoteMap" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="45%"><%- @T('Sipgate') %>/<%- @T('User') %>
|
||||
<th width="45%"><%- @T('Zammad') %>/<%- @T('User') %>
|
||||
<th width="10%"><%- @T('Action') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for row in @config.user_remote_map: %>
|
||||
<tr class="js-row">
|
||||
<td class="settings-list-control-cell"><input name="remote_user_id" value="<%= row.remote_user_id %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-control-cell"><input name="user_id" value="<%= row.user_id %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-row-control"><div class="btn btn--text js-remove"><%- @Icon('trash') %> <%- @T('Remove') %></div>
|
||||
<% end %>
|
||||
<tr>
|
||||
<td class="settings-list-control-cell"><input name="remote_user_id" value="" placeholder="e. g. W123" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-control-cell"><input name="user_id" value="" placeholder="<%- @Ti('e. g. user@example.com') %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-row-control"><div class="btn btn--text btn--create js-add"><%- @Icon('plus-small') %> <%- @T('Add') %></div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn--primary js-submit"><%- @T('Save') %></button>
|
||||
</form>
|
|
@ -14,7 +14,7 @@
|
|||
<li class="divider"></li>
|
||||
<% end %>
|
||||
<% if item.navheader: %>
|
||||
<li class="dropdown-header"><%- @T( item.navheader ) %></li>
|
||||
<li class="dropdown-header"><%- @T(item.navheader) %></li>
|
||||
<% end %>
|
||||
<li><a href="<%= item.target %>"><%- @T( item.name ) %><% if item['count'] isnt undefined: %><span class="badge badge--text count"><%= item['count'] %></span><% end %></a></li>
|
||||
<% end %>
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
<div class="call-widget">
|
||||
<div class="call-widget-header">
|
||||
<%- @Icon('status', "neutral") %>
|
||||
<div class="label"><%- @T('Inbound Call') %></div>
|
||||
<!--
|
||||
<div class="flex-spacer"></div>
|
||||
<div class="btn btn--text js-remove" title="<%- @Ti('Remove') %>"><%- @Icon('diagonal-cross') %></div>
|
||||
-->
|
||||
</div>
|
||||
<div class="horizontal center">
|
||||
<% user = undefined %>
|
||||
<% shown = false %>
|
||||
<% if @item.preferences.from && !_.isEmpty(@item.preferences.from): %>
|
||||
<% for caller_id in @item.preferences.from: %>
|
||||
<% if caller_id.user_id && App.User.exists(caller_id.user_id): %>
|
||||
<% user = App.User.fullLocal(caller_id.user_id) %>
|
||||
<% classes = ['user-popover', 'u-textTruncate'] %>
|
||||
<% classes.push('is-inactive') if !user.active %>
|
||||
<% if shown: %><div class="spacer"></div><% end %>
|
||||
<div class="user-card">
|
||||
<a href="<%- user.uiUrl() %>"><%- user.avatar() %></a>
|
||||
<div class="<%= classes.join(' ') %>" data-id="<%- user.id %>"><% if caller_id.level isnt 'known': %><%- @T('maybe') %>: <% end %><%= user.displayNameLong() %></div>
|
||||
<span class="text-muted"><%= @item.from_pretty %></span>
|
||||
</div>
|
||||
<% shown = true %>
|
||||
<% else if !_.isEmpty(caller_id.comment): %>
|
||||
<% shown = true %>
|
||||
<div class="user-card">
|
||||
<%- @unique_avatar(caller_id.comment, caller_id.comment.split(" ").map((name) -> name[0]).join("")) %>
|
||||
<%- @T('maybe') %>: <%= caller_id.comment %><br>
|
||||
<span class="text-muted"><%= @item.from_pretty %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if !shown && !_.isEmpty(@item.from_comment): %>
|
||||
<% shown = true %>
|
||||
<div class="user-card">
|
||||
<%- @unique_avatar(@item.from_comment, @item.from_comment.split(" ").map((name) -> name[0]).join("")) %>
|
||||
<%= @item.from_comment %><br>
|
||||
<span class="text-muted"><%= @item.from_pretty %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
<% if !shown: %>
|
||||
<div class="user-card">
|
||||
<%- @unique_avatar(@item.from_pretty || @item.from, '??') %>
|
||||
<% if !_.isEmpty(@item.from_pretty): %>
|
||||
<a class="inherit-color" href="<%- App.Utils.phoneify(@item.from_pretty) %>"><%= @item.from_pretty %></a>
|
||||
<% if @item.direction is 'in': %>
|
||||
<div class="btn btn--text btn--create no-padding js-newUser" href="#" data-phone="<%= @item.from_pretty %>"><%- @Icon('plus-small') %> <span><%- @T('Create User') %></div>
|
||||
<% end %>
|
||||
<% else: %>
|
||||
<a class="inherit-color" href="<%- App.Utils.phoneify(@item.from) %>"><%= @item.from %></a>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex-spacer"></div>
|
||||
<div class="btn btn--small btn--quad btn--create space-left js-newTicket" title="<%- @Ti('New Ticket') %>" data-user-id="<% if user: %><%- user.id %><% end %>"><%- @Icon('plus') %></div>
|
||||
</div>
|
||||
</div>
|
|
@ -115,6 +115,10 @@ strong {
|
|||
font-weight: 500;
|
||||
}
|
||||
|
||||
.inherit-color {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: hsl(60,1%,74%);
|
||||
}
|
||||
|
@ -419,6 +423,10 @@ pre code.hljs {
|
|||
&--small {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 4px;
|
||||
|
||||
&.btn--quad {
|
||||
padding: 4px 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&--slim {
|
||||
|
@ -588,12 +596,18 @@ pre code.hljs {
|
|||
background: none;
|
||||
vertical-align: baseline;
|
||||
text-align: start;
|
||||
|
||||
|
||||
.table & {
|
||||
margin: 0;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
&.no-padding {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.icon {
|
||||
opacity: 1;
|
||||
margin-right: 6px;
|
||||
|
@ -662,7 +676,7 @@ pre code.hljs {
|
|||
padding: 10px 12px 9px;
|
||||
|
||||
.icon {
|
||||
margin-top: -1px;
|
||||
margin: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1111,6 +1125,10 @@ th.align-right {
|
|||
}
|
||||
}
|
||||
|
||||
.table > tbody > tr.u-center > td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.table-hover > tbody > tr:hover,
|
||||
.table-hover > tbody > tr.is-hover {
|
||||
background: white;
|
||||
|
@ -1161,9 +1179,15 @@ th.align-right {
|
|||
.table tr.is-grayed-out {
|
||||
color: hsl(120,1%,77%);
|
||||
|
||||
.icon {
|
||||
.icon,
|
||||
.btn span {
|
||||
opacity: 0.33;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
background: hsl(120,1%,86%);
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
td .icon {
|
||||
|
@ -1321,6 +1345,11 @@ td .icon-trash {
|
|||
.table .radio-replacement {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
|
||||
&.checkbox-replacement--fullscreen {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.table-overview tbody .icon-checkbox,
|
||||
|
@ -3665,6 +3694,45 @@ footer {
|
|||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.call-widget {
|
||||
background: hsl(228,17%,91%);
|
||||
padding: 8px 10px;
|
||||
|
||||
& + & {
|
||||
border-top: 1px solid hsl(228,10%,81%);
|
||||
}
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
color: inherit;
|
||||
margin-bottom: 3px;
|
||||
|
||||
.label {
|
||||
color: inherit;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.btn--text {
|
||||
color: inherit;
|
||||
opacity: .5;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-diagonal-cross {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
margin-top: -5px;
|
||||
}
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: hsl(228,6%,67%);
|
||||
}
|
||||
}
|
||||
|
||||
.tasks {
|
||||
background: #2c2d36;
|
||||
flex: 1;
|
||||
|
@ -4810,6 +4878,22 @@ footer {
|
|||
@extend .u-clickable;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
padding: 2px 0 0 50px;
|
||||
position: relative;
|
||||
min-height: 40px;
|
||||
max-width: 192px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.avatar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class CtiController < ApplicationController
|
|||
}
|
||||
]
|
||||
|
||||
result = Cti::Log.log
|
||||
result = Cti::Log.log(current_user)
|
||||
result[:backends] = backends
|
||||
render json: result
|
||||
end
|
||||
|
|
|
@ -6,75 +6,35 @@ class Integration::CtiController < ApplicationController
|
|||
|
||||
# notify about inbound call / block inbound call
|
||||
def event
|
||||
if params['direction'] == 'in'
|
||||
if params['event'] == 'newCall'
|
||||
config_inbound = config_integration[:inbound] || {}
|
||||
block_caller_ids = config_inbound[:block_caller_ids] || []
|
||||
local_params = ActiveSupport::HashWithIndifferentAccess.new(params.permit!.to_h)
|
||||
|
||||
# check if call need to be blocked
|
||||
block_caller_ids.each do |item|
|
||||
next unless item[:caller_id] == params['from']
|
||||
cti = Cti::Driver::Cti.new(params: local_params, config: config_integration)
|
||||
|
||||
render json: { action: 'reject', reason: 'busy' }, status: :ok
|
||||
result = cti.process
|
||||
|
||||
#params['Reject'] = 'busy'
|
||||
params['comment'] = 'reject, busy'
|
||||
if params['user']
|
||||
params['comment'] = "#{params['user']} -> reject, busy"
|
||||
end
|
||||
Cti::Log.process(params)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
Cti::Log.process(params)
|
||||
|
||||
render json: {}, status: :ok
|
||||
return true
|
||||
elsif params['direction'] == 'out'
|
||||
config_outbound = config_integration[:outbound]
|
||||
routing_table = nil
|
||||
default_caller_id = nil
|
||||
if config_outbound.present?
|
||||
routing_table = config_outbound[:routing_table]
|
||||
default_caller_id = config_outbound[:default_caller_id]
|
||||
end
|
||||
|
||||
# set callerId
|
||||
data = {}
|
||||
to = params[:to]
|
||||
from = nil
|
||||
if to && routing_table.present?
|
||||
routing_table.each do |row|
|
||||
dest = row[:dest].gsub(/\*/, '.+?')
|
||||
next if to !~ /^#{dest}$/
|
||||
|
||||
from = row[:caller_id]
|
||||
data = {
|
||||
action: 'dial',
|
||||
caller_id: from,
|
||||
number: params[:to]
|
||||
}
|
||||
break
|
||||
end
|
||||
if data.blank? && default_caller_id.present?
|
||||
from = default_caller_id
|
||||
data = {
|
||||
action: 'dial',
|
||||
caller_id: default_caller_id,
|
||||
number: params[:to]
|
||||
}
|
||||
end
|
||||
end
|
||||
render json: data, status: :ok
|
||||
|
||||
if from.present?
|
||||
params['from'] = from
|
||||
end
|
||||
Cti::Log.process(params)
|
||||
# check if inbound call should get rejected
|
||||
if result[:action] == 'reject'
|
||||
response_ok(action: 'reject', reason: 'busy')
|
||||
return true
|
||||
end
|
||||
render json: { error: 'Invalid direction!' }, status: :unprocessable_entity
|
||||
|
||||
# check if oubound call change the outbound caller_id
|
||||
if result[:action] == 'set_caller_id'
|
||||
data = {
|
||||
action: 'dial',
|
||||
caller_id: result[:params][:from_caller_id],
|
||||
number: result[:params][:to_caller_id],
|
||||
}
|
||||
response_ok(data)
|
||||
return true
|
||||
end
|
||||
|
||||
if result[:action] == 'invalid_direction'
|
||||
response_error('Invalid direction!')
|
||||
return true
|
||||
end
|
||||
|
||||
response_ok({})
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -115,4 +75,8 @@ class Integration::CtiController < ApplicationController
|
|||
render json: { error: error }, status: :unauthorized
|
||||
end
|
||||
|
||||
def response_ok(data)
|
||||
render json: data, status: :ok
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -10,115 +10,29 @@ class Integration::PlacetelController < ApplicationController
|
|||
|
||||
local_params = ActiveSupport::HashWithIndifferentAccess.new(params.permit!.to_h)
|
||||
|
||||
# do placetel event mapping
|
||||
if local_params['event'] == 'IncomingCall'
|
||||
local_params['direction'] = 'in'
|
||||
local_params['event'] = 'newCall'
|
||||
elsif local_params['event'] == 'HungUp'
|
||||
local_params['event'] = 'hangup'
|
||||
elsif local_params['event'] == 'CallAccepted'
|
||||
local_params['event'] = 'answer'
|
||||
end
|
||||
cti = Cti::Driver::Placetel.new(params: local_params, config: config_integration)
|
||||
|
||||
if local_params['user'].blank? && local_params['peer']
|
||||
local_params['user'] = get_voip_user_by_peer(local_params['peer'])
|
||||
end
|
||||
result = cti.process
|
||||
|
||||
if local_params['direction'].blank?
|
||||
entry = Cti::Log.find_by(call_id: params[:call_id])
|
||||
if entry
|
||||
local_params['direction'] = entry.direction
|
||||
end
|
||||
end
|
||||
|
||||
if local_params['type'] == 'missed'
|
||||
local_params['cause'] = 'cancel'
|
||||
elsif local_params['type'] == 'voicemail'
|
||||
local_params['cause'] = 'voicemail'
|
||||
elsif local_params['type'] == 'blocked'
|
||||
local_params['cause'] = 'blocked'
|
||||
elsif local_params['type'] == 'accepted'
|
||||
local_params['cause'] = 'normalClearing'
|
||||
end
|
||||
|
||||
if local_params['direction'] == 'in'
|
||||
if local_params['event'] == 'newCall'
|
||||
config_inbound = config_integration[:inbound] || {}
|
||||
block_caller_ids = config_inbound[:block_caller_ids] || []
|
||||
|
||||
# check if call need to be blocked
|
||||
block_caller_ids.each do |item|
|
||||
next unless item[:caller_id] == local_params['from']
|
||||
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response() do
|
||||
xml.Reject('reason' => 'busy')
|
||||
end
|
||||
send_data content, type: 'application/xml; charset=UTF-8;'
|
||||
|
||||
#local_params['Reject'] = 'busy'
|
||||
local_params['comment'] = 'reject, busy'
|
||||
if local_params['user']
|
||||
local_params['comment'] = "#{local_params['user']} -> reject, busy"
|
||||
end
|
||||
Cti::Log.process(local_params)
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
Cti::Log.process(local_params)
|
||||
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response()
|
||||
send_data content, type: 'application/xml; charset=UTF-8;'
|
||||
return true
|
||||
elsif local_params['direction'] == 'out'
|
||||
config_outbound = config_integration[:outbound]
|
||||
routing_table = nil
|
||||
default_caller_id = nil
|
||||
if config_outbound.present?
|
||||
routing_table = config_outbound[:routing_table]
|
||||
default_caller_id = config_outbound[:default_caller_id]
|
||||
end
|
||||
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
|
||||
# set callerId
|
||||
content = nil
|
||||
to = local_params[:to]
|
||||
from = nil
|
||||
if to && routing_table.present?
|
||||
routing_table.each do |row|
|
||||
dest = row[:dest].gsub(/\*/, '.+?')
|
||||
next if to !~ /^#{dest}$/
|
||||
|
||||
from = row[:caller_id]
|
||||
content = xml.Response() do
|
||||
xml.Dial(callerId: from) { xml.Number(params[:to]) }
|
||||
end
|
||||
break
|
||||
end
|
||||
if !content && default_caller_id.present?
|
||||
from = default_caller_id
|
||||
content = xml.Response() do
|
||||
xml.Dial(callerId: default_caller_id) { xml.Number(params[:to]) }
|
||||
end
|
||||
end
|
||||
else
|
||||
content = xml.Response()
|
||||
end
|
||||
send_data(content, type: 'application/xml; charset=UTF-8;')
|
||||
|
||||
if from.present?
|
||||
local_params['from'] = from
|
||||
end
|
||||
Cti::Log.process(local_params)
|
||||
# check if inbound call should get rejected
|
||||
if result[:action] == 'reject'
|
||||
response_reject(result)
|
||||
return true
|
||||
end
|
||||
response_error('Invalid direction!')
|
||||
|
||||
# check if oubound call change the outbound caller_id
|
||||
if result[:action] == 'set_caller_id'
|
||||
response_set_caller_id(result)
|
||||
return true
|
||||
end
|
||||
|
||||
if result[:action] == 'invalid_direction'
|
||||
response_error('Invalid direction!')
|
||||
return true
|
||||
end
|
||||
|
||||
response_ok(response)
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -168,65 +82,29 @@ class Integration::PlacetelController < ApplicationController
|
|||
xml_error(error, 401)
|
||||
end
|
||||
|
||||
def get_voip_user_by_peer(peer)
|
||||
load_voip_users[peer]
|
||||
def response_reject(_result)
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response() do
|
||||
xml.Reject({ reason: 'busy' })
|
||||
end
|
||||
send_data content, type: 'application/xml; charset=UTF-8;'
|
||||
end
|
||||
|
||||
def load_voip_users
|
||||
return {} if config_integration.blank? || config_integration[:api_token].blank?
|
||||
|
||||
list = Cache.get('placetelGetVoipUsers')
|
||||
return list if list
|
||||
|
||||
response = UserAgent.post(
|
||||
'https://api.placetel.de/api/getVoIPUsers.json',
|
||||
{
|
||||
api_key: config_integration[:api_token],
|
||||
},
|
||||
{
|
||||
log: {
|
||||
facility: 'placetel',
|
||||
},
|
||||
json: true,
|
||||
open_timeout: 4,
|
||||
read_timeout: 6,
|
||||
total_timeout: 6,
|
||||
},
|
||||
)
|
||||
if !response.success?
|
||||
logger.error "Can't fetch getVoipUsers from '#{url}', http code: #{response.code}"
|
||||
Cache.write('placetelGetVoipUsers', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
def response_set_caller_id(result)
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response() do
|
||||
xml.Dial(callerId: result[:params][:from_caller_id]) { xml.Number(result[:params][:to_caller_id]) }
|
||||
end
|
||||
result = response.data
|
||||
if result.blank?
|
||||
logger.error "Can't fetch getVoipUsers from '#{url}', result: #{response.inspect}"
|
||||
Cache.write('placetelGetVoipUsers', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
end
|
||||
if result.is_a?(Hash) && (result['result'] == '-1' || result['result_code'] == 'error')
|
||||
logger.error "Can't fetch getVoipUsers from '#{url}', result: #{result.inspect}"
|
||||
Cache.write('placetelGetVoipUsers', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
end
|
||||
if !result.is_a?(Array)
|
||||
logger.error "Can't fetch getVoipUsers from '#{url}', result: #{result.inspect}"
|
||||
Cache.write('placetelGetVoipUsers', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
end
|
||||
|
||||
list = {}
|
||||
result.each do |entry|
|
||||
next if entry['name'].blank?
|
||||
|
||||
if entry['uid'].present?
|
||||
list[entry['uid']] = entry['name']
|
||||
end
|
||||
next if entry['uid2'].blank?
|
||||
|
||||
list[entry['uid2']] = entry['name']
|
||||
end
|
||||
Cache.write('placetelGetVoipUsers', list, { expires_in: 24.hours })
|
||||
list
|
||||
send_data(content, type: 'application/xml; charset=UTF-8;')
|
||||
end
|
||||
|
||||
def response_ok(_result)
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response()
|
||||
send_data content, type: 'application/xml; charset=UTF-8;'
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -6,84 +6,33 @@ class Integration::SipgateController < ApplicationController
|
|||
before_action :check_configured
|
||||
|
||||
# notify about inbound call / block inbound call
|
||||
def in
|
||||
if params['event'] == 'newCall'
|
||||
config_inbound = config_integration[:inbound] || {}
|
||||
block_caller_ids = config_inbound[:block_caller_ids] || []
|
||||
def event
|
||||
|
||||
# check if call need to be blocked
|
||||
block_caller_ids.each do |item|
|
||||
next if item[:caller_id] != params['from']
|
||||
local_params = ActiveSupport::HashWithIndifferentAccess.new(params.permit!.to_h)
|
||||
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response(onHangup: url, onAnswer: url) do
|
||||
xml.Reject('reason' => 'busy')
|
||||
end
|
||||
cti = Cti::Driver::SipgateIo.new(params: local_params, config: config_integration)
|
||||
|
||||
send_data content, type: 'application/xml; charset=UTF-8;'
|
||||
result = cti.process
|
||||
|
||||
#params['Reject'] = 'busy'
|
||||
params['comment'] = 'reject, busy'
|
||||
if params['user']
|
||||
params['comment'] = "#{params['user']} -> reject, busy"
|
||||
end
|
||||
Cti::Log.process(params)
|
||||
return true
|
||||
end
|
||||
# check if inbound call should get rejected
|
||||
if result[:action] == 'reject'
|
||||
response_reject(result)
|
||||
return true
|
||||
end
|
||||
|
||||
Cti::Log.process(params)
|
||||
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response(onHangup: url, onAnswer: url)
|
||||
send_data content, type: 'application/xml; charset=UTF-8;'
|
||||
end
|
||||
|
||||
# set caller id of outbound call
|
||||
def out
|
||||
config_outbound = config_integration[:outbound]
|
||||
routing_table = nil
|
||||
default_caller_id = nil
|
||||
if config_outbound.present?
|
||||
routing_table = config_outbound[:routing_table]
|
||||
default_caller_id = config_outbound[:default_caller_id]
|
||||
# check if oubound call change the outbound caller_id
|
||||
if result[:action] == 'set_caller_id'
|
||||
response_set_caller_id(result)
|
||||
return true
|
||||
end
|
||||
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
|
||||
# set callerId
|
||||
content = nil
|
||||
to = params[:to]
|
||||
from = nil
|
||||
if to && routing_table.present?
|
||||
routing_table.each do |row|
|
||||
dest = row[:dest].gsub(/\*/, '.+?')
|
||||
next if to !~ /^#{dest}$/
|
||||
|
||||
from = row[:caller_id]
|
||||
content = xml.Response(onHangup: url, onAnswer: url) do
|
||||
xml.Dial(callerId: from) { xml.Number(params[:to]) }
|
||||
end
|
||||
break
|
||||
end
|
||||
if !content && default_caller_id.present?
|
||||
from = default_caller_id
|
||||
content = xml.Response(onHangup: url, onAnswer: url) do
|
||||
xml.Dial(callerId: default_caller_id) { xml.Number(params[:to]) }
|
||||
end
|
||||
end
|
||||
else
|
||||
content = xml.Response(onHangup: url, onAnswer: url)
|
||||
if result[:action] == 'invalid_direction'
|
||||
response_error('Invalid direction!')
|
||||
return true
|
||||
end
|
||||
|
||||
send_data(content, type: 'application/xml; charset=UTF-8;')
|
||||
if from.present?
|
||||
params['from'] = from
|
||||
end
|
||||
Cti::Log.process(params)
|
||||
response_ok(response)
|
||||
true
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -128,4 +77,30 @@ class Integration::SipgateController < ApplicationController
|
|||
def url
|
||||
"#{base_url}/#{params['direction']}"
|
||||
end
|
||||
|
||||
def response_reject(_result)
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response(onHangup: url, onAnswer: url) do
|
||||
xml.Reject({ reason: 'busy' })
|
||||
end
|
||||
send_data content, type: 'application/xml; charset=UTF-8;'
|
||||
end
|
||||
|
||||
def response_set_caller_id(result)
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response(onHangup: url, onAnswer: url) do
|
||||
xml.Dial(callerId: result[:params][:from_caller_id]) { xml.Number(result[:params][:to_caller_id]) }
|
||||
end
|
||||
send_data(content, type: 'application/xml; charset=UTF-8;')
|
||||
end
|
||||
|
||||
def response_ok(_result)
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response(onHangup: url, onAnswer: url)
|
||||
send_data content, type: 'application/xml; charset=UTF-8;'
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -39,6 +39,8 @@ module Cti
|
|||
|
||||
=begin
|
||||
|
||||
get items (users) for a certain caller id
|
||||
|
||||
caller_id_records = Cti::CallerId.lookup('49123456789')
|
||||
|
||||
returns
|
||||
|
@ -312,6 +314,30 @@ returns
|
|||
nil
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
return users by caller_id
|
||||
|
||||
[user1, user2] = Cti::CallerId.known_agents_by_number('491234567')
|
||||
|
||||
=end
|
||||
|
||||
def self.known_agents_by_number(number)
|
||||
users = []
|
||||
caller_ids = Cti::CallerId.extract_numbers(number)
|
||||
caller_id_records = Cti::CallerId.lookup(caller_ids)
|
||||
caller_id_records.each do |caller_id_record|
|
||||
next if caller_id_record.level != 'known'
|
||||
|
||||
user = User.find_by(id: caller_id_record.user_id)
|
||||
next if !user
|
||||
next if !user.permissions?('cti.agent')
|
||||
|
||||
users.push user
|
||||
end
|
||||
users
|
||||
end
|
||||
|
||||
def update_cti_logs
|
||||
return if object != 'User'
|
||||
|
||||
|
|
223
app/models/cti/driver/base.rb
Normal file
223
app/models/cti/driver/base.rb
Normal file
|
@ -0,0 +1,223 @@
|
|||
class Cti::Driver::Base
|
||||
|
||||
def initialize(params = {})
|
||||
@config = params[:config] || config
|
||||
@params = mapping(params[:params])
|
||||
end
|
||||
|
||||
def mapping(params)
|
||||
params
|
||||
end
|
||||
|
||||
def process
|
||||
|
||||
# validate diections
|
||||
result = direction_check
|
||||
return result if result.present?
|
||||
|
||||
# reject inbound call
|
||||
result = reject_check
|
||||
if result.present? && result[:action] == 'reject'
|
||||
@params['comment'] = 'reject, busy'
|
||||
if @params['user'].present?
|
||||
@params['comment'] = "#{@params['user']} -> reject, busy"
|
||||
end
|
||||
Cti::Log.process(@params)
|
||||
return result
|
||||
end
|
||||
|
||||
# set caller id of outbound call
|
||||
result = caller_id_rewrite(@params)
|
||||
if result.present? && result[:action] == 'set_caller_id'
|
||||
@params['from'] = result[:params][:from_caller_id]
|
||||
Cti::Log.process(@params)
|
||||
return result
|
||||
end
|
||||
|
||||
log = Cti::Log.process(@params)
|
||||
|
||||
# push new call notifiation
|
||||
push_incoming_call(log)
|
||||
|
||||
# open screen if call got answerd
|
||||
push_open_ticket_screen(log)
|
||||
|
||||
result || {}
|
||||
end
|
||||
|
||||
def direction_check
|
||||
|
||||
# check possible diections
|
||||
if @params['direction'] != 'in' && @params['direction'] != 'out'
|
||||
return {
|
||||
action: 'invalid_direction',
|
||||
params: @params
|
||||
}
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def reject_check
|
||||
return nil if @params['direction'] != 'in'
|
||||
return nil if @params['event'] != 'newCall'
|
||||
|
||||
config_inbound = @config[:inbound] || {}
|
||||
block_caller_ids = config_inbound[:block_caller_ids] || []
|
||||
|
||||
# check if call need to be blocked
|
||||
block_caller_ids.each do |item|
|
||||
next if item[:caller_id] != @params['from']
|
||||
|
||||
return {
|
||||
action: 'reject'
|
||||
}
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def caller_id_rewrite(params)
|
||||
return nil if params['direction'] != 'out'
|
||||
return nil if params['event'] != 'newCall'
|
||||
|
||||
config_outbound = @config[:outbound]
|
||||
routing_table = nil
|
||||
default_caller_id = nil
|
||||
if config_outbound.present?
|
||||
routing_table = config_outbound[:routing_table]
|
||||
default_caller_id = config_outbound[:default_caller_id]
|
||||
end
|
||||
|
||||
to = params[:to]
|
||||
return nil if to.blank?
|
||||
|
||||
if routing_table.present?
|
||||
routing_table.each do |row|
|
||||
dest = row[:dest].gsub(/\*/, '.+?')
|
||||
next if to !~ /^#{dest}$/
|
||||
|
||||
return {
|
||||
action: 'set_caller_id',
|
||||
params: {
|
||||
from_caller_id: row[:caller_id],
|
||||
to_caller_id: params[:to],
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
if default_caller_id.present?
|
||||
return {
|
||||
action: 'set_caller_id',
|
||||
params: {
|
||||
from_caller_id: default_caller_id,
|
||||
to_caller_id: params[:to],
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def push_open_ticket_screen(log)
|
||||
return if log.destroyed?
|
||||
return if @params[:event] != 'answer'
|
||||
return if @params[:direction] != 'in'
|
||||
|
||||
user = push_open_ticket_screen_recipient
|
||||
return if !user
|
||||
return if !user.permissions?('cti.agent')
|
||||
|
||||
customer_id = log.best_customer_id_of_log_entry
|
||||
|
||||
id = rand(999_999_999)
|
||||
PushMessages.send_to(user.id, {
|
||||
event: 'remote_task',
|
||||
data: {
|
||||
key: "TicketCreateScreen-#{id}",
|
||||
controller: 'TicketCreate',
|
||||
params: { customer_id: customer_id.to_s, title: 'Call', id: id },
|
||||
show: true,
|
||||
url: "ticket/create/id/#{id}"
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
def push_open_ticket_screen_recipient
|
||||
|
||||
# try to find answering which answered call
|
||||
user = nil
|
||||
|
||||
# based on answeringNumber
|
||||
if @params[:answeringNumber].present?
|
||||
user = Cti::CallerId.known_agents_by_number(@params[:answeringNumber]).first
|
||||
end
|
||||
|
||||
# based on user param
|
||||
if !user && @params[:user].present?
|
||||
user = User.find_by(login: @params[:user].downcase)
|
||||
end
|
||||
|
||||
# based on user_id param
|
||||
if !user && @params[:user_id].present?
|
||||
user = User.find_by(id: @params[:user_id])
|
||||
end
|
||||
|
||||
user
|
||||
end
|
||||
|
||||
def push_incoming_call(log)
|
||||
return if log.destroyed?
|
||||
return if @params[:event] != 'newCall'
|
||||
return if @params[:direction] != 'in'
|
||||
|
||||
# check if only a certain user should get the notification
|
||||
if @config[:notify_map].present?
|
||||
user_ids = []
|
||||
@config[:notify_map].each do |row|
|
||||
next if row[:user_ids].blank? || row[:queue] != @params[:to]
|
||||
|
||||
row[:user_ids].each do |user_id|
|
||||
user = User.find_by(id: user_id)
|
||||
next if !user
|
||||
next if !user.permissions?('cti.agent')
|
||||
|
||||
user_ids.push user.id
|
||||
end
|
||||
end
|
||||
|
||||
# add agents which have this number directly assigned
|
||||
Cti::CallerId.known_agents_by_number(@params[:to]).each do |user|
|
||||
next if !user
|
||||
next if !user.permissions?('cti.agent')
|
||||
|
||||
user_ids.push user.id
|
||||
end
|
||||
|
||||
user_ids.uniq.each do |user_id|
|
||||
PushMessages.send_to(
|
||||
user_id,
|
||||
{
|
||||
event: 'cti_event',
|
||||
data: log,
|
||||
},
|
||||
)
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
# send notify about event
|
||||
users = User.with_permissions('cti.agent')
|
||||
users.each do |user|
|
||||
PushMessages.send_to(
|
||||
user.id,
|
||||
{
|
||||
event: 'cti_event',
|
||||
data: log,
|
||||
},
|
||||
)
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
end
|
7
app/models/cti/driver/cti.rb
Normal file
7
app/models/cti/driver/cti.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
class Cti::Driver::Cti < Cti::Driver::Base
|
||||
|
||||
def config
|
||||
Setting.get('cti_config')
|
||||
end
|
||||
|
||||
end
|
139
app/models/cti/driver/placetel.rb
Normal file
139
app/models/cti/driver/placetel.rb
Normal file
|
@ -0,0 +1,139 @@
|
|||
class Cti::Driver::Placetel < Cti::Driver::Base
|
||||
|
||||
def config
|
||||
Setting.get('placetel_config')
|
||||
end
|
||||
|
||||
def mapping(params)
|
||||
|
||||
# do event mapping
|
||||
if params['event'] == 'IncomingCall'
|
||||
params['direction'] = 'in'
|
||||
params['event'] = 'newCall'
|
||||
elsif params['event'] == 'HungUp'
|
||||
params['event'] = 'hangup'
|
||||
elsif params['event'] == 'CallAccepted'
|
||||
params['event'] = 'answer'
|
||||
end
|
||||
|
||||
if params['user'].blank? && params['peer'].present?
|
||||
params['user'] = get_voip_user_by_peer(params['peer'])
|
||||
end
|
||||
|
||||
# lookup current direction if not given
|
||||
if params['direction'].blank?
|
||||
entry = Cti::Log.find_by(call_id: params[:call_id])
|
||||
if entry
|
||||
params['direction'] = entry.direction
|
||||
end
|
||||
end
|
||||
|
||||
# do case mapping
|
||||
if params['type'] == 'missed'
|
||||
params['cause'] = 'cancel'
|
||||
elsif params['type'] == 'voicemail'
|
||||
params['cause'] = 'voicemail'
|
||||
elsif params['type'] == 'blocked'
|
||||
params['cause'] = 'blocked'
|
||||
elsif params['type'] == 'accepted'
|
||||
params['cause'] = 'normalClearing'
|
||||
end
|
||||
|
||||
params
|
||||
end
|
||||
|
||||
def push_open_ticket_screen_recipient
|
||||
|
||||
# try to find answering which answered call
|
||||
user = nil
|
||||
|
||||
# based on peer
|
||||
if @params['peer'].present?
|
||||
user_id = get_user_id_by_peer(@params['peer'])
|
||||
if user_id.present?
|
||||
user = if User.exists?(user_id)
|
||||
User.find(user_id)
|
||||
else
|
||||
User.find_by(email: user_id.downcase)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
user
|
||||
end
|
||||
|
||||
def get_voip_user_by_peer(peer)
|
||||
load_voip_users[peer]
|
||||
end
|
||||
|
||||
def load_voip_users
|
||||
return {} if @config.blank? || @config[:api_token].blank?
|
||||
|
||||
list = Cache.get('placetelGetVoipUsers')
|
||||
return list if list
|
||||
|
||||
response = UserAgent.post(
|
||||
'https://api.placetel.de/api/getVoIPUsers.json',
|
||||
{
|
||||
api_key: @config[:api_token],
|
||||
},
|
||||
{
|
||||
log: {
|
||||
facility: 'placetel',
|
||||
},
|
||||
json: true,
|
||||
open_timeout: 4,
|
||||
read_timeout: 6,
|
||||
total_timeout: 6,
|
||||
},
|
||||
)
|
||||
|
||||
if !response.success?
|
||||
Rails.logger.error "Can't fetch getVoipUsers from '#{url}', http code: #{response.code}"
|
||||
Cache.write('placetelGetVoipUsers', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
end
|
||||
result = response.data
|
||||
if result.blank?
|
||||
Rails.logger.error "Can't fetch getVoipUsers from '#{url}', result: #{response.inspect}"
|
||||
Cache.write('placetelGetVoipUsers', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
end
|
||||
if result.is_a?(Hash) && (result['result'] == '-1' || result['result_code'] == 'error')
|
||||
Rails.logger.error "Can't fetch getVoipUsers from '#{url}', result: #{result.inspect}"
|
||||
Cache.write('placetelGetVoipUsers', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
end
|
||||
if !result.is_a?(Array)
|
||||
Rails.logger.error "Can't fetch getVoipUsers from '#{url}', result: #{result.inspect}"
|
||||
Cache.write('placetelGetVoipUsers', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
end
|
||||
|
||||
list = {}
|
||||
result.each do |entry|
|
||||
next if entry['name'].blank?
|
||||
|
||||
if entry['uid'].present?
|
||||
list[entry['uid']] = entry['name']
|
||||
end
|
||||
next if entry['uid2'].blank?
|
||||
|
||||
list[entry['uid2']] = entry['name']
|
||||
end
|
||||
Cache.write('placetelGetVoipUsers', list, { expires_in: 24.hours })
|
||||
list
|
||||
end
|
||||
|
||||
def get_user_id_by_peer(peer)
|
||||
return if @config.blank? || @config[:user_device_map].blank?
|
||||
|
||||
@config[:user_device_map].each do |row|
|
||||
next if row[:user_id].blank?
|
||||
return row[:user_id] if row[:device_id] == peer
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
107
app/models/cti/driver/sipgate_io.rb
Normal file
107
app/models/cti/driver/sipgate_io.rb
Normal file
|
@ -0,0 +1,107 @@
|
|||
class Cti::Driver::SipgateIo < Cti::Driver::Base
|
||||
|
||||
def config
|
||||
Setting.get('sipgate_config')
|
||||
end
|
||||
|
||||
def push_open_ticket_screen_recipient
|
||||
|
||||
# try to find answering which answered call
|
||||
user = nil
|
||||
|
||||
# based on peer
|
||||
if @params['userId'].present?
|
||||
user_id = get_user_id_by_sipgate_user_id(@params['userId'])
|
||||
if user_id.present?
|
||||
user = if User.exists?(user_id)
|
||||
User.find(user_id)
|
||||
else
|
||||
User.find_by(email: user_id.downcase)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
user
|
||||
end
|
||||
|
||||
def load_voip_users
|
||||
return {} if @config.blank? || @config[:api_user].blank? || @config[:api_password].blank?
|
||||
|
||||
list = Cache.get('sipgateUserList')
|
||||
return list if list
|
||||
|
||||
url = 'https://api.sipgate.com/v2/users'
|
||||
|
||||
response = UserAgent.get(
|
||||
url,
|
||||
{},
|
||||
{
|
||||
user: @config[:api_user],
|
||||
password: @config[:api_password],
|
||||
log: {
|
||||
facility: 'sipagte.io',
|
||||
},
|
||||
json: true,
|
||||
open_timeout: 4,
|
||||
read_timeout: 6,
|
||||
total_timeout: 6,
|
||||
},
|
||||
)
|
||||
|
||||
if !response.success?
|
||||
Rails.logger.error "Can't fetch users from '#{url}', http code: #{response.code}"
|
||||
Cache.write('sipgateUserList', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
end
|
||||
result = response.data
|
||||
if result.blank?
|
||||
Rails.logger.error "Can't fetch users from '#{url}', result: #{response.inspect}"
|
||||
Cache.write('sipgateUserList', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
end
|
||||
if result.is_a?(Array) && (result['result'] == '-1' || result['result_code'] == 'error')
|
||||
Rails.logger.error "Can't fetch users from '#{url}', result: #{result.inspect}"
|
||||
Cache.write('sipgateUserList', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
end
|
||||
if !result.is_a?(Hash)
|
||||
Rails.logger.error "Can't fetch users from '#{url}', result: #{result.inspect}"
|
||||
Cache.write('sipgateUserList', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
end
|
||||
if result['items'].blank?
|
||||
Rails.logger.error "Can't fetch users from '#{url}', no items found, result: #{result.inspect}"
|
||||
Cache.write('sipgateUserList', {}, { expires_in: 1.hour })
|
||||
return {}
|
||||
end
|
||||
|
||||
list = {}
|
||||
result['items'].each do |entry|
|
||||
next if entry['id'].blank?
|
||||
|
||||
name = ''
|
||||
%w[firstname lastname email].each do |item|
|
||||
next if entry[item].blank?
|
||||
|
||||
name += ' ' if name.present?
|
||||
name += entry[item]
|
||||
end
|
||||
|
||||
list[entry['id']] = name
|
||||
end
|
||||
Cache.write('sipgateUserList', list, { expires_in: 24.hours })
|
||||
list
|
||||
end
|
||||
|
||||
def get_user_id_by_sipgate_user_id(user_id)
|
||||
return if @config.blank? || @config[:user_remote_map].blank?
|
||||
|
||||
@config[:user_remote_map].each do |row|
|
||||
next if row[:user_id].blank?
|
||||
return row[:user_id] if row[:remote_user_id] == user_id
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
end
|
|
@ -8,19 +8,20 @@ module Cti
|
|||
|
||||
validates :state, format: { with: /\A(newCall|answer|hangup)\z/, message: 'newCall|answer|hangup is allowed' }
|
||||
|
||||
after_commit :push_incoming_call, :push_caller_list_update
|
||||
after_commit :push_caller_list_update
|
||||
|
||||
=begin
|
||||
|
||||
Cti::Log.create!(
|
||||
direction: 'in',
|
||||
from: '007',
|
||||
from_comment: 'AAA',
|
||||
from_comment: '',
|
||||
to: '008',
|
||||
to_comment: 'BBB',
|
||||
call_id: '1',
|
||||
comment: '',
|
||||
state: 'newCall',
|
||||
done: true,
|
||||
)
|
||||
|
||||
Cti::Log.create!(
|
||||
|
@ -32,6 +33,7 @@ module Cti
|
|||
call_id: '2',
|
||||
comment: '',
|
||||
state: 'answer',
|
||||
done: true,
|
||||
)
|
||||
|
||||
Cti::Log.create!(
|
||||
|
@ -43,6 +45,7 @@ module Cti
|
|||
call_id: '3',
|
||||
comment: '',
|
||||
state: 'hangup',
|
||||
done: true,
|
||||
)
|
||||
|
||||
example data, can be used for demo
|
||||
|
@ -66,7 +69,15 @@ example data, can be used for demo
|
|||
object: 'User',
|
||||
o_id: 2,
|
||||
user_id: 2,
|
||||
}
|
||||
},
|
||||
{
|
||||
caller_id: '4930726128135',
|
||||
comment: nil,
|
||||
level: 'maybe',
|
||||
object: 'User',
|
||||
o_id: 2,
|
||||
user_id: 3,
|
||||
},
|
||||
]
|
||||
},
|
||||
created_at: Time.zone.now,
|
||||
|
@ -81,6 +92,7 @@ example data, can be used for demo
|
|||
call_id: rand(999_999_999),
|
||||
comment: '',
|
||||
state: 'newCall',
|
||||
done: true,
|
||||
preferences: {
|
||||
to: [
|
||||
{
|
||||
|
@ -105,6 +117,7 @@ example data, can be used for demo
|
|||
call_id: rand(999_999_999),
|
||||
comment: '',
|
||||
state: 'answer',
|
||||
done: true,
|
||||
preferences: {
|
||||
from: [
|
||||
{
|
||||
|
@ -163,6 +176,7 @@ example data, can be used for demo
|
|||
call_id: rand(999_999_999),
|
||||
comment: '',
|
||||
state: 'hangup',
|
||||
done: true,
|
||||
start_at: Time.zone.now - 15.seconds,
|
||||
end_at: Time.zone.now,
|
||||
preferences: {
|
||||
|
@ -194,6 +208,7 @@ example data, can be used for demo
|
|||
call_id: rand(999_999_999),
|
||||
comment: '',
|
||||
state: 'hangup',
|
||||
done: true,
|
||||
start_at: Time.zone.now - 15.seconds,
|
||||
end_at: Time.zone.now,
|
||||
preferences: {
|
||||
|
@ -225,6 +240,7 @@ example data, can be used for demo
|
|||
call_id: rand(999_999_999),
|
||||
comment: '',
|
||||
state: 'hangup',
|
||||
done: true,
|
||||
start_at: Time.zone.now - 15.seconds,
|
||||
end_at: Time.zone.now,
|
||||
preferences: {
|
||||
|
@ -254,6 +270,7 @@ example data, can be used for demo
|
|||
call_id: rand(999_999_999),
|
||||
comment: '',
|
||||
state: 'hangup',
|
||||
done: true,
|
||||
start_at: Time.zone.now - 20.seconds,
|
||||
end_at: Time.zone.now,
|
||||
preferences: {},
|
||||
|
@ -269,7 +286,7 @@ example data, can be used for demo
|
|||
|
||||
=begin
|
||||
|
||||
Cti::Log.log
|
||||
Cti::Log.log(current_user)
|
||||
|
||||
returns
|
||||
|
||||
|
@ -280,8 +297,8 @@ returns
|
|||
|
||||
=end
|
||||
|
||||
def self.log
|
||||
list = Cti::Log.log_records
|
||||
def self.log(current_user)
|
||||
list = Cti::Log.log_records(current_user)
|
||||
|
||||
# add assets
|
||||
assets = list.map(&:preferences)
|
||||
|
@ -299,7 +316,7 @@ returns
|
|||
|
||||
=begin
|
||||
|
||||
Cti::Log.log_records
|
||||
Cti::Log.log_records(current_user)
|
||||
|
||||
returns
|
||||
|
||||
|
@ -307,7 +324,12 @@ returns
|
|||
|
||||
=end
|
||||
|
||||
def self.log_records
|
||||
def self.log_records(current_user)
|
||||
cti_config = Setting.get('cti_config')
|
||||
if cti_config[:notify_map].present?
|
||||
return Cti::Log.where(queue: queues_of_user(current_user, cti_config)).order(created_at: :desc).limit(60)
|
||||
end
|
||||
|
||||
Cti::Log.order(created_at: :desc).limit(60)
|
||||
end
|
||||
|
||||
|
@ -349,9 +371,15 @@ Cti::Log.process(
|
|||
to_comment = queue
|
||||
end
|
||||
from_comment, preferences = CallerId.get_comment_preferences(params['from'], 'from')
|
||||
if queue.blank?
|
||||
queue = params['to']
|
||||
end
|
||||
else
|
||||
from_comment = user
|
||||
to_comment, preferences = CallerId.get_comment_preferences(params['to'], 'to')
|
||||
if queue.blank?
|
||||
queue = params['from']
|
||||
end
|
||||
end
|
||||
|
||||
log = find_by(call_id: call_id)
|
||||
|
@ -363,7 +391,7 @@ Cti::Log.process(
|
|||
end
|
||||
raise "call_id #{call_id} already exists!" if log
|
||||
|
||||
create(
|
||||
log = create(
|
||||
direction: params['direction'],
|
||||
from: params['from'],
|
||||
from_comment: from_comment,
|
||||
|
@ -417,29 +445,12 @@ Cti::Log.process(
|
|||
else
|
||||
raise ArgumentError, "Unknown event #{event.inspect}"
|
||||
end
|
||||
end
|
||||
|
||||
def push_incoming_call
|
||||
return true if destroyed?
|
||||
return true if state != 'newCall'
|
||||
return true if direction != 'in'
|
||||
|
||||
# send notify about event
|
||||
users = User.with_permissions('cti.agent')
|
||||
users.each do |user|
|
||||
Sessions.send_to(
|
||||
user.id,
|
||||
{
|
||||
event: 'cti_event',
|
||||
data: self,
|
||||
},
|
||||
)
|
||||
end
|
||||
true
|
||||
log
|
||||
end
|
||||
|
||||
def self.push_caller_list_update?(record)
|
||||
list_ids = Cti::Log.log_records.pluck(:id)
|
||||
list_ids = Cti::Log.order(created_at: :desc).limit(60).pluck(:id)
|
||||
return true if list_ids.include?(record.id)
|
||||
|
||||
false
|
||||
|
@ -498,5 +509,54 @@ optional you can put the max oldest chat entries as argument
|
|||
parsed = TelephoneNumber.parse(to&.sub(/^\+?/, '+'))
|
||||
parsed.send(parsed.valid? ? :international_number : :original_number)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
returnes queues of user
|
||||
|
||||
['queue1', 'queue2'] = Cti::Log.queues_of_user(User.find(123), config)
|
||||
|
||||
=end
|
||||
|
||||
def self.queues_of_user(user, config)
|
||||
queues = []
|
||||
config[:notify_map]&.each do |row|
|
||||
next if row[:user_ids].blank?
|
||||
next if !row[:user_ids].include?(user.id.to_s) && !row[:user_ids].include?(user.id)
|
||||
|
||||
queues.push row[:queue]
|
||||
end
|
||||
if user.phone.present?
|
||||
caller_ids = Cti::CallerId.extract_numbers(user.phone)
|
||||
queues = queues.concat(caller_ids)
|
||||
end
|
||||
queues
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
return best customer id of caller log
|
||||
|
||||
log = Cti::Log.find(123)
|
||||
customer_id = log.best_customer_id_of_log_entry
|
||||
|
||||
=end
|
||||
|
||||
def best_customer_id_of_log_entry
|
||||
customer_id = nil
|
||||
if preferences[:from].present?
|
||||
preferences[:from].each do |entry|
|
||||
if customer_id.blank?
|
||||
customer_id = entry[:user_id]
|
||||
end
|
||||
next if entry[:level] != 'known'
|
||||
|
||||
customer_id = entry[:user_id]
|
||||
break
|
||||
end
|
||||
end
|
||||
customer_id
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
Zammad::Application.routes.draw do
|
||||
|
||||
match '/api/v1/sipgate/in', to: 'integration/sipgate#in', via: :post
|
||||
match '/api/v1/sipgate/out', to: 'integration/sipgate#out', via: :post
|
||||
match '/api/v1/sipgate/in', to: 'integration/sipgate#event', via: :post
|
||||
match '/api/v1/sipgate/out', to: 'integration/sipgate#event', via: :post
|
||||
|
||||
end
|
||||
|
|
|
@ -249,6 +249,39 @@ RSpec.describe Cti::CallerId do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.known_agents_by_number' do
|
||||
context 'with known agent caller_id' do
|
||||
let!(:agent_user1) { create(:agent_user, phone: '0123456') }
|
||||
let!(:agent_user2) { create(:agent_user, phone: '0123457') }
|
||||
|
||||
it 'gives matching agents' do
|
||||
expect(described_class.known_agents_by_number('49123456'))
|
||||
.to match_array([agent_user1])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with known customer caller_id' do
|
||||
let!(:customer_user1) { create(:customer_user, phone: '0123456') }
|
||||
|
||||
it 'returns an empty array' do
|
||||
expect(described_class.known_agents_by_number('49123456')).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with maybe caller_id' do
|
||||
let(:ticket1) do
|
||||
create(:ticket_article, created_by_id: customer_user2.id, body: 'some text 0123457') # create ticket
|
||||
Observer::Transaction.commit
|
||||
Scheduler.worker(true)
|
||||
end
|
||||
let!(:customer_user2) { create(:customer_user) }
|
||||
|
||||
it 'returns an empty array' do
|
||||
expect(described_class.known_agents_by_number('49123457').count).to eq(0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'callbacks' do
|
||||
subject!(:caller_id) { build(:cti_caller_id, caller_id: phone) }
|
||||
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Cti::Log do
|
||||
subject(:log) { create(:'cti/log') }
|
||||
subject(:user) { create(:user, roles: Role.where(name: 'Agent'), phone: phone) }
|
||||
|
||||
let(:phone) { '' }
|
||||
let(:log) { create(:'cti/log') }
|
||||
|
||||
describe '.log' do
|
||||
it 'returns a hash with :list and :assets keys' do
|
||||
expect(described_class.log).to be_a(Hash).and include(:list, :assets)
|
||||
expect(described_class.log(user)).to match(hash_including(:list, :assets))
|
||||
end
|
||||
|
||||
context 'when over 60 Log records exist' do
|
||||
|
@ -17,7 +20,7 @@ RSpec.describe Cti::Log do
|
|||
end
|
||||
|
||||
it 'returns the 60 latest ones in the :list key' do
|
||||
expect(described_class.log[:list]).to match_array(cti_logs.last(60))
|
||||
expect(described_class.log(user)[:list]).to match_array(cti_logs.last(60))
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -25,10 +28,52 @@ RSpec.describe Cti::Log do
|
|||
subject!(:cti_log) { create(:'cti/log', preferences: { from: [caller_id] }) }
|
||||
|
||||
let(:caller_id) { create(:caller_id) }
|
||||
let(:user) { User.find_by(id: caller_id.user_id) }
|
||||
let(:caller_user) { User.find_by(id: caller_id.user_id) }
|
||||
|
||||
it 'returns a hash of the CallerId Users and their assets in the :assets key' do
|
||||
expect(described_class.log[:assets]).to eq(user.assets({}))
|
||||
expect(described_class.log(user)[:assets]).to eq(caller_user.assets({}))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a notify map is defined' do
|
||||
subject!(:cti_logs) do
|
||||
[create(:'cti/log', queue: 'queue0'),
|
||||
create(:'cti/log', queue: 'queue2'),
|
||||
create(:'cti/log', queue: 'queue3'),
|
||||
create(:'cti/log', queue: 'queue4')]
|
||||
end
|
||||
|
||||
before do
|
||||
cti_config = Setting.get('cti_config')
|
||||
cti_config[:notify_map] = [ { queue: 'queue4', user_ids: [user.id.to_s] } ]
|
||||
Setting.set('cti_config', cti_config)
|
||||
end
|
||||
|
||||
it 'returns one matching log record' do
|
||||
|
||||
expect(described_class.log(user)[:list]).to match_array([cti_logs[3]])
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '.push_caller_list_update?' do
|
||||
let!(:existing_logs) { create_list(:'cti/log', 60) }
|
||||
let(:log) { create(:'cti/log') }
|
||||
|
||||
context 'when given log is older than existing logs' do
|
||||
before { travel(-10.seconds) }
|
||||
|
||||
it 'return false' do
|
||||
expect(described_class.push_caller_list_update?(log)).to eq false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when given log is newer than existing logs' do
|
||||
before { travel(10.seconds) }
|
||||
|
||||
it 'return true' do
|
||||
expect(described_class.push_caller_list_update?(log)).to eq true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -52,12 +97,25 @@ RSpec.describe Cti::Log do
|
|||
let(:event) { 'newCall' }
|
||||
|
||||
context 'with unrecognized "call_id"' do
|
||||
it 'creates a new Log record (#state: "newCall", #done: false)' do
|
||||
it 'creates a new Log record' do
|
||||
expect { described_class.process(attributes) }
|
||||
.to change(described_class, :count).by(1)
|
||||
|
||||
expect(described_class.last.attributes)
|
||||
.to include('state' => 'newCall', 'done' => false)
|
||||
.to include(
|
||||
'call_id' => '1',
|
||||
'state' => 'newCall',
|
||||
'done' => false,
|
||||
'queue' => '49123457',
|
||||
'from' => '49123456',
|
||||
'from_comment' => nil,
|
||||
'from_pretty' => '49123456',
|
||||
'start_at' => nil,
|
||||
'end_at' => nil,
|
||||
'to' => '49123457',
|
||||
'to_comment' => 'user 1',
|
||||
'to_pretty' => '49123457'
|
||||
)
|
||||
end
|
||||
|
||||
context 'for direction "in", with a CallerId record matching the "from" number' do
|
||||
|
@ -178,6 +236,93 @@ RSpec.describe Cti::Log do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for preferences.from verification' do
|
||||
subject(:log) do
|
||||
described_class.process(attributes)
|
||||
end
|
||||
|
||||
let(:customer_user_of_ticket) { create(:customer_user) }
|
||||
let(:ticket_sample) do
|
||||
create(:ticket_article, created_by_id: customer_user_of_ticket.id, body: 'some text 0123457')
|
||||
Observer::Transaction.commit
|
||||
Scheduler.worker(true)
|
||||
end
|
||||
let(:caller_id) { '0123456' }
|
||||
let(:attributes) do
|
||||
{
|
||||
'cause' => '',
|
||||
'event' => 'newCall',
|
||||
'user' => 'user 1',
|
||||
'from' => caller_id,
|
||||
'to' => '49123450',
|
||||
'call_id' => '1',
|
||||
'direction' => 'in',
|
||||
}
|
||||
end
|
||||
|
||||
context 'with now related customer' do
|
||||
it 'gives no caller information' do
|
||||
expect(log.preferences[:from]).to eq(nil)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with related known customer' do
|
||||
let!(:customer_user) { create(:customer_user, phone: '0123456') }
|
||||
|
||||
it 'gives caller information' do
|
||||
expect(log.preferences[:from].count).to eq(1)
|
||||
expect(log.preferences[:from].first)
|
||||
.to include(
|
||||
'level' => 'known',
|
||||
'user_id' => customer_user.id,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with related known customers' do
|
||||
let!(:customer_user1) { create(:customer_user, phone: '0123456') }
|
||||
let!(:customer_user2) { create(:customer_user, phone: '0123456') }
|
||||
|
||||
it 'gives caller information' do
|
||||
expect(log.preferences[:from].count).to eq(2)
|
||||
expect(log.preferences[:from].first)
|
||||
.to include(
|
||||
'level' => 'known',
|
||||
'user_id' => customer_user2.id,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with related maybe customer' do
|
||||
let(:caller_id) { '0123457' }
|
||||
let!(:ticket) { ticket_sample }
|
||||
|
||||
it 'gives caller information' do
|
||||
expect(log.preferences[:from].count).to eq(1)
|
||||
expect(log.preferences[:from].first)
|
||||
.to include(
|
||||
'level' => 'maybe',
|
||||
'user_id' => customer_user_of_ticket.id,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with related maybe and known customer' do
|
||||
let(:caller_id) { '0123457' }
|
||||
let!(:customer) { create(:customer_user, phone: '0123457') }
|
||||
let!(:ticket) { ticket_sample }
|
||||
|
||||
it 'gives caller information' do
|
||||
expect(log.preferences[:from].count).to eq(1)
|
||||
expect(log.preferences[:from].first)
|
||||
.to include(
|
||||
'level' => 'known',
|
||||
'user_id' => customer.id,
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Callbacks -' do
|
||||
|
@ -280,6 +425,71 @@ RSpec.describe Cti::Log do
|
|||
end
|
||||
end
|
||||
|
||||
describe '.queues_of_user' do
|
||||
context 'without notify_map and no own phone number' do
|
||||
it 'gives an empty array' do
|
||||
expect(described_class.queues_of_user(user, Setting.get('cti_config'))).to eq([])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with notify_map and no own phone number' do
|
||||
before do
|
||||
cti_config = Setting.get('cti_config')
|
||||
cti_config[:notify_map] = [ { queue: 'queue4', user_ids: [user.id.to_s] } ]
|
||||
Setting.set('cti_config', cti_config)
|
||||
end
|
||||
|
||||
it 'gives an array with queue' do
|
||||
expect(described_class.queues_of_user(user, Setting.get('cti_config'))).to eq(['queue4'])
|
||||
end
|
||||
end
|
||||
|
||||
context 'with notify_map and with own phone number' do
|
||||
let(:phone) { '012345678' }
|
||||
|
||||
before do
|
||||
cti_config = Setting.get('cti_config')
|
||||
cti_config[:notify_map] = [ { queue: 'queue4', user_ids: [user.id.to_s] } ]
|
||||
Setting.set('cti_config', cti_config)
|
||||
end
|
||||
|
||||
it 'gives an array with queue and phone number' do
|
||||
expect(described_class.queues_of_user(user, Setting.get('cti_config'))).to eq(%w[queue4 4912345678])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#best_customer_id_of_log_entry' do
|
||||
subject(:log1) do
|
||||
described_class.process(
|
||||
'event' => 'newCall',
|
||||
'user' => 'user 1',
|
||||
'from' => '01234599',
|
||||
'to' => '49123450',
|
||||
'call_id' => '1',
|
||||
'direction' => 'in',
|
||||
)
|
||||
end
|
||||
|
||||
let!(:agent1) { create(:agent_user, phone: '01234599') }
|
||||
let!(:customer2) { create(:customer_user, phone: '') }
|
||||
let!(:ticket_article1) { create(:ticket_article, created_by_id: customer2.id, body: 'some text 01234599') }
|
||||
|
||||
context 'with agent1 (known), customer1 (known) and customer2 (maybe)' do
|
||||
let!(:customer1) { create(:customer_user, phone: '01234599') }
|
||||
|
||||
it 'gives customer1' do
|
||||
expect(log1.best_customer_id_of_log_entry).to eq(customer1.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with agent1 (known) and customer2 (maybe)' do
|
||||
it 'gives customer2' do
|
||||
expect(log1.best_customer_id_of_log_entry).to eq(agent1.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_json' do
|
||||
it 'includes virtual attributes' do
|
||||
expect(log.as_json).to include('from_pretty', 'to_pretty')
|
||||
|
|
|
@ -61,10 +61,6 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
note: 'some note',
|
||||
}
|
||||
],
|
||||
notify_user_ids: {
|
||||
2 => true,
|
||||
4 => false,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -72,29 +68,47 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
end
|
||||
|
||||
describe 'request handling' do
|
||||
let!(:token) { Setting.get('cti_token') }
|
||||
|
||||
it 'does token check' do
|
||||
params = 'event=newCall&direction=in&from=4912347114711&to=4930600000000&call_id=4991155921769858278-1&user%5B%5D=user+1&user%5B%5D=user+2'
|
||||
post '/api/v1/cti/not_existing_token', params: params
|
||||
post '/api/v1/cti/not_existing_token', params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
from: '4912347114711',
|
||||
to: '4930600000000',
|
||||
call_id: '4991155921769858278-1',
|
||||
user: 'user 1',
|
||||
}
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
expect(json_response).to be_a_kind_of(Hash)
|
||||
expect(json_response['error']).to eq('Invalid token, please contact your admin!')
|
||||
end
|
||||
|
||||
it 'does basic call' do
|
||||
token = Setting.get('cti_token')
|
||||
|
||||
# inbound - I
|
||||
params = 'event=newCall&direction=in&from=4912347114711&to=4930600000000&call_id=4991155921769858278-1&user%5B%5D=user+1&user%5B%5D=user+2'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
from: '4912347114711',
|
||||
to: '4930600000000',
|
||||
call_id: '4991155921769858278-1',
|
||||
user: ['user+1', 'user+2'],
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
expect(json_response).to be_a_kind_of(Hash)
|
||||
expect(json_response).to be_blank
|
||||
|
||||
# inbound - II - block caller
|
||||
params = 'event=newCall&direction=in&from=491715000000&to=4930600000000&call_id=4991155921769858278-2&user%5B%5D=user+1&user%5B%5D=user+2'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
from: '491715000000',
|
||||
to: '4930600000000',
|
||||
call_id: '4991155921769858278-2',
|
||||
user: ['user+1', 'user+2'],
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
expect(json_response).to be_a_kind_of(Hash)
|
||||
|
@ -102,8 +116,14 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(json_response['reason']).to eq('busy')
|
||||
|
||||
# outbound - I - set default_caller_id
|
||||
params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&call_id=8621106404543334274-3&user%5B%5D=user+1'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'out',
|
||||
from: '4930600000000',
|
||||
to: '4912347114711',
|
||||
call_id: '8621106404543334274-3',
|
||||
user: 'user 1',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response).to be_a_kind_of(Hash)
|
||||
expect(json_response['action']).to eq('dial')
|
||||
|
@ -111,8 +131,14 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(json_response['caller_id']).to eq('4930777000000')
|
||||
|
||||
# outbound - II - set caller_id based on routing_table by explicite number
|
||||
params = 'event=newCall&direction=out&from=4930600000000&to=491714000000&call_id=8621106404543334274-4&user%5B%5D=user+1'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'out',
|
||||
from: '4930600000000',
|
||||
to: '491714000000',
|
||||
call_id: '8621106404543334274-4',
|
||||
user: 'user 1',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response).to be_a_kind_of(Hash)
|
||||
expect(json_response['action']).to eq('dial')
|
||||
|
@ -120,8 +146,14 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(json_response['caller_id']).to eq('41715880339000')
|
||||
|
||||
# outbound - III - set caller_id based on routing_table by 41*
|
||||
params = 'event=newCall&direction=out&from=4930600000000&to=4147110000000&call_id=8621106404543334274-5&user%5B%5D=user+1'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'out',
|
||||
from: '4930600000000',
|
||||
to: '4147110000000',
|
||||
call_id: '8621106404543334274-5',
|
||||
user: 'user 1',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response).to be_a_kind_of(Hash)
|
||||
expect(json_response['action']).to eq('dial')
|
||||
|
@ -130,8 +162,15 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
|
||||
# no config
|
||||
Setting.set('cti_config', {})
|
||||
params = 'event=newCall&direction=in&from=4912347114711&to=4930600000000&call_id=4991155921769858278-6&user%5B%5D=user+1&user%5B%5D=user+2'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
from: '4912347114711',
|
||||
to: '4930600000000',
|
||||
call_id: '4991155921769858278-6',
|
||||
user: ['user+1', 'user+2'],
|
||||
|
||||
}
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(json_response).to be_a_kind_of(Hash)
|
||||
expect(json_response['error']).to eq('Feature not configured, please contact your admin!')
|
||||
|
@ -139,11 +178,16 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
end
|
||||
|
||||
it 'does log call' do
|
||||
token = Setting.get('cti_token')
|
||||
|
||||
# outbound - I - new call
|
||||
params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&call_id=1234567890-1&user%5B%5D=user+1'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'out',
|
||||
from: '4930600000000',
|
||||
to: '4912347114711',
|
||||
call_id: '1234567890-1',
|
||||
user: 'user 1',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-1')
|
||||
expect(log).to be_truthy
|
||||
|
@ -153,7 +197,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.from_comment).to eq('user 1')
|
||||
expect(log.to_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to be_nil
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930777000000')
|
||||
expect(log.state).to eq('newCall')
|
||||
expect(log.done).to eq(true)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -165,8 +209,12 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 2.seconds
|
||||
|
||||
# outbound - I - hangup by agent
|
||||
params = 'event=hangup&direction=out&call_id=1234567890-1&cause=cancel'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'hangup',
|
||||
direction: 'out',
|
||||
call_id: '1234567890-1',
|
||||
cause: 'cancel',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-1')
|
||||
expect(log).to be_truthy
|
||||
|
@ -176,7 +224,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.from_comment).to eq('user 1')
|
||||
expect(log.to_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to eq('cancel')
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930777000000')
|
||||
expect(log.state).to eq('hangup')
|
||||
expect(log.done).to eq(true)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -186,8 +234,14 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.duration_talking_time).to be_nil
|
||||
|
||||
# outbound - II - new call
|
||||
params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&call_id=1234567890-2&user%5B%5D=user+1'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'out',
|
||||
from: '4930600000000',
|
||||
to: '4912347114711',
|
||||
call_id: '1234567890-2',
|
||||
user: ['user 1'],
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-2')
|
||||
expect(log).to be_truthy
|
||||
|
@ -197,7 +251,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.from_comment).to eq('user 1')
|
||||
expect(log.to_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to be_nil
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930777000000')
|
||||
expect(log.state).to eq('newCall')
|
||||
expect(log.done).to eq(true)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -209,8 +263,13 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 2.seconds
|
||||
|
||||
# outbound - II - answer by customer
|
||||
params = 'event=answer&direction=out&call_id=1234567890-2&from=4930600000000&to=4912347114711'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'answer',
|
||||
direction: 'out',
|
||||
call_id: '1234567890-2',
|
||||
from: '4930600000000',
|
||||
to: '4912347114711',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-2')
|
||||
expect(log).to be_truthy
|
||||
|
@ -220,7 +279,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.from_comment).to eq('user 1')
|
||||
expect(log.to_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to be_nil
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930777000000')
|
||||
expect(log.state).to eq('answer')
|
||||
expect(log.done).to eq(true)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -232,8 +291,14 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 2.seconds
|
||||
|
||||
# outbound - II - hangup by customer
|
||||
params = 'event=hangup&direction=out&call_id=1234567890-2&cause=normalClearing&from=4930600000000&to=4912347114711'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'hangup',
|
||||
direction: 'out',
|
||||
call_id: '1234567890-2',
|
||||
cause: 'normalClearing',
|
||||
from: '4930600000000',
|
||||
to: '4912347114711',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-2')
|
||||
expect(log).to be_truthy
|
||||
|
@ -243,7 +308,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.from_comment).to eq('user 1')
|
||||
expect(log.to_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to eq('normalClearing')
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930777000000')
|
||||
expect(log.state).to eq('hangup')
|
||||
expect(log.done).to eq(true)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -255,8 +320,14 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 1.second
|
||||
|
||||
# inbound - I - new call
|
||||
params = 'event=newCall&direction=in&to=4930600000000&from=4912347114711&call_id=1234567890-3&user%5B%5D=user+1'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
to: '4930600000000',
|
||||
from: '4912347114711',
|
||||
call_id: '1234567890-3',
|
||||
user: 'user 1',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-3')
|
||||
expect(log).to be_truthy
|
||||
|
@ -266,7 +337,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.to_comment).to eq('user 1')
|
||||
expect(log.from_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to be_nil
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930600000000')
|
||||
expect(log.state).to eq('newCall')
|
||||
expect(log.done).to eq(false)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -278,8 +349,13 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 1.second
|
||||
|
||||
# inbound - I - answer by customer
|
||||
params = 'event=answer&direction=in&call_id=1234567890-3&to=4930600000000&from=4912347114711'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'answer',
|
||||
direction: 'in',
|
||||
call_id: '1234567890-3',
|
||||
to: '4930600000000',
|
||||
from: '4912347114711',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-3')
|
||||
expect(log).to be_truthy
|
||||
|
@ -289,7 +365,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.to_comment).to eq('user 1')
|
||||
expect(log.from_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to be_nil
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930600000000')
|
||||
expect(log.state).to eq('answer')
|
||||
expect(log.done).to eq(true)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -301,8 +377,14 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 1.second
|
||||
|
||||
# inbound - I - hangup by customer
|
||||
params = 'event=hangup&direction=in&call_id=1234567890-3&cause=normalClearing&to=4930600000000&from=4912347114711'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'hangup',
|
||||
direction: 'in',
|
||||
call_id: '1234567890-3',
|
||||
cause: 'normalClearing',
|
||||
to: '4930600000000',
|
||||
from: '4912347114711',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-3')
|
||||
expect(log).to be_truthy
|
||||
|
@ -312,7 +394,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.to_comment).to eq('user 1')
|
||||
expect(log.from_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to eq('normalClearing')
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930600000000')
|
||||
expect(log.state).to eq('hangup')
|
||||
expect(log.done).to eq(true)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -324,18 +406,24 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 1.second
|
||||
|
||||
# inbound - II - new call
|
||||
params = 'event=newCall&direction=in&to=4930600000000&from=4912347114711&call_id=1234567890-4&user%5B%5D=user+1,user+2'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
to: '4930600000000',
|
||||
from: '4912347114711',
|
||||
call_id: '1234567890-4',
|
||||
user: ['user 1', 'user 2'],
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-4')
|
||||
expect(log).to be_truthy
|
||||
expect(log.to).to eq('4930600000000')
|
||||
expect(log.from).to eq('4912347114711')
|
||||
expect(log.direction).to eq('in')
|
||||
expect(log.to_comment).to eq('user 1,user 2')
|
||||
expect(log.to_comment).to eq('user 1, user 2')
|
||||
expect(log.from_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to be_nil
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930600000000')
|
||||
expect(log.state).to eq('newCall')
|
||||
expect(log.done).to eq(false)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -347,8 +435,14 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 1.second
|
||||
|
||||
# inbound - II - answer by voicemail
|
||||
params = 'event=answer&direction=in&call_id=1234567890-4&to=4930600000000&from=4912347114711&user=voicemail'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'answer',
|
||||
direction: 'in',
|
||||
call_id: '1234567890-4',
|
||||
to: '4930600000000',
|
||||
from: '4912347114711',
|
||||
user: 'voicemail',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-4')
|
||||
expect(log).to be_truthy
|
||||
|
@ -358,7 +452,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.to_comment).to eq('voicemail')
|
||||
expect(log.from_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to be_nil
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930600000000')
|
||||
expect(log.state).to eq('answer')
|
||||
expect(log.done).to eq(true)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -370,8 +464,14 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 1.second
|
||||
|
||||
# inbound - II - hangup by customer
|
||||
params = 'event=hangup&direction=in&call_id=1234567890-4&cause=normalClearing&to=4930600000000&from=4912347114711'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'hangup',
|
||||
direction: 'in',
|
||||
call_id: '1234567890-4',
|
||||
cause: 'normalClearing',
|
||||
to: '4930600000000',
|
||||
from: '4912347114711',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-4')
|
||||
expect(log).to be_truthy
|
||||
|
@ -381,7 +481,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.to_comment).to eq('voicemail')
|
||||
expect(log.from_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to eq('normalClearing')
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930600000000')
|
||||
expect(log.state).to eq('hangup')
|
||||
expect(log.done).to eq(false)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -393,8 +493,14 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 1.second
|
||||
|
||||
# inbound - III - new call
|
||||
params = 'event=newCall&direction=in&to=4930600000000&from=4912347114711&call_id=1234567890-5&user%5B%5D=user+1,user+2'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
to: '4930600000000',
|
||||
from: '4912347114711',
|
||||
call_id: '1234567890-5',
|
||||
user: 'user 1,user 2',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-5')
|
||||
expect(log).to be_truthy
|
||||
|
@ -404,7 +510,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.to_comment).to eq('user 1,user 2')
|
||||
expect(log.from_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to be_nil
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930600000000')
|
||||
expect(log.state).to eq('newCall')
|
||||
expect(log.done).to eq(false)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -416,8 +522,14 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 1.second
|
||||
|
||||
# inbound - III - hangup by customer
|
||||
params = 'event=hangup&direction=in&call_id=1234567890-5&cause=normalClearing&to=4930600000000&from=4912347114711'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'hangup',
|
||||
direction: 'in',
|
||||
call_id: '1234567890-5',
|
||||
cause: 'normalClearing',
|
||||
to: '4930600000000',
|
||||
from: '4912347114711',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-5')
|
||||
expect(log).to be_truthy
|
||||
|
@ -427,7 +539,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.to_comment).to eq('user 1,user 2')
|
||||
expect(log.from_comment).to eq('CallerId Customer1')
|
||||
expect(log.comment).to eq('normalClearing')
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930600000000')
|
||||
expect(log.state).to eq('hangup')
|
||||
expect(log.done).to eq(false)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -439,8 +551,14 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 1.second
|
||||
|
||||
# inbound - IV - new call
|
||||
params = 'event=newCall&direction=in&to=4930600000000&from=49999992222222&call_id=1234567890-6&user%5B%5D=user+1,user+2'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
to: '4930600000000',
|
||||
from: '49999992222222',
|
||||
call_id: '1234567890-6',
|
||||
user: 'user 1,user 2',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-6')
|
||||
expect(log).to be_truthy
|
||||
|
@ -452,7 +570,7 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.preferences['to']).to be_falsey
|
||||
expect(log.preferences['from']).to be_truthy
|
||||
expect(log.comment).to be_nil
|
||||
expect(log.queue).to be_nil
|
||||
expect(log.queue).to eq('4930600000000')
|
||||
expect(log.state).to eq('newCall')
|
||||
expect(log.done).to eq(false)
|
||||
expect(log.initialized_at).to be_truthy
|
||||
|
@ -464,8 +582,15 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
travel 1.second
|
||||
|
||||
# inbound - IV - new call
|
||||
params = 'event=newCall&direction=in&to=4930600000000&from=anonymous&call_id=1234567890-7&user%5B%5D=user+1,user+2&queue=some_queue_name'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
to: '4930600000000',
|
||||
from: 'anonymous',
|
||||
call_id: '1234567890-7',
|
||||
user: 'user 1,user 2',
|
||||
queue: 'some_queue_name',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-7')
|
||||
expect(log).to be_truthy
|
||||
|
@ -486,10 +611,10 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.duration_waiting_time).to be_nil
|
||||
expect(log.duration_talking_time).to be_nil
|
||||
|
||||
# get caller list
|
||||
get '/api/v1/cti/log'
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
|
||||
# get caller list
|
||||
authenticated_as(agent_user)
|
||||
get '/api/v1/cti/log', as: :json
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
@ -515,12 +640,110 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(json_response['list'][6]['call_id']).to eq('1234567890-1')
|
||||
end
|
||||
|
||||
it 'does log call with notify group with two a log entry' do
|
||||
|
||||
# outbound - I - new call
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'out',
|
||||
from: '4930600000000',
|
||||
to: '4912347114711',
|
||||
call_id: '1234567890-1',
|
||||
user: 'user 1',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
# outbound - II - new call
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'out',
|
||||
from: '4930600000000',
|
||||
to: '4912347114711',
|
||||
call_id: '1234567890-2',
|
||||
user: 'user 1',
|
||||
}
|
||||
|
||||
# inbound - III - new call
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
to: '4930600000000',
|
||||
from: '4912347114711',
|
||||
call_id: '1234567890-5',
|
||||
user: 'user 1,user 2',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
# get caller list (with notify group with 2 log entries)
|
||||
cti_config = Setting.get('cti_config')
|
||||
cti_config[:notify_map] = [{ queue: '4930777000000', user_ids: [agent_user.id.to_s] }]
|
||||
Setting.set('cti_config', cti_config)
|
||||
|
||||
authenticated_as(agent_user)
|
||||
get '/api/v1/cti/log', as: :json
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response.dig('assets', 'User')).not_to be(nil)
|
||||
expect(json_response['list'].map { |x| x['call_id'] }).to match_array(%w[1234567890-1 1234567890-2])
|
||||
end
|
||||
|
||||
it 'does log call with notify group without a log entry' do
|
||||
|
||||
# outbound - I - new call
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'out',
|
||||
from: '4930600000000',
|
||||
to: '4912347114711',
|
||||
call_id: '1234567890-1',
|
||||
user: 'user 1',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
# outbound - II - new call
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'out',
|
||||
from: '4930600000000',
|
||||
to: '4912347114711',
|
||||
call_id: '1234567890-2',
|
||||
user: 'user 1',
|
||||
}
|
||||
|
||||
# inbound - III - new call
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
to: '4930600000000',
|
||||
from: '4912347114711',
|
||||
call_id: '1234567890-5',
|
||||
user: 'user 1,user 2',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
||||
# get caller list (with notify group without a log entry)
|
||||
cti_config = Setting.get('cti_config')
|
||||
cti_config[:notify_map] = [{ queue: '4912347114711', user_ids: [agent_user.to_s] }]
|
||||
Setting.set('cti_config', cti_config)
|
||||
|
||||
authenticated_as(agent_user)
|
||||
get '/api/v1/cti/log', as: :json
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(json_response['list']).to eq([])
|
||||
end
|
||||
|
||||
it 'does queue param tests' do
|
||||
token = Setting.get('cti_token')
|
||||
|
||||
# inbound - queue & user
|
||||
params = 'event=newCall&direction=in&to=4930600000000&from=anonymous&call_id=1234567890-1&user%5B%5D=user+1,user+2&queue=some_queue_name'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
to: '4930600000000',
|
||||
from: 'anonymous',
|
||||
call_id: '1234567890-1',
|
||||
user: 'user 1,user 2',
|
||||
queue: 'some_queue_name',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-1')
|
||||
expect(log).to be_truthy
|
||||
|
@ -542,8 +765,15 @@ RSpec.describe 'Integration CTI', type: :request do
|
|||
expect(log.duration_talking_time).to be_nil
|
||||
|
||||
# inbound - queue & no user
|
||||
params = 'event=newCall&direction=in&to=4930600000000&from=anonymous&call_id=1234567890-2&user%5B%5D=&queue=some_queue_name'
|
||||
post "/api/v1/cti/#{token}", params: params
|
||||
post "/api/v1/cti/#{token}", params: {
|
||||
event: 'newCall',
|
||||
direction: 'in',
|
||||
to: '4930600000000',
|
||||
from: 'anonymous',
|
||||
call_id: '1234567890-2',
|
||||
user: '',
|
||||
queue: 'some_queue_name',
|
||||
}
|
||||
expect(response).to have_http_status(:ok)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-2')
|
||||
expect(log).to be_truthy
|
||||
|
|
|
@ -153,7 +153,7 @@ class IntegrationCtiTest < TestCase
|
|||
end
|
||||
|
||||
# Regression test for #2096
|
||||
def test_inactive_users_displayed_with_strikethrough_in_caller_log
|
||||
def test_inactive_users_displayed_inactive_in_caller_log
|
||||
id = rand(99_999_999)
|
||||
|
||||
@browser = browser_instance
|
||||
|
@ -205,10 +205,10 @@ class IntegrationCtiTest < TestCase
|
|||
# view caller log
|
||||
click(css: 'a[href="#cti"]')
|
||||
|
||||
# assertion: names appear in strikethrough
|
||||
# assertion: names appear in inactive
|
||||
match(
|
||||
css: 'span.is-inactive',
|
||||
value: 'John Doe',
|
||||
css: 'span.avatar--inactive',
|
||||
value: 'JD',
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -279,12 +279,12 @@ class IntegrationCtiTest < TestCase
|
|||
|
||||
# assertions: Caller ID includes user organization
|
||||
match(
|
||||
css: '.js-callerLog tr:first-of-type span.user-popover',
|
||||
css: '.js-callerLog tr:first-of-type div.user-popover',
|
||||
value: 'John Doe (Zammad Foundation)',
|
||||
)
|
||||
|
||||
match(
|
||||
css: '.js-callerLog tr:last-of-type span.user-popover',
|
||||
css: '.js-callerLog tr:last-of-type div.user-popover',
|
||||
value: 'John Doe (Zammad Foundation)',
|
||||
)
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue