Class: EmployeeRecord

Inherits:
ApplicationRecord show all
Extended by:
FriendlyId
Includes:
Models::Auditable
Defined in:
app/models/employee_record.rb

Overview

== Schema Information

Table name: employee_records(Employee profile and role flags used for rep classification.)
Database name: primary

id :integer not null, primary key
activities_per_day :integer
activities_per_day_priority_tiers :integer
author_bio :text
author_page :boolean default(FALSE), not null
bereavement_days :integer
calendly_link :string
call_block_a :string
call_block_b :string
certifications :text
default_dashboard :string(255)
department(Department label used for organizational filtering and analytics.) :string(30)
disable_self_assign :boolean default(FALSE)
disable_special_events_alerts :boolean default(FALSE), not null
employment_end_date :date
employment_start_date :date
full_bio :text
heatwave_preferences :json
hide_signature_call_text_me_directly :boolean default(FALSE)
hide_signature_pbx_extension :boolean default(FALSE)
hire_date :date
interesting_fact :text
is_visa_holder :boolean
job_description :text
job_title :string(100)
jury_duty_days :integer
kpi_activities :integer
kpi_live_chat :integer
kpi_outbound_calls :integer
kpi_sms :integer
kpi_support_cases :integer
local_sales_rep :boolean default(FALSE), not null
mba_inventory :string default([]), is an Array
menus :string is an Array
monitor_group_sms_numbers :boolean default(FALSE)
monitor_unlinked_extensions :boolean default(FALSE), not null
next_birthday :date
next_work_anniversary :date
payroll_identifier :string
primary_sales_rep(True when employee is classified as a primary sales rep.) :boolean default(FALSE), not null
pto_backup1 :string
pto_backup2 :string
pto_backup3 :string
release_date :date
scheduler_link_type :string default("hidden")
secondary_sales_rep :boolean default(FALSE), not null
show_calendly_link :boolean default(FALSE)
show_signature_photo :boolean default(FALSE)
signature :text
slug :string
std_days :integer
vacation_days :integer
visa_end_date :date
visa_start_date :date
visa_type :string
watch_list_ids :integer default([]), is an Array
watch_list_role_ids :integer default([]), is an Array
created_at :datetime not null
updated_at :datetime not null
backup_rep_id :integer
commission_structure_id :integer
creator_id :integer
party_id :integer not null
print_node_computer_id :integer
profile_picture_id :integer
signature_picture_id :integer
updater_id :integer

Indexes

employee_records_profile_picture_id_idx (profile_picture_id)
employee_records_signature_picture_id_idx (signature_picture_id)
idx_party_secondary_sales_rep (party_id,secondary_sales_rep)
index_employee_records_on_call_block_a (call_block_a)
index_employee_records_on_call_block_b (call_block_b)
index_employee_records_on_employment_end_date (employment_end_date)
index_employee_records_on_employment_start_date (employment_start_date)
index_employee_records_on_is_visa_holder (is_visa_holder)
index_employee_records_on_slug (slug) UNIQUE
index_employee_records_on_visa_end_date (visa_end_date)
index_employee_records_on_visa_start_date (visa_start_date)
index_employee_records_on_visa_type (visa_type)

Foreign Keys

fk_rails_... (party_id => parties.id) ON DELETE => cascade
fk_rails_... (profile_picture_id => digital_assets.id)
fk_rails_... (signature_picture_id => digital_assets.id)

Constant Summary collapse

DEFAULT_ACTIVITIES_PER_DAY_PRIORITY_TIERS =
90
DEFAULT_ACTIVITIES_PER_DAY =
40
MANAGER_SQL =
<<-EOS
  exists(select 1 from accounts a
         inner join accounts_roles ar on ar.account_id = a.id
         inner join roles r on r.id = ar.role_id
         inner join parties e on e.id = a.party_id and e.type = 'Employee'
         where e.id = employee_records.party_id
          and (r.name ILIKE '%manager%' or r.name ILIKE '%director%')
          and e.inactive = false)
  or exists(select 1 from employee_records er
    where er.id = employee_records.id and er.party_id IN (select distinct e.parent_id
                         from parties e
                         where e.parent_id is not null))
