hr_lite 0.53.0 → 0.55.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.
Files changed (149) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +73 -0
  3. data/README.md +5 -2
  4. data/app/assets/javascripts/hr_lite/confirm.js +9 -5
  5. data/app/assets/javascripts/hr_lite/countdown.js +9 -2
  6. data/app/assets/javascripts/hr_lite/emoji_pick.js +4 -4
  7. data/app/assets/javascripts/hr_lite/search.js +1 -1
  8. data/app/assets/stylesheets/hr_lite/hr_lite.css +24 -14
  9. data/app/controllers/hr_lite/admin/attendances_controller.rb +12 -3
  10. data/app/controllers/hr_lite/admin/audit_logs_controller.rb +1 -0
  11. data/app/controllers/hr_lite/admin/comp_off_requests_controller.rb +1 -1
  12. data/app/controllers/hr_lite/admin/documents_controller.rb +1 -1
  13. data/app/controllers/hr_lite/admin/employees_controller.rb +11 -4
  14. data/app/controllers/hr_lite/admin/expenses_controller.rb +17 -1
  15. data/app/controllers/hr_lite/admin/hr_requests_controller.rb +4 -0
  16. data/app/controllers/hr_lite/admin/leave_requests_controller.rb +5 -2
  17. data/app/controllers/hr_lite/admin/payroll_runs_controller.rb +9 -5
  18. data/app/controllers/hr_lite/admin/regularization_requests_controller.rb +1 -1
  19. data/app/controllers/hr_lite/admin/reports_controller.rb +3 -2
  20. data/app/controllers/hr_lite/admin/salary_structures_controller.rb +2 -0
  21. data/app/controllers/hr_lite/admin/statutory_rate_cards_controller.rb +2 -1
  22. data/app/controllers/hr_lite/admin/tax_declarations_controller.rb +15 -4
  23. data/app/controllers/hr_lite/admin/work_locations_controller.rb +1 -1
  24. data/app/controllers/hr_lite/approvals_controller.rb +1 -0
  25. data/app/controllers/hr_lite/home_controller.rb +2 -11
  26. data/app/controllers/hr_lite/loans_controller.rb +1 -1
  27. data/app/controllers/hr_lite/regularization_requests_controller.rb +4 -4
  28. data/app/controllers/hr_lite/tax_declarations_controller.rb +9 -5
  29. data/app/helpers/hr_lite/application_helper.rb +34 -9
  30. data/app/jobs/hr_lite/attendance_close_job.rb +1 -1
  31. data/app/jobs/hr_lite/payroll_compute_job.rb +12 -0
  32. data/app/models/concerns/hr_lite/audited.rb +3 -1
  33. data/app/models/concerns/hr_lite/encrypted_money.rb +8 -5
  34. data/app/models/hr_lite/attendance_record.rb +21 -4
  35. data/app/models/hr_lite/comp_off_request.rb +2 -0
  36. data/app/models/hr_lite/document.rb +1 -1
  37. data/app/models/hr_lite/expense.rb +3 -0
  38. data/app/models/hr_lite/hr_request.rb +2 -0
  39. data/app/models/hr_lite/kudo.rb +2 -2
  40. data/app/models/hr_lite/leave_request.rb +25 -6
  41. data/app/models/hr_lite/payroll_line_item.rb +8 -0
  42. data/app/models/hr_lite/payroll_run.rb +15 -6
  43. data/app/models/hr_lite/regularization_request.rb +2 -2
  44. data/app/models/hr_lite/resignation.rb +5 -1
  45. data/app/models/hr_lite/role.rb +5 -0
  46. data/app/models/hr_lite/salary_slip.rb +2 -1
  47. data/app/models/hr_lite/statutory_rate_card_record.rb +6 -0
  48. data/app/models/hr_lite/tax_declaration.rb +5 -0
  49. data/app/services/hr_lite/attendance_puncher.rb +4 -0
  50. data/app/services/hr_lite/overview_query.rb +0 -12
  51. data/app/services/hr_lite/work_status.rb +5 -2
  52. data/app/views/hr_lite/admin/assets/index.html.erb +9 -2
  53. data/app/views/hr_lite/admin/attendances/index.html.erb +4 -4
  54. data/app/views/hr_lite/admin/attendances/show.html.erb +4 -2
  55. data/app/views/hr_lite/admin/attendances/summary.html.erb +5 -2
  56. data/app/views/hr_lite/admin/audit_logs/index.html.erb +6 -4
  57. data/app/views/hr_lite/admin/benefits/index.html.erb +10 -4
  58. data/app/views/hr_lite/admin/ca_reviews/index.html.erb +3 -3
  59. data/app/views/hr_lite/admin/checklists/index.html.erb +3 -3
  60. data/app/views/hr_lite/admin/comp_off_requests/index.html.erb +1 -2
  61. data/app/views/hr_lite/admin/comp_off_requests/show.html.erb +6 -6
  62. data/app/views/hr_lite/admin/designation_changes/new.html.erb +4 -3
  63. data/app/views/hr_lite/admin/documents/index.html.erb +9 -4
  64. data/app/views/hr_lite/admin/documents/show.html.erb +1 -0
  65. data/app/views/hr_lite/admin/employees/_form.html.erb +12 -5
  66. data/app/views/hr_lite/admin/employees/show.html.erb +40 -33
  67. data/app/views/hr_lite/admin/expense_categories/index.html.erb +1 -1
  68. data/app/views/hr_lite/admin/expenses/index.html.erb +7 -4
  69. data/app/views/hr_lite/admin/holidays/index.html.erb +7 -5
  70. data/app/views/hr_lite/admin/hr_requests/index.html.erb +3 -3
  71. data/app/views/hr_lite/admin/hr_requests/show.html.erb +5 -4
  72. data/app/views/hr_lite/admin/leave_balances/index.html.erb +6 -4
  73. data/app/views/hr_lite/admin/leave_requests/index.html.erb +1 -2
  74. data/app/views/hr_lite/admin/leave_requests/show.html.erb +6 -4
  75. data/app/views/hr_lite/admin/leave_types/edit.html.erb +4 -4
  76. data/app/views/hr_lite/admin/leave_types/index.html.erb +5 -5
  77. data/app/views/hr_lite/admin/loans/index.html.erb +1 -1
  78. data/app/views/hr_lite/admin/loans/new.html.erb +1 -1
  79. data/app/views/hr_lite/admin/loans/show.html.erb +8 -12
  80. data/app/views/hr_lite/admin/office_locations/edit.html.erb +4 -4
  81. data/app/views/hr_lite/admin/office_locations/index.html.erb +1 -1
  82. data/app/views/hr_lite/admin/overview/index.html.erb +10 -31
  83. data/app/views/hr_lite/admin/payroll_line_items/new.html.erb +2 -1
  84. data/app/views/hr_lite/admin/payroll_runs/index.html.erb +3 -3
  85. data/app/views/hr_lite/admin/payroll_runs/show.html.erb +15 -10
  86. data/app/views/hr_lite/admin/performance_plans/_form.html.erb +2 -1
  87. data/app/views/hr_lite/admin/policies/index.html.erb +2 -2
  88. data/app/views/hr_lite/admin/policies/new.html.erb +4 -3
  89. data/app/views/hr_lite/admin/regularization_requests/index.html.erb +3 -4
  90. data/app/views/hr_lite/admin/regularization_requests/show.html.erb +5 -6
  91. data/app/views/hr_lite/admin/reports/index.html.erb +3 -6
  92. data/app/views/hr_lite/admin/reports/show.html.erb +16 -2
  93. data/app/views/hr_lite/admin/roles/_form.html.erb +8 -6
  94. data/app/views/hr_lite/admin/roles/edit.html.erb +4 -2
  95. data/app/views/hr_lite/admin/roles/index.html.erb +1 -1
  96. data/app/views/hr_lite/admin/salary_slips/show.html.erb +3 -2
  97. data/app/views/hr_lite/admin/salary_structures/_form.html.erb +1 -0
  98. data/app/views/hr_lite/admin/settings/edit.html.erb +5 -1
  99. data/app/views/hr_lite/admin/statutory_rate_cards/_form.html.erb +14 -10
  100. data/app/views/hr_lite/admin/statutory_rate_cards/index.html.erb +3 -1
  101. data/app/views/hr_lite/admin/tax_declarations/index.html.erb +1 -1
  102. data/app/views/hr_lite/admin/tax_declarations/show.html.erb +8 -4
  103. data/app/views/hr_lite/admin/wfh_requests/index.html.erb +17 -16
  104. data/app/views/hr_lite/admin/wfh_requests/show.html.erb +3 -5
  105. data/app/views/hr_lite/admin/work_locations/index.html.erb +1 -1
  106. data/app/views/hr_lite/admin/work_modes/index.html.erb +1 -1
  107. data/app/views/hr_lite/approvals/index.html.erb +5 -4
  108. data/app/views/hr_lite/attendance/_custom_status.html.erb +7 -9
  109. data/app/views/hr_lite/attendance/_month_grid.html.erb +9 -9
  110. data/app/views/hr_lite/attendance/_penalties.html.erb +2 -2
  111. data/app/views/hr_lite/attendance/_punch_card.html.erb +10 -4
  112. data/app/views/hr_lite/attendance/_slot.html.erb +9 -6
  113. data/app/views/hr_lite/attendance/_trail.html.erb +1 -1
  114. data/app/views/hr_lite/attendance/_work_mode.html.erb +1 -1
  115. data/app/views/hr_lite/attendance/show.html.erb +7 -5
  116. data/app/views/hr_lite/calendar/show.html.erb +2 -2
  117. data/app/views/hr_lite/comp_off_requests/index.html.erb +1 -1
  118. data/app/views/hr_lite/comp_off_requests/new.html.erb +1 -1
  119. data/app/views/hr_lite/documents/_upload_form.html.erb +1 -1
  120. data/app/views/hr_lite/employee_profiles/show.html.erb +4 -2
  121. data/app/views/hr_lite/home/index.html.erb +6 -5
  122. data/app/views/hr_lite/id_cards/show.html.erb +5 -1
  123. data/app/views/hr_lite/leave_requests/_balance_chips.html.erb +3 -2
  124. data/app/views/hr_lite/leave_requests/new.html.erb +17 -8
  125. data/app/views/hr_lite/leave_requests/show.html.erb +2 -0
  126. data/app/views/hr_lite/loans/index.html.erb +1 -1
  127. data/app/views/hr_lite/org/show.html.erb +1 -1
  128. data/app/views/hr_lite/regularization_requests/index.html.erb +7 -7
  129. data/app/views/hr_lite/regularization_requests/new.html.erb +7 -5
  130. data/app/views/hr_lite/resignations/show.html.erb +2 -1
  131. data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +7 -7
  132. data/app/views/hr_lite/salary_slips/pdf.html.erb +2 -1
  133. data/app/views/hr_lite/tax/_computation.html.erb +1 -1
  134. data/app/views/hr_lite/tax_declarations/show.html.erb +4 -4
  135. data/app/views/hr_lite/team/show.html.erb +8 -7
  136. data/app/views/hr_lite/timelines/show.html.erb +1 -0
  137. data/app/views/hr_lite/wfh_requests/index.html.erb +3 -3
  138. data/app/views/hr_lite/work_time/_bars.html.erb +5 -4
  139. data/app/views/hr_lite/work_time/_donut.html.erb +1 -1
  140. data/app/views/hr_lite/work_time/_report.html.erb +14 -8
  141. data/app/views/layouts/hr_lite/application.html.erb +1 -0
  142. data/app/views/layouts/hr_lite/mailer.html.erb +10 -2
  143. data/app/views/layouts/hr_lite/mailer.text.erb +4 -1
  144. data/config/routes.rb +1 -1
  145. data/lib/hr_lite/amount_in_words.rb +7 -5
  146. data/lib/hr_lite/notifications.rb +5 -2
  147. data/lib/hr_lite/statutory_seeds.rb +1 -1
  148. data/lib/hr_lite/version.rb +1 -1
  149. metadata +2 -1
