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
# File 'app/models/hr_lite/regularization_request.rb', line 50

def approve!(actor:, note: nil)
  transition!("approved", actor, note) do
    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



95
96
97
98
99
100
101
102
103
104
# File 'app/models/hr_lite/regularization_request.rb', line 95

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



83
84
85
86
87
88
89
90
91
92
93
# File 'app/models/hr_lite/regularization_request.rb', line 83

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