EOS
SALES_REP_SQL =
<<-EOS
  exists(select 1 from accounts a
         inner join accounts_roles ar on ar.account_id = a.id
         inner join roles r on r.id = ar.role_id
         inner join parties e on e.id = a.party_id and e.type = 'Employee'
         where e.id = employee_records.party_id
          and (r.name ILIKE '%sales%')
          and e.inactive = false)
  or exists(select 1 from employee_records er
    where er.id = employee_records.id and er.party_id IN (select distinct e.parent_id
                         from parties e
                         where e.parent_id is not null))
EOS
CALL_BLOCK_OPTIONS =
[%w[None none], %w[Morning morning], %w[Afternoon afternoon]]
TYPES_OF_VISA =
[
  ['F-1 (Foreign academic student)', 'F-1 (Foreign academic student)'],
  ['H-1B (Temporary worker)', 'H-1B (Temporary worker)'],
  ['H-3 (Temporary worker)', 'H-3 (Temporary worker)'],
  ['J-1 (Exchange visitor)', 'J-1 (Exchange visitor)'],
  ['K-1 (Fiancé of a U.S. citizen)', 'K-1 (Fiancé of a U.S. citizen)']
]
%w[hidden external heatwave].freeze

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#creator, #updater

Has many collapse

Delegated Instance Attributes collapse

Class Method Summary collapse

Instance Method Summary collapse

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

#publish_event

Instance Attribute Details

#activities_per_dayObject (readonly)



159
# File 'app/models/employee_record.rb', line 159

validates :activities_per_day, numericality: { greater_than_or_equal_to: 0 }

#activities_per_day_priority_tiersObject (readonly)



160
# File 'app/models/employee_record.rb', line 160

validates :activities_per_day_priority_tiers, numericality: { greater_than_or_equal_to: 0 }


184
# File 'app/models/employee_record.rb', line 184

validates :scheduler_link_type, inclusion: { in: SCHEDULER_LINK_TYPES }, allow_nil: true

Class Method Details

.active_employeesActiveRecord::Relation<EmployeeRecord>

A relation of EmployeeRecords that are active employees. Active Record Scope

Returns:

See Also:



170
# File 'app/models/employee_record.rb', line 170

scope :active_employees, -> { joins(:party).merge(Employee.active_employees) }

.active_employees_in_department(department) ⇒ Object



253
254
255
# File 'app/models/employee_record.rb', line 253

def self.active_employees_in_department(department)
  active_employees.includes(:party).where(EmployeeRecord[:department].matches(department))
end

.department_select_optionsObject



330
331
332
# File 'app/models/employee_record.rb', line 330

def self.department_select_options
  where.not(department: nil).order('department').distinct.pluck(:department)
end

.employees_and_departments_hybrid_for_selectObject



243
244
245
246
247
248
249
250
251
# File 'app/models/employee_record.rb', line 243

def self.employees_and_departments_hybrid_for_select
  collection = []
  collection << ["\u221e Everyone", '*']
  active_employees.includes(:party).order(:department).group_by(&:department).each do |department, employee_records|
    collection << ["\u272a #{department}", "D|#{department}", { class: 'department-select-option' }]
    collection += employee_records.sort_by { |er| er.party.full_name }.map { |er| [er.party.full_name, "EID|#{er.party.id}", { class: 'employee-select-option' }] }
  end
  collection
end

.employees_grouped_by_departmentObject



231
232
233
234
235
236
237
238
239
240
241
# File 'app/models/employee_record.rb', line 231

def self.employees_grouped_by_department
  options = active_employees.includes(:party).order(:department).group_by(&:department)
  options.each do |department, employee_records|
    options[department] = (begin
      employee_records.map { |s| [s.party.full_name, s.party.id] }
    rescue StandardError
      nil
    end) || []
  end
  options
end

.job_title_select_optionsObject



326
327
328
# File 'app/models/employee_record.rb', line 326

def self.job_title_select_options
  where.not(job_title: nil).order('job_title').distinct.pluck(:job_title)
end

.managed_byActiveRecord::Relation<EmployeeRecord>

A relation of EmployeeRecords that are managed by. Active Record Scope

Returns:

See Also:



172
# File 'app/models/employee_record.rb', line 172

scope :managed_by, ->(manager_party_id) { joins(:party).where(party_id: Employee.descendants_ids(manager_party_id)) }