@@ -13,7 +13,7 @@
13
13
  </section>
14
14
  <% end %>
15
15
 
16
- <% rows = hrl_inbox_rows %>
16
+ <% rows = hrl_inbox_rows.reject { |_, record| @in_flow.include?([ record.class.polymorphic_name, record.id ]) } %>
17
17
  <section class="hrl-card">
18
18
  <% if @approvals.empty? && rows.empty? %>
19
19
  <p class="hrl-muted">Nothing is waiting on you.</p>
@@ -45,13 +45,14 @@
45
45
  </td>
46
46
  </tr>
47
47
  <% end %>
48
- <%# The HR queues this viewer decides; Open lands on that queue's own page. %>
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><%= tab[:label] %></strong></td>
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="<%= hr_lite.public_send(tab[:path]) %>">Open</a></td>
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 (text + emoji), Slack-style. Independent of check-in. %>
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 = %w[🏠 🏢 ✈️ 🚗 🚆 🏖️ 🤒 🤧 🩺 🍽️ ☕ 🎧 🎯 💻 📞 📅 🗓️ 🤝 💼 📍 🧳 🏔️ 🌴 🏕️ 📝 📊 🔒 🚫 ⏳ 🔕 🎉 🙏 👶 👨‍👩‍👧 🏥 📚 🎤 🛠️ 🌙 🌧️] %>
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, data: { "hrl-emoji-form": true } do %>
14
- <div class="hrl-field"><label for="st-emoji">Emoji</label>
15
- <input class="hrl-input" id="st-emoji" name="emoji" maxlength="16" value="<%= custom&.emoji %>" placeholder="Pick or type one" autocomplete="off">
16
- <div class="hrl-emoji-grid" role="group" aria-label="Pick an emoji">
17
- <% picks.each do |e| %><button type="button" class="hrl-emoji" data-hrl-emoji="<%= e %>" aria-label="Use <%= e %>"><%= e %></button><% end %>
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>
@@ -4,6 +4,15 @@
4
4
  link_builder = local_assigns[:link_builder]
