hr_lite 0.26.0 → 0.28.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 +9 -0
- data/app/controllers/hr_lite/admin/leave_balances_controller.rb +1 -0
- data/app/controllers/hr_lite/admin/leave_requests_controller.rb +26 -5
- data/app/models/hr_lite/leave_balance.rb +2 -1
- data/app/models/hr_lite/leave_request.rb +25 -7
- data/app/views/hr_lite/admin/leave_balances/index.html.erb +1 -1
- data/app/views/hr_lite/admin/leave_requests/edit.html.erb +30 -0
- data/app/views/hr_lite/admin/leave_requests/show.html.erb +17 -3
- data/config/routes.rb +1 -1
- data/lib/hr_lite/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 175c2fc4265dd0055f39926d529060dedeb14386991eb9f43d1b3f0e80112dce
|
|
4
|
+
data.tar.gz: 733cc4423053d7ca4b8d27acd03165dd073481f767131731179432392a3be335
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d26ff16fb6dc9bc592374a0920d659556b7ad8d466fccfa6a24084bfada3bd95fd4b756e2de2ae01138dab005d7e612fd58883b80e1a7ef333f53bd75748f191
|
|
7
|
+
data.tar.gz: 96b6f9c358ecba6d459da48a97cbdc866f5dbabb1a7ab1dff4345961cdf4e6780c0ae4f7dd56635a66aa41a5078b830423cb1740eada0acda5c4350b4d84590d
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.28.0] - 2026-10-10
|
|
11
|
+
|
|
12
|
+
- HR and leadership can cancel any approved or pending leave at any time, past dates included. A reason is required; it is kept as the decision note and in the audit log. Employees still cancel their own leave only before it starts. A cancelled past day with no punch shows as absent.
|
|
13
|
+
- Leave always accrues from the joining date. Somebody with no joining date (no HR profile) now accrues nothing instead of a full year, and the team balances grid flags them with "No joining date".
|
|
14
|
+
|
|
15
|
+
## [0.27.0] - 2026-10-10
|
|
16
|
+
|
|
17
|
+
- HR and admins can fix leave they recorded (type, dates, half day) at any time, past dates included, without cancelling it. A reason is required; it is kept as the decision note and in the audit log (`leave.corrected`), and the balance and loss-of-pay split are worked out again.
|
|
18
|
+
|
|
10
19
|
## [0.26.0] - 2026-10-10
|
|
11
20
|
|
|
12
21
|
Two migrations: `hr_lite_ca_reviews`, and a data migration creating the CA role and giving Super Admin the new `payroll.verify` permission. Schedule `CaReviewReminderJob` weekly and `CaReviewOverdueJob` on 31 March.
|
|
@@ -5,6 +5,7 @@ module HrLite
|
|
|
5
5
|
@year = sanitized_year
|
|
6
6
|
@types = LeaveType.active.where(paid: true).where.not(annual_quota: nil)
|
|
7
7
|
@employees = HrLite.employees.select { |employee| hr_reaches?("leave.view", employee) }
|
|
8
|
+
@no_joining_date = @employees.map(&:id) - EmployeeProfile.where(user_id: @employees.map(&:id)).where.not(date_of_joining: nil).pluck(:user_id)
|
|
8
9
|
end
|
|
9
10
|
|
|
10
11
|
# Manual credit/debit — also the comp-off credit mechanism.
|
|
@@ -37,6 +37,23 @@ module HrLite
|
|
|
37
37
|
render :new, status: :unprocessable_entity
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
+
# Leave HR recorded is fixed in place, any time, with a reason.
|
|
41
|
+
def edit
|
|
42
|
+
@request = find_recorded
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def update
|
|
46
|
+
@request = find_recorded
|
|
47
|
+
reason = params[:correction_reason].to_s.strip
|
|
48
|
+
@request.errors.add(:base, "A reason is required to fix this leave") if reason.blank?
|
|
49
|
+
attrs = params.require(:leave_request).permit(:leave_type_id, :start_date, :end_date, :half_day_part)
|
|
50
|
+
if reason.present? && @request.correct!(actor: hr_current_user, attrs: attrs, reason: reason)
|
|
51
|
+
return redirect_to(admin_leave_request_path(@request), notice: "Leave fixed.")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
render :edit, status: :unprocessable_entity
|
|
55
|
+
end
|
|
56
|
+
|
|
40
57
|
def approve
|
|
41
58
|
request = find_decidable
|
|
42
59
|
if request.approve!(actor: hr_current_user, note: params[:decision_note].presence)
|
|
@@ -56,12 +73,14 @@ module HrLite
|
|
|
56
73
|
# not be released at all, and the quota stayed spent.
|
|
57
74
|
def cancel
|
|
58
75
|
request = find_decidable
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
redirect_to
|
|
76
|
+
note = params[:decision_note].to_s.strip
|
|
77
|
+
if !request.cancellable_by?(hr_current_user)
|
|
78
|
+
redirect_to admin_leave_request_path(request), alert: "This leave can no longer be cancelled."
|
|
79
|
+
elsif note.blank?
|
|
80
|
+
redirect_to admin_leave_request_path(request), alert: "A reason is required to cancel."
|
|
62
81
|
else
|
|
63
|
-
|
|
64
|
-
|
|
82
|
+
request.cancel!(actor: hr_current_user, note: note)
|
|
83
|
+
redirect_to admin_leave_requests_path, notice: "Leave cancelled — the balance is released."
|
|
65
84
|
end
|
|
66
85
|
end
|
|
67
86
|
|
|
@@ -91,6 +110,8 @@ module HrLite
|
|
|
91
110
|
|
|
92
111
|
def find_decidable = decidable.find(params[:id])
|
|
93
112
|
|
|
113
|
+
def find_recorded = decidable.approved.where.not(created_by_id: nil).where("created_by_id <> user_id").find(params[:id])
|
|
114
|
+
|
|
94
115
|
def recordable = hr_scope(EmployeeProfile.includes(:user), "leave.approve").where.not(user_id: hr_current_user.id)
|
|
95
116
|
end
|
|
96
117
|
end
|
|
@@ -97,7 +97,8 @@ module HrLite
|
|
|
97
97
|
# nil when the person joins only after this leave year ends.
|
|
98
98
|
def accrual_start(range)
|
|
99
99
|
doj = employment_window.first
|
|
100
|
-
return
|
|
100
|
+
return nil if doj.nil? # no joining date, no accrual — never a full year by default
|
|
101
|
+
return range.first if doj <= range.first
|
|
101
102
|
|
|
102
103
|
start = doj.day <= 15 ? doj.beginning_of_month : doj.next_month.beginning_of_month
|
|
103
104
|
start > range.last ? nil : start
|
|
@@ -91,23 +91,25 @@ module HrLite
|
|
|
91
91
|
|
|
92
92
|
# Owner may cancel while pending, or an approved future leave (quota
|
|
93
93
|
# returns automatically because `used` is computed). Admins may cancel
|
|
94
|
-
#
|
|
94
|
+
# anybody else's pending/approved leave at any time, past dates included.
|
|
95
95
|
def cancellable_by?(actor)
|
|
96
|
-
return false unless pending? ||
|
|
96
|
+
return false unless pending? || approved?
|
|
97
|
+
return pending? || start_date > Date.current if user_id == actor.id
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
HrLite.admin?(actor) || HrLite.leadership?(actor)
|
|
99
100
|
end
|
|
100
101
|
|
|
101
|
-
def cancel!(actor:)
|
|
102
|
+
def cancel!(actor:, note: nil)
|
|
102
103
|
was_approved = approved?
|
|
103
104
|
transaction do
|
|
104
105
|
self.status = "cancelled"
|
|
105
106
|
self.decided_by_id = actor.id
|
|
106
107
|
self.decided_at = Time.current
|
|
108
|
+
self.decision_note = note if note.present?
|
|
107
109
|
save!
|
|
108
110
|
# Cancelling an APPROVED leave hands the days back to the balance, so
|
|
109
111
|
# it is a quota change as much as a status change.
|
|
110
|
-
audit!("cancelled", actor, was_approved ? "was approved" : nil)
|
|
112
|
+
audit!("cancelled", actor, [ note.presence, was_approved ? "was approved" : nil ].compact.join("; ").presence)
|
|
111
113
|
# And nobody should still be asked to decide a request that has been
|
|
112
114
|
# called off — it would sit in their inbox for ever.
|
|
113
115
|
approval_route.cancel_all!
|
|
@@ -123,6 +125,24 @@ module HrLite
|
|
|
123
125
|
true
|
|
124
126
|
end
|
|
125
127
|
|
|
128
|
+
# HR fixes leave it recorded — any time, even past dates — without a
|
|
129
|
+
# cancel. Re-run as a fresh recording so the create checks and the
|
|
130
|
+
# balance / loss-of-pay split are worked out exactly as the first time.
|
|
131
|
+
def correct!(actor:, attrs:, reason:)
|
|
132
|
+
fixed = false
|
|
133
|
+
transaction do
|
|
134
|
+
assign_attributes(attrs.merge(status: "pending", paid_days: nil, half_day: false))
|
|
135
|
+
raise ActiveRecord::Rollback unless valid?(:create) && save(validate: false)
|
|
136
|
+
|
|
137
|
+
audit!("corrected", actor, reason)
|
|
138
|
+
fixed = approve_outright!(actor: actor, note: "Corrected: #{reason}") || raise(ActiveRecord::Rollback)
|
|
139
|
+
end
|
|
140
|
+
errors.add(:base, "Not enough #{leave_type.name} balance") unless fixed || errors.any?
|
|
141
|
+
fixed
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def recorded_by_hr? = created_by_id.present? && created_by_id != user_id
|
|
145
|
+
|
|
126
146
|
def date_range_label
|
|
127
147
|
if start_date == end_date
|
|
128
148
|
"#{start_date.strftime('%d %b')}#{half_day ? " (#{half_day_part.present? ? "#{half_day_part} half" : 'half day'})" : ''}"
|
|
@@ -230,8 +250,6 @@ module HrLite
|
|
|
230
250
|
)
|
|
231
251
|
end
|
|
232
252
|
|
|
233
|
-
def recorded_by_hr? = created_by_id.present? && created_by_id != user_id
|
|
234
|
-
|
|
235
253
|
# The balance covers whole half-days only, never less than nothing.
|
|
236
254
|
def covered_by(available) = [ (BigDecimal(available.to_s) * 2).floor / BigDecimal(2), 0 ].max
|
|
237
255
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<tbody>
|
|
15
15
|
<% @employees.each do |employee| %>
|
|
16
16
|
<tr>
|
|
17
|
-
<td data-label="Employee"><%= hr_display_name(employee) %></td>
|
|
17
|
+
<td data-label="Employee"><%= hr_display_name(employee) %><% if @no_joining_date.include?(employee.id) %><br><span class="hrl-badge hrl-badge--bad">No joining date — no leave accrues</span><% end %></td>
|
|
18
18
|
<% @types.each do |type| %>
|
|
19
19
|
<% balance = HrLite::LeaveBalance.for(employee, type, @year) %>
|
|
20
20
|
<td data-label="<%= type.code %>" class="hrl-num"><span><strong><%= hrl_days(balance.available) %></strong> left<br><span class="hrl-small hrl-muted"><%= hrl_days(balance.used) %>/<%= hrl_days(balance.entitled) %> used</span></span></td>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Fix leave" } %>
|
|
2
|
+
<a class="hrl-backlink" href="<%= hr_lite.admin_leave_request_path(@request) %>"><span aria-hidden="true">‹</span> Leave</a>
|
|
3
|
+
<div class="hrl-page__head">
|
|
4
|
+
<h1 class="hrl-page__title">Fix leave — <%= hr_display_name(@request.user) %></h1>
|
|
5
|
+
<p class="hrl-page__sub">Change the type, dates or day. The balance and loss of pay are worked out again, and they get an email.</p>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<section class="hrl-card">
|
|
9
|
+
<%= form_with model: @request, url: hr_lite.admin_leave_request_path(@request), method: :patch, local: true do |f| %>
|
|
10
|
+
<%= render "hr_lite/shared/form_errors", record: @request %>
|
|
11
|
+
<div class="hrl-field">
|
|
12
|
+
<%= f.label :leave_type_id, "Type" %>
|
|
13
|
+
<%= f.collection_select :leave_type_id, HrLite::LeaveType.active, :id, :name %>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="hrl-field"><%= f.label :start_date, "From" %><%= f.date_field :start_date %></div>
|
|
16
|
+
<div class="hrl-field"><%= f.label :end_date, "To" %><%= f.date_field :end_date %></div>
|
|
17
|
+
<div class="hrl-field">
|
|
18
|
+
<%= f.label :half_day_part, "Day" %>
|
|
19
|
+
<%= f.select :half_day_part, HrLite::DAY_PARTS %>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="hrl-field">
|
|
22
|
+
<%= label_tag :correction_reason, "Reason for the fix (required)" %>
|
|
23
|
+
<%= text_area_tag :correction_reason, params[:correction_reason], required: true, class: "hrl-input" %>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="hrl-form-actions">
|
|
26
|
+
<%= f.submit "Save fix", class: "hrl-btn hrl-btn--primary" %>
|
|
27
|
+
<a class="hrl-btn" href="<%= hr_lite.admin_leave_request_path(@request) %>">Back</a>
|
|
28
|
+
</div>
|
|
29
|
+
<% end %>
|
|
30
|
+
</section>
|
|
@@ -24,15 +24,29 @@
|
|
|
24
24
|
</dl>
|
|
25
25
|
</section>
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
<% if @request.approved? && @request.recorded_by_hr? %>
|
|
28
|
+
<section class="hrl-card">
|
|
29
|
+
<h2 class="hrl-card__title">Fix</h2>
|
|
30
|
+
<p class="hrl-small hrl-muted">Recorded by HR. Change it any time — no need to cancel.</p>
|
|
31
|
+
<div class="hrl-form-actions">
|
|
32
|
+
<a class="hrl-btn" href="<%= hr_lite.edit_admin_leave_request_path(@request) %>">Fix this leave</a>
|
|
33
|
+
</div>
|
|
34
|
+
</section>
|
|
35
|
+
<% end %>
|
|
36
|
+
|
|
37
|
+
<%# HR can call off approved leave at any time, past dates included — the
|
|
28
38
|
balance is recomputed live, so cancelling gives the days straight back. %>
|
|
29
39
|
<% if @request.cancellable_by?(hr_current_user) && !@request.pending? %>
|
|
30
40
|
<section class="hrl-card">
|
|
31
41
|
<h2 class="hrl-card__title">Cancel</h2>
|
|
32
|
-
<p class="hrl-small hrl-muted">Releases the days back to their balance.
|
|
33
|
-
|
|
42
|
+
<p class="hrl-small hrl-muted">Releases the days back to their balance. A past day with
|
|
43
|
+
no punch will then show as absent.</p>
|
|
34
44
|
<%= form_with url: hr_lite.cancel_admin_leave_request_path(@request), method: :post, local: true,
|
|
35
45
|
html: { data: { turbo_confirm: "Cancel this approved leave?" } } do %>
|
|
46
|
+
<div class="hrl-field">
|
|
47
|
+
<label for="cancel_note">Reason (required)</label>
|
|
48
|
+
<textarea name="decision_note" id="cancel_note" class="hrl-input" required></textarea>
|
|
49
|
+
</div>
|
|
36
50
|
<div class="hrl-form-actions">
|
|
37
51
|
<button class="hrl-btn hrl-btn--danger" type="submit">Cancel this leave</button>
|
|
38
52
|
</div>
|
data/config/routes.rb
CHANGED
|
@@ -60,7 +60,7 @@ HrLite::Engine.routes.draw do
|
|
|
60
60
|
resources :attendances, only: %i[index show update], param: :user_id do
|
|
61
61
|
collection { get :week }
|
|
62
62
|
end
|
|
63
|
-
resources :leave_requests, only: %i[index show new create] do
|
|
63
|
+
resources :leave_requests, only: %i[index show new create edit update] do
|
|
64
64
|
member { post :approve; post :reject; post :cancel }
|
|
65
65
|
end
|
|
66
66
|
resources :leave_balances, only: :index do
|
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.28.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kshitiz sinha
|
|
@@ -262,6 +262,7 @@ files:
|
|
|
262
262
|
- app/views/hr_lite/admin/hr_requests/index.html.erb
|
|
263
263
|
- app/views/hr_lite/admin/hr_requests/show.html.erb
|
|
264
264
|
- app/views/hr_lite/admin/leave_balances/index.html.erb
|
|
265
|
+
- app/views/hr_lite/admin/leave_requests/edit.html.erb
|
|
265
266
|
- app/views/hr_lite/admin/leave_requests/index.html.erb
|
|
266
267
|
- app/views/hr_lite/admin/leave_requests/new.html.erb
|
|
267
268
|
- app/views/hr_lite/admin/leave_requests/show.html.erb
|