.managersActiveRecord::Relation<EmployeeRecord>

A relation of EmployeeRecords that are managers. Active Record Scope

Returns:

See Also:



168
# File 'app/models/employee_record.rb', line 168

scope :managers, -> { where(MANAGER_SQL) }

.managers_select_optionsObject



322
323
324
# File 'app/models/employee_record.rb', line 322

def self.managers_select_options
  managers.joins(:party).order('parties.full_name').pluck('parties.full_name', :id) # select("parties.full_name as employee_name, employee_records.id").map{|e| [e.employee_name, e.id]}
end

.mba_inventory_for_selectObject



314
315
316
# File 'app/models/employee_record.rb', line 314

def self.mba_inventory_for_select
  ['Receiving Gifts', 'Quality Time', 'Words of Affirmation', 'Acts of Service', 'Physical Touch']
end


218
219
220
221
222
223
224
225
226
227
228
229
# File 'app/models/employee_record.rb', line 218

def self.menu_options_for_select
  res = %w[activities articles catalogs communications customers customers_training quotes orders engineering ap ar ledger employee_training items logistics marketing reports tech phone it].sort
  res.insert(0, 'home') # Always first
  hsh = res.index_with { |m| m.humanize.titleize }
  # Special overrides
  hsh['ap'] = 'A/P'
  hsh['ar'] = 'A/R'
  hsh['it'] = 'Information Technology'
  hsh['image_profiles'] = 'Image Profile Manager'
  # Magic flip
  hsh.invert
end

.primary_sales_rep_for_selectObject



257
258
259
260
261
# File 'app/models/employee_record.rb', line 257

def self.primary_sales_rep_for_select
  EmployeeRecord.joins(:party)
                .includes(:party)
                .merge(Employee.primary_sales_reps.active_employees).map { |er| [er.employee_name, er.id] }
end

.sales_repsActiveRecord::Relation<EmployeeRecord>

A relation of EmployeeRecords that are sales reps. Active Record Scope

Returns:

See Also:



169
# File 'app/models/employee_record.rb', line 169

scope :sales_reps, -> { where(SALES_REP_SQL) }

.select_options(scope = nil) ⇒ Object



309
310
311
312
# File 'app/models/employee_record.rb', line 309

def self.select_options(scope = nil)
  results = (scope.present? ? EmployeeRecord.send(scope) : EmployeeRecord.all).joins(:party).includes(:party).merge(Employee.active_employees).merge(Employee.sorted)
  results.map { |er| [er.employee_name, er.id] }
end

.with_author_pageActiveRecord::Relation<EmployeeRecord>

A relation of EmployeeRecords that are with author page. Active Record Scope

Returns:

See Also:



192
# File 'app/models/employee_record.rb', line 192

scope :with_author_page, -> { where(author_page: true) }

.without_an_open_reviewActiveRecord::Relation<EmployeeRecord>

A relation of EmployeeRecords that are without an open review. Active Record Scope

Returns:

See Also:



171
# File 'app/models/employee_record.rb', line 171

scope :without_an_open_review, -> { active_employees.where("not exists(select 1 from employee_reviews er where er.employee_record_id = employee_records.id and er.state in ('pending'))") }

Instance Method Details

#active?Object

Alias for Party#active?

Returns:

  • (Object)

    Party#active?

See Also:



176
# File 'app/models/employee_record.rb', line 176

delegate :full_name, :inactive?, :active?, to: :party

#anniversary_dateObject



303
304
305
306
307
# File 'app/models/employee_record.rb', line 303

def anniversary_date
  return unless hire_date

  hire_date.strftime("%B #{hire_date.day.ordinalize}")
end

#appraisalsActiveRecord::Relation<Appraisal>

Returns:

See Also:



154
# File 'app/models/employee_record.rb', line 154

has_many :appraisals

#author_slug_sourceObject



194
195
196
# File 'app/models/employee_record.rb', line 194

def author_slug_source
  party&.full_name
end

#backup_repEmployee

Returns:

See Also:



142
# File 'app/models/employee_record.rb', line 142

belongs_to :backup_rep, class_name: 'Employee', optional: true

#clifton_strengthsActiveRecord::Relation<CliftonStrength>