5
5
  pending_dates = local_assigns[:pending_dates]
6
6
  states = HrLite::ApplicationHelper::DAY_STATES %>
7
+ <div class="hrl-legend hrl-small">
8
+ <% states.except(:upcoming).each_value do |code, word, mod| %>
9
+ <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--<%= mod %>"><%= code %> <%= word %></span>
10
+ <% end %>
11
+ <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--today">Today</span>
12
+ <span class="hrl-badge hrl-mgrid__key"><span class="hrl-mgrid__flag" aria-hidden="true"></span> Flagged</span>
13
+ <% if pending_dates %><span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--pending">Fix pending</span><% end %>
14
+ <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--muted">Upcoming</span>
15
+ </div>
7
16
  <div class="hrl-mgrid" role="group" aria-label="Attendance for <%= month.strftime('%B %Y') %>">
8
17
  <% %w[Mo Tu We Th Fr Sa Su].each do |d| %>
9
18
  <div class="hrl-mgrid__head"><%= d %></div>
@@ -25,12 +34,3 @@
25
34
  </div>
26
35
  <% end %>
27
36
  </div>
28
- <div class="hrl-legend hrl-small">
29
- <% states.except(:upcoming).each_value do |code, word, mod| %>
30
- <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--<%= mod %>"><%= code %> <%= word %></span>
31
- <% end %>
32
- <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--today">Today</span>
33
- <span class="hrl-badge hrl-mgrid__key"><span class="hrl-mgrid__flag" aria-hidden="true"></span> Flagged</span>
34
- <% if pending_dates %><span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--pending">Fix pending</span><% end %>
35
- <span class="hrl-badge hrl-mgrid__key hrl-mgrid__day--muted">Upcoming</span>
36
- </div>
@@ -3,8 +3,8 @@
3
3
  leave_type = HrLite::Setting.instance.penalty_leave_type %>
4
4
  <section class="hrl-card" id="penalties">
5
5
  <h2 class="hrl-card__title">Penalty hours</h2>
6
- <p><strong><%= hrl_duration(held * 60) %></strong> held · <%= hrl_days(HrLite::AttendancePenalty.days_for(held)) %> days<%= " of #{leave_type.name}" if leave_type %></p>
7
- <p class="hrl-hint">Penalties add up until HR removes them. Every 5 hours holds half a day of leave, which can't be applied for.</p>
6
+ <p><strong><%= hrl_duration(held * 60) %></strong> held · <%= pluralize(hrl_days(HrLite::AttendancePenalty.days_for(held)), "day") %><%= " of #{leave_type.name}" if leave_type %></p>
7
+ <p class="hrl-hint">Penalties add up until HR removes them.<% if leave_type %> Every 5 hours holds half a day of leave, which can't be applied for.<% end %></p>
8
8
  <% HrLite::PenaltyRemoval.where(user_id: user.id).includes(:removed_by).order(created_at: :desc).each do |removal| %>
9
9
  <div class="hrl-feed__item hrl-small"><div class="hrl-feed__body">Removed <%= hrl_duration(removal.minutes * 60) %> on <%= removal.created_at.strftime("%-d %b %Y") %> by <%= hr_display_name(removal.removed_by) %>: <%= removal.reason %></div></div>
10
10
  <% end %>
@@ -13,7 +13,7 @@
13
13
 
14
14
  <div class="hrl-row hrl-row--mb hrl-small">
15
15
  <% if record&.check_in_at && !record.check_out_at %>
