hr_lite 0.53.0 → 0.54.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 +13 -0
- data/README.md +5 -2
- data/app/assets/javascripts/hr_lite/emoji_pick.js +4 -4
- data/app/assets/stylesheets/hr_lite/hr_lite.css +7 -6
- data/app/controllers/hr_lite/admin/attendances_controller.rb +2 -0
- data/app/controllers/hr_lite/admin/comp_off_requests_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/documents_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/expenses_controller.rb +13 -1
- data/app/controllers/hr_lite/admin/leave_requests_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/regularization_requests_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/statutory_rate_cards_controller.rb +2 -1
- data/app/controllers/hr_lite/admin/tax_declarations_controller.rb +2 -1
- data/app/controllers/hr_lite/admin/work_locations_controller.rb +1 -1
- data/app/controllers/hr_lite/home_controller.rb +0 -9
- data/app/controllers/hr_lite/loans_controller.rb +1 -1
- data/app/helpers/hr_lite/application_helper.rb +21 -2
- data/app/models/hr_lite/attendance_record.rb +7 -4
- data/app/models/hr_lite/salary_slip.rb +2 -1
- data/app/models/hr_lite/statutory_rate_card_record.rb +6 -0
- data/app/views/hr_lite/admin/attendances/index.html.erb +4 -4
- data/app/views/hr_lite/admin/attendances/show.html.erb +1 -1
- data/app/views/hr_lite/admin/benefits/index.html.erb +10 -4
- data/app/views/hr_lite/admin/comp_off_requests/show.html.erb +1 -1
- data/app/views/hr_lite/admin/documents/index.html.erb +2 -2
- data/app/views/hr_lite/admin/employees/show.html.erb +2 -1
- data/app/views/hr_lite/admin/expense_categories/index.html.erb +1 -1
- data/app/views/hr_lite/admin/expenses/index.html.erb +4 -2
- data/app/views/hr_lite/admin/holidays/index.html.erb +2 -1
- data/app/views/hr_lite/admin/hr_requests/index.html.erb +2 -2
- data/app/views/hr_lite/admin/hr_requests/show.html.erb +1 -1
- data/app/views/hr_lite/admin/leave_requests/show.html.erb +1 -1
- data/app/views/hr_lite/admin/leave_types/index.html.erb +2 -2
- data/app/views/hr_lite/admin/loans/new.html.erb +1 -1
- data/app/views/hr_lite/admin/office_locations/index.html.erb +1 -1
- data/app/views/hr_lite/admin/overview/index.html.erb +10 -31
- data/app/views/hr_lite/admin/payroll_line_items/new.html.erb +2 -1
- data/app/views/hr_lite/admin/payroll_runs/index.html.erb +1 -1
- data/app/views/hr_lite/admin/payroll_runs/show.html.erb +6 -4
- data/app/views/hr_lite/admin/performance_plans/_form.html.erb +2 -1
- data/app/views/hr_lite/admin/policies/index.html.erb +1 -1
- data/app/views/hr_lite/admin/policies/new.html.erb +2 -1
- data/app/views/hr_lite/admin/regularization_requests/show.html.erb +1 -1
- data/app/views/hr_lite/admin/roles/_form.html.erb +2 -1
- data/app/views/hr_lite/admin/roles/edit.html.erb +4 -2
- data/app/views/hr_lite/admin/roles/index.html.erb +1 -1
- data/app/views/hr_lite/admin/statutory_rate_cards/_form.html.erb +11 -7
- data/app/views/hr_lite/admin/tax_declarations/show.html.erb +6 -4
- data/app/views/hr_lite/admin/wfh_requests/show.html.erb +1 -1
- data/app/views/hr_lite/approvals/index.html.erb +4 -3
- data/app/views/hr_lite/attendance/_custom_status.html.erb +7 -9
- data/app/views/hr_lite/attendance/_punch_card.html.erb +8 -0
- data/app/views/hr_lite/attendance/show.html.erb +5 -3
- data/app/views/hr_lite/home/index.html.erb +4 -3
- data/app/views/hr_lite/leave_requests/_balance_chips.html.erb +2 -1
- data/app/views/hr_lite/loans/index.html.erb +1 -1
- data/app/views/hr_lite/resignations/show.html.erb +2 -1
- data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +5 -5
- data/app/views/hr_lite/salary_slips/pdf.html.erb +1 -0
- data/app/views/hr_lite/work_time/_bars.html.erb +4 -3
- data/app/views/layouts/hr_lite/mailer.html.erb +10 -2
- data/app/views/layouts/hr_lite/mailer.text.erb +4 -1
- data/config/routes.rb +1 -1
- data/lib/hr_lite/amount_in_words.rb +7 -5
- data/lib/hr_lite/statutory_seeds.rb +1 -1
- data/lib/hr_lite/version.rb +1 -1
- metadata +1 -1
|
@@ -3,50 +3,29 @@
|
|
|
3
3
|
<h1 class="hrl-page__title">HR overview</h1>
|
|
4
4
|
</div>
|
|
5
5
|
|
|
6
|
+
<%# The Inbox badge's count and rows, so the board never counts what the Inbox hides. %>
|
|
7
|
+
<% pending = hrl_inbox_count %>
|
|
6
8
|
<div class="hrl-kpis">
|
|
7
|
-
<div class="hrl-kpi"><div class="hrl-kpi__value"><%=
|
|
9
|
+
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= pending %></div><div class="hrl-kpi__label">Pending approvals</div></div>
|
|
8
10
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @kpis[:on_leave] %></div><div class="hrl-kpi__label">On leave today</div></div>
|
|
9
11
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @kpis[:flagged] %></div><div class="hrl-kpi__label">Flagged punches</div></div>
|
|
10
12
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @kpis[:missing_checkout] %></div><div class="hrl-kpi__label">Missing checkouts</div></div>
|
|
11
13
|
</div>
|
|
12
14
|
|
|
13
|
-
<% if @
|
|
15
|
+
<% if pending.zero? && @kpis.except(:pending).values.all?(&:zero?) %>
|
|
14
16
|
<p class="hrl-card hrl-card--empty">All present and accounted for.</p>
|
|
15
17
|
<% else %>
|
|
16
|
-
<% if
|
|
18
|
+
<% if pending.positive? %>
|
|
17
19
|
<section class="hrl-card">
|
|
18
20
|
<h2 class="hrl-card__title">Pending approvals</h2>
|
|
19
|
-
<%
|
|
21
|
+
<% hrl_inbox_rows(HrLite::Admin::OverviewController::SECTION_CAP).each do |tab, request| %>
|
|
22
|
+
<% what, href = hrl_inbox_item(tab, request) %>
|
|
20
23
|
<div class="hrl-feed__item">
|
|
21
|
-
<div class="hrl-feed__body">
|
|
22
|
-
|
|
23
|
-
— <%= request.leave_type.name %>, <%= request.date_range_label %> (<%= hrl_days(request.days_count) %> d)
|
|
24
|
-
</div>
|
|
25
|
-
<a class="hrl-btn" href="<%= hr_lite.admin_leave_request_path(request) %>">Decide</a>
|
|
26
|
-
</div>
|
|
27
|
-
<% end %>
|
|
28
|
-
<%# Comp-off and regularization are approvals too. Listing leave alone
|
|
29
|
-
left both queues invisible on the board that exists to surface work. %>
|
|
30
|
-
<% @query.pending_comp_offs.limit(HrLite::Admin::OverviewController::SECTION_CAP).each do |request| %>
|
|
31
|
-
<div class="hrl-feed__item">
|
|
32
|
-
<div class="hrl-feed__body">
|
|
33
|
-
<strong><%= hr_display_name(request.user) %></strong>
|
|
34
|
-
— comp-off for <%= request.date_worked.strftime("%d %b") %>
|
|
35
|
-
(<%= hrl_days(request.credit_days) %> d)
|
|
36
|
-
</div>
|
|
37
|
-
<a class="hrl-btn" href="<%= hr_lite.admin_comp_off_request_path(request) %>">Decide</a>
|
|
38
|
-
</div>
|
|
39
|
-
<% end %>
|
|
40
|
-
<% @query.pending_regularizations.limit(HrLite::Admin::OverviewController::SECTION_CAP).each do |request| %>
|
|
41
|
-
<div class="hrl-feed__item">
|
|
42
|
-
<div class="hrl-feed__body">
|
|
43
|
-
<strong><%= hr_display_name(request.user) %></strong>
|
|
44
|
-
— attendance fix for <%= request.date.strftime("%d %b") %>
|
|
45
|
-
</div>
|
|
46
|
-
<a class="hrl-btn" href="<%= hr_lite.admin_regularization_request_path(request) %>">Decide</a>
|
|
24
|
+
<div class="hrl-feed__body"><strong><%= hr_display_name(request.user) %></strong> — <%= what %></div>
|
|
25
|
+
<a class="hrl-btn" href="<%= href %>">Decide</a>
|
|
47
26
|
</div>
|
|
48
27
|
<% end %>
|
|
49
|
-
<a class="hrl-card__link" href="<%= hr_lite.
|
|
28
|
+
<a class="hrl-card__link" href="<%= hr_lite.approvals_path %>">All requests</a>
|
|
50
29
|
</section>
|
|
51
30
|
<% end %>
|
|
52
31
|
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
<div class="hrl-field">
|
|
14
14
|
<%= f.label :user_id, "Employee" %>
|
|
15
15
|
<%= f.select :user_id, options_for_select(HrLite.employees.map { |u| [ hr_display_name(u), u.id ] },
|
|
16
|
-
@item.user_id)
|
|
16
|
+
@item.user_id),
|
|
17
|
+
{ include_blank: "Choose…" }, required: true, data: { hrl_filter: "Type a name to filter" } %>
|
|
17
18
|
</div>
|
|
18
19
|
<div class="hrl-field">
|
|
19
20
|
<%= f.label :component_id, "Item" %>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="hrl-page__head">
|
|
3
3
|
<h1 class="hrl-page__title">Payroll runs</h1>
|
|
4
4
|
<div class="hrl-page__actions">
|
|
5
|
-
|
|
5
|
+
<% if hr_can?("payroll.manage", scope: :all) %><a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_admin_payroll_run_path %>">New run</a><% end %>
|
|
6
6
|
</div>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
</div>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
|
-
<div class="hrl-kpis">
|
|
10
|
+
<div class="hrl-kpis hrl-kpis--money">
|
|
11
11
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @slips.size %></div><div class="hrl-kpi__label">Slips</div></div>
|
|
12
12
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_money(@run.total_gross) %></div><div class="hrl-kpi__label">Gross</div></div>
|
|
13
13
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_money(@run.total_net) %></div><div class="hrl-kpi__label">Net payout</div></div>
|
|
14
|
-
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_money(@run.total_employer_cost) %></div><div class="hrl-kpi__label">Employer
|
|
14
|
+
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_money(@run.total_employer_cost) %></div><div class="hrl-kpi__label">Employer contributions</div></div>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
17
|
<% if @run.warnings.any? %>
|
|
@@ -29,12 +29,14 @@
|
|
|
29
29
|
<% manager = hr_can?("payroll.manage", scope: :all) %>
|
|
30
30
|
<% if manager && (@run.draft? || @run.review? || @run.processing?) %>
|
|
31
31
|
<%= button_to @run.draft? ? "Compute" : "Recompute", hr_lite.compute_admin_payroll_run_path(@run),
|
|
32
|
-
method: :post, class: "hrl-btn"
|
|
32
|
+
method: :post, class: "hrl-btn",
|
|
33
|
+
form: { data: { turbo_confirm: ("Recompute #{@run.label}? This clears #{hr_display_name(@run.first_approved_by)}'s approval." if @run.first_approved_by) } } %>
|
|
33
34
|
<% end %>
|
|
34
35
|
<% if @run.review? && hr_can?("payroll.approve", scope: :all) && @run.first_approved_by_id != hr_current_user.id %>
|
|
35
36
|
<%= button_to @run.first_approved_by_id ? "Approve and publish" : "Approve", hr_lite.approve_admin_payroll_run_path(@run),
|
|
36
37
|
method: :post, class: "hrl-btn hrl-btn--primary",
|
|
37
|
-
form: { data: { turbo_confirm: @run.first_approved_by_id ? "Approve #{@run.label}? This finalizes the run and publishes the slips." : "Approve #{@run.label}?"
|
|
38
|
+
form: { data: { turbo_confirm: [ @run.first_approved_by_id ? "Approve #{@run.label}? This finalizes the run and publishes the slips." : "Approve #{@run.label}?",
|
|
39
|
+
*("Blocking warnings: #{@run.blocking_warnings.join('; ')}" if @run.blocking_warnings.any?) ].join("\n\n") } } %>
|
|
38
40
|
<% end %>
|
|
39
41
|
<% if manager && @run.finalized? %>
|
|
40
42
|
<%= button_to "Unlock", hr_lite.unlock_admin_payroll_run_path(@run), method: :post, class: "hrl-btn" %>
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
<div class="hrl-field"><%= f.label :outcome_note, "Outcome note" %><%= f.text_area :outcome_note, rows: 3 %></div>
|
|
23
23
|
<% end %>
|
|
24
24
|
<div class="hrl-form-actions">
|
|
25
|
-
<%= f.submit plan.persisted? ? "Save" : "Start PIP", class: "hrl-btn hrl-btn--primary"
|
|
25
|
+
<%= f.submit plan.persisted? ? "Save" : "Start PIP", class: "hrl-btn hrl-btn--primary",
|
|
26
|
+
data: { turbo_confirm: ("Save? Passed or failed closes the plan for good and tells the employee." if plan.persisted?) } %>
|
|
26
27
|
<a class="hrl-btn" href="<%= hr_lite.admin_performance_plans_path %>">Cancel</a>
|
|
27
28
|
</div>
|
|
28
29
|
<% end %>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<tbody>
|
|
15
15
|
<% @policies.each do |policy| %>
|
|
16
16
|
<tr>
|
|
17
|
-
<td data-label="Title"><%= policy.title
|
|
17
|
+
<td data-label="Title"><a class="hrl-card__link" href="<%= hr_lite.policy_path(policy) %>"><%= policy.title %></a> <span class="hrl-badge hrl-badge--muted">v<%= policy.version %></span></td>
|
|
18
18
|
<td data-label="Kind"><%= policy.announcement? ? "Announcement" : "Policy" %></td>
|
|
19
19
|
<td data-label="In force"><%= policy.effective_from.strftime("%d %b %Y") %></td>
|
|
20
20
|
<td data-label="Still to acknowledge"><%= policy.announcement? ? "—" : policy.outstanding_for.count %></td>
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
<%= f.label :acknowledgement_required, "Policy — everyone must acknowledge it (untick for an announcement)" %>
|
|
16
16
|
</div>
|
|
17
17
|
<div class="hrl-form-actions">
|
|
18
|
-
<%= f.submit "Publish", class: "hrl-btn hrl-btn--primary"
|
|
18
|
+
<%= f.submit "Publish", class: "hrl-btn hrl-btn--primary",
|
|
19
|
+
data: { turbo_confirm: "Publish and notify #{pluralize(HrLite.active_employees.size, 'person')} by email?" } %>
|
|
19
20
|
<a class="hrl-btn" href="<%= hr_lite.admin_policies_path %>">Cancel</a>
|
|
20
21
|
</div>
|
|
21
22
|
<% end %>
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
<%= form_with url: hr_lite.reject_admin_regularization_request_path(@request), method: :post, local: true do |f| %>
|
|
55
55
|
<div class="hrl-field">
|
|
56
56
|
<%= f.label :decision_note, "Reason (required)", for: "reject_note" %>
|
|
57
|
-
<%= f.text_area :decision_note, id: "reject_note" %>
|
|
57
|
+
<%= f.text_area :decision_note, id: "reject_note", required: true %>
|
|
58
58
|
</div>
|
|
59
59
|
<div class="hrl-form-actions">
|
|
60
60
|
<%= f.button "Reject", type: :submit, class: "hrl-btn hrl-btn--danger" %>
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
<% end %>
|
|
41
41
|
|
|
42
42
|
<div class="hrl-form-actions">
|
|
43
|
-
<%= f.submit "Save", class: "hrl-btn hrl-btn--primary"
|
|
43
|
+
<%= f.submit "Save", class: "hrl-btn hrl-btn--primary",
|
|
44
|
+
data: { turbo_confirm: ("Save? Everyone holding #{role.name} gets these permissions at once." if role.persisted?) } %>
|
|
44
45
|
<a class="hrl-btn" href="<%= hr_lite.admin_roles_path %>">Cancel</a>
|
|
45
46
|
</div>
|
|
46
47
|
<% end %>
|
|
@@ -38,13 +38,15 @@
|
|
|
38
38
|
</div>
|
|
39
39
|
<% end %>
|
|
40
40
|
|
|
41
|
-
<%= form_with url: hr_lite.admin_role_assignments_path(@role), method: :post, local: true
|
|
41
|
+
<%= form_with url: hr_lite.admin_role_assignments_path(@role), method: :post, local: true,
|
|
42
|
+
data: { turbo_confirm: "Give the #{@role.name} role to the person you picked? Their access changes at once." } do %>
|
|
42
43
|
<div class="hrl-field">
|
|
43
44
|
<%= label_tag :user_id, "Add somebody to this role" %>
|
|
44
45
|
<% held = @role.role_assignments.map(&:user_id) %>
|
|
45
46
|
<%= select_tag :user_id,
|
|
46
47
|
options_for_select(HrLite.employees.reject { |u| held.include?(u.id) }
|
|
47
|
-
.map { |u| [ hr_display_name(u), u.id ] })
|
|
48
|
+
.map { |u| [ hr_display_name(u), u.id ] }),
|
|
49
|
+
include_blank: "Choose…", required: true, data: { hrl_filter: "Type a name to filter" } %>
|
|
48
50
|
</div>
|
|
49
51
|
<div class="hrl-form-actions">
|
|
50
52
|
<%= submit_tag "Add", class: "hrl-btn hrl-btn--primary" %>
|
|
@@ -68,16 +68,20 @@
|
|
|
68
68
|
the open-ended top rate.
|
|
69
69
|
</p>
|
|
70
70
|
<% slabs = Array(table["slabs"]) %>
|
|
71
|
-
<% (
|
|
72
|
-
|
|
71
|
+
<% num = ->(v) { v && number_with_precision(v, precision: 4, strip_insignificant_zeros: true) } %>
|
|
72
|
+
<div class="hrl-slabs">
|
|
73
|
+
<span class="hrl-small" aria-hidden="true">From ₹</span>
|
|
74
|
+
<span class="hrl-small" aria-hidden="true">Up to ₹</span>
|
|
75
|
+
<span class="hrl-small" aria-hidden="true">Rate (0.05 = 5%)</span>
|
|
76
|
+
<% (slabs + [ nil, nil ]).each_with_index do |row, index| %>
|
|
73
77
|
<%= text_field_tag "statutory_rate_card[income_tax][#{regime}][slabs][#{index}][from]",
|
|
74
|
-
row&.first,
|
|
78
|
+
num.(row&.first), class: "hrl-input", inputmode: "decimal", "aria-label": "Band #{index + 1} from ₹" %>
|
|
75
79
|
<%= text_field_tag "statutory_rate_card[income_tax][#{regime}][slabs][#{index}][to]",
|
|
76
|
-
row&.at(1),
|
|
80
|
+
num.(row&.at(1)), class: "hrl-input", inputmode: "decimal", "aria-label": "Band #{index + 1} up to ₹ (blank = top)" %>
|
|
77
81
|
<%= text_field_tag "statutory_rate_card[income_tax][#{regime}][slabs][#{index}][rate]",
|
|
78
|
-
row&.last,
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
num.(row&.last), class: "hrl-input", inputmode: "decimal", "aria-label": "Band #{index + 1} rate" %>
|
|
83
|
+
<% end %>
|
|
84
|
+
</div>
|
|
81
85
|
</fieldset>
|
|
82
86
|
<% end %>
|
|
83
87
|
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
<% end %>
|
|
49
49
|
<div class="hrl-form-actions">
|
|
50
50
|
<%= f.submit "Record amounts", class: "hrl-btn" %>
|
|
51
|
+
<%= f.submit "Record and verify", name: "verify", class: "hrl-btn" %>
|
|
51
52
|
</div>
|
|
52
53
|
</section>
|
|
53
54
|
<% end %>
|
|
@@ -55,16 +56,17 @@
|
|
|
55
56
|
<section class="hrl-card">
|
|
56
57
|
<h2 class="hrl-card__title">Decide</h2>
|
|
57
58
|
<div class="hrl-row">
|
|
59
|
+
<% unproven = @declaration.tax_declaration_items.reject { |item| item.proofs.attached? }.map { |item| item.section_label.split(" — ").first } %>
|
|
58
60
|
<%= button_to "Accept all and verify", hr_lite.accept_all_admin_tax_declaration_path(@declaration),
|
|
59
|
-
method: :post, class: "hrl-btn hrl-btn--primary"
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
method: :post, class: "hrl-btn hrl-btn--primary",
|
|
62
|
+
form: { data: { turbo_confirm: [ "Accept every claimed amount and verify? The employee can no longer edit it.",
|
|
63
|
+
("No proof attached: #{unproven.to_sentence}." if unproven.any?) ].compact.join(" ") } } %>
|
|
62
64
|
</div>
|
|
63
65
|
<%= form_with url: hr_lite.reject_admin_tax_declaration_path(@declaration),
|
|
64
66
|
method: :post, local: true do %>
|
|
65
67
|
<div class="hrl-field">
|
|
66
68
|
<%= label_tag :note, "What is missing (required to send back)" %>
|
|
67
|
-
<%= text_field_tag :note %>
|
|
69
|
+
<%= text_field_tag :note, nil, class: "hrl-input" %>
|
|
68
70
|
</div>
|
|
69
71
|
<%= submit_tag "Send back", class: "hrl-btn" %>
|
|
70
72
|
<% end %>
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
<section class="hrl-card">
|
|
34
34
|
<h2 class="hrl-card__title">Reject</h2>
|
|
35
35
|
<%= form_with url: hr_lite.reject_admin_wfh_request_path(@request), method: :post, local: true do |f| %>
|
|
36
|
-
<div class="hrl-field"><%= f.label :decision_note, "Remark (required)", for: "reject_note" %><%= f.text_area :decision_note, id: "reject_note" %></div>
|
|
36
|
+
<div class="hrl-field"><%= f.label :decision_note, "Remark (required)", for: "reject_note" %><%= f.text_area :decision_note, id: "reject_note", required: true %></div>
|
|
37
37
|
<div class="hrl-form-actions"><%= f.button "Reject", type: :submit, class: "hrl-btn hrl-btn--danger" %></div>
|
|
38
38
|
<% end %>
|
|
39
39
|
</section>
|
|
@@ -45,13 +45,14 @@
|
|
|
45
45
|
</td>
|
|
46
46
|
</tr>
|
|
47
47
|
<% end %>
|
|
48
|
-
<%# The HR queues this viewer decides; Open lands on
|
|
48
|
+
<%# The HR queues this viewer decides; Open lands on the request itself where it has a page. %>
|
|
49
49
|
<% rows.each do |tab, record| %>
|
|
50
|
+
<% what, href = hrl_inbox_item(tab, record) %>
|
|
50
51
|
<tr>
|
|
51
|
-
<td data-label="Request"><strong><%=
|
|
52
|
+
<td data-label="Request"><strong><%= what %></strong></td>
|
|
52
53
|
<td data-label="From"><%= hr_display_name(record.user) %></td>
|
|
53
54
|
<td data-label="Waiting"><%= time_ago_in_words(record.created_at) %></td>
|
|
54
|
-
<td data-label=""><a class="hrl-btn" href="<%=
|
|
55
|
+
<td data-label=""><a class="hrl-btn" href="<%= href %>">Open</a></td>
|
|
55
56
|
</tr>
|
|
56
57
|
<% end %>
|
|
57
58
|
</tbody>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
<%# The person's own status
|
|
1
|
+
<%# The person's own status, Slack-style. Independent of check-in. Quick picks are words: no emoji in the UI. %>
|
|
2
2
|
<% custom = status.custom
|
|
3
|
-
picks =
|
|
3
|
+
picks = [ "In a meeting", "On a call", "Focus time", "At lunch", "Commuting", "Travelling", "At a client", "Out sick" ] %>
|
|
4
4
|
<% if custom %>
|
|
5
5
|
<div class="hrl-row hrl-row--mb hrl-small">
|
|
6
6
|
<span class="hrl-custom-status"><% if custom.emoji %><span aria-hidden="true"><%= custom.emoji %></span> <% end %><%= custom.title || "Status" %></span>
|
|
@@ -10,15 +10,13 @@
|
|
|
10
10
|
<% end %>
|
|
11
11
|
<details>
|
|
12
12
|
<summary class="hrl-btn hrl-btn--block"><%= custom ? "Change my status" : "Set a status" %></summary>
|
|
13
|
-
<%= form_with url: hr_lite.status_work_mode_path, method: :post, local: true
|
|
14
|
-
<div class="hrl-field"><label for="st-
|
|
15
|
-
<input class="hrl-input" id="st-
|
|
16
|
-
<div class="hrl-
|
|
17
|
-
<% picks.each do |
|
|
13
|
+
<%= form_with url: hr_lite.status_work_mode_path, method: :post, local: true do %>
|
|
14
|
+
<div class="hrl-field"><label for="st-text">What's your status?</label>
|
|
15
|
+
<input class="hrl-input" id="st-text" name="text" maxlength="100" value="<%= custom&.title %>" placeholder="e.g. Visiting the Ladakh camp">
|
|
16
|
+
<div class="hrl-row hrl-mt" role="group" aria-label="Quick picks">
|
|
17
|
+
<% picks.each do |text| %><button type="button" class="hrl-btn" data-hrl-pick="<%= text %>"><%= text %></button><% end %>
|
|
18
18
|
</div>
|
|
19
19
|
</div>
|
|
20
|
-
<div class="hrl-field"><label for="st-text">What's your status?</label>
|
|
21
|
-
<input class="hrl-input" id="st-text" name="text" maxlength="100" value="<%= custom&.title %>" placeholder="e.g. Visiting the Ladakh camp"></div>
|
|
22
20
|
<div class="hrl-field"><label for="st-clear">Clear after</label>
|
|
23
21
|
<%= select_tag :clear_after, options_for_select([ [ "30 minutes", "30" ], [ "1 hour", "60" ], [ "4 hours", "240" ], [ "Today", "today" ], [ "Don't clear", "never" ] ], "today"), id: "st-clear" %></div>
|
|
24
22
|
<button type="submit" class="hrl-btn hrl-btn--primary">Save status</button>
|
|
@@ -93,6 +93,14 @@
|
|
|
93
93
|
</fieldset></div>
|
|
94
94
|
<% end %>
|
|
95
95
|
<button type="submit" class="hrl-btn hrl-btn--primary hrl-btn--big hrl-btn--block"><%= record&.check_out_at ? "Check in again" : "Check in" %></button>
|
|
96
|
+
<% if HrLite.config.attendance_slots && !record&.check_out_at %>
|
|
97
|
+
<%# The late rules AttendancePuncher#apply_slot_rules applies, shifted by the person's buffer like there. %>
|
|
98
|
+
<% shift = HrLite::EmployeeProfile.where(user_id: hr_current_user.id).pick(:buffer_minutes).to_i
|
|
99
|
+
by = HrLite::AttendanceSlot.start_minute_for(hr_current_user.id, Date.current) + HrLite::AttendanceSlot::GRACE_MINUTES + shift
|
|
100
|
+
at = ->(minute) { HrLite::AttendanceSlot.label(minute + shift) } %>
|
|
101
|
+
<p class="hrl-small hrl-mt">Check in by <strong><%= HrLite::AttendanceSlot.label(by) %></strong>. Later adds 30 min to your day for every half hour;
|
|
102
|
+
after <%= at.(HrLite::AttendanceSlot::CARRY_AFTER_MINUTE) %> also 1 hour on each of the next 2 working days; after <%= at.(HrLite::AttendanceSlot::HALF_DAY_AFTER_MINUTE) %> the day is a half day.</p>
|
|
103
|
+
<% end %>
|
|
96
104
|
<p class="hrl-small hrl-muted" data-hrl-geo-status role="status" aria-live="polite"></p>
|
|
97
105
|
<% end %>
|
|
98
106
|
<% elsif record.check_out_at.nil? %>
|
|
@@ -20,9 +20,11 @@
|
|
|
20
20
|
</div>
|
|
21
21
|
<%= render "hr_lite/attendance/month_grid", month: @month, day_status: @day_status, pending_dates: @pending_dates,
|
|
22
22
|
link_builder: ->(d) { hr_lite.attendance_path(month: @month.strftime("%Y-%m"), day: d.iso8601, anchor: "day") if d <= Date.current } %>
|
|
23
|
-
<p class="hrl-small hrl-muted hrl-mt">Forgot to check in or out on some day
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
<p class="hrl-small hrl-muted hrl-mt">Forgot to check in or out on some day?</p>
|
|
24
|
+
<div class="hrl-row">
|
|
25
|
+
<a class="hrl-btn" href="<%= hr_lite.new_regularization_request_path %>">Raise a regularization ticket</a>
|
|
26
|
+
<a class="hrl-btn" href="<%= hr_lite.regularization_requests_path %>">My tickets</a>
|
|
27
|
+
</div>
|
|
26
28
|
</section>
|
|
27
29
|
|
|
28
30
|
<% if HrLite.config.attendance_slots %>
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
<h1 class="hrl-page__title">Hello, <%= hr_display_name(hr_current_user) %></h1>
|
|
3
3
|
</div>
|
|
4
4
|
|
|
5
|
-
<%
|
|
5
|
+
<% waiting = @waiting + hrl_inbox_count %>
|
|
6
|
+
<% if waiting.positive? %>
|
|
6
7
|
<section class="hrl-card">
|
|
7
|
-
<h2 class="hrl-card__title"><%= pluralize(
|
|
8
|
-
<a class="hrl-btn hrl-btn--primary hrl-mt" href="<%=
|
|
8
|
+
<h2 class="hrl-card__title"><%= pluralize(waiting, "request") %> waiting on you</h2>
|
|
9
|
+
<a class="hrl-btn hrl-btn--primary hrl-mt" href="<%= hr_lite.approvals_path %>">Review requests</a>
|
|
9
10
|
</section>
|
|
10
11
|
<% end %>
|
|
11
12
|
|
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
<div class="hrl-row">
|
|
8
8
|
<% types.each do |type| %>
|
|
9
9
|
<% balance = HrLite::LeaveBalance.for(user, type, HrLite::LeaveYear.current_key) %>
|
|
10
|
+
<% pending = HrLite::LeaveRequest.pending.where(user_id: user.id, leave_type_id: type.id, start_date: HrLite::LeaveYear.range(HrLite::LeaveYear.current_key)).sum(&:days_count) %>
|
|
10
11
|
<span class="hrl-badge hrl-badge--type" style="--hrl-type: <%= type.color %>">
|
|
11
|
-
<%= type.name %> · <%= hrl_days(balance.available) %> of <%= hrl_days(balance.entitled) %> left<% if (held = balance.penalty_days).positive? %> · <%= hrl_days(held) %> held for penalties<% end %>
|
|
12
|
+
<%= type.name %> · <%= hrl_days(balance.available) %> of <%= hrl_days(balance.entitled) %> left<% if pending.positive? %> · <%= hrl_days(pending) %> pending<% end %><% if (held = balance.penalty_days).positive? %> · <%= hrl_days(held) %> held for penalties<% end %>
|
|
12
13
|
</span>
|
|
13
14
|
<% end %>
|
|
14
15
|
</div>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<dl class="hrl-deflist">
|
|
22
22
|
<dt>Monthly instalment</dt><dd><%= hrl_money(loan.monthly_instalment) %></dd>
|
|
23
23
|
<dt>Repaid so far</dt><dd><%= hrl_money(loan.repaid) %></dd>
|
|
24
|
-
<dt>Outstanding</dt><dd><strong><%= hrl_money(loan.outstanding) %></strong></dd>
|
|
24
|
+
<dt>Outstanding</dt><dd><strong><%= loan.active? ? hrl_money(loan.outstanding) : "—" %></strong></dd>
|
|
25
25
|
<dt>Next deduction</dt>
|
|
26
26
|
<dd>
|
|
27
27
|
<% amount = loan.instalment_for(@next_month) %>
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
<%= f.text_area :reason %>
|
|
36
36
|
</div>
|
|
37
37
|
<div class="hrl-form-actions">
|
|
38
|
-
<%= f.submit "Submit resignation", class: "hrl-btn hrl-btn--primary"
|
|
38
|
+
<%= f.submit "Submit resignation", class: "hrl-btn hrl-btn--primary",
|
|
39
|
+
data: { turbo_confirm: "Submit your resignation? Leadership is told straight away." } %>
|
|
39
40
|
</div>
|
|
40
41
|
<% end %>
|
|
41
42
|
</section>
|
|
@@ -9,17 +9,17 @@
|
|
|
9
9
|
<section class="hrl-card">
|
|
10
10
|
<h2 class="hrl-card__title">Earnings</h2>
|
|
11
11
|
<div class="hrl-table-wrap">
|
|
12
|
-
<table class="hrl-table">
|
|
12
|
+
<table class="hrl-table hrl-table--stack">
|
|
13
13
|
<thead><tr><th>Component</th><th class="hrl-num">Full</th><th class="hrl-num">Earned</th></tr></thead>
|
|
14
14
|
<tbody>
|
|
15
15
|
<% slip.earnings_rows.each do |row| %>
|
|
16
16
|
<tr>
|
|
17
|
-
<td><%= row["label"] %></td>
|
|
18
|
-
<td class="hrl-num"><%= hrl_money(row["full_amount"]) %></td>
|
|
19
|
-
<td class="hrl-num"><%= hrl_money(row["amount"]) %></td>
|
|
17
|
+
<td data-label="Component"><%= row["label"] %></td>
|
|
18
|
+
<td data-label="Full" class="hrl-num"><%= hrl_money(row["full_amount"]) %></td>
|
|
19
|
+
<td data-label="Earned" class="hrl-num"><%= hrl_money(row["amount"]) %></td>
|
|
20
20
|
</tr>
|
|
21
21
|
<% end %>
|
|
22
|
-
<tr><td><strong>Gross</strong></td><td></td><td class="hrl-num"><strong><%= hrl_money(slip.gross_earnings) %></strong></td></tr>
|
|
22
|
+
<tr><td data-label="Component"><strong>Gross</strong></td><td class="hrl-cell--void"></td><td data-label="Earned" class="hrl-num"><strong><%= hrl_money(slip.gross_earnings) %></strong></td></tr>
|
|
23
23
|
</tbody>
|
|
24
24
|
</table>
|
|
25
25
|
</div>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
<% end %>
|
|
8
8
|
<div class="pdf-company"><%= company[:name] %></div>
|
|
9
9
|
<% if company[:address].present? %><div class="pdf-muted"><%= company[:address] %></div><% end %>
|
|
10
|
+
<% if (contact = company.values_at(:phone, :email, :website).compact_blank).any? %><div class="pdf-muted"><%= contact.join(" · ") %></div><% end %>
|
|
10
11
|
</div>
|
|
11
12
|
<div style="text-align:right;">
|
|
12
13
|
<h1>Salary slip</h1>
|
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
<section class="hrl-card">
|
|
8
8
|
<h2 class="hrl-card__title">By <%= period.bucket %></h2>
|
|
9
9
|
<div class="hrl-chart-scroll">
|
|
10
|
-
<svg class="hrl-bars" viewBox="0 0 <%= buckets.size * (w + gap) %> <%= h + 14 %>" role="img"
|
|
10
|
+
<svg class="hrl-bars" style="--hrl-bars-n: <%= buckets.size %>" viewBox="0 0 <%= buckets.size * (w + gap) %> <%= h + 14 %>" role="img"
|
|
11
11
|
aria-label="Time per <%= period.bucket %> stacked by work mode; tallest bar <%= hrl_duration(max) %>; details in the table">
|
|
12
12
|
<% buckets.each_with_index do |(key, modes), i| %>
|
|
13
13
|
<% x = i * (w + gap); y = h %>
|
|
14
|
-
<a href="<%= path.(period: period.bucket == :day ? "day" : (period.bucket == :week ? "week" : "month"), date: key) %>"
|
|
14
|
+
<a href="<%= path.(period: period.bucket == :day ? "day" : (period.bucket == :week ? "week" : "month"), date: key) %>"
|
|
15
|
+
aria-label="<%= key.strftime(period.bucket == :day ? "%a %-d %b" : fmt) %>: <%= hrl_duration(totals[key]) %>">
|
|
15
16
|
<% modes.except("offline").sort.each do |mode, secs| %>
|
|
16
17
|
<% next unless secs.positive? %>
|
|
17
18
|
<% bh = secs / max * h; y -= bh %>
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
</svg>
|
|
29
30
|
</div>
|
|
30
31
|
<details class="hrl-small">
|
|
31
|
-
<summary>Show as a table</summary>
|
|
32
|
+
<summary class="hrl-card__link">Show as a table</summary>
|
|
32
33
|
<table class="hrl-table">
|
|
33
34
|
<thead><tr><th><%= period.bucket.to_s.capitalize %></th><th class="hrl-num">Working (breaks incl.)</th><th class="hrl-num">Of which breaks</th><th class="hrl-num">Total</th></tr></thead>
|
|
34
35
|
<tbody>
|
|
@@ -21,9 +21,17 @@
|
|
|
21
21
|
<body>
|
|
22
22
|
<div class="wrap">
|
|
23
23
|
<div class="card">
|
|
24
|
-
|
|
24
|
+
<% company = HrLite.config.company.call %>
|
|
25
|
+
<%# Mail clients strip data: URIs, so this is the host's absolute https logo, not logo_url. %>
|
|
26
|
+
<% if company[:email_logo_url].present? %>
|
|
27
|
+
<img src="<%= company[:email_logo_url] %>" alt="<%= company[:name] %>" height="40" style="display:block; height:40px; width:auto; border:0; margin-bottom:12px;">
|
|
28
|
+
<% end %>
|
|
29
|
+
<div class="brand"><%= company[:name].presence || "HR" %></div>
|
|
25
30
|
<%= yield %>
|
|
26
|
-
<p class="foot">
|
|
31
|
+
<p class="foot">
|
|
32
|
+
Sent by the HR system of <%= company[:name].presence || "your company" %>. Do not reply to this email.
|
|
33
|
+
<% if (contact = company.values_at(:phone, :email, :website).compact_blank).any? %><br>Questions: <%= contact.join(" · ") %><% end %>
|
|
34
|
+
</p>
|
|
27
35
|
</div>
|
|
28
36
|
</div>
|
|
29
37
|
</body>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
<% company = HrLite.config.company.call %>
|
|
2
|
+
<%= company[:name].presence || "HR" %>
|
|
2
3
|
|
|
3
4
|
<%= yield %>
|
|
4
5
|
|
|
5
6
|
--
|
|
6
7
|
Sent by the HR system. Do not reply to this email.
|
|
8
|
+
<% if (contact = company.values_at(:phone, :email, :website).compact_blank).any? %>Questions: <%= contact.join(" · ") %>
|
|
9
|
+
<% end %>
|
data/config/routes.rb
CHANGED
|
@@ -115,7 +115,7 @@ HrLite::Engine.routes.draw do
|
|
|
115
115
|
end
|
|
116
116
|
resources :reports, only: %i[index show]
|
|
117
117
|
resources :expenses, only: :index do
|
|
118
|
-
member { post :approve; post :reject; post :reimburse }
|
|
118
|
+
member { post :approve; post :reject; post :reimburse; get :receipt }
|
|
119
119
|
end
|
|
120
120
|
resources :hr_requests, only: %i[index show] do
|
|
121
121
|
member { post :assign; post :resolve }
|
|
@@ -10,15 +10,17 @@ module HrLite
|
|
|
10
10
|
|
|
11
11
|
module_function
|
|
12
12
|
|
|
13
|
+
# Paise are spelled too: a slip showing ₹99,612.91 must not say "six hundred thirteen".
|
|
13
14
|
def words(amount)
|
|
14
|
-
|
|
15
|
-
return "Zero rupees" if
|
|
15
|
+
paise = (Money.d(amount) * 100).round(0, BigDecimal::ROUND_HALF_UP).to_i
|
|
16
|
+
return "Zero rupees" if paise.zero?
|
|
16
17
|
|
|
18
|
+
rupees, cents = paise.abs.divmod(100)
|
|
17
19
|
# A negative amount rendered as bare " rupees": no group was >= its
|
|
18
20
|
# divisor, so nothing was ever appended.
|
|
19
|
-
sentence = groups(rupees
|
|
20
|
-
sentence = "minus #{sentence}" if
|
|
21
|
-
"#{sentence.capitalize} rupees"
|
|
21
|
+
sentence = rupees.zero? ? "zero" : groups(rupees).join(" ")
|
|
22
|
+
sentence = "minus #{sentence}" if paise.negative?
|
|
23
|
+
"#{sentence.capitalize} rupees#{" and #{two_digit(cents)} paise" if cents.positive?}"
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def groups(rupees)
|
|
@@ -22,7 +22,7 @@ module HrLite
|
|
|
22
22
|
income_tax: card[:income_tax].transform_values { |regime| stringify_regime(regime) },
|
|
23
23
|
notes: StatutoryRateCard::SOURCES[effective_from] ||
|
|
24
24
|
"Shipped with hr_lite #{HrLite::VERSION}. Confirm with your accountant, then record who verified it.",
|
|
25
|
-
verified_by: (StatutoryRateCard::SOURCES.key?(effective_from)
|
|
25
|
+
verified_by: (StatutoryRateCardRecord::SHIPPED_SIGN_OFF if StatutoryRateCard::SOURCES.key?(effective_from)),
|
|
26
26
|
verified_on: (Date.current if StatutoryRateCard::SOURCES.key?(effective_from))
|
|
27
27
|
)
|
|
28
28
|
"rate card FY #{FinancialYear.label(effective_from)}"
|
data/lib/hr_lite/version.rb
CHANGED