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 :enum default("api")
date_set :datetime
dnd_alert_threshold_minutes :integer
extension :integer
last_alert :datetime
message :string
pbx_integration :enum 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
Constants included from Schedulable
Schedulable::SIMPLE_FORM_OPTIONS
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 Schedulable
Methods included from Models::AfterCommittable
Methods included from Models::EventPublishable
Instance Attribute Details
#switchvox_account_id ⇒ Object (readonly)
53 |
# File 'app/models/employee_phone_status.rb', line 53 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
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'app/models/employee_phone_status.rb', line 171 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
67 68 69 70 71 |
# File 'app/models/employee_phone_status.rb', line 67 scope :dnd_alerts, -> { where.not(dnd_alert_threshold_minutes: nil).where.not(date_set: nil) .where(presence: 'dnd') .where("date_set + dnd_alert_threshold_minutes * INTERVAL '1 minutes' > ?", Time.current) } |
.extension_to_employee_id_hash ⇒ Object
145 146 147 148 149 150 151 |
# File 'app/models/employee_phone_status.rb', line 145 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? } .to_h { |emp| [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
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 |
# File 'app/models/employee_phone_status.rb', line 81 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
127 128 129 130 131 132 133 |
# File 'app/models/employee_phone_status.rb', line 127 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
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'app/models/employee_phone_status.rb', line 98 def self.migrate_extensions Employee.find_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
136 137 138 139 140 141 142 143 |
# File 'app/models/employee_phone_status.rb', line 136 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
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
# File 'app/models/employee_phone_status.rb', line 190 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
116 117 118 119 120 121 122 123 124 |
# File 'app/models/employee_phone_status.rb', line 116 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
322 323 324 |
# File 'app/models/employee_phone_status.rb', line 322 def self. find_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
292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'app/models/employee_phone_status.rb', line 292 def auto_away_time_check if time_to_set_away? && (presence != 'away') && !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
153 154 155 |
# File 'app/models/employee_phone_status.rb', line 153 def broadcast_agent_status self.class.broadcast_agents_status(self) end |
#contact_point ⇒ ContactPoint
47 |
# File 'app/models/employee_phone_status.rb', line 47 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
354 355 356 357 358 359 360 361 362 363 364 |
# File 'app/models/employee_phone_status.rb', line 354 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
286 287 288 |
# File 'app/models/employee_phone_status.rb', line 286 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
272 273 274 |
# File 'app/models/employee_phone_status.rb', line 272 def dnd_alert_threshold_seconds dnd_alert_threshold_minutes * 60 if dnd_alert_threshold_minutes end |
#dnd_presence_alert? ⇒ Boolean
276 277 278 |
# File 'app/models/employee_phone_status.rb', line 276 def dnd_presence_alert? minutes_in_current_status && dnd_alert_threshold_minutes && minutes_in_current_status >= dnd_alert_threshold_minutes end |
#employee ⇒ Employee
46 |
# File 'app/models/employee_phone_status.rb', line 46 belongs_to :employee, inverse_of: :employee_phone_status |
#employee_phone_status_changes ⇒ ActiveRecord::Relation<EmployeePhoneStatusChange>
49 |
# File 'app/models/employee_phone_status.rb', line 49 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.
160 161 162 163 164 165 166 167 168 |
# File 'app/models/employee_phone_status.rb', line 160 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
318 319 320 |
# File 'app/models/employee_phone_status.rb', line 318 def logged_in_queue? (queue_statuses || {}).values.all? { |v| v['logged_in_status'] == 'logged_in' } || false end |
#minutes_in_current_status ⇒ Object
266 267 268 269 270 |
# File 'app/models/employee_phone_status.rb', line 266 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
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'app/models/employee_phone_status.rb', line 245 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? || 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
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'app/models/employee_phone_status.rb', line 217 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
327 328 329 330 331 332 333 |
# File 'app/models/employee_phone_status.rb', line 327 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 ?
311 312 313 |
# File 'app/models/employee_phone_status.rb', line 311 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
338 339 340 341 |
# File 'app/models/employee_phone_status.rb', line 338 def status_id_for_presence if .blank? curated_presence_list.find { |so| so[:presence].presence == presence.to_s.presence && so[:sub_presence].presence == sub_presence.to_s.presence }.try(:[], :id) end |
#sub_presence_options ⇒ Object
306 307 308 |
# File 'app/models/employee_phone_status.rb', line 306 def curated_presence_list.pluck(:sub_presence).compact end |
#time_to_set_away? ⇒ Boolean
Checks if it's time to go on auto away
281 282 283 284 |
# File 'app/models/employee_phone_status.rb', line 281 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
344 345 346 347 348 349 350 |
# File 'app/models/employee_phone_status.rb', line 344 def valid_presence_list .select do |so| so['presence'] == 'available' || (so['presence'] == 'away' && so['sub_presence'].blank?) || so['presence'] == 'dnd' end end |