16
- <span class="hrl-badge hrl-badge--ok">In since <%= record.check_in_at.strftime("%H:%M") %> · <%= hrl_duration(Time.current - record.check_in_at) %> so far</span>
16
+ <span class="hrl-badge hrl-badge--ok">In since <%= record.check_in_at.strftime("%H:%M") %> · <span data-hrl-since="<%= record.check_in_at.iso8601 %>"><%= hrl_duration(Time.current - record.check_in_at) %> so far</span></span>
17
17
  <% elsif record&.check_in_at %>
18
18
  <span class="hrl-badge hrl-badge--ok">In <%= record.check_in_at.strftime("%H:%M") %></span>
19
19
  <% end %>
@@ -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? %>
@@ -134,9 +142,7 @@
134
142
 
135
143
  <% if local_assigns.key?(:attention) %>
136
144
  <div class="hrl-row">
137
- <a class="hrl-btn" href="<%= hr_lite.new_leave_request_path %>">Apply leave</a>
138
- <% if HrLite.config.office_geofence %><a class="hrl-btn" href="<%= hr_lite.wfh_requests_path %>">Request WFH</a><% end %>
139
- <a class="hrl-btn" href="<%= hr_lite.new_regularization_request_path %>">Fix a punch</a>
145
+ <a class="hrl-btn" href="<%= hr_lite.new_regularization_request_path %>">Fix a day</a>
140
146
  <a class="hrl-btn" href="<%= hr_lite.new_comp_off_request_path %>">Comp-off</a>
141
147
  <% if hr_lite_route?(:salary_slips_path) %><a class="hrl-btn" href="<%= hr_lite.salary_slips_path %>">Payslip</a><% end %>
142
148
  </div>
@@ -12,7 +12,7 @@
12
12
  <% elsif due %>
13
13
  <% left = due - Time.current %>
14
14
  <p class="hrl-small">Checked in <%= record.check_in_at.strftime("%H:%M") %> · checkout at <strong><%= due.strftime("%H:%M") %></strong> ·
15
- <%= left.positive? ? "#{hrl_duration(left)} left" : "you can check out" %></p>
15
+ <span data-hrl-countdown="<%= due.iso8601 %>" data-hrl-done="you can check out"><%= left.positive? ? "#{hrl_duration(left)} left" : "you can check out" %></span></p>
16
16
  <% end %>
17
17
  <% HrLite::AttendancePenalty.active.where(user_id: hr_current_user.id, date: Date.current).each do |penalty| %>
18
18
  <p class="hrl-small"><span class="hrl-badge hrl-badge--warn">+<%= penalty.minutes %> min</span> <%= penalty.label %></p>
@@ -23,11 +23,14 @@
23
23
  <label for="hrl-slot-start">Check-in slot</label>
24
24
  <%= select_tag :start_minute, options_for_select(HrLite::AttendanceSlot::START_MINUTES.map { |m| [ "#{HrLite::AttendanceSlot.label(m)} – out #{HrLite::AttendanceSlot.label(m + HrLite::EmployeeProfile.day_minutes_for(hr_current_user.id))}", m ] }, start), id: "hrl-slot-start" %>
25
25
  </div>
26
- <div class="hrl-field">
27
- <label for="hrl-slot-period">For</label>
28
- <%= select_tag :period, options_for_select(HrLite::AttendanceSlot::PERIODS.invert), id: "hrl-slot-period" %>
29
- </div>
30
- <button type="submit" class="hrl-btn hrl-btn--block">Save and lock slot</button>
26
+ <fieldset class="hrl-field">
27
+ <legend>Lock this slot for</legend>
28
+ <div class="hrl-mode-switch">
29
+ <% HrLite::AttendanceSlot::PERIODS.each do |period, word| %>
30
+ <button type="submit" name="period" value="<%= period %>" class="hrl-btn" data-turbo-confirm="Lock this slot for <%= word.downcase %>? After that only HR can change it."><%= word %></button>
31
+ <% end %>
32
+ </div>
33
+ </fieldset>
31
34
  <p class="hrl-small hrl-muted">Once saved, only HR can change it for that period.</p>
32
35
  <% end %>
33
36
  <% end %>
@@ -8,7 +8,7 @@
8
8
  <% if home_m %> · <%= number_with_delimiter(home_m) %> m from home<% end %>
9
9
  <% if accuracy %> · ±<%= accuracy %> m<% end %></div>
10
10
  <% end %>
11
- <% if (punches = record.punches).size > 2 %>
11
+ <% if (punches = local_assigns.fetch(:punches) { record.punches }).size > 2 %>
12
12
  <div class="hrl-punch-line">All punches: <%= punches.join(" · ") %></div>
13
13
  <% end %>
14
14
  <% if (route = record.route_url) %>
@@ -13,7 +13,7 @@
13
13
  </div>
14
14
  <%= render "hr_lite/attendance/custom_status", status: status %>
15
15
  <%= render "hr_lite/attendance/calendar_connection" %>
16
- <% if status.half_day_leave %><p class="hrl-small hrl-muted">Half-day leave today (<%= status.half_day_leave.half_day_part || "half day" %>).</p><% end %>
16
+ <% if status.half_day_leave %><p class="hrl-small hrl-muted">Half-day leave today (<%= HrLite::DAY_PARTS.rassoc(status.half_day_leave.half_day_part)&.first&.downcase || "half day" %>).</p><% end %>
17
17
 
18
18
  <% if working %>
19
19
  <% if status.overlay %>
@@ -14,15 +14,17 @@
14
14
  <div class="hrl-row hrl-right">
15
15
  <% { present: "ok", half_day: "ok", leave: nil, absent: "bad" }.each do |kind, tone| %>
16
16
  <% code, word = HrLite::ApplicationHelper::DAY_STATES[kind] %>
17
- <span class="<%= class_names("hrl-badge", "hrl-badge--#{tone}": tone) %>" role="img" aria-label="<%= word %> <%= pluralize(@counts[kind], "day") %>"><%= code %> <%= @counts[kind] %></span>
17
+ <span class="<%= class_names("hrl-badge", "hrl-badge--#{tone}": tone) %>" role="img" aria-label="<%= word %> <%= pluralize(@counts[kind], "day") %>"><%= word %> <%= @counts[kind] %></span>
18
18
  <% end %>
