Class: HrLite::LeaveRequest

Inherits:
ApplicationRecord show all
Defined in:
app/models/hr_lite/leave_request.rb

Constant Summary collapse

STATUSES =
%w[pending approved rejected cancelled].freeze

Instance Method Summary collapse

Instance Method Details

#approve!(actor:, note: nil) ⇒ Object

Returns false (leaving the request pending) when the balance no longer covers it — the re-check runs inside the row lock so two concurrent approvals cannot overdraw one balance.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'app/models/hr_lite/leave_request.rb', line 44

def approve!(actor:, note: nil)
  insufficient = false
  transition!("approved", actor, note) do
    # Serialize on the BALANCE row. `transition!`'s own lock is on this
    # request, and two pending requests are two different rows — so both
    # approvals could read the same untouched balance and overdraw it.
    LeaveBalance.lock_for(user, leave_type, LeaveYear.key_for(start_date)) unless leave_type.unlimited?

    if insufficient_balance_now?
      insufficient = true
      raise ActiveRecord::Rollback
    end
  end
  return false if insufficient

  notify_decision("Leave approved")
  notify_team
  true
end

#balanceObject



104
105
106
# File 'app/models/hr_lite/leave_request.rb', line 104

def balance
  LeaveBalance.for(user, leave_type, LeaveYear.key_for(start_date))
end

#cancel!(actor:) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'app/models/hr_lite/leave_request.rb', line 79

def cancel!(actor:)
  was_approved = approved?
  self.status = "cancelled"
  self.decided_by_id = actor.id
  self.decided_at = Time.current
  save!

  Notifications.publish(
    "leave.cancelled",
    title: "#{HrLite.display_name(user)} cancelled #{was_approved ? 'approved ' : ''}leave " \
           "(#{date_range_label})",
    path: "/admin/leave_requests",
    bell_to: HrLite.admin_users
  )
  true
end

#cancellable_by?(actor) ⇒ Boolean

Owner may cancel while pending, or an approved future leave (quota returns automatically because used is computed). Admins may cancel any pending/approved future leave.

Returns:

  • (Boolean)


73
74
75
76
77
# File 'app/models/hr_lite/leave_request.rb', line 73

def cancellable_by?(actor)
  return false unless pending? || (approved? && start_date > Date.current)

  user_id == actor.id || HrLite.admin?(actor) || HrLite.leadership?(actor)
end

#date_range_labelObject



96
97
98
99
100
101
102
# File 'app/models/hr_lite/leave_request.rb', line 96

def date_range_label
  if start_date == end_date
    "#{start_date.strftime('%d %b')}#{half_day ? ' (half day)' : ''}"
  else
    "#{start_date.strftime('%d %b')}#{end_date.strftime('%d %b')}"
  end
end

#paid?Boolean

Returns:

  • (Boolean)


35
36
37
# File 'app/models/hr_lite/leave_request.rb', line 35

def paid?
  leave_type.paid
end

#reject!(actor:, note:) ⇒ Object



64
65
66
67
68
# File 'app/models/hr_lite/leave_request.rb', line 64

def reject!(actor:, note:)
  transition!("rejected", actor, note)
  notify_decision("Leave rejected")
  true
end