app/models/user.rb
require 'net/ldap'
require 'rails_extensions'
require 'memoist'
class User < ActiveRecord::Base
acts_as_authentic do |options|
options.maintain_sessions false
end
has_and_belongs_to_many :roles
has_and_belongs_to_many :subs_requested, class_name: 'SubRequest'
has_many :departments_users
has_many :departments, through: :departments_users
has_many :payforms
has_many :payform_items, through: :payforms
has_many :permissions, through: :roles
has_many :shifts
has_many :notices, as: :author
has_many :notices, as: :remover
has_one :punch_clock
has_many :sub_requests, through: :shifts #the sub requests this user owns
has_many :shift_preferences
has_many :requested_shifts
has_many :restrictions
# New user configs are created by a user observer, after create
has_one :user_config, dependent: :destroy
has_one :user_profile, dependent: :destroy
attr_protected :superusercreate_
scope :superusers, -> { where(superuser: true).order(:last_name) }
delegate :default_department, to: 'user_config'
validates_presence_of :first_name
validates_presence_of :last_name
validates_presence_of :login
#validates_presence_of :auth_type
validates_uniqueness_of :login
validate :departments_not_empty
#extend ActiveSupport::Memoizable
extend Memoist
def role=(name)
self.roles << Role.where(name: name).first if name && Role.where(name: name).first
end
def role
self.roles.first.name if self.roles.first
end
def active_departments
self.departments.select {|d| self.is_active?(d)}
end
def set_random_password(size=20)
chars = (('a'..'z').to_a + ('0'..'9').to_a)
self.password=self.password_confirmation=(1..size).collect{|a| chars[rand(chars.size)] }.join
end
def self.search_ldap(first_name, last_name, email, limit)
appconfig = AppConfig.first
first_name+='*'
last_name+='*'
email+='*'
# Set up our LDAP connection
begin
ldap = Net::LDAP.new( host: appconfig.ldap_host_address, port: appconfig.ldap_port )
filter = Net::LDAP::Filter.eq(appconfig.ldap_first_name, first_name) & Net::LDAP::Filter.eq(appconfig.ldap_last_name, last_name) & Net::LDAP::Filter.eq(appconfig.ldap_email, email)
out=[]
ldap.open do |ldap|
ldap.search(base: appconfig.ldap_base, filter: filter, return_result: false) do |entry|
out << {login: entry[appconfig.ldap_login][0], email: entry[appconfig.ldap_email][0], first_name: entry[appconfig.ldap_first_name][0], last_name: entry[appconfig.ldap_last_name][0]}
break if out.length>=limit
end
end
out
rescue Exception => e
false
end
end
def self.mass_add(logins, department)
failed = []
logins.split(/\W+/).map do |n|
if user = self.where(login: n).first
user.departments << department
# else
# user = import_from_ldap(n, department, true)
end
failed << "From login #{user.login}: #{user.errors.full_messages.to_sentence} (LDAP import may have failed)" if user.new_record?
end
failed
end
def permission_list
roles.collect { |r| r.permissions }.flatten
end
def current_shift
Shift.where(user_id: self.id, signed_in: true).first
end
# check if a user can see locations and shifts under this loc group
def can_view?(loc_group)
return false unless loc_group
(self.is_superuser? || permission_list.include?(loc_group.view_permission) || permission_list.include?(loc_group.department.admin_permission)) && self.is_active?(loc_group.department)
end
# check if a user can sign up for a shift in this loc group
def can_signup?(loc_group)
return false unless loc_group
(permission_list.include?(loc_group.signup_permission) && self.is_active?(loc_group.department)) if permission_list
end
# check if a user has permission to take a sub
def can_take_sub?(sub_request)
return false unless sub_request
can_signup?(sub_request.loc_group) && (sub_request.user != self) && (sub_request.users_with_permission.include?(self) || sub_request.users_with_permission.blank?)
end
# check for admin permission given a dept, location group, or location
def is_admin_of?(thing)
return false unless thing
((permission_list.include?(thing.department.admin_permission) || permission_list.include?(thing.admin_permission)) || self.is_superuser?) && self.is_active?(thing.department)
end
# Can only be called on objects which have a user method
def is_owner_of?(thing)
return false unless thing.user == self
true
end
# now superuser is an attribute of User model, we use this instead
# supermode lets an user turn on or off his superuser privilege
# user .superuser? is you wanna test superuser no matter if supermode is on or not
def is_superuser?
superuser? && supermode?
end
# check to make sure the user is "active" in the given dept
def is_active?(dept)
if DepartmentsUser.where(user_id: self, department_id: dept, active: true).first
true
else
false
end
end
# Given a department, check to see if the user can admin any loc groups in it
def is_loc_group_admin?(dept)
dept.loc_groups.any?{|lg| self.is_admin_of?(lg)}
end
# given an object with roles, checks to see if the user belongs to one of those roles
def has_proper_role_for?(thing)
self.roles.each do |role|
if thing.roles.include?(role)
return true
end
end
return false
end
# Given a department, return any location groups within that department that the user can admin
def loc_groups_to_admin(dept)
return dept.loc_groups if self.is_admin_of?(dept)
dept.loc_groups.delete_if{|lg| !self.is_admin_of?(lg)}
end
def name
[self.goes_by, last_name].join(" ")
end
def goes_by
((nick_name.nil? or nick_name.length == 0) ? first_name : nick_name)
end
def proper_name
[first_name, last_name].join(" ")
end
# Useful for alphabetical sorting of lists containing duplicate last names
def reverse_name
[last_name, first_name].join(" ")
end
def full_name_with_nick
if nick_name && !nick_name.blank?
[first_name, "'#{nick_name}'" , last_name].join(" ")
else
name
end
end
def self.search(search_string)
User.all.each do |u|
return u if u.name == search_string || u.proper_name == search_string || u.full_name_with_nick == search_string || u.login == search_string
end
nil
end
#Keeping permissions consistent.
def user
self
end
#We do still need this for polymorphism. I want to be able to call @user.users.
def users
[self]
end
def loc_groups(dept=nil)
if dept #specified department
dept.loc_groups.select{|lg| self.can_view?(lg)}
else #all departments
[departments.collect(&:loc_groups).flatten.select {|lg| self.can_view?(lg)}].flatten
end
end
def locations(dept=nil)
[loc_groups(dept).collect(&:locations).flatten.uniq].flatten
end
#returns upcoming sub_requests user has permission to take. Default is for all departments
def available_sub_requests(source)
@all_subs = []
@all_subs = SubRequest.where("end >= ?", Time.now).select { |sub| self.can_take_sub?(sub) }.select{ |sub| !sub.shift.missed?}
if !source.blank?
case
when source.class.name == "Department"
@all_subs.select {|sub| source == sub.shift.department }
when source.class.name == "LocGroup"
@all_subs.select {|sub| source == sub.loc_group }
when source.class.name == "Location"
@all_subs.select {|sub| source == sub.shift.location }
end
end
return @all_subs
end
def restrictions #TODO: this could probably be optimized
Restriction.current.select{|r| r.users.include?(self)}
end
def toggle_active(department) #TODO why don't we just update the attribues on the current entry and save it?
new_entry = DepartmentsUser.new();
old_entry = DepartmentsUser.where(user_id: self, department_id: department).first
shifts = Shift.for_user(self).select{|s| s.start > Time.now}
new_entry.attributes = old_entry.attributes
new_entry.active = !old_entry.active
DepartmentsUser.delete_all( user_id: self, department_id: department)
new_entry.save
shifts.each do |shift|
shift.active = new_entry.active
shift.save
end
return true
end
def password_reset_instructions!(mailer)
self.reset_perishable_token!
mailer.call(self)
end
memoize :name, :permission_list
def accessible_departments
(superuser? && supermode?) ? Department.all : departments
end
def payrate(department)
DepartmentsUser.where(user_id: self, department_id: department ).first.payrate
end
def set_payrate(value, department)
new_entry = DepartmentsUser.new();
old_entry = DepartmentsUser.where(user_id: self, department_id: department).first
new_entry.attributes = old_entry.attributes
new_entry.payrate = value
DepartmentsUser.delete_all(user_id: self, department_id: department)
new_entry.save
end
def summary_stats(start_date, end_date)
shifts_set = shifts.on_days(start_date, end_date).active
summary_stats = {}
summary_stats[:start_date] = start_date
summary_stats[:end_date] = end_date
summary_stats[:total] = shifts_set.size
summary_stats[:late] = shifts_set.select{|s| s.late == true}.size
summary_stats[:missed] = shifts_set.select{|s| s.missed == true}.size
summary_stats[:left_early] = shifts_set.select{|s| s.left_early == true}.size
return summary_stats
end
def detailed_stats(start_date, end_date)
shifts_set = shifts.on_days(start_date, end_date).active
detailed_stats = {}
shifts_set.each do |s|
stat_entry = {}
stat_entry[:id] = s.id
stat_entry[:shift] = s.short_display
stat_entry[:in] = s.created_at
stat_entry[:out] = s.updated_at
# if s.missed
# stat_entry[:notes] = "Missed"
# elsif s.late && s.left_early
# stat_entry[:notes] = "Late " + (s.created_at - s.start)/60 + " minutes, and left early " + (s.end - s.updated_at)/60 + " minutes"
# elsif s.late
# stat_entry[:notes] = "Late " + (s.created_at - s.start)/60 + " minutes"
# elsif s.left_early
# stat_entry[:notes] = "Left early " + (s.end - s.updated_at)/60 + " minutes"
# else
# stat_entry[:notes]
# end
if s.missed
stat_entry[:notes] = "Missed"
elsif s.late && s.left_early
stat_entry[:notes] = "Late and left early"
elsif s.late
stat_entry[:notes] = "Late"
elsif s.left_early
stat_entry[:notes] = "Left early"
else
stat_entry[:notes]
end
stat_entry[:missed] = s.missed
stat_entry[:late] = s.late
stat_entry[:left_early] = s.left_early
stat_entry[:updates_hour] = s.updates_hour
detailed_stats[s.id] = stat_entry
end
return detailed_stats
end
# Defines the columns that we use for CSV files
def self.csv_headers
['login', 'first_name', 'nick_name', 'last_name', 'email', 'employee_id', 'role']
end
def self.to_csv
CSV.generate do |csv|
csv << User.csv_headers
all.each do |user|
csv << user.attributes.values_at(*User.csv_headers)
end
end
end
# Creates new User entries based on the specified CSV file
def self.import(file)
results = {successes: [], failures: []}
CSV.foreach(file.path, headers: true) do |row|
attrs = row.to_hash
# De-humanize row labels (if using Rails 2 CSV export which has them)
attrs = Hash[attrs.map {|k, v| [k.sub(' ', '').underscore, v]}]
# Allow for CSVs with either `role` and/or `roles` headers
unless attrs['roles'].nil?
attrs['role'] ||= attrs['roles']
end
# Get roles from database and only include those that exist
roles = [Role.where(name: attrs["role"]).first].compact
# Chuck the record if no valid roles are included
if roles.empty?
results[:failures] << {name: attrs['first_name'] + ' ' + attrs['last_name'], errors: ['Invalid role']}
next
end
attrs.delete("role") # Not useful for initial User creation
u = User.new(attrs)
u.set_random_password
u.roles = roles
u.departments = [roles.first.department]
if u.save
results[:successes] << {name: "#{u.first_name} #{u.last_name}"}
else
# Prepare errors for rendering in a readable form
results[:failures] << {name: "#{u.first_name} #{u.last_name}", \
errors: u.errors.messages.map { |subject, failure| \
subject.to_s.humanize + ' ' + failure.join('; ') }}
end
end
results
end
private
def departments_not_empty
errors.add("User must have at least one department.", "") if departments.empty?
end
end