19
19
  </div>
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
- <a href="<%= hr_lite.new_regularization_request_path %>">Raise a regularization ticket</a>
25
- · <a href="<%= hr_lite.regularization_requests_path %>">My tickets</a></p>
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 %>">Fix a day</a>
26
+ <a class="hrl-btn" href="<%= hr_lite.regularization_requests_path %>">Day fixes</a>
27
+ </div>
26
28
  </section>
27
29
 
28
30
  <% if HrLite.config.attendance_slots %>
@@ -46,7 +48,7 @@
46
48
  <% if leave %><dt>Leave</dt><dd><%= leave.leave_type.name %></dd><% end %>
47
49
  <% if @day_info.kind == :holiday %><dt>Holiday</dt><dd><%= HrLite::Holiday.company_wide.for_office(HrLite::WorkingCalendar.office_for(hr_current_user.id)).where(date: @day).pick(:name) %></dd><% end %>
48
50
  <% if rec&.flagged? %><dt>Flag</dt><dd><%= rec.flag_note %></dd><% end %>
49
- <% if @day_ticket %><dt>Ticket</dt><dd><%= hrl_request_status_badge(@day_ticket.status) %> <%= @day_ticket.times_label %></dd><% end %>
51
+ <% if @day_ticket %><dt>Day fix</dt><dd><%= hrl_request_status_badge(@day_ticket.status) %> <%= @day_ticket.times_label %></dd><% end %>
50
52
  </dl>
51
53
  <% unless %i[holiday weekend leave].include?(@day_info.kind) || @day_ticket&.pending? %>
52
54
  <a class="hrl-btn hrl-btn--primary hrl-btn--block hrl-mt" href="<%= hr_lite.new_regularization_request_path(date: @day) %>">Fix this day</a>
@@ -20,7 +20,7 @@
20
20
  <% (first.cwday - 1).times do %><div></div><% end %>
21
21
  <% (first..@month.end_of_month).each do |date| %>
22
22
  <% cell_class, code =
23
- if @holidays[date] then [ "hrl-mgrid__day--holiday", "H" ]
23
+ if @calendar.holiday?(date) then [ "hrl-mgrid__day--holiday", "H" ]
24
24
  elsif @calendar.weekend?(date) then "hrl-mgrid__day--weekend"
25
25
  elsif @leaves[date].any? then [ "hrl-mgrid__day--leave", "#{@leaves[date].size} off" ]
26
26
  end %>
@@ -40,7 +40,7 @@
40
40
 
41
41
  <%# Names only — the leave type is the employee's own business. Leave keeps its label's "(first half)" note. %>
42
42
  <% day = ->(d) { d.strftime("%a %-d %b") }
43
- agenda = @holidays.values.flatten.map { |h| [ h.date, "#{day.(h.date)}: #{h.name} (holiday)" ] } +
43
+ agenda = @holidays.values.flatten.map { |h| [ h.date, "#{day.(h.date)}: #{h.name} (#{h.optional ? 'optional holiday' : 'holiday'})" ] } +
44
44
  @leaves.values.flatten.uniq.map { |r| [ [ r.start_date, first ].max, "#{[ r.start_date, r.end_date ].uniq.map(&day).join(' – ')}#{r.date_range_label[/ \(.+\)/]}: #{hr_display_name(r.user)}, on leave" ] } %>
45
45
  <% if agenda.any? %>
46
46
  <ul class="hrl-feed hrl-small hrl-mt">
@@ -9,7 +9,7 @@
9
9
 
10
10
  <section class="hrl-card">
11
11
  <p class="hrl-small hrl-muted">
12
- Worked on a weekend or holiday? Request a comp-off — once an admin approves,
12
+ Worked on a weekend or holiday? Request a comp-off — once it is approved,
13
13
  <%= @comp_off_type ? "your #{@comp_off_type.name} balance is credited" : "a day is credited to your balance" %>
14
14
  and you spend it through the normal leave flow.
15
15
  </p>
@@ -18,7 +18,7 @@
18
18
  <%= f.label :half_day, "I worked only half the day (credits 0.5)" %>
19
19
  </div>
20
20
  <div class="hrl-field">
21
- <%= f.label :reason, "What did you work on? (required)" %>
21
+ <%= f.label :reason, "What did you work on?" %>
22
22
  <%= f.text_area :reason, required: true %>
23
23
  </div>
24
24
  <div class="hrl-form-actions">
@@ -1,6 +1,6 @@
1
1
  <%# locals: document, url (where it posts), open (start expanded) — one upload form for My documents and HR's view of a person. %>
2
2
  <details class="hrl-card" <%= "open" if open || document.errors.any? %>>
3
- <summary class="hrl-btn hrl-btn--primary">Upload a document</summary>
3
+ <summary class="hrl-btn">Upload a document</summary>
4
4
  <%= form_with model: document, url: url, method: :post, local: true, class: "hrl-mt" do |f| %>
5
5
  <%= render "hr_lite/shared/form_errors", record: document %>
6
6
  <div class="hrl-field">
@@ -19,8 +19,10 @@
19
19
  <dt>Bank</dt><dd><%= [ @profile.bank_name, @profile.masked_account ].compact.join(" ").presence || "—" %></dd>
20
20
  </dl>
21
21
  <a class="hrl-btn hrl-mt" href="<%= hr_lite.org_path %>">Org chart</a>
22
- <p class="hrl-small hrl-muted hrl-mt">Something wrong? Ask leadership to correct it — changes are audited.</p>
23
- <a class="hrl-btn hrl-mt" href="<%= hr_lite.resignation_path %>">Resignation</a>
22
+ <p class="hrl-small hrl-muted hrl-mt">Something wrong?
23
+ <%= link_to_if hr_can?("hr_request.raise"), "Ask leadership to correct it", hr_lite.new_hr_request_path, class: "hrl-card__link" %> — changes are audited.</p>
24
+ <hr>
25
+ <a class="hrl-btn" href="<%= hr_lite.resignation_path %>">Resignation</a>
24
26
  </section>