Returns:

See Also:



150
# File 'app/models/employee_record.rb', line 150

has_many :clifton_strengths, -> { order('strength_themes.priority') }, through: :strength_themes

#commission_structureCommissionStructure



143
# File 'app/models/employee_record.rb', line 143

belongs_to :commission_structure, optional: true

#days_until_next_anniversaryObject



297
298
299
300
301
# File 'app/models/employee_record.rb', line 297

def days_until_next_anniversary
  return unless next_work_anniversary

  (next_work_anniversary - Date.current).to_i
end


275
276
277
278
279
280
281
282
283
# File 'app/models/employee_record.rb', line 275

def effective_scheduler_link
  case scheduler_link_type
  when 'heatwave'
    slug = party&.scheduler_profile&.slug
    slug.present? ? "#{WEB_URL}/en-US/meet/#{slug}" : nil
  when 'external'
    calendly_link
  end
end

#employeeObject



202
203
204
# File 'app/models/employee_record.rb', line 202

def employee
  party
end

#employee_goalsActiveRecord::Relation<EmployeeGoal>

Returns:

See Also:



153
# File 'app/models/employee_record.rb', line 153

has_many :employee_goals, through: :employee_reviews

#employee_nameObject



206
207
208
# File 'app/models/employee_record.rb', line 206

def employee_name
  party&.full_name
end

#employee_records_printersActiveRecord::Relation<EmployeeRecordsPrinter>

Returns:

  • (ActiveRecord::Relation<EmployeeRecordsPrinter>)

See Also:



156
# File 'app/models/employee_record.rb', line 156

has_many :employee_records_printers

#employee_reviewedActiveRecord::Relation<EmployeeReview>

Returns:

See Also:



152
# File 'app/models/employee_record.rb', line 152

has_many :employee_reviewed, class_name: 'EmployeeReview', foreign_key: 'reviewer_id', inverse_of: :reviewer

#employee_reviewsActiveRecord::Relation<EmployeeReview>

Returns:

See Also:



151
# File 'app/models/employee_record.rb', line 151

has_many :employee_reviews, inverse_of: :employee_record

#full_nameObject

Alias for Party#full_name

Returns:

  • (Object)

    Party#full_name

See Also:



176
# File 'app/models/employee_record.rb', line 176

delegate :full_name, :inactive?, :active?, to: :party

#inactive?Object

Alias for Party#inactive?

Returns:

  • (Object)

    Party#inactive?

See Also:



176
# File 'app/models/employee_record.rb', line 176

delegate :full_name, :inactive?, :active?, to: :party

#is_manager?Boolean

Returns:

  • (Boolean)


263
264
265
# File 'app/models/employee_record.rb', line 263

def is_manager?
  self.class.managers.where(id:).present?
end

#managers_select_optionsObject



318
319
320
# File 'app/models/employee_record.rb', line 318

def managers_select_options
  self.class.managers_select_options.reject { |v| v[1] == id }
end

#managesObject



267
268
269
# File 'app/models/employee_record.rb', line 267

def manages
  self.class.active_employees.managed_by(party_id)
end

#manages_employee_record_idsObject



271
272
273
# File 'app/models/employee_record.rb', line 271

def manages_employee_record_ids
  manages.pluck(:id)
end

#mba_inventory_1Object



334
335
336
# File 'app/models/employee_record.rb', line 334

def mba_inventory_1
  mba_inventory&.dig(0)
end

#mba_inventory_1=(val) ⇒ Object



338
339
340
341
# File 'app/models/employee_record.rb', line 338

def mba_inventory_1=(val)
  self.mba_inventory ||= Array.new(3)
  self.mba_inventory[0] = val
end

#mba_inventory_2Object



343
344
345
# File 'app/models/employee_record.rb', line 343

def mba_inventory_2
  mba_inventory&.dig(1)
end

#mba_inventory_2=(val) ⇒ Object



347
348
349
350
# File 'app/models/employee_record.rb', line 347

def mba_inventory_2=(val)
  self.mba_inventory ||= Array.new(3)
  self.mba_inventory[1] = val
end

#mba_inventory_3Object



352
353
354
# File 'app/models/employee_record.rb', line 352

def mba_inventory_3
  mba_inventory&.dig(2)
