Class: HrLite::RegularizationRequest

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

Overview

"I forgot to punch — please fix that day." The employee proposes the times; an admin approves, which applies them to the attendance record with the full regularization audit trail (same fields the manual admin fix writes), or rejects with a note.

Defined Under Namespace

Classes: InvalidMerge

Constant Summary collapse

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

Instance Method Summary collapse

Instance Method Details

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

Applies the proposed times to the day's record (creating it if the person never punched at all). Only the provided times overwrite — a forgot-checkout ticket keeps the genuine GPS check-in untouched. A merge that would produce a nonsense record (checkout before the existing check-in, or a checkout with no check-in at all) raises InvalidMerge with the real story so the admin knows what to fix.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'app/models/hr_lite/regularization_request.rb', line 50

def approve!(actor:, note: nil)
  transition!("approved", actor, note) do
    # Re-checked at approval, not only at create: leave can be approved for
    # that day while the ticket waits in the queue, and DayStatus ranks
    # full-day leave above any punch — so the "fix" would write a record
    # nothing reads while telling everyone attendance had been corrected.
    if LeaveRequest.active_on(date).where(user_id: user_id, half_day: false).exists?
      raise InvalidMerge, "the day is now covered by approved leave — cancel the leave first"
    end

    record = AttendanceRecord.find_or_initialize_by(user_id: user_id, date: date)
    record.check_in_at = check_in_at if check_in_at
    record.check_out_at = check_out_at if check_out_at
    if record.check_in_at.nil?
      raise InvalidMerge, "the day has no check-in — the ticket needs a check-in time too"
    end

    record.status = "present" if record.status.blank?
    record.regularized_by_id = actor.id
    record.regularized_at = Time.current
    record.regularization_note = "Ticket ##{id}: #{reason}"
    raise InvalidMerge, record.errors.full_messages.to_sentence unless record.valid?

    record.save!
    AuditLog.create!(
      actor: actor, action: "regularize",
      subject_type: record.class.name, subject_id: record.id,
      audited_changes: { "date" => record.date.to_s, "ticket" => id, "note" => reason }
    )
  end

  Notifications.publish(
    "regularization.approved",
    title: "Attendance fixed for #{date.strftime('%d %b')} (#{times_label})",
    body: decision_note.presence,
    path: "/regularization_requests",
    bell_to: [ user ], email_to: [ user ]
  )
  true
end

#cancel!(actor:) ⇒ Object



103
104
105
106
107
108
109
110
111
112
# File 'app/models/hr_lite/regularization_request.rb', line 103

def cancel!(actor:)
  transition!("cancelled", actor, nil)
  Notifications.publish(
    "regularization.cancelled",
    title: "#{HrLite.display_name(user)} cancelled a regularization ticket (#{date.strftime('%d %b')})",
    path: "/admin/regularization_requests",
    bell_to: HrLite.admin_users
  )
  true
end

#punchObject

Current punch state for the approver's context.



38
39
40
# File 'app/models/hr_lite/regularization_request.rb', line 38

def punch
  AttendanceRecord.find_by(user_id: user_id, date: date)
end

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



91
92
93
94
95
96
97
98
99
100
101
# File 'app/models/hr_lite/regularization_request.rb', line 91

def reject!(actor:, note:)
  transition!("rejected", actor, note)
  Notifications.publish(
    "regularization.rejected",
    title: "Regularization rejected — #{date.strftime('%d %b')}",
    body: decision_note.presence,
    path: "/regularization_requests",
    bell_to: [ user ], email_to: [ user ]
  )
  true
end

#times_labelObject



33
34
35
# File 'app/models/hr_lite/regularization_request.rb', line 33

def times_label
  [ check_in_at&.strftime("%H:%M"), check_out_at&.strftime("%H:%M") ].compact.join("").presence || ""
end