25
27
  <% else %>
26
28
  <p class="hrl-card hrl-card--empty">Your HR profile has not been set up yet.</p>
@@ -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
- <% if (@waiting + @queue).positive? %>
5
+ <% waiting = @waiting + hrl_inbox_count %>
6
+ <% if waiting.positive? %>
6
7
  <section class="hrl-card">
7
- <h2 class="hrl-card__title"><%= pluralize(@waiting + @queue, "request") %> waiting on you</h2>
8
- <a class="hrl-btn hrl-btn--primary hrl-mt" href="<%= @waiting.positive? ? hr_lite.approvals_path : hr_lite.admin_leave_requests_path %>">Review requests</a>
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
 
@@ -19,7 +20,8 @@
19
20
  <% @mine_pending.each do |req| %>
20
21
  <% title, detail, href = case req
21
22
  when HrLite::LeaveRequest then [ req.leave_type.name, "#{req.date_range_label} · #{pluralize(hrl_days(req.days_count), 'day')}", hr_lite.leave_request_path(req) ]
22
- when HrLite::RegularizationRequest then [ "Attendance fix", "#{req.date.strftime('%a %-d %b')} · #{req.times_label}", hr_lite.regularization_requests_path ]
23
+ when HrLite::RegularizationRequest then [ "Day fix", "#{req.date.strftime('%a %-d %b')} · #{req.times_label}", hr_lite.regularization_requests_path ]
24
+ when HrLite::WfhRequest then [ "Work from home", [ req.start_date, req.end_date ].uniq.map { |d| d.strftime("%a %-d %b") }.join(" – "), hr_lite.wfh_requests_path ]
23
25
  else [ "Comp-off", "Worked #{req.date_worked.strftime('%a %-d %b')}", hr_lite.comp_off_requests_path ]
24
26
  end %>
25
27
  <a class="hrl-listrow" href="<%= href %>">
@@ -36,7 +38,6 @@
36
38
  <section class="hrl-card">
37
39
  <h2 class="hrl-card__title">On leave this week</h2>
38
40
  <%= render "hr_lite/leave_requests/out_list", leaves: @out_week, empty: "Nobody is on leave this week." %>
39
- <a class="hrl-card__link" href="<%= hr_lite.team_path %>">Team board</a>
40
41
  </section>
41
42
 
42
43
  <% if @awards.any? %>
@@ -1,5 +1,6 @@
1
1
  <% content_for(:page_title) { "ID card" } %>
2
2
  <% content_for(:tabrow) { render "hr_lite/shared/person_tabs", user: @user } if params[:user_id] %>
3
+ <% content_for(:back, "Employees") if params[:user_id] %>
3
4
  <% company = HrLite.config.company.call %>
4
5
  <% brand = company[:brand].presence || company[:name].presence || "HR" %>
5
6
  <% own = @user == hr_current_user %>
@@ -24,7 +25,7 @@
24
25
  </div>
25
26
  <div class="hrl-idcard__photo">
26
27
  <% if @profile.photo.attached? %>
27
- <img src="<%= Rails.application.routes.url_helpers.rails_storage_proxy_path(@profile.photo, disposition: "inline", only_path: true) %>" alt="<%= hr_display_name(@user) %>">
28
+ <img src="<%= Rails.application.routes.url_helpers.rails_storage_proxy_path(@profile.photo, disposition: "inline", only_path: true, expires_in: 1.hour) %>" alt="<%= hr_display_name(@user) %>">
28
29
  <% else %>
29
30
  <span><%= hr_display_name(@user).to_s.scan(/\b[[:alpha:]]/).first(2).join.upcase %></span>
30
31
  <% end %>
@@ -63,6 +64,9 @@
63
64
  </div>
64
65
 
65
66
  <div class="hrl-idcard-actions">
67
+ <% if own && (@profile.blood_group.blank? || @profile.emergency_contact.blank?) && hr_can?("hr_request.raise") %>
68
+ <a class="hrl-card__link hrl-idcard-help" href="<%= hr_lite.new_hr_request_path %>">Missing details? Ask HR</a>
69
+ <% end %>
66
70
  <button type="button" class="hrl-btn hrl-btn--primary" data-hrl-print>Print</button>
67
71
  <button type="button" class="hrl-btn" data-hrl-share="id-card-<%= @profile.employee_code %>.png">Share</button>
68
72
  <% if own && @print_requested %>
@@ -6,9 +6,10 @@
6
6
  <% if types.any? %>
7
7
  <div class="hrl-row">
8
8
  <% types.each do |type| %>
