Class: EmployeePhoneStatus
- Inherits:
-
ApplicationRecord
- Object
- ActiveRecord::Base
- ApplicationRecord
- EmployeePhoneStatus
- Includes:
- Models::Auditable
- Defined in:
- app/models/employee_phone_status.rb
Overview
Model to keep track of a user's phone system settings and presence
== Schema Information
Table name: employee_phone_statuses
Database name: primary
id :integer not null, primary key
auto_away_after :time
click_to_call_integration :string(10) default("api")
date_set :datetime
dnd_alert_threshold_minutes :integer
extension :integer
last_alert :datetime
message :string
pbx_integration :integer default("none")
presence :string(20)
queue_statuses :jsonb
status_options :jsonb
sub_presence :string
created_at :datetime
updated_at :datetime
contact_point_id :integer
employee_id :integer
switchvox_account_id :integer
Indexes
by_pres_spres (presence,sub_presence)
employee_phone_statuses_employee_id_idx (employee_id)
idx_eps_contact_point_id (contact_point_id)
idx_eps_switchvox_account_id (switchvox_account_id)
Foreign Keys
fk_rails_... (contact_point_id => contact_points.id)
fk_rails_... (employee_id => parties.id) ON DELETE => cascade
Defined Under Namespace
Classes: StatusAlerter
Constant Summary
Constants included from Models::Auditable
Models::Auditable::ALWAYS_IGNORED
Instance Attribute Summary collapse
- #switchvox_account_id ⇒ Object readonly
Belongs to collapse
Methods included from Models::Auditable
Has many collapse
Class Method Summary collapse
-
.broadcast_agents_status(employee_phone_statuses = nil, _options = {}) ⇒ Object
Retrieve the current database state of agent statuses and broadcast.
-
.dnd_alerts ⇒ ActiveRecord::Relation<EmployeePhoneStatus>
A relation of EmployeePhoneStatuses that are dnd alerts.
- .extension_to_employee_id_hash ⇒ Object
-
.garnish_employees(employees = nil, only_with_phone_record = false) ⇒ Object
Takes an employee relation and filter on it and associate all records for performance in phone operation.
-
.initialize_and_push_presence(employees, presence, sub_presence = nil, options = {}) ⇒ Object
This method will set the status on one or multiple employees then sync statuses to the phone system.
- .migrate_extensions ⇒ Object
-
.pull_presence(employees = nil, _options = {}) ⇒ Object
Pull all current presence statuses from the phone system.
-
.pull_queue_status(_options = {}) ⇒ Object
Retrieves switchvox queue statuses and record them in our database You can force a full pull rather than targetted to only queues retrieved previously by passing ignore_queue_filter: true to the option hash.
-
.push_presence(employees = nil, _options = {}) ⇒ Object
Pushes the presence of an employee to the phone system.
- .refresh_all_status_options ⇒ Object
Instance Method Summary collapse
-
#auto_away_time_check ⇒ Object
Checks if the user should be logged out, if so saves the presence away automatically Then return true, otherwise returns false.
- #broadcast_agent_status ⇒ Object
-
#curated_presence_list ⇒ Object
Retrives a list of status options, business rules dictate we only care about your available options, there's only one away and one dnd.
- #current_status_set_after_auto_away? ⇒ Boolean
- #dnd_alert_threshold_seconds ⇒ Object
- #dnd_presence_alert? ⇒ Boolean
-
#enqueue_crm_navbar_presence_refresh ⇒ Object
Targeted (single-user) refresh of the navbar presence dot.
-
#logged_in_queue? ⇒ Boolean
Is the user logged in queue? we detect in the last retrieved queue status if user is logged in all queue.
- #minutes_in_current_status ⇒ Object
-
#pull_presence(_force = false) ⇒ Object
Pulls the pbx presence and update locally.
-
#push_presence(new_presence = nil, new_sub_presence = nil, options = {}) ⇒ Object
Pushes the local presence settings to the pbx api.
-
#refresh_status_options ⇒ Object
Update presence status list.
-
#should_be_in_queue? ⇒ Boolean
Should be in queue ?.
-
#status_id_for_presence ⇒ Object
Retrieve the presence status id for a given presence and sub status Using the cached status_options previously stored by refresh_status_options WIll attempt to retrieve status options if attribute is blank.
- #sub_presence_options ⇒ Object
-
#time_to_set_away? ⇒ Boolean
Checks if it's time to go on auto away.
-
#valid_presence_list ⇒ Object
Filters out status options.
Methods included from Models::Auditable
#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record
Methods inherited from ApplicationRecord
ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation
Methods included from Models::EventPublishable
Instance Attribute Details
#switchvox_account_id ⇒ Object (readonly)
51 |
# File 'app/models/employee_phone_status.rb', line 51 validates :switchvox_account_id, presence: true, numericality: { greater_than: 0 }, if: :pbx_integration_switchvox? |
Class Method Details
.broadcast_agents_status(employee_phone_statuses = nil, _options = {}) ⇒ Object
Retrieve the current database state of agent statuses and broadcast
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
# File 'app/models/employee_phone_status.rb', line 169 def self.broadcast_agents_status(employee_phone_statuses = nil, = {}) employee_phone_statuses ||= EmployeePhoneStatus.all state_hsh = {} [employee_phone_statuses].flatten.each do |eps| state_hsh[eps.employee_id] = { presence: eps.presence, sub_presence: eps.sub_presence, message: eps., date_set: eps.date_set, timestamp: (eps.date_set.to_f * 1000).to_i # Browser timestamps are in ms since epoch (not seconds) } end # Todo Replace with SSE # ActionCable.server.broadcast('agent_statuses', state_hsh) if state_hsh.present? end |
.dnd_alerts ⇒ ActiveRecord::Relation<EmployeePhoneStatus>
A relation of EmployeePhoneStatuses that are dnd alerts. Active Record Scope
65 66 67 68 69 |
# File 'app/models/employee_phone_status.rb', line 65 scope :dnd_alerts, lambda { where.not(dnd_alert_threshold_minutes: nil, date_set: nil) .where(presence: 'dnd') .where("date_set + dnd_alert_threshold_minutes * INTERVAL '1 minutes' > ?", Time.current) } |
.extension_to_employee_id_hash ⇒ Object
143 144 145 146 147 148 149 |
# File 'app/models/employee_phone_status.rb', line 143 def self.extension_to_employee_id_hash Rails.cache.fetch(:pbx_extension_to_employee_id_index, expires_in: 1.day) do Employee.active_employees.includes(:employee_phone_status, :contact_points) .reject { |emp| emp.pbx_extension.nil? } .each_with_object({}) { |emp, hsh| hsh[emp.pbx_extension] = emp.id } end end |
.garnish_employees(employees = nil, only_with_phone_record = false) ⇒ Object
Takes an employee relation and filter on it and associate all records
for performance in phone operation
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'app/models/employee_phone_status.rb', line 79 def self.garnish_employees(employees = nil, only_with_phone_record = false) # Handle single id employees = [employees] if employees.is_a? Integer # Handle array of ids employees = Employee.where(id: employees) if employees && employees.try(:[], 0).try(:is_a?, Integer) # Defaults to all employees ||= Employee.all # Includes common records and filter by active and phone enabled employees employees = employees.active_employees.phone_enabled.joins(:employee_record).includes(:employee_record, :employee_phone_status) # Enforce presence of employee phone status if only with phone record option specified employees = employees.joins(:employee_phone_status) if only_with_phone_record employees end |
.initialize_and_push_presence(employees, presence, sub_presence = nil, options = {}) ⇒ Object
This method will set the status on one or multiple employees then sync statuses to the phone system
125 126 127 128 129 130 131 |
# File 'app/models/employee_phone_status.rb', line 125 def self.initialize_and_push_presence(employees, presence, sub_presence = nil, = {}) garnish_employees(employees, false).flatten.each do |employee| # Find out the status id we should be using eps = employee.employee_phone_status || employee.build_employee_phone_status eps.push_presence presence, sub_presence, end end |
.migrate_extensions ⇒ Object
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'app/models/employee_phone_status.rb', line 96 def self.migrate_extensions Employee.all.each do |employee| next unless cp = employee.contact_points.where("detail ILIKE '+1847550%'").first ext = "8#{cp.detail.last(2)}" puts "Employee id #{employee.id} #{employee.full_name} Ext: #{ext}" if eps = employee.employee_phone_status eps.update_attribute(:extension, ext) end # Find 800 # if cp800 = employee.contact_points.where("detail ILIKE '+18008755285'").first cp800.detail = cp800.detail + " x#{ext}" cp800.save end end end |
.pull_presence(employees = nil, _options = {}) ⇒ Object
Pull all current presence statuses from the phone system
134 135 136 137 138 139 140 141 |
# File 'app/models/employee_phone_status.rb', line 134 def self.pull_presence(employees = nil, = {}) result = {} garnish_employees(employees).each do |employee| employee_phone_status = employee.employee_phone_status || employee.build_employee_phone_status result[employee.id] = employee_phone_status.pull_presence end result end |
.pull_queue_status(_options = {}) ⇒ Object
Retrieves switchvox queue statuses and record them in our database
You can force a full pull rather than targetted to only queues
retrieved previously by passing ignore_queue_filter: true to the option hash
188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'app/models/employee_phone_status.rb', line 188 def self.pull_queue_status( = {}) queue_entries = [] # Hash keys are the employee switchvox account id queue_status_hsh = Phone::Pbx.instance.get_queues_status updated_employee_phone_statuses_ids = [] # Loop through all accounts and queue statuses queue_status_hsh.each do |switchvox_account_id, queue_statuses| next unless employee_phone_status = EmployeePhoneStatus.where(switchvox_account_id: switchvox_account_id).first # Store the raw results from the api employee_phone_status.queue_statuses = queue_statuses # Save which will store a new version if in queue changed employee_phone_status.save # Mark this eps as good updated_employee_phone_statuses_ids << employee_phone_status.id end # Now any employee phone status without an entry get cleared invalid_eps = EmployeePhoneStatus.all invalid_eps = invalid_eps.where.not(id: updated_employee_phone_statuses_ids) if updated_employee_phone_statuses_ids.present? invalid_eps.update_all(queue_statuses: {}) queue_entries end |
.push_presence(employees = nil, _options = {}) ⇒ Object
Pushes the presence of an employee to the phone system
114 115 116 117 118 119 120 121 122 |
# File 'app/models/employee_phone_status.rb', line 114 def self.push_presence(employees = nil, = {}) employees = garnish_employees(employees, true) employees.each do |employee| employee.employee_phone_status.push_presence end # Retrieve call status broadcast_agents_status employees.map(&:employee_phone_status) end |
.refresh_all_status_options ⇒ Object
320 321 322 |
# File 'app/models/employee_phone_status.rb', line 320 def self. all.each(&:refresh_status_options) end |
Instance Method Details
#auto_away_time_check ⇒ Object
Checks if the user should be logged out, if so saves the presence away automatically
Then return true, otherwise returns false
290 291 292 293 294 295 296 297 298 299 300 301 302 |
# File 'app/models/employee_phone_status.rb', line 290 def auto_away_time_check if time_to_set_away? and presence != 'away' and !current_status_set_after_auto_away? logger.info "[EmployeePhoneStatus:auto_away_time_check] Time check on employee id #{employee_id}, auto away at #{auto_away_after}, current presence: #{presence}. Changing status to away" self.date_set = Time.current self.presence = 'away' self.sub_presence = nil save true else logger.info "[EmployeePhoneStatus:auto_away_time_check] Time check on employee id #{employee_id}, auto away at #{auto_away_after}, current status #{presence} set at #{date_set}. Skipping" false end end |
#broadcast_agent_status ⇒ Object
151 152 153 |
# File 'app/models/employee_phone_status.rb', line 151 def broadcast_agent_status self.class.broadcast_agents_status(self) end |
#contact_point ⇒ ContactPoint
45 |
# File 'app/models/employee_phone_status.rb', line 45 belongs_to :contact_point, optional: true |
#curated_presence_list ⇒ Object
Retrives a list of status options, business rules dictate we only care
about your available options, there's only one away and one dnd
352 353 354 355 356 357 358 359 360 361 362 |
# File 'app/models/employee_phone_status.rb', line 352 def curated_presence_list # Retrieve 'available' statuses followed by away and dnd status valid_presence_list.map do |opt| { active: (opt['active'] == '1'), id: opt['id'].to_i, sub_presence: opt['sub_presence'].presence, presence: opt['presence'] } end end |
#current_status_set_after_auto_away? ⇒ Boolean
284 285 286 |
# File 'app/models/employee_phone_status.rb', line 284 def current_status_set_after_auto_away? date_set.today? && date_set.to_time_of_day > auto_away_after end |
#dnd_alert_threshold_seconds ⇒ Object
270 271 272 |
# File 'app/models/employee_phone_status.rb', line 270 def dnd_alert_threshold_seconds dnd_alert_threshold_minutes * 60 if dnd_alert_threshold_minutes end |
#dnd_presence_alert? ⇒ Boolean
274 275 276 |
# File 'app/models/employee_phone_status.rb', line 274 def dnd_presence_alert? minutes_in_current_status && dnd_alert_threshold_minutes && minutes_in_current_status >= dnd_alert_threshold_minutes end |
#employee ⇒ Employee
44 |
# File 'app/models/employee_phone_status.rb', line 44 belongs_to :employee, inverse_of: :employee_phone_status |
#employee_phone_status_changes ⇒ ActiveRecord::Relation<EmployeePhoneStatusChange>
47 |
# File 'app/models/employee_phone_status.rb', line 47 has_many :employee_phone_status_changes |
#enqueue_crm_navbar_presence_refresh ⇒ Object
Targeted (single-user) refresh of the navbar presence dot. Only enqueues
when something the dot reflects actually changed (presence, sub_presence,
or message). Coalescing happens inside CrmNavbarRefreshWorker.
158 159 160 161 162 163 164 165 166 |
# File 'app/models/employee_phone_status.rb', line 158 def return if employee_id.blank? return unless previously_new_record? || saved_change_to_presence? || saved_change_to_sub_presence? || CrmNavbarRefreshWorker.schedule(user_id: employee_id, badge: :presence_dot) end |
#logged_in_queue? ⇒ Boolean
Is the user logged in queue? we detect in the last retrieved queue status
if user is logged in all queue. It's an all or nothing, logged in all return true
otherwise false
316 317 318 |
# File 'app/models/employee_phone_status.rb', line 316 def logged_in_queue? (queue_statuses || {}).values.all? { |v| v['logged_in_status'] == 'logged_in' } || false end |
#minutes_in_current_status ⇒ Object
264 265 266 267 268 |
# File 'app/models/employee_phone_status.rb', line 264 def minutes_in_current_status return unless date_set ((Time.current - date_set) / 60).ceil end |
#pull_presence(_force = false) ⇒ Object
Pulls the pbx presence and update locally
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 |
# File 'app/models/employee_phone_status.rb', line 243 def pull_presence(_force = false) return true if auto_away_time_check # Skip the API, just go away if status_info = Phone::Pbx.instance.get_presence_status(switchvox_account_id) self.presence = status_info[:presence].presence self.sub_presence = status_info[:sub_presence].presence logger.debug("[EmployeePhoneStatus:pull_presence] pulled", employee_phone_status_id: id, switchvox_account_id: switchvox_account_id) return :no_status_change unless presence_changed? or sub_presence_changed? self. = status_info[:message].presence self.date_set = status_info[:date_set].presence return :status_changed if save :error_saving_record else logger.error "[EmployeePhoneStatus:pull_presence:#{id}] no presence status could be retrieved for switchvox_account_id #{switchvox_account_id}" :api_call_failure end end |
#push_presence(new_presence = nil, new_sub_presence = nil, options = {}) ⇒ Object
Pushes the local presence settings to the pbx api
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'app/models/employee_phone_status.rb', line 215 def push_presence(new_presence = nil, new_sub_presence = nil, = {}) # First save new presence if specified if new_presence self.presence = new_presence self.sub_presence = new_sub_presence self.date_set = Time.current else auto_away_time_check end return false unless valid? status_id = status_id_for_presence if status_id logger.info "[EmployeePhoneStatus(#{id})::push_presence] Setting presence id #{status_id} -> presence: #{presence}, sub_presence: #{sub_presence}, queue: #{should_be_in_queue?}, employee: #{employee_id}" success = Phone::Pbx.instance.update_unified_presence(account_id: switchvox_account_id, status_id: status_id, log_in_queue: should_be_in_queue?, call_queue_account_ids: queue_statuses.try(:keys) || []) if success save # Only when api call is successful do we save the record self.class.broadcast_agents_status(self) if [:broadcast] end else logger.error "[EmployeePhoneStatus(#{id})::push_presence] is unable to find a status_id to match to, status id is #{status_id}, " end end |
#refresh_status_options ⇒ Object
Update presence status list
325 326 327 328 329 330 331 |
# File 'app/models/employee_phone_status.rb', line 325 def return unless switchvox_account_id && switchvox_account_id > 0 option_list = Phone::Pbx.instance. switchvox_account_id update_attribute :status_options, option_list option_list end |
#should_be_in_queue? ⇒ Boolean
Should be in queue ?
309 310 311 |
# File 'app/models/employee_phone_status.rb', line 309 def should_be_in_queue? presence == 'available' && sub_presence != 'Non Queue' end |
#status_id_for_presence ⇒ Object
Retrieve the presence status id for a given presence and sub status
Using the cached status_options previously stored by refresh_status_options
WIll attempt to retrieve status options if attribute is blank
336 337 338 339 |
# File 'app/models/employee_phone_status.rb', line 336 def status_id_for_presence unless .present? curated_presence_list.detect { |so| so[:presence].presence == presence.to_s.presence && so[:sub_presence].presence == sub_presence.to_s.presence }.try(:[], :id) end |
#sub_presence_options ⇒ Object
304 305 306 |
# File 'app/models/employee_phone_status.rb', line 304 def curated_presence_list.map { |s| s[:sub_presence] }.compact end |
#time_to_set_away? ⇒ Boolean
Checks if it's time to go on auto away
279 280 281 282 |
# File 'app/models/employee_phone_status.rb', line 279 def time_to_set_away? tod_time_now = Time.current.to_time_of_day auto_away_after and tod_time_now >= auto_away_after end |
#valid_presence_list ⇒ Object
Filters out status options
342 343 344 345 346 347 348 |
# File 'app/models/employee_phone_status.rb', line 342 def valid_presence_list .select do |so| so['presence'] == 'available' || (so['presence'] == 'away' && so['sub_presence'].blank?) || so['presence'] == 'dnd' end end |