Class: Phone::Pbx
- Inherits:
-
Object
- Object
- Phone::Pbx
- Includes:
- Singleton
- Defined in:
- app/services/phone/pbx.rb
Defined Under Namespace
Classes: PbxResponse
Constant Summary collapse
- QUEUES =
{ tech_24x7: 1147, sales: 1142, tech1: 1143, tech2: 1148, operator: 1141, test: 1162, customer_contact: 1200, management: 1204, accounting: 1225, sales_homeowner: 1276, sales_commercial: 1277, sales_bg: 1278, sales_trade: 1279, sales_ecommerce: 1280 }
- STATUSES =
{ dnd: 11, available: 7 }
- UNIFIED_STATUS =
{ available: { pbx: 7, queue: true }, available_non_queue: { pbx: 7, queue: false }, dnd: { pbx: 11, queue: false }, not_working: { pbx: 9, queue: false } }
- SERVER_TIME_FORMAT =
"%Y-%m-%d %H:%M:%S"
Instance Attribute Summary collapse
-
#logger ⇒ Object
readonly
Returns the value of attribute logger.
-
#server_time_zone ⇒ Object
readonly
Returns the value of attribute server_time_zone.
-
#switchvox ⇒ Object
readonly
Returns the value of attribute switchvox.
-
#uri ⇒ Object
readonly
Returns the value of attribute uri.
Instance Method Summary collapse
-
#call_log_search(api_params = {}, &block) ⇒ Object
Call Log search.
-
#convert_to_obj(arg) ⇒ Object
Converts API response hashes to objects with method-style access.
- #current_calls ⇒ Object
-
#employee_places_call(employee, to_number, options = {}) ⇒ Object
This method extracts all the necessary information for an employee to make an outbound call to a number.
- #format_datetime(datetime) ⇒ Object
-
#get_presence_options_list(switchvox_account_id) ⇒ Object
Get list of presence statuses.
-
#get_presence_status(switchvox_account_id) ⇒ Object
Returns the presence status for one employee based on switchvox_account_id, will look like: 13:15:43", :id=>"11", :presence=>"dnd", :message=>nil, :sub_presence=>nil or nil will be returned if nothing was found or on error.
- #get_queue_members_status(queue_ids = nil) ⇒ Object
-
#get_queues_status(queue_ids = nil) ⇒ Object
Calls switchvox api and retrieve a hash of switchvox_account_id with queue_account_id and status in that queue.
-
#initialize(options = {}) ⇒ Pbx
constructor
A new instance of Pbx.
-
#member_queue_log_search(api_params = {}, &block) ⇒ Object
QueueMemberLogs.search.
- #missed_call_search(missed_call_uniqueid) ⇒ Object
- #parse_datetime(datetime_string) ⇒ Object
-
#place_call(from_number, to_number, caller_account_id, options = {}) ⇒ Object
Generic method wrapper to place call through switchvox (click to call style) http://developers.digium.com/switchvox/wiki/index.php/Switchvox.users.call.
- #prepare_json_payload(hash) ⇒ Object
-
#process_request(api_method, api_params, block = nil) ⇒ Object
Wrapper method to call the switchvox api and paginate results, calls block with each page.
- #prune_account_ids(account_ids) ⇒ Object
- #queue_log_search(api_params = {}, &block) ⇒ Object
-
#retrieve_extension_account_id(pbx_extension) ⇒ Object
For a given extension code (e.g 800) retrieves the associated switchvox account id via api call.
-
#retrieve_extension_info(pbx_extension = nil, options = {}) ⇒ Object
Wrapper for switchvox.extensions.search.
- #start_packet_capture(duration) ⇒ Object
- #stringify_values(hash) ⇒ Object
- #switchvox_request(api_method, options = {}, ignore_error_codes = []) ⇒ Object
-
#update_presence_status(account_id, presence_option_id) ⇒ Object
Updates the PBX Presence flag.
-
#update_queue_status(account_id:, log_in_queue:, call_queue_account_ids: []) ⇒ Object
Account id: the switchvox account id of the user log in queue: true to login, false to log out call_queue_account_id: an optional queue account id to sign in/out of, default to all.
-
#update_unified_presence(account_id:, status_id:, log_in_queue: false, call_queue_account_ids: []) ⇒ Object
Synchronized status update using a unified symbol map.
- #valid_sip_account_ids ⇒ Object
Constructor Details
#initialize(options = {}) ⇒ Pbx
Returns a new instance of Pbx.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
# File 'app/services/phone/pbx.rb', line 28 def initialize( = {}) @logger = [:logger] || Rails.logger host = Heatwave::Configuration.fetch(:switchvox, :host) username = Heatwave::Configuration.fetch(:switchvox, :username) password = Heatwave::Configuration.fetch(:switchvox, :password) # Validate credentials are present to fail fast with helpful error if host.blank? raise ArgumentError, 'Switchvox host not configured. Add switchvox.host to Rails credentials.' end if username.blank? || password.blank? raise ArgumentError, 'Switchvox credentials not configured. Add switchvox.username and switchvox.password to Rails credentials.' end @uri = "https://#{host}/json" @domain = "https://#{host}" @client = HTTPClient.new { self.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE } @client.set_auth(@domain, username, password) @server_time_zone = ActiveSupport::TimeZone.new("Central Time (US & Canada)") end |
Instance Attribute Details
#logger ⇒ Object (readonly)
Returns the value of attribute logger.
26 27 28 |
# File 'app/services/phone/pbx.rb', line 26 def logger @logger end |
#server_time_zone ⇒ Object (readonly)
Returns the value of attribute server_time_zone.
26 27 28 |
# File 'app/services/phone/pbx.rb', line 26 def server_time_zone @server_time_zone end |
#switchvox ⇒ Object (readonly)
Returns the value of attribute switchvox.
26 27 28 |
# File 'app/services/phone/pbx.rb', line 26 def switchvox @switchvox end |
#uri ⇒ Object (readonly)
Returns the value of attribute uri.
26 27 28 |
# File 'app/services/phone/pbx.rb', line 26 def uri @uri end |
Instance Method Details
#call_log_search(api_params = {}, &block) ⇒ Object
Call Log search
102 103 104 105 106 107 108 109 110 |
# File 'app/services/phone/pbx.rb', line 102 def call_log_search(api_params = {}, &block) api_params['start_date'] ||= Time.current.beginning_of_day api_params['end_date'] ||= Time.current.end_of_day api_params['start_date'] = format_datetime(api_params['start_date']) api_params['end_date'] = format_datetime(api_params['end_date']) api_params['sort_field'] = 'start_time' api_params['sort_direction'] = 'ASC' process_request "switchvox.callLogs.search", api_params, block end |
#convert_to_obj(arg) ⇒ Object
Converts API response hashes to objects with method-style access.
NOTE: OpenStruct is intentionally kept here because API responses have dynamic
structures that vary by endpoint. Data.define requires known members at compile time.
416 417 418 419 420 421 422 423 424 425 |
# File 'app/services/phone/pbx.rb', line 416 def convert_to_obj(arg) if arg.is_a? Hash arg.each { |k, v| arg[k] = convert_to_obj(v) } OpenStruct.new(arg) elsif arg.is_a? Array arg.map! { |v| convert_to_obj(v) } else arg end end |
#current_calls ⇒ Object
174 175 176 177 178 179 180 181 182 183 |
# File 'app/services/phone/pbx.rb', line 174 def current_calls result = switchvox_request("switchvox.currentCalls.getList", {}) return [] if result.is_a?(PbxResponse) calls = [result.current_calls&.current_call].flatten.compact calls.map do |v| v.start_time = parse_datetime(v.start_time) v.marshal_dump end end |
#employee_places_call(employee, to_number, options = {}) ⇒ Object
This method extracts all the necessary information for an employee to
make an outbound call to a number
90 91 92 93 94 95 96 97 98 99 |
# File 'app/services/phone/pbx.rb', line 90 def employee_places_call(employee, to_number, ={}) caller_account_id = employee.employee_phone_status.switchvox_account_id [:caller_id_name] ||= employee.full_name raise "Employee is not setup with switchvox account id" unless caller_account_id.present? pbx_extension = employee.pbx_extension raise "Employee is not setup with pbx extension" unless pbx_extension.present? pbx = Phone::Pbx.instance pbx.place_call(pbx_extension, to_number, caller_account_id, ) end |
#format_datetime(datetime) ⇒ Object
51 52 53 |
# File 'app/services/phone/pbx.rb', line 51 def format_datetime(datetime) datetime.in_time_zone(server_time_zone).strftime(SERVER_TIME_FORMAT) end |
#get_presence_options_list(switchvox_account_id) ⇒ Object
Get list of presence statuses
327 328 329 330 331 332 333 334 335 336 |
# File 'app/services/phone/pbx.rb', line 327 def (switchvox_account_id) api_params = { account_id: switchvox_account_id }.with_indifferent_access result = switchvox_request("switchvox.users.presence.options.getList", api_params) # Return empty array on error (PbxResponse) or if errors present in API response if result.is_a?(PbxResponse) || result.try(:[], :errors).present? [] else [result..presence_option].flatten.map(&:marshal_dump) end end |
#get_presence_status(switchvox_account_id) ⇒ Object
Returns the presence status for one employee based on switchvox_account_id, will look like:
13:15:43", :id=>"11", :presence=>"dnd", :message=>nil, :sub_presence=>nil
or nil will be returned if nothing was found or on error
198 199 200 201 202 203 204 |
# File 'app/services/phone/pbx.rb', line 198 def get_presence_status(switchvox_account_id) api_params = { account_id: switchvox_account_id }.with_indifferent_access result = switchvox_request("switchvox.users.presence.getInfo", api_params) return nil if result.is_a?(PbxResponse) result.presence end |
#get_queue_members_status(queue_ids = nil) ⇒ Object
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'app/services/phone/pbx.rb', line 236 def get_queue_members_status(queue_ids = nil) results_hsh = {} queues = QUEUES queues = queues.select { |_k, v| queue_ids.include?(v) } if queue_ids.present? queues.each do |queue_name, queue_id| logger.info " Retrieving queue status for queue id: #{queue_id} #{queue_name}" api_params = { account_id: queue_id }.with_indifferent_access result = switchvox_request("switchvox.callQueues.getCurrentStatus", api_params) next if result.is_a?(PbxResponse) if result.call_queue&.queue_members&.queue_member.present? queue_members = result.call_queue.queue_members.queue_member queue_name = result.call_queue.call_queue_name queue_strategy = result.call_queue.strategy results_hsh[queue_id] ||= {} results_hsh[queue_id]['name'] = queue_name results_hsh[queue_id]['strategy'] = queue_strategy queue_members.each do |member| results_hsh[queue_id]['members'] ||= [] results_hsh[queue_id]['members'] << member.fullname end end end results_hsh end |
#get_queues_status(queue_ids = nil) ⇒ Object
Calls switchvox api and retrieve a hash of switchvox_account_id with queue_account_id and status in that queue
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'app/services/phone/pbx.rb', line 207 def get_queues_status(queue_ids = nil) results_hsh = {} queues = QUEUES queues = queues.select { |_k, v| queue_ids.include?(v) } if queue_ids.present? queues.each do |queue_name, queue_id| logger.info " Retrieving queue status for queue id: #{queue_id} #{queue_name}" api_params = { account_id: queue_id }.with_indifferent_access result = switchvox_request("switchvox.callQueues.getCurrentStatus", api_params) next if result.is_a?(PbxResponse) if result.call_queue&.queue_members&.queue_member.present? queue_members = result.call_queue.queue_members.queue_member # queue_name from API response available via result.call_queue.call_queue_name if needed if queue_members.kind_of?(Array) queue_members.each do |member| switchvox_account_id = member.account_id.to_i results_hsh[switchvox_account_id] ||= {} results_hsh[switchvox_account_id][queue_id] = member.logged_in_status end else switchvox_account_id = queue_members.account_id.to_i results_hsh[switchvox_account_id] ||= {} results_hsh[switchvox_account_id][queue_id] = queue_members.logged_in_status end end end results_hsh end |
#member_queue_log_search(api_params = {}, &block) ⇒ Object
QueueMemberLogs.search
137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'app/services/phone/pbx.rb', line 137 def member_queue_log_search(api_params = {}, &block) api_params = api_params.with_indifferent_access api_params['start_date'] ||= Time.current.beginning_of_day api_params['end_date'] ||= Time.current.end_of_day api_params['start_date'] = format_datetime(api_params['start_date']) api_params['end_date'] = format_datetime(api_params['end_date']) api_params['member_account_ids'] ||= valid_sip_account_ids api_params['sort_field'] = 'start_time' api_params['sort_direction'] = 'ASC' api_params['call_types'] = %w(missed_calls completed_calls) process_request "switchvox.callQueueMemberLogs.search", api_params, block end |
#missed_call_search(missed_call_uniqueid) ⇒ Object
112 113 114 115 116 117 118 |
# File 'app/services/phone/pbx.rb', line 112 def missed_call_search(missed_call_uniqueid) api_params = {} api_params['uniqueid'] = missed_call_uniqueid api_params['sort_field'] = 'missed_time' api_params['sort_direction'] = 'ASC' process_request "switchvox.callQueueMissedCalls.getList", api_params end |
#parse_datetime(datetime_string) ⇒ Object
55 56 57 |
# File 'app/services/phone/pbx.rb', line 55 def parse_datetime(datetime_string) server_time_zone.parse(datetime_string) end |
#place_call(from_number, to_number, caller_account_id, options = {}) ⇒ Object
Generic method wrapper to place call through switchvox (click to call style)
http://developers.digium.com/switchvox/wiki/index.php/Switchvox.users.call
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
# File 'app/services/phone/pbx.rb', line 61 def place_call(from_number, to_number, caller_account_id, = {}) caller_id_name = [:caller_id_name] || "WARMLYYOURS" vars = [] vars << "party_id=#{[:party_id]}" if [:party_id].present? vars << "activity_id=#{[:activity_id]}" if [:activity_id].present? if from_number.to_s.size > 3 && (pf = PhoneNumber.parse(from_number)) from_number = pf.dial_string end ignore_user_call_rules = [:ignore_user_call_rules].to_b ? 1 : 0 if to_number.to_s.size > 3 && (pt = PhoneNumber.parse(to_number)) to_number = pt.dial_string end api_params = { caller_id_name: caller_id_name, dial_as_account_id: caller_account_id, dial_first: from_number, dial_second: to_number, timeout: 60, ignore_user_api_settings: 0, ignore_user_call_rules: ignore_user_call_rules, #This might be useful timeout_second_call: 60, auto_answer: 1, variables: vars }.with_indifferent_access switchvox_request("switchvox.call", api_params) end |
#prepare_json_payload(hash) ⇒ Object
427 428 429 430 431 432 |
# File 'app/services/phone/pbx.rb', line 427 def prepare_json_payload(hash) hash_string = stringify_values(hash) json = ActiveSupport::JSON.encode(hash_string) json.gsub!(/^\s{8}/,'') json end |
#process_request(api_method, api_params, block = nil) ⇒ Object
Wrapper method to call the switchvox api and paginate results, calls block with each page.
api_params is a hash, :items_per_page defaults to 100, :page_number defaults to 1
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'app/services/phone/pbx.rb', line 341 def process_request(api_method, api_params, block = nil) logger.debug("Starting switchvox request", api_method: api_method) api_params['items_per_page'] ||= 100 api_params['page_number'] ||= 1 results = [] total_pages = nil total_items = nil loop do logger.debug("Starting switchvox paginated request", api_method: api_method, page: api_params['page_number'], total_pages: total_pages) page_results = switchvox_request(api_method, api_params) break if page_results.is_a?(PbxResponse) # Error occurred break unless page_results&.calls&.call.present? total_pages ||= page_results.calls.total_pages.to_i total_items ||= page_results.calls.total_items.to_i new_results = [page_results.calls.call].flatten if block block.call(new_results) else results += new_results end api_params['page_number'] += 1 end if block return total_items else return results end end |
#prune_account_ids(account_ids) ⇒ Object
191 192 193 |
# File 'app/services/phone/pbx.rb', line 191 def prune_account_ids(account_ids) account_ids & valid_sip_account_ids end |
#queue_log_search(api_params = {}, &block) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'app/services/phone/pbx.rb', line 122 def queue_log_search(api_params = {}, &block) api_params = api_params.with_indifferent_access api_params['start_date'] ||= Time.current.beginning_of_day api_params['end_date'] ||= Time.current.end_of_day api_params['start_date'] = format_datetime(api_params['start_date']) api_params['end_date'] = format_datetime(api_params['end_date']) api_params['queue_account_ids'] = QUEUES.values api_params['call_types'] = %w(completed_calls abandoned_calls redirected_calls) api_params['sort_field'] = 'start_time' api_params['sort_direction'] = 'ASC' process_request "switchvox.callQueueLogs.search", api_params, block end |
#retrieve_extension_account_id(pbx_extension) ⇒ Object
For a given extension code (e.g 800) retrieves the associated switchvox account id
via api call
187 188 189 |
# File 'app/services/phone/pbx.rb', line 187 def retrieve_extension_account_id(pbx_extension) retrieve_extension_info(pbx_extension).account_id.to_i end |
#retrieve_extension_info(pbx_extension = nil, options = {}) ⇒ Object
Wrapper for switchvox.extensions.search
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'app/services/phone/pbx.rb', line 153 def retrieve_extension_info(pbx_extension = nil, = {}) api_params = {}.with_indifferent_access if pbx_extension api_params[:min_extension] = pbx_extension api_params[:max_extension] = pbx_extension end api_params[:min_extension] ||= 800 api_params[:max_extension] ||= 899 api_params[:extension_types] = [:extension_types] || ['sip'] result = switchvox_request("switchvox.extensions.search", api_params) return [] if result.is_a?(PbxResponse) result.extensions&.extension || [] end |
#start_packet_capture(duration) ⇒ Object
286 287 288 289 290 291 292 293 294 295 296 |
# File 'app/services/phone/pbx.rb', line 286 def start_packet_capture(duration) result = switchvox_request("switchvox.debug.pcap.startSession", duration) = "Starting packet capture session with duration #{duration}" if result.try(:success) logger.info return true else logger.error return false end end |
#stringify_values(hash) ⇒ Object
434 435 436 437 438 439 440 441 442 443 444 445 446 |
# File 'app/services/phone/pbx.rb', line 434 def stringify_values(hash) new_hsh = {} hash.each do |key, value| if value.is_a?(Array) new_hsh[key.to_s] = value.map{|v| v.to_s} elsif value.is_a?(Hash) new_hsh[key.to_s] = stringify_values(value) else new_hsh[key.to_s] = value.to_s end end new_hsh end |
#switchvox_request(api_method, options = {}, ignore_error_codes = []) ⇒ Object
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 |
# File 'app/services/phone/pbx.rb', line 372 def switchvox_request(api_method, ={}, ignore_error_codes=[]) logger.debug("[pbx] Initiated", api_method: api_method) body_hsh = { request: { method: api_method, parameters: } } body_json = prepare_json_payload(body_hsh) logger.info "[pbx:#{api_method}] Raw request: #{body_json}" # Have to do it twice to work headers = [['Content-Type','application/json']] #res = @client.get(@uri) # A first call is required to 'login' begin res = @client.post(@uri, body_json, headers) response_body = res.body # For now just log in info, later switch to debug logger.info "[pbx:#{api_method}] Raw response: #{response_body}" parsed_response = Oj.load(response_body) obj = convert_to_obj(parsed_response["response"]) if obj.result return obj.result elsif obj.errors && !(ignore_error_codes.present? && ignore_error_codes.include?(obj.errors.error.code)) logger.error "[pbx:#{api_method}] Error returned #{obj.errors.inspect}" return PbxResponse.new(success: false, errors: obj.errors) else return PbxResponse.new(success: true, errors: nil) end rescue Oj::ParseError, JSON::ParserError => exc logger.error "[pbx:#{api_method}] JSON parse error: #{exc.class} - #{exc.}" return PbxResponse.new(success: false, errors: ["Malformed JSON response. #{exc.class}: #{exc.}"]) rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, SocketError => exc logger.error "[pbx:#{api_method}] Connection error: #{exc.class} - #{exc.}" return PbxResponse.new(success: false, errors: ["PBX connection error. #{exc.class}: #{exc.}"]) rescue Net::ReadTimeout, Net::OpenTimeout, HTTPClient::ConnectTimeoutError, HTTPClient::ReceiveTimeoutError => exc logger.error "[pbx:#{api_method}] Timeout error: #{exc.class} - #{exc.}" return PbxResponse.new(success: false, errors: ["PBX timeout. #{exc.class}: #{exc.}"]) end end |
#update_presence_status(account_id, presence_option_id) ⇒ Object
Updates the PBX Presence flag
271 272 273 274 275 276 277 278 279 280 281 282 283 284 |
# File 'app/services/phone/pbx.rb', line 271 def update_presence_status(account_id, presence_option_id) api_params = { presence_option_id: presence_option_id, account_id: account_id }.with_indifferent_access result = switchvox_request("switchvox.users.presence.update", api_params) = "Update presence status for account_id #{account_id} with api_params #{api_params} returned #{result.inspect}" if result.try(:success) logger.info return true else logger.error return false end end |
#update_queue_status(account_id:, log_in_queue:, call_queue_account_ids: []) ⇒ Object
Account id: the switchvox account id of the user
log in queue: true to login, false to log out
call_queue_account_id: an optional queue account id to sign in/out of, default to all
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 |
# File 'app/services/phone/pbx.rb', line 301 def update_queue_status(account_id:, log_in_queue:, call_queue_account_ids: []) unless call_queue_account_ids.present? logger.error "Update queue status for account_id #{account_id} not possible without call_queue_account_ids specified" return false end cmd = log_in_queue ? 'login' : 'logout' # Log in/out of each queue call_queue_account_ids.each do |call_queue_account_id| api_params = { call_queue_account_id: call_queue_account_id, account_id: account_id }.with_indifferent_access #Ignore error code 78956 which will be returned if user is already logged out result = switchvox_request("switchvox.users.callQueues.#{cmd}", api_params, ['78956']) = "Update queue status for account_id #{account_id} with api_params #{api_params} returned #{result.inspect}" if result.try(:success) logger.info else logger.error end end end |
#update_unified_presence(account_id:, status_id:, log_in_queue: false, call_queue_account_ids: []) ⇒ Object
Synchronized status update using a unified symbol map
263 264 265 266 267 268 |
# File 'app/services/phone/pbx.rb', line 263 def update_unified_presence(account_id:, status_id:, log_in_queue: false, call_queue_account_ids: []) if call_queue_account_ids.present? update_queue_status account_id: account_id, log_in_queue: log_in_queue, call_queue_account_ids: call_queue_account_ids end update_presence_status account_id, status_id end |
#valid_sip_account_ids ⇒ Object
170 171 172 |
# File 'app/services/phone/pbx.rb', line 170 def valid_sip_account_ids retrieve_extension_info.map(&:account_id).map(&:to_i).sort end |