9
- <% balance = HrLite::LeaveBalance.for(user, type, HrLite::LeaveYear.current_key) %>
9
+ <% balance = HrLite::LeaveBalance.for(user, type, HrLite::LeaveYear.current_key); entitled = balance.entitled %>
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(entitled - balance.used) %> of <%= hrl_days(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>
@@ -7,38 +7,47 @@
7
7
  <section class="hrl-card">
8
8
  <%= form_with model: @request, url: hr_lite.leave_requests_path, local: true do |f| %>
9
9
  <%= render "hr_lite/shared/form_errors", record: @request %>
10
+ <% bad = ->(attr) { "hrl-#{attr}-error" if @request.errors[attr].any? } %>
11
+ <% say = ->(attr) { tag.p(@request.errors.full_messages_for(attr).to_sentence, id: bad.(attr), class: "hrl-hint hrl-hint--bad") if bad.(attr) } %>
10
12
  <div class="hrl-field">
11
13
  <%= f.label :leave_type_id, "Type", id: "hrl-leave-type", for: nil %>
12
14
  <% left = @balances.index_by(&:leave_type_id) %>
13
- <div class="hrl-chiprow" role="radiogroup" aria-labelledby="hrl-leave-type">
14
- <% HrLite::LeaveType.active.each_with_index do |type, i| %>
15
+ <%= tag.div(class: "hrl-chiprow", role: "radiogroup", aria: { labelledby: "hrl-leave-type", describedby: bad.(:leave_type) }) do %>
16
+ <% HrLite::LeaveType.active.reject { |t| t.comp_off && (b = left[t.id]) && b.available <= 0 }.each_with_index do |type, i| %>
15
17
  <label><%= f.radio_button :leave_type_id, type.id, required: i.zero? %><span><%= type.name.sub(/ leave\z/i, "") %><%= " · #{hrl_days(left[type.id].available)} left" if left[type.id] %><% if left[type.id] && (held = left[type.id].penalty_days).positive? %> · <%= hrl_days(held) %> held for penalties<% end %><%= " · Unpaid" unless type.paid %></span></label>
16
18
  <% end %>
17
- </div>
19
+ <% end %>
20
+ <%= say.(:leave_type) %>
18
21
  <p class="hrl-hint">Days beyond your balance are unpaid (loss of pay), cut from that month's salary.<% if (capped = @balances.find { |b| b.penalty_days.positive? }) %> Today you can apply for at most <%= hrl_days(capped.applicable) %> days of <%= capped.leave_type.name %>, <%= hrl_days(capped.penalty_days) %> held for penalties.<% end %></p>
22
+ <% if (profile = HrLite::EmployeeProfile.find_by(user_id: hr_current_user.id))&.on_probation? %>
23
+ <p class="hrl-hint">On probation until <%= profile.probation_until.strftime("%d %b %Y") %>: at most 1 day of leave a month, of any type.</p>
24
+ <% end %>
19
25
  </div>
20
26
  <div class="hrl-field">
21
27
  <%= f.label :start_date, "From" %>
22
- <%= f.date_field :start_date, required: true %>
28
+ <%= f.date_field :start_date, required: true, aria: { describedby: bad.(:start_date) } %>
29
+ <%= say.(:start_date) %>
23
30
  </div>
24
31
  <div class="hrl-field">
25
32
  <%= f.label :end_date, "To (leave blank for one day)" %>
26
- <%= f.date_field :end_date %>
33
+ <%= f.date_field :end_date, aria: { describedby: bad.(:end_date) } %>
34
+ <%= say.(:end_date) %>
27
35
  </div>
28
36
  <div class="hrl-field">
29
37
  <%= f.label :half_day_part, "Day", id: "hrl-day-part", for: nil %>
30
- <div class="hrl-chiprow" role="radiogroup" aria-labelledby="hrl-day-part">
38
+ <%= tag.div(class: "hrl-chiprow", role: "radiogroup", aria: { labelledby: "hrl-day-part", describedby: bad.(:half_day) }) do %>
31
39
  <% HrLite::DAY_PARTS.each do |label, value| %>
32
40
  <label><%= f.radio_button :half_day_part, value %><span><%= label %></span></label>
33
41
  <% end %>
34
- </div>
42
+ <% end %>
43
+ <%= say.(:half_day) %>
35
44
  </div>
36
45
  <div class="hrl-field">
37
46
  <%= f.label :reason, "Reason (optional)" %>
38
47
  <%= f.text_area :reason %>
39
48
  </div>
40
49
  <div class="hrl-form-actions">
41
- <%= f.submit "Submit request", class: "hrl-btn hrl-btn--primary" %>
50
+ <%= f.submit "Send for approval", class: "hrl-btn hrl-btn--primary" %>
42
51
  <a class="hrl-btn" href="<%= hr_lite.leave_requests_path %>">Cancel</a>
43
52
  </div>
44
53
  <% end %>
@@ -19,5 +19,7 @@
19
19
  <%= button_to "Cancel this leave", hr_lite.cancel_leave_request_path(@request), method: :post,
20
20
  class: "hrl-btn hrl-btn--danger hrl-mt",
21
21
  form: { data: { turbo_confirm: "Cancel this leave request?" } } %>
22
+ <% elsif @request.approved? %>
23
+ <p class="hrl-hint hrl-mt">This leave has started, so it can no longer be cancelled here. Ask HR if it needs cancelling.</p>
22
24
  <% end %>
23
25
  </section>
@@ -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) %>
@@ -1,6 +1,6 @@
1
1
  <% content_for(:page_title) { "Org chart" } %>
2
2
  <div class="hrl-page__head">
3
- <h1 class="hrl-page__title">Organisation</h1>
3
+ <h1 class="hrl-page__title">Org chart</h1>
4
4
  </div>
5
5
 
6
6
  <% if @own_chain.any? %>
@@ -1,16 +1,16 @@
1
- <% content_for(:page_title) { "Regularization" } %>
1
+ <% content_for(:page_title) { "Day fixes" } %>
2
2
  <% content_for(:back, "Attendance") %>
3
3
  <div class="hrl-page__head">
4
- <h1 class="hrl-page__title">My regularization tickets</h1>
4
+ <h1 class="hrl-page__title">Day fixes</h1>
5
5
  <div class="hrl-page__actions">
6
- <a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_regularization_request_path %>">Raise a ticket</a>
6
+ <a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_regularization_request_path %>">Fix a day</a>
7
7
  </div>
8
8
  </div>
9
9
 
10
10
  <section class="hrl-card">
11
11
  <p class="hrl-small hrl-muted">
12
- Forgot to check in or out? Raise a ticket with the actual times — once an
13
- admin approves, your attendance for that day is fixed automatically.
12
+ Forgot to check in or out? Fix a day with the actual times — once your
13
+ manager or HR approves, your attendance for that day is fixed automatically.
14
14
  </p>
15
15
  <% if @requests.any? %>
16
16
  <div class="hrl-table-wrap">
@@ -26,7 +26,7 @@
26
26
  <td data-label="" class="<%= 'hrl-cell--void' unless request.pending? %>">
27
27
  <% if request.pending? %>
28
28
  <%= button_to "Cancel", hr_lite.cancel_regularization_request_path(request), method: :post,