end

#mba_inventory_3=(val) ⇒ Object



356
357
358
359
# File 'app/models/employee_record.rb', line 356

def mba_inventory_3=(val)
  self.mba_inventory ||= Array.new(3)
  self.mba_inventory[2] = val
end

#partyParty

attr_accessible :title, :body

Returns:

See Also:



141
# File 'app/models/employee_record.rb', line 141

belongs_to :party, optional: true, touch: true

#praisesActiveRecord::Relation<Praise>

Returns:

  • (ActiveRecord::Relation<Praise>)

See Also:



155
# File 'app/models/employee_record.rb', line 155

has_many :praises, through: :appraisals

#printersActiveRecord::Relation<Printer>

Returns:

  • (ActiveRecord::Relation<Printer>)

See Also:



157
# File 'app/models/employee_record.rb', line 157

has_many :printers, through: :employee_records_printers

#profile_pictureImage

Returns:

See Also:



144
# File 'app/models/employee_record.rb', line 144

belongs_to :profile_picture, class_name: 'Image', optional: true

#selected_menu_optionsObject



210
211
212
213
214
215
216
# File 'app/models/employee_record.rb', line 210

def selected_menu_options
  res = self.class.menu_options_for_select.invert
  if menus.present?
    res = res.select { |k, _v| k.in?(menus || []) }
  end # Only if preference are set do we select
  res
end

#set_first_anniversaryObject



372
373
374
375
376
377
378
379
380
381
382
# File 'app/models/employee_record.rb', line 372

def set_first_anniversary
  # No need to set it if set already and valid
  unless hire_date.present?
    return
  end # Can't quite set  if hire date not specified
  return unless hire_date_changed? || next_work_anniversary.nil? || next_work_anniversary < Date.current

  na = hire_date.dup.change(year: Date.current.year)
  na += 1.year if na < Date.current
  self.next_work_anniversary = na
end

#set_first_birthdayObject



361
362
363
364
365
366
367
368
369
370
# File 'app/models/employee_record.rb', line 361

def set_first_birthday
  if next_birthday.present? && next_birthday > Date.current
    return
  end # already set don't worry
  return unless employee.dob.present? # can't set, no dob

  nb = employee.dob.dup.change(year: Date.current.year)
  nb += 1.year if nb < Date.current
  self.next_birthday = nb
end

#should_generate_new_friendly_id?Boolean

Returns:

  • (Boolean)


198
199
200
# File 'app/models/employee_record.rb', line 198

def should_generate_new_friendly_id?
  slug.blank? || will_save_change_to_party_id?
end

#signature_pictureImage

Returns:

See Also:



145
# File 'app/models/employee_record.rb', line 145

belongs_to :signature_picture, class_name: 'Image', optional: true

#signature_picture_smallObject



285
286
287
288
289
# File 'app/models/employee_record.rb', line 285

def signature_picture_small
  return nil if signature_picture.nil?

  signature_picture.image_url(width: 80)
end

#strength_themesActiveRecord::Relation<StrengthTheme>

Returns:

See Also:



149
# File 'app/models/employee_record.rb', line 149

has_many :strength_themes, -> { order(:priority) }

#sync_profile_picture_to_partyObject

Sync profile_picture_id to Party.profile_image_id
This keeps the legacy employee_record.profile_picture_id in sync with the
consolidated Party.profile_image_id column



387
388
389
390
391
# File 'app/models/employee_record.rb', line 387

def sync_profile_picture_to_party
  return unless profile_picture_id_changed? && party.present?

  party.update_column(:profile_image_id, profile_picture_id)
end

#visa_duration_monthsObject



393
394
395
396
397
398
399
# File 'app/models/employee_record.rb', line 393

def visa_duration_months
  return 0 if visa_start_date.blank? || visa_end_date.blank?

  months = (visa_end_date.year * 12 + visa_end_date.month) - (visa_start_date.year * 12 + visa_start_date.month)
  months -= 1 if visa_end_date.day < visa_start_date.day
  [months, 0].max
end

#years_at_workObject



291
292
293
294
295
# File 'app/models/employee_record.rb', line 291

def years_at_work
  return unless hire_date

  ((Date.current - hire_date) / 365).floor
end