hr_lite 0.16.0 → 0.18.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 +63 -0
- data/README.md +1 -0
- data/app/controllers/hr_lite/admin/benefits_controller.rb +61 -0
- data/app/controllers/hr_lite/admin/expense_categories_controller.rb +43 -0
- data/app/controllers/hr_lite/admin/leave_balances_controller.rb +4 -1
- data/app/controllers/hr_lite/admin/leave_requests_controller.rb +3 -3
- data/app/controllers/hr_lite/admin/payroll_line_items_controller.rb +47 -0
- data/app/controllers/hr_lite/admin/payroll_runs_controller.rb +3 -1
- data/app/controllers/hr_lite/leave_requests_controller.rb +3 -1
- data/app/controllers/hr_lite/salary_slips_controller.rb +6 -6
- data/app/helpers/hr_lite/application_helper.rb +12 -6
- data/app/jobs/hr_lite/payroll_auto_draft_job.rb +4 -3
- data/app/jobs/hr_lite/slips_ready_job.rb +10 -0
- data/app/models/hr_lite/audit_log.rb +1 -1
- data/app/models/hr_lite/benefit_enrolment.rb +1 -1
- data/app/models/hr_lite/leave_balance.rb +2 -1
- data/app/models/hr_lite/leave_request.rb +38 -17
- data/app/models/hr_lite/payroll_line_item.rb +5 -3
- data/app/models/hr_lite/payroll_run.rb +19 -3
- data/app/models/hr_lite/salary_slip.rb +6 -0
- data/app/services/hr_lite/attendance_summary.rb +12 -15
- data/app/views/hr_lite/admin/benefits/index.html.erb +65 -0
- data/app/views/hr_lite/admin/benefits/new.html.erb +37 -0
- data/app/views/hr_lite/admin/expense_categories/_form.html.erb +16 -0
- data/app/views/hr_lite/admin/expense_categories/edit.html.erb +3 -0
- data/app/views/hr_lite/admin/expense_categories/index.html.erb +30 -0
- data/app/views/hr_lite/admin/expense_categories/new.html.erb +3 -0
- data/app/views/hr_lite/admin/leave_balances/index.html.erb +4 -0
- data/app/views/hr_lite/admin/leave_requests/show.html.erb +3 -0
- data/app/views/hr_lite/admin/leave_types/index.html.erb +1 -0
- data/app/views/hr_lite/admin/payroll_line_items/index.html.erb +47 -0
- data/app/views/hr_lite/admin/payroll_line_items/new.html.erb +42 -0
- data/app/views/hr_lite/admin/payroll_runs/show.html.erb +1 -0
- data/app/views/hr_lite/admin/salary_structures/_form.html.erb +5 -1
- data/app/views/hr_lite/admin/shared/_approvals_tabs.html.erb +3 -1
- data/app/views/hr_lite/leave_requests/index.html.erb +1 -0
- data/app/views/hr_lite/leave_requests/new.html.erb +1 -0
- data/app/views/hr_lite/salary_slips/index.html.erb +1 -1
- data/app/views/layouts/hr_lite/application.html.erb +4 -4
- data/config/routes.rb +5 -0
- data/db/migrate/20260929081109_add_paid_days_to_hr_lite_leave_requests.rb +5 -0
- data/lib/hr_lite/configuration.rb +11 -1
- data/lib/hr_lite/notifications.rb +6 -2
- data/lib/hr_lite/version.rb +1 -1
- metadata +14 -1
|
@@ -2,7 +2,8 @@ module HrLite
|
|
|
2
2
|
# Month roll-up per user — the payroll contract. Exact math per date:
|
|
3
3
|
#
|
|
4
4
|
# holiday / weekend / present / paid full-day leave -> payable +1
|
|
5
|
-
# unpaid full-day leave (LWP
|
|
5
|
+
# unpaid full-day leave (LWP, or a paid-type day beyond the balance —
|
|
6
|
+
# LeaveRequest#unpaid_on) / absent -> lop +1
|
|
6
7
|
# half-day punch (no leave) -> payable +0.5, lop +0.5
|
|
7
8
|
# half-day PAID leave: leave half payable; other half payable if
|
|
8
9
|
# punched, else lop (unpaid half-day leave: leave half is lop too)
|
|
@@ -54,13 +55,11 @@ module HrLite
|
|
|
54
55
|
summary[:payable_days] += BigDecimal("0.5")
|
|
55
56
|
summary[:lop_days] += BigDecimal("0.5")
|
|
56
57
|
when :leave
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
summary[:lop_days] += 1
|
|
63
|
-
end
|
|
58
|
+
unpaid = day.leave.paid? ? day.leave.unpaid_on(date) : 1
|
|
59
|
+
summary[:paid_leave] += 1 - unpaid
|
|
60
|
+
summary[:payable_days] += 1 - unpaid
|
|
61
|
+
summary[:unpaid_leave] += unpaid
|
|
62
|
+
summary[:lop_days] += unpaid
|
|
64
63
|
when :half_day_leave
|
|
65
64
|
apply_half_day_leave(summary, day)
|
|
66
65
|
when :upcoming
|
|
@@ -77,13 +76,11 @@ module HrLite
|
|
|
77
76
|
def self.apply_half_day_leave(summary, day)
|
|
78
77
|
half = BigDecimal("0.5")
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
summary[:lop_days] += half
|
|
86
|
-
end
|
|
79
|
+
unpaid = day.leave.paid? ? day.leave.unpaid_on(day.leave.start_date) : half
|
|
80
|
+
summary[:paid_leave] += half - unpaid
|
|
81
|
+
summary[:payable_days] += half - unpaid
|
|
82
|
+
summary[:unpaid_leave] += unpaid
|
|
83
|
+
summary[:lop_days] += unpaid
|
|
87
84
|
|
|
88
85
|
if day.record&.check_in_at
|
|
89
86
|
summary[:present] += half
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Benefits" } %>
|
|
2
|
+
<div class="hrl-page__head">
|
|
3
|
+
<h1 class="hrl-page__title">Benefits</h1>
|
|
4
|
+
<div class="hrl-page__actions">
|
|
5
|
+
<a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_admin_benefit_path %>">Add benefit</a>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<% if @benefits.none? %>
|
|
10
|
+
<section class="hrl-card"><p class="hrl-card--empty">No benefits yet — add one, then enrol people here.</p></section>
|
|
11
|
+
<% end %>
|
|
12
|
+
<% people = HrLite.active_employees.map { |u| [ hr_display_name(u), u.id ] } %>
|
|
13
|
+
<% @benefits.each do |benefit| %>
|
|
14
|
+
<section class="hrl-card">
|
|
15
|
+
<h2 class="hrl-card__title"><%= benefit.name %></h2>
|
|
16
|
+
<p class="hrl-small hrl-muted">
|
|
17
|
+
<%= benefit.kind.humanize %><%= " · #{benefit.provider}" if benefit.provider.present? %>
|
|
18
|
+
· cover <%= hrl_money(benefit.coverage) %>
|
|
19
|
+
</p>
|
|
20
|
+
<%= form_with url: hr_lite.enrol_admin_benefit_path(benefit), method: :post, local: true do %>
|
|
21
|
+
<div class="hrl-field">
|
|
22
|
+
<%= label_tag "enrol_user_#{benefit.id}", "Employee" %>
|
|
23
|
+
<%= select_tag :user_id, options_for_select(people), id: "enrol_user_#{benefit.id}" %>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="hrl-field">
|
|
26
|
+
<%= label_tag "enrol_on_#{benefit.id}", "Covered from" %>
|
|
27
|
+
<%= date_field_tag :enrolled_on, Date.current, id: "enrol_on_#{benefit.id}" %>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="hrl-field">
|
|
30
|
+
<%= label_tag "enrol_dependants_#{benefit.id}", "Dependants covered" %>
|
|
31
|
+
<%= number_field_tag :dependants, 0, min: 0, id: "enrol_dependants_#{benefit.id}" %>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="hrl-form-actions"><%= submit_tag "Enrol", class: "hrl-btn hrl-btn--primary" %></div>
|
|
34
|
+
<% end %>
|
|
35
|
+
</section>
|
|
36
|
+
<% end %>
|
|
37
|
+
|
|
38
|
+
<% if @enrolments.any? %>
|
|
39
|
+
<section class="hrl-card">
|
|
40
|
+
<h2 class="hrl-card__title">Enrolled</h2>
|
|
41
|
+
<div class="hrl-table-wrap">
|
|
42
|
+
<table class="hrl-table hrl-table--stack">
|
|
43
|
+
<thead><tr><th>Who</th><th>Benefit</th><th>Since</th><th>Dependants</th><th></th></tr></thead>
|
|
44
|
+
<tbody>
|
|
45
|
+
<% @enrolments.each do |enrolment| %>
|
|
46
|
+
<tr>
|
|
47
|
+
<td data-label="Who"><%= hr_display_name(enrolment.user) %></td>
|
|
48
|
+
<td data-label="Benefit"><%= enrolment.benefit.name %></td>
|
|
49
|
+
<td data-label="Since"><%= enrolment.enrolled_on.strftime("%d %b %Y") %></td>
|
|
50
|
+
<td data-label="Dependants" class="hrl-num"><%= enrolment.dependants %></td>
|
|
51
|
+
<td data-label="">
|
|
52
|
+
<%= form_with url: hr_lite.unenrol_admin_benefit_path(enrolment.benefit), method: :post do %>
|
|
53
|
+
<%= hidden_field_tag :user_id, enrolment.user_id %>
|
|
54
|
+
<%= date_field_tag :ended_on, Date.current, class: "hrl-input", "aria-label": "Last day of cover" %>
|
|
55
|
+
<%= submit_tag "End cover", class: "hrl-btn" %>
|
|
56
|
+
<% end %>
|
|
57
|
+
</td>
|
|
58
|
+
</tr>
|
|
59
|
+
<% end %>
|
|
60
|
+
</tbody>
|
|
61
|
+
</table>
|
|
62
|
+
</div>
|
|
63
|
+
<%= hrl_pagination %>
|
|
64
|
+
</section>
|
|
65
|
+
<% end %>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Add benefit" } %>
|
|
2
|
+
<div class="hrl-page__head"><h1 class="hrl-page__title">Add benefit</h1></div>
|
|
3
|
+
|
|
4
|
+
<section class="hrl-card">
|
|
5
|
+
<%= form_with model: @benefit, url: hr_lite.admin_benefits_path, method: :post, local: true do |f| %>
|
|
6
|
+
<%= render "hr_lite/shared/form_errors", record: @benefit %>
|
|
7
|
+
<div class="hrl-field"><%= f.label :name %><%= f.text_field :name %></div>
|
|
8
|
+
<div class="hrl-field">
|
|
9
|
+
<%= f.label :kind %>
|
|
10
|
+
<%= f.select :kind, HrLite::Benefit::KINDS.map { |k| [ k.humanize, k ] } %>
|
|
11
|
+
</div>
|
|
12
|
+
<div class="hrl-field"><%= f.label :provider, "Provider (optional)" %><%= f.text_field :provider %></div>
|
|
13
|
+
<div class="hrl-field">
|
|
14
|
+
<%= f.label :policy_number, "Policy number (optional)" %>
|
|
15
|
+
<%= f.text_field :policy_number %>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="hrl-field">
|
|
18
|
+
<%= f.label :coverage, "Cover (₹)" %>
|
|
19
|
+
<%= f.text_field :coverage, value: @benefit.coverage&.to_s("F"), inputmode: "decimal" %>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="hrl-field">
|
|
22
|
+
<%= f.label :employer_premium, "Premium the company pays (₹)" %>
|
|
23
|
+
<%= f.text_field :employer_premium, value: @benefit.employer_premium&.to_s("F"), inputmode: "decimal" %>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="hrl-field">
|
|
26
|
+
<%= f.label :employee_premium, "Premium the employee pays (₹)" %>
|
|
27
|
+
<%= f.text_field :employee_premium, value: @benefit.employee_premium&.to_s("F"), inputmode: "decimal" %>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="hrl-field"><%= f.label :effective_from, "Starts" %><%= f.date_field :effective_from %></div>
|
|
30
|
+
<div class="hrl-field"><%= f.label :expires_on, "Expires" %><%= f.date_field :expires_on %></div>
|
|
31
|
+
<div class="hrl-field"><%= f.label :notes %><%= f.text_field :notes %></div>
|
|
32
|
+
<div class="hrl-form-actions">
|
|
33
|
+
<%= f.submit "Save", class: "hrl-btn hrl-btn--primary" %>
|
|
34
|
+
<a class="hrl-btn" href="<%= hr_lite.admin_benefits_path %>">Cancel</a>
|
|
35
|
+
</div>
|
|
36
|
+
<% end %>
|
|
37
|
+
</section>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<section class="hrl-card">
|
|
2
|
+
<%= form_with model: expense_category, url: url, method: method, local: true do |f| %>
|
|
3
|
+
<%= render "hr_lite/shared/form_errors", record: expense_category %>
|
|
4
|
+
<div class="hrl-field"><%= f.label :name %><%= f.text_field :name %></div>
|
|
5
|
+
<div class="hrl-field">
|
|
6
|
+
<%= f.label :monthly_cap, "Monthly cap per person (₹, blank = no cap)" %>
|
|
7
|
+
<%= f.number_field :monthly_cap, step: 0.01, min: 0, value: expense_category.monthly_cap&.to_s("F") %>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="hrl-field hrl-field--check"><%= f.check_box :receipt_required %><%= f.label :receipt_required, "Receipt required" %></div>
|
|
10
|
+
<div class="hrl-field hrl-field--check"><%= f.check_box :active %><%= f.label :active, "Active (offered on the claim form)" %></div>
|
|
11
|
+
<div class="hrl-form-actions">
|
|
12
|
+
<%= f.submit "Save", class: "hrl-btn hrl-btn--primary" %>
|
|
13
|
+
<a class="hrl-btn" href="<%= hr_lite.admin_expense_categories_path %>">Cancel</a>
|
|
14
|
+
</div>
|
|
15
|
+
<% end %>
|
|
16
|
+
</section>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Edit expense category" } %>
|
|
2
|
+
<div class="hrl-page__head"><h1 class="hrl-page__title">Edit <%= @expense_category.name %></h1></div>
|
|
3
|
+
<%= render "form", expense_category: @expense_category, url: hr_lite.admin_expense_category_path(@expense_category), method: :patch %>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Expense categories" } %>
|
|
2
|
+
<div class="hrl-page__head">
|
|
3
|
+
<h1 class="hrl-page__title">Expense categories</h1>
|
|
4
|
+
<div class="hrl-page__actions">
|
|
5
|
+
<a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_admin_expense_category_path %>">Add category</a>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<section class="hrl-card">
|
|
10
|
+
<% if @expense_categories.any? %>
|
|
11
|
+
<div class="hrl-table-wrap">
|
|
12
|
+
<table class="hrl-table hrl-table--stack">
|
|
13
|
+
<thead><tr><th>Name</th><th>Monthly cap</th><th>Receipt</th><th>Active</th><th></th></tr></thead>
|
|
14
|
+
<tbody>
|
|
15
|
+
<% @expense_categories.each do |category| %>
|
|
16
|
+
<tr>
|
|
17
|
+
<td data-label="Name"><%= category.name %></td>
|
|
18
|
+
<td data-label="Monthly cap" class="hrl-num"><%= category.uncapped? ? "No cap" : hrl_money(category.monthly_cap) %></td>
|
|
19
|
+
<td data-label="Receipt"><%= category.receipt_required ? "Required" : "Optional" %></td>
|
|
20
|
+
<td data-label="Active"><%= category.active ? "Yes" : "No" %></td>
|
|
21
|
+
<td data-label=""><a class="hrl-small" href="<%= hr_lite.edit_admin_expense_category_path(category) %>">Edit</a></td>
|
|
22
|
+
</tr>
|
|
23
|
+
<% end %>
|
|
24
|
+
</tbody>
|
|
25
|
+
</table>
|
|
26
|
+
</div>
|
|
27
|
+
<% else %>
|
|
28
|
+
<p class="hrl-card--empty">No categories yet — nobody can file an expense claim until one exists.</p>
|
|
29
|
+
<% end %>
|
|
30
|
+
</section>
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
<h1 class="hrl-page__title">Team leave balances <%= HrLite::LeaveYear.label(@year) %></h1>
|
|
4
4
|
</div>
|
|
5
5
|
|
|
6
|
+
<%= render "hr_lite/admin/shared/approvals_tabs", active: :balances %>
|
|
7
|
+
|
|
6
8
|
<section class="hrl-card">
|
|
7
9
|
<div class="hrl-table-wrap">
|
|
8
10
|
<table class="hrl-table">
|
|
@@ -24,6 +26,7 @@
|
|
|
24
26
|
</div>
|
|
25
27
|
</section>
|
|
26
28
|
|
|
29
|
+
<% if hr_can?("leave.manage", scope: :team) %>
|
|
27
30
|
<section class="hrl-card">
|
|
28
31
|
<h2 class="hrl-card__title">Adjust a balance</h2>
|
|
29
32
|
<p class="hrl-small hrl-muted">Positive credits (comp-off), negative debits. Fully audited.</p>
|
|
@@ -56,3 +59,4 @@
|
|
|
56
59
|
</div>
|
|
57
60
|
<% end %>
|
|
58
61
|
</section>
|
|
62
|
+
<% end %>
|
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
<% unless @request.leave_type.unlimited? %>
|
|
19
19
|
<dt>Balance</dt>
|
|
20
20
|
<dd><%= @balance.available.to_f %> of <%= @balance.entitled.to_f %> available</dd>
|
|
21
|
+
<% if (@request.pending? || @request.approved?) && (short = @request.days_beyond_balance).positive? %>
|
|
22
|
+
<dt>Loss of pay</dt><dd><%= short.to_s("F") %> day(s) beyond the balance will be unpaid</dd>
|
|
23
|
+
<% end %>
|
|
21
24
|
<% end %>
|
|
22
25
|
<% if @request.decision_note.present? %><dt>Decision note</dt><dd><%= @request.decision_note %></dd><% end %>
|
|
23
26
|
</dl>
|
|
@@ -35,5 +35,6 @@
|
|
|
35
35
|
<div class="hrl-row">
|
|
36
36
|
<a class="hrl-btn" href="<%= hr_lite.admin_office_locations_path %>">Office locations</a>
|
|
37
37
|
<a class="hrl-btn" href="<%= hr_lite.admin_holidays_path %>">Holidays</a>
|
|
38
|
+
<a class="hrl-btn" href="<%= hr_lite.admin_expense_categories_path %>">Expense categories</a>
|
|
38
39
|
<a class="hrl-btn" href="<%= hr_lite.edit_admin_setting_path %>">Weekend policy</a>
|
|
39
40
|
</div>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<% content_for(:page_title) { "One-off pay items" } %>
|
|
2
|
+
<div class="hrl-page__head">
|
|
3
|
+
<h1 class="hrl-page__title">One-off pay items</h1>
|
|
4
|
+
<div class="hrl-page__actions">
|
|
5
|
+
<a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_admin_payroll_line_item_path(month: @month.strftime("%Y-%m")) %>">Add an item</a>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<section class="hrl-card">
|
|
10
|
+
<div class="hrl-row">
|
|
11
|
+
<a class="hrl-btn" href="<%= hr_lite.admin_payroll_line_items_path(month: (@month - 1.month).strftime("%Y-%m")) %>">←</a>
|
|
12
|
+
<strong><%= @month.strftime("%B %Y") %></strong>
|
|
13
|
+
<a class="hrl-btn" href="<%= hr_lite.admin_payroll_line_items_path(month: (@month + 1.month).strftime("%Y-%m")) %>">→</a>
|
|
14
|
+
</div>
|
|
15
|
+
<p class="hrl-muted hrl-small">
|
|
16
|
+
Payroll reads these when the month's run is computed. Add them before
|
|
17
|
+
computing, or recompute the run while it is still in review.
|
|
18
|
+
</p>
|
|
19
|
+
</section>
|
|
20
|
+
|
|
21
|
+
<section class="hrl-card">
|
|
22
|
+
<% if @items.empty? %>
|
|
23
|
+
<p class="hrl-muted">Nothing added for <%= @month.strftime("%B %Y") %>.</p>
|
|
24
|
+
<% else %>
|
|
25
|
+
<div class="hrl-table-wrap">
|
|
26
|
+
<table class="hrl-table hrl-table--stack">
|
|
27
|
+
<thead><tr><th>Person</th><th>Item</th><th>Amount</th><th>Note</th><th></th></tr></thead>
|
|
28
|
+
<tbody>
|
|
29
|
+
<% @items.each do |item| %>
|
|
30
|
+
<tr>
|
|
31
|
+
<td data-label="Person"><%= hr_display_name(item.user) %></td>
|
|
32
|
+
<td data-label="Item"><%= item.component.label %> (<%= item.component.kind %>)</td>
|
|
33
|
+
<td data-label="Amount" class="hrl-num"><%= hrl_money(item.amount) %></td>
|
|
34
|
+
<td data-label="Note"><%= item.note %></td>
|
|
35
|
+
<td data-label="">
|
|
36
|
+
<%= button_to "Remove", hr_lite.admin_payroll_line_item_path(item),
|
|
37
|
+
method: :delete, class: "hrl-btn hrl-btn--danger",
|
|
38
|
+
form: { data: { turbo_confirm: "Remove this item?" } } %>
|
|
39
|
+
</td>
|
|
40
|
+
</tr>
|
|
41
|
+
<% end %>
|
|
42
|
+
</tbody>
|
|
43
|
+
</table>
|
|
44
|
+
</div>
|
|
45
|
+
<% end %>
|
|
46
|
+
<%= hrl_pagination %>
|
|
47
|
+
</section>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Add a pay item" } %>
|
|
2
|
+
<div class="hrl-page__head"><h1 class="hrl-page__title">Add a pay item</h1></div>
|
|
3
|
+
|
|
4
|
+
<section class="hrl-card">
|
|
5
|
+
<p class="hrl-muted hrl-small">
|
|
6
|
+
A bonus, incentive, arrears, reimbursement or one-time deduction on one
|
|
7
|
+
month's payslip. Payroll picks it up when that month's run is computed —
|
|
8
|
+
add it first, or recompute the run while it is in review. A month that
|
|
9
|
+
has already been paid is refused.
|
|
10
|
+
</p>
|
|
11
|
+
<%= form_with model: @item, url: hr_lite.admin_payroll_line_items_path, method: :post, local: true do |f| %>
|
|
12
|
+
<%= render "hr_lite/shared/form_errors", record: @item %>
|
|
13
|
+
<div class="hrl-field">
|
|
14
|
+
<%= f.label :user_id, "Employee" %>
|
|
15
|
+
<%= f.select :user_id, options_for_select(HrLite.employees.map { |u| [ hr_display_name(u), u.id ] },
|
|
16
|
+
@item.user_id) %>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="hrl-field">
|
|
19
|
+
<%= f.label :component_id, "Item" %>
|
|
20
|
+
<%# Loan repayment is left out: finalize books that line against the person's loans. %>
|
|
21
|
+
<%= f.select :component_id,
|
|
22
|
+
HrLite::SalaryComponent.active.ordered.where.not(code: "loan_repayment")
|
|
23
|
+
.map { |c| [ "#{c.label} (#{c.kind})", c.id ] } %>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="hrl-field">
|
|
26
|
+
<%= f.label :period_month, "Month" %>
|
|
27
|
+
<%= f.month_field :period_month, value: @item.period_month&.strftime("%Y-%m") %>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="hrl-field">
|
|
30
|
+
<%= f.label :amount, "Amount (₹)" %>
|
|
31
|
+
<%= f.text_field :amount, value: @item.amount&.to_s("F"), inputmode: "decimal" %>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="hrl-field">
|
|
34
|
+
<%= f.label :note, "Note (optional)" %>
|
|
35
|
+
<%= f.text_field :note %>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="hrl-form-actions">
|
|
38
|
+
<%= f.submit "Add", class: "hrl-btn hrl-btn--primary" %>
|
|
39
|
+
<a class="hrl-btn" href="<%= hr_lite.admin_payroll_line_items_path(month: @item.period_month&.strftime("%Y-%m")) %>">Cancel</a>
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
</section>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
<div class="hrl-page__head">
|
|
3
3
|
<h1 class="hrl-page__title">Payroll — <%= @run.label %></h1>
|
|
4
4
|
<div class="hrl-page__actions">
|
|
5
|
+
<a class="hrl-btn" href="<%= hr_lite.admin_payroll_line_items_path(month: @run.period_month.strftime("%Y-%m")) %>">One-off items</a>
|
|
5
6
|
<a class="hrl-btn" href="<%= hr_lite.admin_payroll_runs_path %>">All runs</a>
|
|
6
7
|
</div>
|
|
7
8
|
</div>
|
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
<div class="hrl-field hrl-field--check"><%= f.check_box :esi_applicable %><%= f.label :esi_applicable, "ESI applicable (auto-exempt above gross ceiling)" %></div>
|
|
15
15
|
<div class="hrl-field">
|
|
16
16
|
<%= f.label :pt_state, "Professional tax state" %>
|
|
17
|
-
|
|
17
|
+
<%# A stored rate card carries no PT (slabs live in their own table), so list those states too. %>
|
|
18
|
+
<% pt_states = [ HrLite::ProfessionalTaxSlab::NO_LEVY, structure.pt_state ] |
|
|
19
|
+
HrLite::ProfessionalTaxSlab.distinct.order(:state).pluck(:state) |
|
|
20
|
+
HrLite::StatutoryRateCard.for(Date.current)[:pt].keys %>
|
|
21
|
+
<%= f.select :pt_state, pt_states.map { |s| [ s.humanize, s ] } %>
|
|
18
22
|
</div>
|
|
19
23
|
<div class="hrl-field"><%= f.label :notes %><%= f.text_area :notes %></div>
|
|
20
24
|
<div class="hrl-form-actions">
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<%# locals: active (:leaves | :comp_off | :regularizations) %>
|
|
1
|
+
<%# locals: active (:leaves | :comp_off | :regularizations | :balances) %>
|
|
2
2
|
<div class="hrl-row" style="margin-bottom:.85rem;">
|
|
3
3
|
<a class="hrl-btn <%= 'hrl-btn--primary' if active == :leaves %>"
|
|
4
4
|
href="<%= hr_lite.admin_leave_requests_path %>">Leaves</a>
|
|
@@ -8,4 +8,6 @@
|
|
|
8
8
|
<a class="hrl-btn <%= 'hrl-btn--primary' if active == :regularizations %>"
|
|
9
9
|
href="<%= hr_lite.admin_regularization_requests_path %>">Regularization
|
|
10
10
|
<% if (n = HrLite::RegularizationRequest.pending.count).positive? %><span class="hrl-badge hrl-badge--warn"><%= n %></span><% end %></a>
|
|
11
|
+
<a class="hrl-btn <%= 'hrl-btn--primary' if active == :balances %>"
|
|
12
|
+
href="<%= hr_lite.admin_leave_balances_path %>">Balances</a>
|
|
11
13
|
</div>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
<div class="hrl-page__head">
|
|
3
3
|
<h1 class="hrl-page__title">My leaves</h1>
|
|
4
4
|
<div class="hrl-page__actions">
|
|
5
|
+
<a class="hrl-btn" href="<%= hr_lite.leave_balances_path %>">Balances</a>
|
|
5
6
|
<a class="hrl-btn" href="<%= hr_lite.comp_off_requests_path %>">Comp-off</a>
|
|
6
7
|
<a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_leave_request_path %>">Apply</a>
|
|
7
8
|
</div>
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
</div>
|
|
5
5
|
|
|
6
6
|
<%= render "hr_lite/leave_requests/balance_chips" %>
|
|
7
|
+
<p class="hrl-small hrl-muted">Days beyond your balance are unpaid (loss of pay), cut from that month's salary.</p>
|
|
7
8
|
|
|
8
9
|
<section class="hrl-card">
|
|
9
10
|
<%= form_with model: @request, url: hr_lite.leave_requests_path, local: true do |f| %>
|
|
@@ -24,6 +24,6 @@
|
|
|
24
24
|
</div>
|
|
25
25
|
<%= hrl_pagination %>
|
|
26
26
|
<% else %>
|
|
27
|
-
<p class="hrl-card--empty">No published salary slips yet
|
|
27
|
+
<p class="hrl-card--empty">No published salary slips yet.<% if (day = HrLite.config.slip_release_day) %> Each month's slip opens on the <%= day.ordinalize %> of the next month.<% end %></p>
|
|
28
28
|
<% end %>
|
|
29
29
|
</section>
|
|
@@ -30,10 +30,10 @@
|
|
|
30
30
|
<%= hrl_nav_link(item) %>
|
|
31
31
|
<% end %>
|
|
32
32
|
|
|
33
|
-
<% if hr_admin? || hr_leadership? %>
|
|
33
|
+
<% if hr_admin? || hr_leadership? || hr_can?("expense.approve", scope: :team) %>
|
|
34
34
|
<span class="hrl-side__group">Manage</span>
|
|
35
35
|
<% hrl_nav_items(HrLite::ApplicationHelper::ADMIN_NAV_ITEMS).each do |item| %>
|
|
36
|
-
<%= hrl_nav_link(item) %>
|
|
36
|
+
<%= hrl_nav_link(item) if item[:if] || hr_admin? || hr_leadership? %>
|
|
37
37
|
<% end %>
|
|
38
38
|
<% end %>
|
|
39
39
|
|
|
@@ -100,10 +100,10 @@
|
|
|
100
100
|
<% rest.each do |item| %>
|
|
101
101
|
<%= hrl_nav_link(item) %>
|
|
102
102
|
<% end %>
|
|
103
|
-
<% if hr_admin? || hr_leadership? %>
|
|
103
|
+
<% if hr_admin? || hr_leadership? || hr_can?("expense.approve", scope: :team) %>
|
|
104
104
|
<span class="hrl-side__group">Manage</span>
|
|
105
105
|
<% hrl_nav_items(HrLite::ApplicationHelper::ADMIN_NAV_ITEMS).each do |item| %>
|
|
106
|
-
<%= hrl_nav_link(item) %>
|
|
106
|
+
<%= hrl_nav_link(item) if item[:if] || hr_admin? || hr_leadership? %>
|
|
107
107
|
<% end %>
|
|
108
108
|
<% end %>
|
|
109
109
|
<% if hr_leadership? || hr_superadmin? %>
|
data/config/routes.rb
CHANGED
|
@@ -91,11 +91,15 @@ HrLite::Engine.routes.draw do
|
|
|
91
91
|
resources :assets, only: %i[index new create] do
|
|
92
92
|
member { post :assign; post :take_back }
|
|
93
93
|
end
|
|
94
|
+
resources :benefits, only: %i[index new create] do
|
|
95
|
+
member { post :enrol; post :unenrol }
|
|
96
|
+
end
|
|
94
97
|
resources :checklists, only: :index do
|
|
95
98
|
member { post :complete; post :reopen }
|
|
96
99
|
end
|
|
97
100
|
resources :leave_types, except: :show
|
|
98
101
|
resources :office_locations, except: :show
|
|
102
|
+
resources :expense_categories, except: %i[show destroy]
|
|
99
103
|
resources :holidays, only: %i[index create update destroy] do
|
|
100
104
|
collection { post :bulk_create }
|
|
101
105
|
end
|
|
@@ -119,6 +123,7 @@ HrLite::Engine.routes.draw do
|
|
|
119
123
|
get :register
|
|
120
124
|
end
|
|
121
125
|
end
|
|
126
|
+
resources :payroll_line_items, only: %i[index new create destroy]
|
|
122
127
|
resources :salary_slips, only: %i[show update]
|
|
123
128
|
resource :setting, only: %i[edit update]
|
|
124
129
|
resources :audit_logs, only: :index
|
|
@@ -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
|
|
17
|
+
attr_reader :leave_year_start_month, :slip_release_day
|
|
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.
|
|
@@ -27,6 +27,15 @@ module HrLite
|
|
|
27
27
|
@leave_year_start_month = month
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
+
# nil = an employee sees a slip the moment it is published; 10 = the
|
|
31
|
+
# October slip opens on 10 November. 1..28 so every month has that day.
|
|
32
|
+
def slip_release_day=(value)
|
|
33
|
+
day = value.presence && Integer(value)
|
|
34
|
+
raise ArgumentError, "slip_release_day must be 1..28 or nil, got #{value.inspect}" if day && !(1..28).cover?(day)
|
|
35
|
+
|
|
36
|
+
@slip_release_day = day
|
|
37
|
+
end
|
|
38
|
+
|
|
30
39
|
# 0.1.0 pre-release name for public_url_base; kept as an alias so early
|
|
31
40
|
# adopters' initializers don't break.
|
|
32
41
|
alias_method :mail_link_base, :public_url_base
|
|
@@ -67,6 +76,7 @@ module HrLite
|
|
|
67
76
|
@notification_matrix = nil # resolved lazily to Notifications::DEFAULT_MATRIX
|
|
68
77
|
@back_link = nil # optional {label:, url:} for the shell nav
|
|
69
78
|
@leave_year_start_month = 1 # 1 = calendar year; 7 = July–June leave year
|
|
79
|
+
@slip_release_day = nil # e.g. 10 = slips open on the 10th of the next month
|
|
70
80
|
|
|
71
81
|
# Leadership onboarding/offboarding. onboard_user must return a saved
|
|
72
82
|
# user record (default: create on user_class with whatever of
|
|
@@ -48,7 +48,7 @@ module HrLite
|
|
|
48
48
|
"resignation.accepted" => { bell: true, email: true, leadership_email: true, leadership_bell: false },
|
|
49
49
|
"resignation.withdrawn" => { bell: true, email: false, leadership_email: true, leadership_bell: false },
|
|
50
50
|
"employee.onboarded" => { bell: true, email: true, leadership_email: true, leadership_bell: false },
|
|
51
|
-
"payroll.draft_ready" => { bell:
|
|
51
|
+
"payroll.draft_ready" => { bell: true, email: true, leadership_email: false, leadership_bell: false },
|
|
52
52
|
"leave.team_notice" => { bell: true, email: false, leadership_email: false, leadership_bell: false },
|
|
53
53
|
"comp_off.requested" => { bell: true, email: false, leadership_email: true, leadership_bell: true },
|
|
54
54
|
"comp_off.approved" => { bell: true, email: true, leadership_email: true, leadership_bell: false },
|
|
@@ -73,8 +73,10 @@ module HrLite
|
|
|
73
73
|
# employee-scoped path ("Your appraisal has been shared", /appraisals/7):
|
|
74
74
|
# fanned out verbatim, leadership read them as being about themselves and
|
|
75
75
|
# followed a link their own scope 404s or resolves to their own record.
|
|
76
|
+
# `skip_leadership:` is for a delayed employee half of an event whose
|
|
77
|
+
# leadership copy already went out.
|
|
76
78
|
def publish(event, title:, body: nil, path: nil, bell_to: [], email_to: [], lines: [],
|
|
77
|
-
diff: nil, link_url: nil, leadership: {})
|
|
79
|
+
diff: nil, link_url: nil, leadership: {}, skip_leadership: false)
|
|
78
80
|
row = matrix[event.to_s]
|
|
79
81
|
unless row
|
|
80
82
|
Rails.logger.warn("[hr_lite] unknown notification event #{event}")
|
|
@@ -87,6 +89,8 @@ module HrLite
|
|
|
87
89
|
|
|
88
90
|
deliver_bells(event, row, bell_to, title, body, path)
|
|
89
91
|
deliver_emails(row, email_to, title, body, path, lines, link_url)
|
|
92
|
+
return if skip_leadership
|
|
93
|
+
|
|
90
94
|
deliver_leadership_email(event, row, lead_title, lead_body, lead_path, lines, diff)
|
|
91
95
|
deliver_leadership_bells(event, row, bell_to, lead_title, lead_body, lead_path)
|
|
92
96
|
nil
|
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.18.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kshitiz sinha
|
|
@@ -75,11 +75,13 @@ files:
|
|
|
75
75
|
- app/controllers/hr_lite/admin/attendances_controller.rb
|
|
76
76
|
- app/controllers/hr_lite/admin/audit_logs_controller.rb
|
|
77
77
|
- app/controllers/hr_lite/admin/base_controller.rb
|
|
78
|
+
- app/controllers/hr_lite/admin/benefits_controller.rb
|
|
78
79
|
- app/controllers/hr_lite/admin/checklists_controller.rb
|
|
79
80
|
- app/controllers/hr_lite/admin/comp_off_requests_controller.rb
|
|
80
81
|
- app/controllers/hr_lite/admin/designation_changes_controller.rb
|
|
81
82
|
- app/controllers/hr_lite/admin/documents_controller.rb
|
|
82
83
|
- app/controllers/hr_lite/admin/employees_controller.rb
|
|
84
|
+
- app/controllers/hr_lite/admin/expense_categories_controller.rb
|
|
83
85
|
- app/controllers/hr_lite/admin/expenses_controller.rb
|
|
84
86
|
- app/controllers/hr_lite/admin/holidays_controller.rb
|
|
85
87
|
- app/controllers/hr_lite/admin/hr_requests_controller.rb
|
|
@@ -90,6 +92,7 @@ files:
|
|
|
90
92
|
- app/controllers/hr_lite/admin/loans_controller.rb
|
|
91
93
|
- app/controllers/hr_lite/admin/office_locations_controller.rb
|
|
92
94
|
- app/controllers/hr_lite/admin/overview_controller.rb
|
|
95
|
+
- app/controllers/hr_lite/admin/payroll_line_items_controller.rb
|
|
93
96
|
- app/controllers/hr_lite/admin/payroll_runs_controller.rb
|
|
94
97
|
- app/controllers/hr_lite/admin/policies_controller.rb
|
|
95
98
|
- app/controllers/hr_lite/admin/regularization_requests_controller.rb
|
|
@@ -137,6 +140,7 @@ files:
|
|
|
137
140
|
- app/jobs/hr_lite/document_expiry_job.rb
|
|
138
141
|
- app/jobs/hr_lite/leave_year_rollover_job.rb
|
|
139
142
|
- app/jobs/hr_lite/payroll_auto_draft_job.rb
|
|
143
|
+
- app/jobs/hr_lite/slips_ready_job.rb
|
|
140
144
|
- app/mailers/hr_lite/application_mailer.rb
|
|
141
145
|
- app/mailers/hr_lite/event_mailer.rb
|
|
142
146
|
- app/models/concerns/hr_lite/approvable.rb
|
|
@@ -214,6 +218,8 @@ files:
|
|
|
214
218
|
- app/views/hr_lite/admin/attendances/index.html.erb
|
|
215
219
|
- app/views/hr_lite/admin/attendances/show.html.erb
|
|
216
220
|
- app/views/hr_lite/admin/audit_logs/index.html.erb
|
|
221
|
+
- app/views/hr_lite/admin/benefits/index.html.erb
|
|
222
|
+
- app/views/hr_lite/admin/benefits/new.html.erb
|
|
217
223
|
- app/views/hr_lite/admin/checklists/index.html.erb
|
|
218
224
|
- app/views/hr_lite/admin/comp_off_requests/index.html.erb
|
|
219
225
|
- app/views/hr_lite/admin/comp_off_requests/show.html.erb
|
|
@@ -225,6 +231,10 @@ files:
|
|
|
225
231
|
- app/views/hr_lite/admin/employees/index.html.erb
|
|
226
232
|
- app/views/hr_lite/admin/employees/new.html.erb
|
|
227
233
|
- app/views/hr_lite/admin/employees/show.html.erb
|
|
234
|
+
- app/views/hr_lite/admin/expense_categories/_form.html.erb
|
|
235
|
+
- app/views/hr_lite/admin/expense_categories/edit.html.erb
|
|
236
|
+
- app/views/hr_lite/admin/expense_categories/index.html.erb
|
|
237
|
+
- app/views/hr_lite/admin/expense_categories/new.html.erb
|
|
228
238
|
- app/views/hr_lite/admin/expenses/index.html.erb
|
|
229
239
|
- app/views/hr_lite/admin/holidays/index.html.erb
|
|
230
240
|
- app/views/hr_lite/admin/hr_requests/index.html.erb
|
|
@@ -245,6 +255,8 @@ files:
|
|
|
245
255
|
- app/views/hr_lite/admin/office_locations/index.html.erb
|
|
246
256
|
- app/views/hr_lite/admin/office_locations/new.html.erb
|
|
247
257
|
- app/views/hr_lite/admin/overview/index.html.erb
|
|
258
|
+
- app/views/hr_lite/admin/payroll_line_items/index.html.erb
|
|
259
|
+
- app/views/hr_lite/admin/payroll_line_items/new.html.erb
|
|
248
260
|
- app/views/hr_lite/admin/payroll_runs/index.html.erb
|
|
249
261
|
- app/views/hr_lite/admin/payroll_runs/new.html.erb
|
|
250
262
|
- app/views/hr_lite/admin/payroll_runs/show.html.erb
|
|
@@ -361,6 +373,7 @@ files:
|
|
|
361
373
|
- db/migrate/20260818010401_create_hr_lite_assets_and_onboarding.rb
|
|
362
374
|
- db/migrate/20260927100145_add_id_card_fields_to_hr_lite_employee_profiles.rb
|
|
363
375
|
- db/migrate/20260927101031_add_created_by_id_to_hr_lite_leave_requests.rb
|
|
376
|
+
- db/migrate/20260929081109_add_paid_days_to_hr_lite_leave_requests.rb
|
|
364
377
|
- lib/generators/hr_lite/install/install_generator.rb
|
|
365
378
|
- lib/generators/hr_lite/install/templates/AFTER_INSTALL
|
|
366
379
|
- lib/generators/hr_lite/install/templates/initializer.rb
|