29
- class: "hrl-btn hrl-btn--danger", form: { data: { turbo_confirm: "Cancel this ticket?" } } %>
29
+ class: "hrl-btn hrl-btn--danger", form: { data: { turbo_confirm: "Cancel this day fix?" } } %>
30
30
  <% end %>
31
31
  </td>
32
32
  </tr>
@@ -36,6 +36,6 @@
36
36
  </div>
37
37
  <%= hrl_pagination %>
38
38
  <% else %>
39
- <p class="hrl-card--empty">No tickets yet.</p>
39
+ <p class="hrl-card--empty">No day fixes yet.</p>
40
40
  <% end %>
41
41
  </section>
@@ -1,14 +1,14 @@
1
- <% content_for(:page_title) { "Raise a regularization ticket" } %>
1
+ <% content_for(:page_title) { "Fix a day" } %>
2
2
  <% content_for(:back, "Attendance") %>
3
3
  <div class="hrl-page__head">
4
- <h1 class="hrl-page__title">Raise a regularization ticket</h1>
4
+ <h1 class="hrl-page__title">Fix a day</h1>
5
5
  </div>
6
6
 
7
7
  <section class="hrl-card">
8
8
  <%= form_with model: @request, url: hr_lite.regularization_requests_path, scope: :regularization_request, local: true do |f| %>
9
9
  <%= render "hr_lite/shared/form_errors", record: @request %>
10
10
  <% if (rule = HrLite.config.self_regularization) %>
11
- <p class="hrl-small hrl-muted">A missed punch or short day on a working day from today back to <%= rule[:within_days] %> days ago is fixed as soon as you submit, up to <%= rule[:per_week] %> times a week. Anything else goes to HR as a ticket.</p>
11
+ <p class="hrl-small hrl-muted">A missed punch or short day on a working day from today back to <%= rule[:within_days] %> days ago is fixed as soon as you submit, up to <%= rule[:per_week] %> times a week. Anything else goes to your manager or HR for approval.</p>
12
12
  <% end %>
13
13
  <div class="hrl-field">
14
14
  <%= f.label :date, "Which day needs fixing?" %>
@@ -25,11 +25,13 @@
25
25
  <p class="hrl-hint">Leave blank if only check-in is missing. Short day? Leave both times blank and give the reason.</p>
26
26
  </div>
27
27
  <div class="hrl-field">
28
- <%= f.label :reason, "Why does this day need fixing? (required)" %>
28
+ <%= f.label :reason, "Why does this day need fixing?" %>
29
29
  <%= f.text_area :reason, required: true %>
30
30
  </div>
31
+ <% fix_now = rule && @request.date && (probe = HrLite::RegularizationRequest.new(date: @request.date, user_id: hr_current_user.id)).self_fixable? %>
32
+ <% if fix_now %><p class="hrl-hint"><%= @request.date.strftime("%a %-d %b") %> is fixed as soon as you submit. Self-fixes left this week: <%= probe.self_fixes_left %>.</p><% end %>
31
33
  <div class="hrl-form-actions">
32
- <%= f.submit "Raise ticket", class: "hrl-btn hrl-btn--primary" %>
34
+ <%= f.submit fix_now ? "Fix now" : "Send for approval", class: "hrl-btn hrl-btn--primary" %>
33
35
  <a class="hrl-btn" href="<%= hr_lite.regularization_requests_path %>">Cancel</a>
34
36
  </div>
35
37
  <% end %>
@@ -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>
@@ -3,23 +3,23 @@
3
3
  <div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_money(slip.net_pay) %></div><div class="hrl-kpi__label">Net pay</div></div>
4
4
  <div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_money(slip.gross_earnings) %></div><div class="hrl-kpi__label">Gross</div></div>
5
5
  <div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_money(slip.total_deductions) %></div><div class="hrl-kpi__label">Deductions</div></div>
6
- <div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_days(slip.payable_days) %> / <%= hrl_days(slip.effective_lop_days) %></div><div class="hrl-kpi__label">Payable / LOP days</div></div>
6
+ <div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_days(slip.payable_days) %> / <%= hrl_days(slip.effective_lop_days) %></div><div class="hrl-kpi__label">Payable / <abbr title="Loss of pay: unpaid days">LOP</abbr> days</div></div>
7
7
  </div>
8
8
  <div class="hrl-grid hrl-grid--2">
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>
@@ -46,7 +46,7 @@
46
46
  <%# Out-of-window days are named rather than silently dropped: without them
47
47
  a mid-month joiner's row does not add up to the month. %>
48
48
  <dt>Days</dt>
49
- <dd><%= hrl_days(slip.payable_days) %> payable / <%= hrl_days(slip.effective_lop_days) %> LOP<% if slip.out_of_window_days.to_f.positive? %>
49
+ <dd><%= hrl_days(slip.payable_days) %> payable / <%= hrl_days(slip.effective_lop_days) %> <abbr title="Loss of pay: unpaid days">LOP</abbr><% if slip.out_of_window_days.to_f.positive? %>
50
50
  / <%= hrl_days(slip.out_of_window_days) %> outside employment<% end %> of <%= slip.days_in_month %></dd>
51
51
  <% if profile %>
52
52
  <dt>Employee code</dt><dd><%= profile.employee_code %></dd>
@@ -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>
@@ -32,7 +33,7 @@
32
33
  </td>
33
34
  </tr>
34
35
  <tr>
35
- <td>Payable days: <%= @slip.payable_days %> / <%= @slip.days_in_month %> (LOP <%= @slip.effective_lop_days %>)</td>
36
+ <td>Payable days: <%= @slip.payable_days.to_s.delete_suffix(".0") %> / <%= @slip.days_in_month %> (unpaid <%= @slip.effective_lop_days.to_s.delete_suffix(".0") %>)</td>
36
37
  <td class="num"><% if @profile&.masked_account %>Bank: <%= @profile.bank_name %> <%= @profile.masked_account %><% end %></td>
37
38
  </tr>
38
39
  </tbody>