hr_lite 0.15.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 +111 -0
- data/README.md +5 -1
- data/app/assets/javascripts/hr_lite/print.js +5 -0
- data/app/assets/stylesheets/hr_lite/hr_lite.css +72 -0
- data/app/controllers/hr_lite/admin/benefits_controller.rb +61 -0
- data/app/controllers/hr_lite/admin/employees_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/expense_categories_controller.rb +43 -0
- data/app/controllers/hr_lite/admin/holidays_controller.rb +7 -0
- data/app/controllers/hr_lite/admin/leave_balances_controller.rb +4 -1
- data/app/controllers/hr_lite/admin/leave_requests_controller.rb +24 -1
- 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/admin/policies_controller.rb +29 -0
- data/app/controllers/hr_lite/hr_requests_controller.rb +2 -0
- data/app/controllers/hr_lite/id_cards_controller.rb +25 -0
- 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 +21 -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/employee_profile.rb +21 -3
- data/app/models/hr_lite/hr_request.rb +12 -3
- data/app/models/hr_lite/leave_balance.rb +2 -1
- data/app/models/hr_lite/leave_request.rb +51 -19
- 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/policy.rb +15 -0
- data/app/models/hr_lite/salary_slip.rb +6 -0
- data/app/models/hr_lite/setting.rb +1 -1
- 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/employees/_form.html.erb +5 -0
- data/app/views/hr_lite/admin/employees/show.html.erb +1 -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/hr_requests/show.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/index.html.erb +3 -0
- data/app/views/hr_lite/admin/leave_requests/new.html.erb +35 -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/policies/index.html.erb +27 -0
- data/app/views/hr_lite/admin/policies/new.html.erb +22 -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/id_cards/show.html.erb +86 -0
- 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/hr_lite/shared/_bell.html.erb +3 -0
- data/app/views/layouts/hr_lite/application.html.erb +6 -4
- data/config/routes.rb +9 -1
- data/db/migrate/20260927100145_add_id_card_fields_to_hr_lite_employee_profiles.rb +6 -0
- data/db/migrate/20260927101031_add_created_by_id_to_hr_lite_leave_requests.rb +5 -0
- data/db/migrate/20260929081109_add_paid_days_to_hr_lite_leave_requests.rb +5 -0
- data/lib/hr_lite/configuration.rb +15 -2
- data/lib/hr_lite/notifications.rb +10 -5
- data/lib/hr_lite/version.rb +1 -1
- data/lib/hr_lite.rb +1 -0
- metadata +38 -1
|
@@ -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>
|
|
@@ -57,6 +57,11 @@
|
|
|
57
57
|
<div class="hrl-field"><%= f.label :work_location %><%= f.text_field :work_location %></div>
|
|
58
58
|
<div class="hrl-field"><%= f.label :date_of_birth %><%= f.date_field :date_of_birth %></div>
|
|
59
59
|
<div class="hrl-field"><%= f.label :date_of_joining %><%= f.date_field :date_of_joining %></div>
|
|
60
|
+
<div class="hrl-field">
|
|
61
|
+
<%= f.label :blood_group %>
|
|
62
|
+
<%= f.select :blood_group, HrLite::EmployeeProfile::BLOOD_GROUPS, include_blank: "—" %>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="hrl-field"><%= f.label :emergency_contact, "Emergency contact (printed on the ID card)" %><%= f.telephone_field :emergency_contact %></div>
|
|
60
65
|
<div class="hrl-field">
|
|
61
66
|
<%= f.label :date_of_exit, "Date of exit (leave blank while employed)" %>
|
|
62
67
|
<%= f.date_field :date_of_exit %>
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<h1 class="hrl-page__title"><%= hr_display_name(@profile.user) %> <span class="hrl-muted">(<%= @profile.employee_code %>)</span></h1>
|
|
4
4
|
<div class="hrl-page__actions">
|
|
5
5
|
<a class="hrl-btn" href="<%= hr_lite.edit_admin_employee_path(@profile) %>">Edit</a>
|
|
6
|
+
<a class="hrl-btn" href="<%= hr_lite.id_card_path(user_id: @profile.user_id) %>">ID card</a>
|
|
6
7
|
<% if hr_superadmin? %>
|
|
7
8
|
<a class="hrl-btn" href="<%= hr_lite.new_admin_employee_appraisal_path(@profile) %>">New appraisal</a>
|
|
8
9
|
<a class="hrl-btn" href="<%= hr_lite.new_admin_employee_designation_change_path(@profile) %>">Role change</a>
|
|
@@ -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>
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
<%= @request.created_at.to_date.strftime("%d %b %Y") %> ·
|
|
8
8
|
<%= hrl_request_status_badge(@request.status) %>
|
|
9
9
|
</p>
|
|
10
|
+
<% if @request.category == "id_card" %>
|
|
11
|
+
<a class="hrl-btn hrl-mt" href="<%= hr_lite.id_card_path(user_id: @request.user_id) %>">Open the ID card to print</a>
|
|
12
|
+
<% end %>
|
|
10
13
|
<% if @request.body.present? %>
|
|
11
14
|
<div class="hrl-prose"><%= simple_format(@request.body) %></div>
|
|
12
15
|
<% end %>
|
|
@@ -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 %>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<% content_for(:page_title) { "Leave approvals" } %>
|
|
2
2
|
<div class="hrl-page__head">
|
|
3
3
|
<h1 class="hrl-page__title">Leave approvals</h1>
|
|
4
|
+
<div class="hrl-page__actions">
|
|
5
|
+
<a class="hrl-btn" href="<%= hr_lite.new_admin_leave_request_path %>">Record leave</a>
|
|
6
|
+
</div>
|
|
4
7
|
</div>
|
|
5
8
|
|
|
6
9
|
<%= render "hr_lite/admin/shared/approvals_tabs", active: :leaves %>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Record leave" } %>
|
|
2
|
+
<div class="hrl-page__head">
|
|
3
|
+
<h1 class="hrl-page__title">Record leave for an employee</h1>
|
|
4
|
+
<p class="hrl-page__sub">For leave somebody took but never applied for. It is saved as approved, and they get an email.</p>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<section class="hrl-card">
|
|
8
|
+
<%= form_with model: @request, url: hr_lite.admin_leave_requests_path, method: :post, local: true do |f| %>
|
|
9
|
+
<%= render "hr_lite/shared/form_errors", record: @request %>
|
|
10
|
+
<div class="hrl-field">
|
|
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" %>
|
|
13
|
+
<datalist id="hrl-employees">
|
|
14
|
+
<% @people.each do |p| %>
|
|
15
|
+
<option value="<%= p.employee_code %>"><%= hr_display_name(p.user) %></option>
|
|
16
|
+
<% end %>
|
|
17
|
+
</datalist>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="hrl-field">
|
|
20
|
+
<%= f.label :leave_type_id, "Type" %>
|
|
21
|
+
<%= f.collection_select :leave_type_id, HrLite::LeaveType.active, :id, :name %>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="hrl-field"><%= f.label :start_date, "From" %><%= f.date_field :start_date %></div>
|
|
24
|
+
<div class="hrl-field"><%= f.label :end_date, "To" %><%= f.date_field :end_date %></div>
|
|
25
|
+
<div class="hrl-field hrl-field--check">
|
|
26
|
+
<%= f.check_box :half_day %>
|
|
27
|
+
<%= f.label :half_day, "Half day (single-day only)" %>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="hrl-field"><%= f.label :reason, "Note (optional)" %><%= f.text_area :reason %></div>
|
|
30
|
+
<div class="hrl-form-actions">
|
|
31
|
+
<%= f.submit "Record leave", class: "hrl-btn hrl-btn--primary" %>
|
|
32
|
+
<a class="hrl-btn" href="<%= hr_lite.admin_leave_requests_path %>">Cancel</a>
|
|
33
|
+
</div>
|
|
34
|
+
<% end %>
|
|
35
|
+
</section>
|
|
@@ -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>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Policies and announcements" } %>
|
|
2
|
+
<div class="hrl-page__head">
|
|
3
|
+
<h1 class="hrl-page__title">Policies and announcements</h1>
|
|
4
|
+
<div class="hrl-page__actions"><a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_admin_policy_path %>">Publish</a></div>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<section class="hrl-card">
|
|
8
|
+
<% if @policies.empty? %>
|
|
9
|
+
<p class="hrl-muted">Nothing published yet.</p>
|
|
10
|
+
<% else %>
|
|
11
|
+
<div class="hrl-table-wrap">
|
|
12
|
+
<table class="hrl-table hrl-table--stack">
|
|
13
|
+
<thead><tr><th>Title</th><th>Kind</th><th>In force</th><th>Still to acknowledge</th></tr></thead>
|
|
14
|
+
<tbody>
|
|
15
|
+
<% @policies.each do |policy| %>
|
|
16
|
+
<tr>
|
|
17
|
+
<td data-label="Title"><%= policy.title %> <span class="hrl-badge hrl-badge--muted">v<%= policy.version %></span></td>
|
|
18
|
+
<td data-label="Kind"><%= policy.announcement? ? "Announcement" : "Policy" %></td>
|
|
19
|
+
<td data-label="In force"><%= policy.effective_from.strftime("%d %b %Y") %></td>
|
|
20
|
+
<td data-label="Still to acknowledge"><%= policy.announcement? ? "—" : policy.outstanding_for.count %></td>
|
|
21
|
+
</tr>
|
|
22
|
+
<% end %>
|
|
23
|
+
</tbody>
|
|
24
|
+
</table>
|
|
25
|
+
</div>
|
|
26
|
+
<% end %>
|
|
27
|
+
</section>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Publish" } %>
|
|
2
|
+
<div class="hrl-page__head">
|
|
3
|
+
<h1 class="hrl-page__title">Publish a policy or announcement</h1>
|
|
4
|
+
<p class="hrl-page__sub">Every employee gets an email and a notification. Reusing a title publishes the next version and asks everyone again.</p>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<section class="hrl-card">
|
|
8
|
+
<%= form_with model: @policy, url: hr_lite.admin_policies_path, method: :post, local: true do |f| %>
|
|
9
|
+
<%= render "hr_lite/shared/form_errors", record: @policy %>
|
|
10
|
+
<div class="hrl-field"><%= f.label :title %><%= f.text_field :title, required: true %></div>
|
|
11
|
+
<div class="hrl-field"><%= f.label :body, "Text" %><%= f.text_area :body, rows: 10, required: true %></div>
|
|
12
|
+
<div class="hrl-field"><%= f.label :effective_from, "In force from" %><%= f.date_field :effective_from %></div>
|
|
13
|
+
<div class="hrl-field hrl-field--check">
|
|
14
|
+
<%= f.check_box :acknowledgement_required %>
|
|
15
|
+
<%= f.label :acknowledgement_required, "Policy — everyone must acknowledge it (untick for an announcement)" %>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="hrl-form-actions">
|
|
18
|
+
<%= f.submit "Publish", class: "hrl-btn hrl-btn--primary" %>
|
|
19
|
+
<a class="hrl-btn" href="<%= hr_lite.admin_policies_path %>">Cancel</a>
|
|
20
|
+
</div>
|
|
21
|
+
<% end %>
|
|
22
|
+
</section>
|
|
@@ -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>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<% content_for(:page_title) { "ID card" } %>
|
|
2
|
+
<% company = HrLite.config.company.call %>
|
|
3
|
+
<% brand = company[:brand].presence || company[:name].presence || "HR" %>
|
|
4
|
+
<% own = @user == hr_current_user %>
|
|
5
|
+
<div class="hrl-page__head">
|
|
6
|
+
<h1 class="hrl-page__title"><%= own ? "My ID card" : "ID card — #{hr_display_name(@user)}" %></h1>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<% if @profile.nil? %>
|
|
10
|
+
<p class="hrl-card hrl-card--empty"><%= own ? "Your HR profile has not been set up yet, so there is no card to show." : "No HR profile yet." %></p>
|
|
11
|
+
<% else %>
|
|
12
|
+
<% first, rest = hr_display_name(@user).to_s.split(" ", 2) %>
|
|
13
|
+
<div class="hrl-idcard-faces">
|
|
14
|
+
<div class="hrl-idcard-face">
|
|
15
|
+
<span class="hrl-idcard__label">FRONT</span>
|
|
16
|
+
<div class="hrl-idcard">
|
|
17
|
+
<div class="hrl-idcard__band">
|
|
18
|
+
<span class="hrl-idcard__slot"></span>
|
|
19
|
+
<span class="hrl-idcard__logo">
|
|
20
|
+
<% if company[:logo_url].present? %><img src="<%= company[:logo_url] %>" alt="<%= brand %>"><% else %><%= brand %><% end %>
|
|
21
|
+
</span>
|
|
22
|
+
<span class="hrl-idcard__kicker">EMPLOYEE IDENTITY CARD</span>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="hrl-idcard__photo">
|
|
25
|
+
<% if @profile.photo.attached? %>
|
|
26
|
+
<img src="<%= Rails.application.routes.url_helpers.rails_blob_path(@profile.photo, disposition: "inline", only_path: true) %>" alt="<%= hr_display_name(@user) %>">
|
|
27
|
+
<% else %>
|
|
28
|
+
<span><%= hr_display_name(@user).to_s.scan(/\b[[:alpha:]]/).first(2).join.upcase %></span>
|
|
29
|
+
<% end %>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="hrl-idcard__who">
|
|
32
|
+
<div class="hrl-idcard__name"><span class="hrl-idcard__green"><%= first %></span> <span class="hrl-idcard__blue"><%= rest %></span></div>
|
|
33
|
+
<div class="hrl-idcard__role"><%= @profile.designation %></div>
|
|
34
|
+
</div>
|
|
35
|
+
<dl class="hrl-idcard__facts">
|
|
36
|
+
<div><dt>EMPLOYEE ID</dt><dd><%= @profile.employee_code %></dd></div>
|
|
37
|
+
<div><dt>DEPARTMENT</dt><dd><%= @profile.department.presence || "—" %></dd></div>
|
|
38
|
+
<div><dt>BLOOD GROUP</dt><dd class="hrl-idcard__red"><%= @profile.blood_group.presence || "—" %></dd></div>
|
|
39
|
+
<div><dt>JOINED</dt><dd><%= @profile.date_of_joining.strftime("%b %Y") %></dd></div>
|
|
40
|
+
</dl>
|
|
41
|
+
<div class="hrl-idcard__strip"><span></span><span></span><span></span></div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="hrl-idcard-face">
|
|
46
|
+
<span class="hrl-idcard__label">BACK</span>
|
|
47
|
+
<div class="hrl-idcard hrl-idcard--back">
|
|
48
|
+
<span class="hrl-idcard__slot hrl-idcard__slot--back"></span>
|
|
49
|
+
<div class="hrl-idcard__title"><span class="hrl-idcard__green">Scan to</span> <span class="hrl-idcard__blue">Verify</span></div>
|
|
50
|
+
<div class="hrl-idcard__qr"><%= hrl_office_qr_svg(company, brand) %></div>
|
|
51
|
+
<div class="hrl-idcard__code"><%= @profile.employee_code %></div>
|
|
52
|
+
<div class="hrl-idcard__rows">
|
|
53
|
+
<div><span>Emergency contact</span><b><%= @profile.emergency_contact.presence || "—" %></b></div>
|
|
54
|
+
<hr>
|
|
55
|
+
<div><span>Issued</span><b><%= @issued_on&.strftime("%b %Y") || "Not yet issued" %></b></div>
|
|
56
|
+
</div>
|
|
57
|
+
<p class="hrl-idcard__note">This card is the property of <%= brand %>. If found, please return it to our office<%= " or call #{company[:phone]}" if company[:phone].present? %>.</p>
|
|
58
|
+
<div class="hrl-idcard__foot"><b><%= brand %></b><span>AUTHORISED SIGNATORY</span></div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div class="hrl-idcard-actions">
|
|
64
|
+
<button type="button" class="hrl-btn hrl-btn--primary" data-hrl-print>Print</button>
|
|
65
|
+
<% if own && @print_requested %>
|
|
66
|
+
<p class="hrl-small hrl-muted">Printed card requested on <%= @print_requested.to_date.strftime("%d %b %Y") %>.</p>
|
|
67
|
+
<% elsif own %>
|
|
68
|
+
<%= button_to "Request printed card", hr_lite.hr_requests_path, class: "hrl-btn",
|
|
69
|
+
params: { hr_request: { category: "id_card", subject: "Please print my ID card" } } %>
|
|
70
|
+
<% end %>
|
|
71
|
+
<% if own %>
|
|
72
|
+
<%= form_with url: hr_lite.id_card_photo_path, method: :patch, multipart: true, class: "hrl-row" do |f| %>
|
|
73
|
+
<div class="hrl-field">
|
|
74
|
+
<label for="photo">Change photo</label>
|
|
75
|
+
<%= f.file_field :photo, id: "photo", accept: HrLite::EmployeeProfile::PHOTO_TYPES.join(","), required: true %>
|
|
76
|
+
<p class="hrl-hint">Face centred, plain background. JPG, PNG or WebP, under <%= HrLite::EmployeeProfile::PHOTO_MAX_BYTES / 1.megabyte %> MB.</p>
|
|
77
|
+
</div>
|
|
78
|
+
<%= f.submit "Upload photo", class: "hrl-btn" %>
|
|
79
|
+
<% end %>
|
|
80
|
+
<% end %>
|
|
81
|
+
</div>
|
|
82
|
+
<% end %>
|
|
83
|
+
|
|
84
|
+
<% content_for :scripts do %>
|
|
85
|
+
<%= javascript_include_tag "hr_lite/print", defer: true %>
|
|
86
|
+
<% end %>
|
|
@@ -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
|
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
|
|
57
57
|
<div class="hrl-side__foot">
|
|
58
58
|
<span class="hrl-side__user" title="<%= hr_current_user&.email %>"><%= hr_display_name(hr_current_user) %></span>
|
|
59
|
+
<%= render "hr_lite/shared/bell" %>
|
|
59
60
|
<% if HrLite.config.back_link %>
|
|
60
61
|
<a class="hrl-side__back" href="<%= HrLite.config.back_link[:url] %>"><%= HrLite.config.back_link[:label] %></a>
|
|
61
62
|
<% end %>
|
|
@@ -71,6 +72,7 @@
|
|
|
71
72
|
<%= company[:name].presence || "HR" %>
|
|
72
73
|
<% end %>
|
|
73
74
|
</a>
|
|
75
|
+
<%= render "hr_lite/shared/bell" %>
|
|
74
76
|
<span class="hrl-appbar__user"><%= hr_display_name(hr_current_user) %></span>
|
|
75
77
|
</header>
|
|
76
78
|
|
|
@@ -98,10 +100,10 @@
|
|
|
98
100
|
<% rest.each do |item| %>
|
|
99
101
|
<%= hrl_nav_link(item) %>
|
|
100
102
|
<% end %>
|
|
101
|
-
<% if hr_admin? || hr_leadership? %>
|
|
103
|
+
<% if hr_admin? || hr_leadership? || hr_can?("expense.approve", scope: :team) %>
|
|
102
104
|
<span class="hrl-side__group">Manage</span>
|
|
103
105
|
<% hrl_nav_items(HrLite::ApplicationHelper::ADMIN_NAV_ITEMS).each do |item| %>
|
|
104
|
-
<%= hrl_nav_link(item) %>
|
|
106
|
+
<%= hrl_nav_link(item) if item[:if] || hr_admin? || hr_leadership? %>
|
|
105
107
|
<% end %>
|
|
106
108
|
<% end %>
|
|
107
109
|
<% if hr_leadership? || hr_superadmin? %>
|
data/config/routes.rb
CHANGED
|
@@ -43,6 +43,8 @@ HrLite::Engine.routes.draw do
|
|
|
43
43
|
get "calendar", to: "calendar#show"
|
|
44
44
|
resources :salary_slips, only: %i[index show]
|
|
45
45
|
resource :employee_profile, only: :show, path: "profile"
|
|
46
|
+
patch "id_card/photo", to: "id_cards#photo", as: :id_card_photo
|
|
47
|
+
get "id_card(/:user_id)", to: "id_cards#show", as: :id_card
|
|
46
48
|
resource :resignation, only: %i[show create], controller: "resignations" do
|
|
47
49
|
post :withdraw
|
|
48
50
|
end
|
|
@@ -50,9 +52,10 @@ HrLite::Engine.routes.draw do
|
|
|
50
52
|
resources :appraisals, only: %i[index show]
|
|
51
53
|
|
|
52
54
|
namespace :admin do
|
|
55
|
+
resources :policies, only: %i[index new create]
|
|
53
56
|
get "overview", to: "overview#index"
|
|
54
57
|
resources :attendances, only: %i[index show update], param: :user_id
|
|
55
|
-
resources :leave_requests, only: %i[index show] do
|
|
58
|
+
resources :leave_requests, only: %i[index show new create] do
|
|
56
59
|
member { post :approve; post :reject; post :cancel }
|
|
57
60
|
end
|
|
58
61
|
resources :leave_balances, only: :index do
|
|
@@ -88,11 +91,15 @@ HrLite::Engine.routes.draw do
|
|
|
88
91
|
resources :assets, only: %i[index new create] do
|
|
89
92
|
member { post :assign; post :take_back }
|
|
90
93
|
end
|
|
94
|
+
resources :benefits, only: %i[index new create] do
|
|
95
|
+
member { post :enrol; post :unenrol }
|
|
96
|
+
end
|
|
91
97
|
resources :checklists, only: :index do
|
|
92
98
|
member { post :complete; post :reopen }
|
|
93
99
|
end
|
|
94
100
|
resources :leave_types, except: :show
|
|
95
101
|
resources :office_locations, except: :show
|
|
102
|
+
resources :expense_categories, except: %i[show destroy]
|
|
96
103
|
resources :holidays, only: %i[index create update destroy] do
|
|
97
104
|
collection { post :bulk_create }
|
|
98
105
|
end
|
|
@@ -116,6 +123,7 @@ HrLite::Engine.routes.draw do
|
|
|
116
123
|
get :register
|
|
117
124
|
end
|
|
118
125
|
end
|
|
126
|
+
resources :payroll_line_items, only: %i[index new create destroy]
|
|
119
127
|
resources :salary_slips, only: %i[show update]
|
|
120
128
|
resource :setting, only: %i[edit update]
|
|
121
129
|
resources :audit_logs, only: :index
|