hr_lite 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -0
- data/README.md +11 -0
- data/app/assets/javascripts/hr_lite/nav.js +21 -0
- data/app/assets/stylesheets/hr_lite/hr_lite.css +1 -1
- data/app/controllers/hr_lite/regularization_requests_controller.rb +10 -1
- data/app/jobs/hr_lite/attendance_close_job.rb +42 -0
- data/app/models/hr_lite/attendance_record.rb +1 -0
- data/app/models/hr_lite/regularization_request.rb +40 -3
- data/app/services/hr_lite/overview_query.rb +4 -1
- data/app/views/hr_lite/admin/leave_requests/new.html.erb +8 -1
- data/app/views/hr_lite/leave_requests/index.html.erb +3 -0
- data/app/views/hr_lite/regularization_requests/new.html.erb +3 -0
- data/app/views/layouts/hr_lite/application.html.erb +1 -0
- data/lib/hr_lite/configuration.rb +11 -1
- data/lib/hr_lite/notifications.rb +2 -0
- data/lib/hr_lite/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d99d3408ecc4296bfb60310343f56df91495a48a6bc277881f6ebbd79a57de80
|
|
4
|
+
data.tar.gz: 9286e4b08358dbbe9f360967a57b134aa0ee6d74841daf80310ad79b372ae7b2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a96b511a5031812bf57b8b10314c1316eb29857f25f0e5dd4e454caea586bf88c3feff01180e3d5bdd8da693421c52524c9da93038740266c8bf9c079dca24ed
|
|
7
|
+
data.tar.gz: dc3d6659e50a6421935556bb13db5a0749198e119b6bc01fa5a84415a913d26e4a38397c71f2e529c379fdc14663eb9afb8825c1f402e9723e7c1fb2ba933ba8
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.19.0] - 2026-09-30
|
|
11
|
+
|
|
12
|
+
Each night closes the day. A missed check-out becomes a half day, and
|
|
13
|
+
employees can fix a recent missed punch themselves. No migration.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`HrLite::AttendanceCloseJob`**, scheduled by the host each night (a run
|
|
18
|
+
before noon closes the previous day, so retries are safe). On a working
|
|
19
|
+
day it checks out every punch still open at the day's end (23:59), marks the
|
|
20
|
+
day `half_day` (payroll pays
|
|
21
|
+
half and counts half as loss of pay), and emails the employee. Anyone who
|
|
22
|
+
never checked in is emailed that the day is absent; no record is written.
|
|
23
|
+
Weekends, holidays and approved leave (full or half day) are skipped. Run it
|
|
24
|
+
once a day: a re-run emails the no-shows again.
|
|
25
|
+
- **Self-fix.** With `config.self_regularization = { within_days:, per_week: }`,
|
|
26
|
+
a ticket that fills a missed punch on a working day, today or up to
|
|
27
|
+
`within_days` back, is approved as soon as the employee submits it, for up to
|
|
28
|
+
`per_week` days in a Monday–Sunday week. The punch is written the way an HR
|
|
29
|
+
approval writes it, with the usual audit row, and nobody is notified. Older
|
|
30
|
+
days, off days, fixes past the weekly limit, a change to a real punch, and a
|
|
31
|
+
day HR already regularized or rejected go to HR as ordinary tickets; the
|
|
32
|
+
flash says which happened. A check-out-only ticket for a day with no check-in
|
|
33
|
+
stays pending for HR.
|
|
34
|
+
`nil` (default) keeps HR as the only one who fixes punches. The ticket form
|
|
35
|
+
states the rule when it is on.
|
|
36
|
+
- The Leaves page links "Record leave for someone" for anyone who can approve
|
|
37
|
+
leave.
|
|
38
|
+
- The record-leave form says why somebody may be missing from the employee
|
|
39
|
+
list (no HR profile yet). Leadership gets a link to set one up.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- Approving a regularization ticket on a day the close job marked half day
|
|
44
|
+
sets it back to present. A half day HR set on purpose stays.
|
|
45
|
+
- Approving a ticket for a month whose payroll run is in review adds a warning
|
|
46
|
+
to that run, so the slip is recomputed before it is finalized.
|
|
47
|
+
- The admin overview and daily digest still count a day the close job checked
|
|
48
|
+
out as a missing checkout.
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- The sidebar, and the phone's More sheet, open scrolled to the current screen
|
|
53
|
+
instead of the top. A Manage item such as Approvals is no longer below the
|
|
54
|
+
fold.
|
|
55
|
+
- Page action buttons wrap on a narrow phone instead of running off the screen.
|
|
56
|
+
|
|
57
|
+
### Upgrading
|
|
58
|
+
|
|
59
|
+
Schedule `HrLite::AttendanceCloseJob` daily at `55 23 * * * Asia/Kolkata` in
|
|
60
|
+
your job scheduler (a run before noon closes the previous day). To let employees fix their own recent misses, set
|
|
61
|
+
`c.self_regularization = { within_days: 2, per_week: 2 }` in the initializer.
|
|
62
|
+
No migration.
|
|
63
|
+
|
|
10
64
|
## [0.18.0] - 2026-09-29
|
|
11
65
|
|
|
12
66
|
Salary follows leave. One migration (`paid_days` on leave requests, nullable;
|
data/README.md
CHANGED
|
@@ -142,6 +142,9 @@ HrLite.configure do |c|
|
|
|
142
142
|
PdfRenderer.render(template: template, assigns: assigns, cache_key: cache_key)
|
|
143
143
|
}
|
|
144
144
|
c.slip_release_day = 10 # employees see a slip from the 10th of the next month; nil (default) = on publish
|
|
145
|
+
# Self-fix: employees correct their own missed punch within 2 days, at most
|
|
146
|
+
# 2 a week. nil (default) = only HR fixes it, via a regularization ticket.
|
|
147
|
+
c.self_regularization = { within_days: 2, per_week: 2 }
|
|
145
148
|
|
|
146
149
|
# Mirror promotions into your own user model (optional).
|
|
147
150
|
c.on_designation_change = ->(user, designation) { user.update!(designation: designation) }
|
|
@@ -267,6 +270,13 @@ approved; regularization tickets (`/regularization_requests`) let someone who
|
|
|
267
270
|
forgot to punch propose the real times for admin approval, which writes them
|
|
268
271
|
onto the attendance record with a full audit trail.
|
|
269
272
|
|
|
273
|
+
With `config.self_regularization = { within_days: 2, per_week: 2 }`, a ticket that
|
|
274
|
+
fills a missed punch on a working day, today or up to 2 days back, is approved on
|
|
275
|
+
submit, by the employee, at most twice per Mon–Sun week. No approver is notified;
|
|
276
|
+
the audit row is the trail. Older days, off days, the third fix of a week, a change
|
|
277
|
+
to a real punch, a day HR already regularized or rejected, or a fix that cannot
|
|
278
|
+
apply (no check-in that day) stay ordinary tickets for HR.
|
|
279
|
+
|
|
270
280
|
### Holidays and calendar
|
|
271
281
|
|
|
272
282
|
`bin/rails hr_lite:seed` inserts the three fixed-date national holidays
|
|
@@ -353,6 +363,7 @@ whatever you run). Each is idempotent and sends nothing on a quiet day.
|
|
|
353
363
|
| `HrLite::DailyDigestJob` | each morning | Leadership digest: who is out today, pending approvals, flagged punches, missing checkouts |
|
|
354
364
|
| `HrLite::PayrollAutoDraftJob` | monthly, on the 1st | Draft and compute the previous month's payroll from attendance, then notify leadership for review (publishing stays a human action) |
|
|
355
365
|
| `HrLite::LeaveYearRolloverJob` | leave year's first day (Jan 1, or Jul 1 for a July–June year) | Materialize carry-forward into the new year's balances |
|
|
366
|
+
| `HrLite::AttendanceCloseJob` | each night, `55 23 * * * Asia/Kolkata` | Close the day: an open punch is checked out at 23:59 and marked half day; a run before noon (a retry, or a morning schedule that lets night shifts check out first) closes the previous day instead; nobody checked in stays absent. Each is emailed how to fix it. Skips weekends, holidays and approved leave. Run it once a day — a re-run tells the no-shows again. |
|
|
356
367
|
|
|
357
368
|
## Rake tasks
|
|
358
369
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Keeps the current screen in view. The rail and the phone's More sheet both
|
|
2
|
+
// scroll, and a Manage item sits below the fold. scrollIntoView is avoided on
|
|
3
|
+
// purpose: it would scroll the page as well as the menu.
|
|
4
|
+
(function () {
|
|
5
|
+
function centre(panel) {
|
|
6
|
+
var link = panel && panel.querySelector(".hrl-nav__link--active");
|
|
7
|
+
if (!link) return;
|
|
8
|
+
var box = panel.getBoundingClientRect();
|
|
9
|
+
var item = link.getBoundingClientRect();
|
|
10
|
+
panel.scrollTop += item.top - box.top - (box.height - item.height) / 2;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
centre(document.querySelector(".hrl-side"));
|
|
14
|
+
|
|
15
|
+
var more = document.querySelector(".hrl-tabbar__more");
|
|
16
|
+
if (more) {
|
|
17
|
+
more.addEventListener("toggle", function () {
|
|
18
|
+
if (more.open) centre(more.querySelector(".hrl-sheet"));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
})();
|
|
@@ -143,7 +143,7 @@
|
|
|
143
143
|
.hrl-page__head { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; margin: .25rem 0 .9rem; }
|
|
144
144
|
.hrl-page__title { font-size: 1.25rem; font-weight: 700; margin: 0; }
|
|
145
145
|
.hrl-page__sub { color: var(--hrl-muted); font-size: .86rem; margin: .15rem 0 0; width: 100%; }
|
|
146
|
-
.hrl-page__actions { margin-left: auto; display: flex; gap: .5rem; }
|
|
146
|
+
.hrl-page__actions { margin-left: auto; display: flex; flex-wrap: wrap; gap: .5rem; }
|
|
147
147
|
|
|
148
148
|
/* Cards */
|
|
149
149
|
.hrl-card {
|
|
@@ -13,7 +13,7 @@ module HrLite
|
|
|
13
13
|
def create
|
|
14
14
|
@request = RegularizationRequest.new(request_params.merge(user_id: hr_current_user.id))
|
|
15
15
|
if @request.save
|
|
16
|
-
redirect_to regularization_requests_path, notice:
|
|
16
|
+
redirect_to regularization_requests_path, notice: create_notice
|
|
17
17
|
else
|
|
18
18
|
render :new, status: :unprocessable_entity
|
|
19
19
|
end
|
|
@@ -29,6 +29,15 @@ module HrLite
|
|
|
29
29
|
|
|
30
30
|
private
|
|
31
31
|
|
|
32
|
+
def create_notice
|
|
33
|
+
rule = HrLite.config.self_regularization
|
|
34
|
+
return "Ticket raised — an admin will review it." unless rule
|
|
35
|
+
return "Fixed. #{helpers.pluralize(@request.self_fixes_left, 'self-fix')} left this week." if @request.approved?
|
|
36
|
+
|
|
37
|
+
"Sent to HR as a ticket — you can fix only a missed punch on a working day in the last #{rule[:within_days]} days yourself, " \
|
|
38
|
+
"#{rule[:per_week]} times a week."
|
|
39
|
+
end
|
|
40
|
+
|
|
32
41
|
def request_params
|
|
33
42
|
params.require(:regularization_request).permit(:date, :check_in_at, :check_out_at, :reason)
|
|
34
43
|
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module HrLite
|
|
2
|
+
# Closes the working day that ended last: a run at 23:55 closes today, and a
|
|
3
|
+
# retry or late run after midnight (or a morning schedule) closes yesterday.
|
|
4
|
+
# A punch left open is checked out at the day's end and paid as a half day.
|
|
5
|
+
# Nobody punched in already reads as absent (DayStatus), so they are only
|
|
6
|
+
# told. Leave days are left alone.
|
|
7
|
+
class AttendanceCloseJob < ApplicationJob
|
|
8
|
+
def perform(date: (Time.current - 12.hours).to_date)
|
|
9
|
+
return unless WorkingCalendar.new(date..date).working_day?(date)
|
|
10
|
+
|
|
11
|
+
on_leave = LeaveRequest.active_on(date).pluck(:user_id)
|
|
12
|
+
records = AttendanceRecord.for_date(date).index_by(&:user_id)
|
|
13
|
+
day = date.strftime("%d %b")
|
|
14
|
+
HrLite.active_employees(on: date).each do |user|
|
|
15
|
+
next if on_leave.include?(user.id)
|
|
16
|
+
|
|
17
|
+
record = records[user.id]
|
|
18
|
+
if record.nil? || record.check_in_at.nil?
|
|
19
|
+
tell(user, date, "attendance.missed_check_in", "You didn't check in on #{day} — marked absent")
|
|
20
|
+
elsif record.check_out_at.nil?
|
|
21
|
+
record.update!(check_out_at: date.end_of_day, status: "half_day",
|
|
22
|
+
regularization_note: AttendanceRecord::AUTO_CHECKOUT_NOTE)
|
|
23
|
+
tell(user, date, "attendance.missed_check_out", "You didn't check out on #{day} — marked half day")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def tell(user, date, event, title)
|
|
31
|
+
rule = HrLite.config.self_regularization
|
|
32
|
+
body = if rule
|
|
33
|
+
"Fix it yourself by #{(date + rule[:within_days]).strftime('%d %b')} (up to #{rule[:per_week]} a week), " \
|
|
34
|
+
"or raise a ticket for HR."
|
|
35
|
+
else
|
|
36
|
+
"Raise a regularization ticket to get it fixed."
|
|
37
|
+
end
|
|
38
|
+
Notifications.publish(event, title: title, body: body, path: "/regularization_requests/new?date=#{date.iso8601}",
|
|
39
|
+
bell_to: [ user ], email_to: [ user ])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module HrLite
|
|
2
2
|
class AttendanceRecord < ApplicationRecord
|
|
3
3
|
STATUSES = %w[present half_day].freeze
|
|
4
|
+
AUTO_CHECKOUT_NOTE = "Auto check-out: no check-out punched".freeze
|
|
4
5
|
|
|
5
6
|
belongs_to :user, class_name: HrLite.config.user_class
|
|
6
7
|
belongs_to :regularized_by, class_name: HrLite.config.user_class, optional: true
|
|
@@ -21,7 +21,7 @@ module HrLite
|
|
|
21
21
|
validate :no_approved_leave_conflict
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
after_create :
|
|
24
|
+
after_create :self_fix_or_notify
|
|
25
25
|
|
|
26
26
|
scope :pending, -> { where(status: "pending") }
|
|
27
27
|
scope :recent_first, -> { order(date: :desc, id: :desc) }
|
|
@@ -39,6 +39,27 @@ module HrLite
|
|
|
39
39
|
AttendanceRecord.find_by(user_id: user_id, date: date)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
# config.self_regularization: a missed punch on a recent working day, under
|
|
43
|
+
# the weekly cap, is fixed by the employee without HR. A real punch, or a
|
|
44
|
+
# day HR already decided or rejected, stays HR's call.
|
|
45
|
+
def self_fixable?
|
|
46
|
+
rule = HrLite.config.self_regularization
|
|
47
|
+
return false unless rule && date >= Date.current - rule[:within_days] && WorkingCalendar.new(date..date).working_day?(date)
|
|
48
|
+
|
|
49
|
+
day = punch
|
|
50
|
+
fills_gap = (check_in_at.nil? || !day&.check_in_at) &&
|
|
51
|
+
(check_out_at.nil? || !day&.check_out_at || day.regularization_note == AttendanceRecord::AUTO_CHECKOUT_NOTE)
|
|
52
|
+
fills_gap && !day&.regularized? && !self.class.exists?(user_id: user_id, date: date, status: "rejected") &&
|
|
53
|
+
self_fixes_left.positive?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Self-fixes the user still has in this ticket's Mon–Sun week.
|
|
57
|
+
def self_fixes_left
|
|
58
|
+
week = date.beginning_of_week(:monday)..date.end_of_week(:monday)
|
|
59
|
+
used = self.class.where(user_id: user_id, decided_by_id: user_id, status: "approved", date: week).count
|
|
60
|
+
HrLite.config.self_regularization[:per_week] - used
|
|
61
|
+
end
|
|
62
|
+
|
|
42
63
|
# --- transitions -------------------------------------------------------
|
|
43
64
|
|
|
44
65
|
# Applies the proposed times to the day's record (creating it if the
|
|
@@ -47,7 +68,7 @@ module HrLite
|
|
|
47
68
|
# A merge that would produce a nonsense record (checkout before the
|
|
48
69
|
# existing check-in, or a checkout with no check-in at all) raises
|
|
49
70
|
# InvalidMerge with the real story so the admin knows what to fix.
|
|
50
|
-
def approve!(actor:, note: nil)
|
|
71
|
+
def approve!(actor:, note: nil, notify: true)
|
|
51
72
|
transition!("approved", actor, note) do
|
|
52
73
|
# Re-checked at approval, not only at create: leave can be approved for
|
|
53
74
|
# that day while the ticket waits in the queue, and DayStatus ranks
|
|
@@ -64,7 +85,8 @@ module HrLite
|
|
|
64
85
|
raise InvalidMerge, "the day has no check-in — the ticket needs a check-in time too"
|
|
65
86
|
end
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
# Lifts only the close job's half day; one HR set on purpose stays.
|
|
89
|
+
record.status = "present" if record.status.blank? || record.regularization_note == AttendanceRecord::AUTO_CHECKOUT_NOTE
|
|
68
90
|
record.regularized_by_id = actor.id
|
|
69
91
|
record.regularized_at = Time.current
|
|
70
92
|
record.regularization_note = "Ticket ##{id}: #{reason}"
|
|
@@ -75,7 +97,12 @@ module HrLite
|
|
|
75
97
|
action: "regularize", subject: record, actor: actor,
|
|
76
98
|
changes: { "date" => record.date.to_s, "ticket" => id, "note" => reason }
|
|
77
99
|
)
|
|
100
|
+
# A slip computed before this fix still counts the old day; a recompute clears the note.
|
|
101
|
+
run = PayrollRun.find_by(period_month: date.beginning_of_month, status: "review")
|
|
102
|
+
run&.update!(warnings: run.warnings + [ "#{HrLite.display_name(user)}'s attendance on #{date.strftime('%d %b')} " \
|
|
103
|
+
"was fixed after compute — recompute" ])
|
|
78
104
|
end
|
|
105
|
+
return true unless notify # self-fix: the flash and the audit row are the trail
|
|
79
106
|
|
|
80
107
|
Notifications.publish(
|
|
81
108
|
"regularization.approved",
|
|
@@ -174,6 +201,16 @@ module HrLite
|
|
|
174
201
|
errors.add(:date, "is covered by your approved leave — cancel the leave first") if leave.exists?
|
|
175
202
|
end
|
|
176
203
|
|
|
204
|
+
# A self-fix approve! can't merge (e.g. no check-in yet) -> an ordinary ticket.
|
|
205
|
+
def self_fix_or_notify
|
|
206
|
+
user.lock! if HrLite.config.self_regularization # one user's tickets at once can't both spend the last fix
|
|
207
|
+
return approve!(actor: user, note: "Self-fixed", notify: false) if self_fixable?
|
|
208
|
+
|
|
209
|
+
notify_requested
|
|
210
|
+
rescue InvalidMerge
|
|
211
|
+
notify_requested
|
|
212
|
+
end
|
|
213
|
+
|
|
177
214
|
def notify_requested
|
|
178
215
|
Notifications.publish(
|
|
179
216
|
"regularization.requested",
|
|
@@ -30,8 +30,11 @@ module HrLite
|
|
|
30
30
|
AttendanceRecord.for_date(@date).flagged.includes(:user)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
# AttendanceCloseJob fills check_out_at, so the days it closed count too.
|
|
33
34
|
def missing_checkout_yesterday
|
|
34
|
-
AttendanceRecord.for_date(@date - 1)
|
|
35
|
+
yesterday = AttendanceRecord.for_date(@date - 1)
|
|
36
|
+
yesterday.missing_checkout.or(yesterday.where(regularization_note: AttendanceRecord::AUTO_CHECKOUT_NOTE))
|
|
37
|
+
.includes(:user)
|
|
35
38
|
end
|
|
36
39
|
|
|
37
40
|
def kpis
|
|
@@ -9,12 +9,19 @@
|
|
|
9
9
|
<%= render "hr_lite/shared/form_errors", record: @request %>
|
|
10
10
|
<div class="hrl-field">
|
|
11
11
|
<%= label_tag :employee_code, "Employee (type a name or code)" %>
|
|
12
|
-
<%= text_field_tag :employee_code, params[:employee_code], list: "hrl-employees", required: true, autocomplete: "off" %>
|
|
12
|
+
<%= text_field_tag :employee_code, params[:employee_code], list: "hrl-employees", required: true, autocomplete: "off", placeholder: "Start typing a name or code…" %>
|
|
13
13
|
<datalist id="hrl-employees">
|
|
14
14
|
<% @people.each do |p| %>
|
|
15
15
|
<option value="<%= p.employee_code %>"><%= hr_display_name(p.user) %></option>
|
|
16
16
|
<% end %>
|
|
17
17
|
</datalist>
|
|
18
|
+
<p class="hrl-small hrl-muted">Someone missing? Only people with an HR profile are listed.
|
|
19
|
+
<% if hr_leadership? %>
|
|
20
|
+
<a href="<%= hr_lite.admin_employees_path %>">Set up their profile</a>
|
|
21
|
+
<% else %>
|
|
22
|
+
Ask leadership to set up their HR profile.
|
|
23
|
+
<% end %>
|
|
24
|
+
</p>
|
|
18
25
|
</div>
|
|
19
26
|
<div class="hrl-field">
|
|
20
27
|
<%= f.label :leave_type_id, "Type" %>
|
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
<div class="hrl-page__actions">
|
|
5
5
|
<a class="hrl-btn" href="<%= hr_lite.leave_balances_path %>">Balances</a>
|
|
6
6
|
<a class="hrl-btn" href="<%= hr_lite.comp_off_requests_path %>">Comp-off</a>
|
|
7
|
+
<% if hr_can?("leave.approve", scope: :team) %>
|
|
8
|
+
<a class="hrl-btn" href="<%= hr_lite.new_admin_leave_request_path %>">Record leave for someone</a>
|
|
9
|
+
<% end %>
|
|
7
10
|
<a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_leave_request_path %>">Apply</a>
|
|
8
11
|
</div>
|
|
9
12
|
</div>
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
<section class="hrl-card">
|
|
7
7
|
<%= form_with model: @request, url: hr_lite.regularization_requests_path, scope: :regularization_request, local: true do |f| %>
|
|
8
8
|
<%= render "hr_lite/shared/form_errors", record: @request %>
|
|
9
|
+
<% if (rule = HrLite.config.self_regularization) %>
|
|
10
|
+
<p class="hrl-small hrl-muted">A missed punch on a working day from today back to <%= rule[:within_days] %> days ago is fixed as soon as you submit, up to <%= rule[:per_week] %> times a week. Anything else goes to HR as a ticket.</p>
|
|
11
|
+
<% end %>
|
|
9
12
|
<div class="hrl-field">
|
|
10
13
|
<%= f.label :date, "Which day needs fixing?" %>
|
|
11
14
|
<%= f.date_field :date, value: @request.date&.strftime("%Y-%m-%d"), max: Date.current %>
|
|
@@ -14,7 +14,7 @@ module HrLite
|
|
|
14
14
|
# the HR shell; nil (default) shows nothing.
|
|
15
15
|
:notifications
|
|
16
16
|
|
|
17
|
-
attr_reader :leave_year_start_month, :slip_release_day
|
|
17
|
+
attr_reader :leave_year_start_month, :slip_release_day, :self_regularization
|
|
18
18
|
|
|
19
19
|
# Misconfiguration must fail at boot, not as production 500s on every
|
|
20
20
|
# balance screen. Accepts "7" (ENV-friendly) and validates 1..12.
|
|
@@ -36,6 +36,15 @@ module HrLite
|
|
|
36
36
|
@slip_release_day = day
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
+
# nil = only HR fixes a missed punch. { within_days: 2, per_week: 2 } = an
|
|
40
|
+
# employee may fix their own day for 2 days after it, at most 2 a week.
|
|
41
|
+
def self_regularization=(value)
|
|
42
|
+
ok = value.nil? || (value.is_a?(Hash) && %i[within_days per_week].all? { |k| value[k].is_a?(Integer) && value[k].positive? })
|
|
43
|
+
raise ArgumentError, "self_regularization must be nil or { within_days:, per_week: } (positive integers), got #{value.inspect}" unless ok
|
|
44
|
+
|
|
45
|
+
@self_regularization = value
|
|
46
|
+
end
|
|
47
|
+
|
|
39
48
|
# 0.1.0 pre-release name for public_url_base; kept as an alias so early
|
|
40
49
|
# adopters' initializers don't break.
|
|
41
50
|
alias_method :mail_link_base, :public_url_base
|
|
@@ -77,6 +86,7 @@ module HrLite
|
|
|
77
86
|
@back_link = nil # optional {label:, url:} for the shell nav
|
|
78
87
|
@leave_year_start_month = 1 # 1 = calendar year; 7 = July–June leave year
|
|
79
88
|
@slip_release_day = nil # e.g. 10 = slips open on the 10th of the next month
|
|
89
|
+
@self_regularization = nil # e.g. { within_days: 2, per_week: 2 } — employees fix their own missed punch
|
|
80
90
|
|
|
81
91
|
# Leadership onboarding/offboarding. onboard_user must return a saved
|
|
82
92
|
# user record (default: create on user_class with whatever of
|
|
@@ -18,6 +18,8 @@ module HrLite
|
|
|
18
18
|
"leave.cancelled" => { bell: true, email: false, leadership_email: true, leadership_bell: false },
|
|
19
19
|
"attendance.flagged" => { bell: true, email: false, leadership_email: false, leadership_bell: false },
|
|
20
20
|
"attendance.regularized" => { bell: true, email: true, leadership_email: true, leadership_bell: false },
|
|
21
|
+
"attendance.missed_check_in" => { bell: true, email: true, leadership_email: false, leadership_bell: false },
|
|
22
|
+
"attendance.missed_check_out" => { bell: true, email: true, leadership_email: false, leadership_bell: false },
|
|
21
23
|
"payroll.finalized" => { bell: false, email: false, leadership_email: true, leadership_bell: true },
|
|
22
24
|
"payroll.published" => { bell: true, email: true, leadership_email: true, leadership_bell: false },
|
|
23
25
|
"kudos.mentioned" => { bell: true, email: true, leadership_email: false, leadership_bell: false },
|
data/lib/hr_lite/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hr_lite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.19.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kshitiz sinha
|
|
@@ -67,6 +67,7 @@ files:
|
|
|
67
67
|
- app/assets/javascripts/hr_lite/confirm.js
|
|
68
68
|
- app/assets/javascripts/hr_lite/geo_punch.js
|
|
69
69
|
- app/assets/javascripts/hr_lite/mention.js
|
|
70
|
+
- app/assets/javascripts/hr_lite/nav.js
|
|
70
71
|
- app/assets/javascripts/hr_lite/print.js
|
|
71
72
|
- app/assets/stylesheets/hr_lite/application.css
|
|
72
73
|
- app/assets/stylesheets/hr_lite/hr_lite.css
|
|
@@ -136,6 +137,7 @@ files:
|
|
|
136
137
|
- app/helpers/hr_lite/application_helper.rb
|
|
137
138
|
- app/jobs/hr_lite/application_job.rb
|
|
138
139
|
- app/jobs/hr_lite/approval_escalation_job.rb
|
|
140
|
+
- app/jobs/hr_lite/attendance_close_job.rb
|
|
139
141
|
- app/jobs/hr_lite/daily_digest_job.rb
|
|
140
142
|
- app/jobs/hr_lite/document_expiry_job.rb
|
|
141
143
|
- app/jobs/hr_lite/leave_year_rollover_job.rb
|