hr_lite 0.1.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 (176) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +33 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +166 -0
  5. data/Rakefile +3 -0
  6. data/app/assets/javascripts/hr_lite/geo_punch.js +61 -0
  7. data/app/assets/javascripts/hr_lite/mention.js +100 -0
  8. data/app/assets/stylesheets/hr_lite/application.css +15 -0
  9. data/app/assets/stylesheets/hr_lite/hr_lite.css +239 -0
  10. data/app/controllers/hr_lite/admin/appraisals_controller.rb +74 -0
  11. data/app/controllers/hr_lite/admin/attendances_controller.rb +79 -0
  12. data/app/controllers/hr_lite/admin/audit_logs_controller.rb +9 -0
  13. data/app/controllers/hr_lite/admin/base_controller.rb +16 -0
  14. data/app/controllers/hr_lite/admin/designation_changes_controller.rb +33 -0
  15. data/app/controllers/hr_lite/admin/employees_controller.rb +53 -0
  16. data/app/controllers/hr_lite/admin/holidays_controller.rb +70 -0
  17. data/app/controllers/hr_lite/admin/leadership_controller.rb +18 -0
  18. data/app/controllers/hr_lite/admin/leave_balances_controller.rb +41 -0
  19. data/app/controllers/hr_lite/admin/leave_requests_controller.rb +41 -0
  20. data/app/controllers/hr_lite/admin/leave_types_controller.rb +54 -0
  21. data/app/controllers/hr_lite/admin/office_locations_controller.rb +46 -0
  22. data/app/controllers/hr_lite/admin/overview_controller.rb +12 -0
  23. data/app/controllers/hr_lite/admin/payroll_runs_controller.rb +93 -0
  24. data/app/controllers/hr_lite/admin/salary_slips_controller.rb +44 -0
  25. data/app/controllers/hr_lite/admin/salary_structures_controller.rb +49 -0
  26. data/app/controllers/hr_lite/admin/settings_controller.rb +18 -0
  27. data/app/controllers/hr_lite/application_controller.rb +76 -0
  28. data/app/controllers/hr_lite/appraisals_controller.rb +18 -0
  29. data/app/controllers/hr_lite/attendance_controller.rb +37 -0
  30. data/app/controllers/hr_lite/calendar_controller.rb +20 -0
  31. data/app/controllers/hr_lite/career_controller.rb +9 -0
  32. data/app/controllers/hr_lite/employee_profiles_controller.rb +9 -0
  33. data/app/controllers/hr_lite/holidays_controller.rb +9 -0
  34. data/app/controllers/hr_lite/home_controller.rb +7 -0
  35. data/app/controllers/hr_lite/kudos_controller.rb +36 -0
  36. data/app/controllers/hr_lite/leave_balances_controller.rb +11 -0
  37. data/app/controllers/hr_lite/leave_requests_controller.rb +56 -0
  38. data/app/controllers/hr_lite/salary_slips_controller.rb +39 -0
  39. data/app/controllers/hr_lite/users_controller.rb +9 -0
  40. data/app/helpers/hr_lite/application_helper.rb +105 -0
  41. data/app/jobs/hr_lite/application_job.rb +4 -0
  42. data/app/jobs/hr_lite/daily_digest_job.rb +33 -0
  43. data/app/jobs/hr_lite/leave_year_rollover_job.rb +29 -0
  44. data/app/mailers/hr_lite/application_mailer.rb +5 -0
  45. data/app/mailers/hr_lite/event_mailer.rb +33 -0
  46. data/app/models/concerns/hr_lite/audited.rb +70 -0
  47. data/app/models/concerns/hr_lite/encrypted_money.rb +32 -0
  48. data/app/models/hr_lite/application_record.rb +5 -0
  49. data/app/models/hr_lite/appraisal.rb +89 -0
  50. data/app/models/hr_lite/attendance_record.rb +41 -0
  51. data/app/models/hr_lite/audit_log.rb +17 -0
  52. data/app/models/hr_lite/designation_change.rb +52 -0
  53. data/app/models/hr_lite/employee_profile.rb +72 -0
  54. data/app/models/hr_lite/holiday.rb +18 -0
  55. data/app/models/hr_lite/kudo.rb +54 -0
  56. data/app/models/hr_lite/kudo_mention.rb +8 -0
  57. data/app/models/hr_lite/leave_balance.rb +42 -0
  58. data/app/models/hr_lite/leave_request.rb +207 -0
  59. data/app/models/hr_lite/leave_type.rb +22 -0
  60. data/app/models/hr_lite/office_location.rb +21 -0
  61. data/app/models/hr_lite/payroll_run.rb +112 -0
  62. data/app/models/hr_lite/salary_slip.rb +78 -0
  63. data/app/models/hr_lite/salary_structure.rb +49 -0
  64. data/app/models/hr_lite/setting.rb +14 -0
  65. data/app/services/hr_lite/attendance_puncher.rb +103 -0
  66. data/app/services/hr_lite/attendance_summary.rb +98 -0
  67. data/app/services/hr_lite/calculators/esi.rb +25 -0
  68. data/app/services/hr_lite/calculators/pf.rb +32 -0
  69. data/app/services/hr_lite/calculators/professional_tax.rb +20 -0
  70. data/app/services/hr_lite/calculators/proration.rb +30 -0
  71. data/app/services/hr_lite/calculators/tds.rb +74 -0
  72. data/app/services/hr_lite/day_status.rb +49 -0
  73. data/app/services/hr_lite/leave_day_counter.rb +24 -0
  74. data/app/services/hr_lite/overview_query.rb +39 -0
  75. data/app/services/hr_lite/payroll_run_processor.rb +46 -0
  76. data/app/services/hr_lite/pdf_renderer.rb +23 -0
  77. data/app/services/hr_lite/slip_builder.rb +106 -0
  78. data/app/services/hr_lite/working_calendar.rb +41 -0
  79. data/app/views/hr_lite/admin/appraisals/_form.html.erb +29 -0
  80. data/app/views/hr_lite/admin/appraisals/edit.html.erb +6 -0
  81. data/app/views/hr_lite/admin/appraisals/new.html.erb +6 -0
  82. data/app/views/hr_lite/admin/attendances/index.html.erb +49 -0
  83. data/app/views/hr_lite/admin/attendances/show.html.erb +55 -0
  84. data/app/views/hr_lite/admin/audit_logs/index.html.erb +39 -0
  85. data/app/views/hr_lite/admin/designation_changes/new.html.erb +19 -0
  86. data/app/views/hr_lite/admin/employees/_form.html.erb +40 -0
  87. data/app/views/hr_lite/admin/employees/edit.html.erb +3 -0
  88. data/app/views/hr_lite/admin/employees/index.html.erb +40 -0
  89. data/app/views/hr_lite/admin/employees/new.html.erb +3 -0
  90. data/app/views/hr_lite/admin/employees/show.html.erb +75 -0
  91. data/app/views/hr_lite/admin/holidays/index.html.erb +59 -0
  92. data/app/views/hr_lite/admin/leave_balances/index.html.erb +58 -0
  93. data/app/views/hr_lite/admin/leave_requests/index.html.erb +35 -0
  94. data/app/views/hr_lite/admin/leave_requests/show.html.erb +39 -0
  95. data/app/views/hr_lite/admin/leave_types/_form.html.erb +27 -0
  96. data/app/views/hr_lite/admin/leave_types/edit.html.erb +10 -0
  97. data/app/views/hr_lite/admin/leave_types/index.html.erb +39 -0
  98. data/app/views/hr_lite/admin/leave_types/new.html.erb +3 -0
  99. data/app/views/hr_lite/admin/office_locations/_form.html.erb +18 -0
  100. data/app/views/hr_lite/admin/office_locations/edit.html.erb +10 -0
  101. data/app/views/hr_lite/admin/office_locations/index.html.erb +30 -0
  102. data/app/views/hr_lite/admin/office_locations/new.html.erb +3 -0
  103. data/app/views/hr_lite/admin/overview/index.html.erb +72 -0
  104. data/app/views/hr_lite/admin/payroll_runs/index.html.erb +33 -0
  105. data/app/views/hr_lite/admin/payroll_runs/new.html.erb +20 -0
  106. data/app/views/hr_lite/admin/payroll_runs/show.html.erb +76 -0
  107. data/app/views/hr_lite/admin/salary_slips/show.html.erb +42 -0
  108. data/app/views/hr_lite/admin/salary_structures/_form.html.erb +25 -0
  109. data/app/views/hr_lite/admin/salary_structures/edit.html.erb +6 -0
  110. data/app/views/hr_lite/admin/salary_structures/new.html.erb +6 -0
  111. data/app/views/hr_lite/admin/settings/edit.html.erb +18 -0
  112. data/app/views/hr_lite/appraisals/index.html.erb +22 -0
  113. data/app/views/hr_lite/appraisals/show.html.erb +32 -0
  114. data/app/views/hr_lite/attendance/_month_grid.html.erb +41 -0
  115. data/app/views/hr_lite/attendance/_punch_card.html.erb +41 -0
  116. data/app/views/hr_lite/attendance/show.html.erb +22 -0
  117. data/app/views/hr_lite/calendar/show.html.erb +45 -0
  118. data/app/views/hr_lite/career/show.html.erb +49 -0
  119. data/app/views/hr_lite/employee_profiles/show.html.erb +22 -0
  120. data/app/views/hr_lite/event_mailer/event.html.erb +10 -0
  121. data/app/views/hr_lite/event_mailer/event.text.erb +10 -0
  122. data/app/views/hr_lite/event_mailer/leadership.html.erb +27 -0
  123. data/app/views/hr_lite/event_mailer/leadership.text.erb +16 -0
  124. data/app/views/hr_lite/holidays/_next_holiday.html.erb +12 -0
  125. data/app/views/hr_lite/holidays/index.html.erb +30 -0
  126. data/app/views/hr_lite/home/index.html.erb +25 -0
  127. data/app/views/hr_lite/kudos/_kudo.html.erb +17 -0
  128. data/app/views/hr_lite/kudos/index.html.erb +39 -0
  129. data/app/views/hr_lite/leave_balances/index.html.erb +19 -0
  130. data/app/views/hr_lite/leave_requests/_balance_chips.html.erb +17 -0
  131. data/app/views/hr_lite/leave_requests/index.html.erb +37 -0
  132. data/app/views/hr_lite/leave_requests/new.html.erb +36 -0
  133. data/app/views/hr_lite/leave_requests/show.html.erb +25 -0
  134. data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +48 -0
  135. data/app/views/hr_lite/salary_slips/index.html.erb +29 -0
  136. data/app/views/hr_lite/salary_slips/pdf.html.erb +72 -0
  137. data/app/views/hr_lite/salary_slips/show.html.erb +12 -0
  138. data/app/views/hr_lite/shared/_form_errors.html.erb +10 -0
  139. data/app/views/hr_lite/shared/_pagination.html.erb +11 -0
  140. data/app/views/layouts/hr_lite/application.html.erb +56 -0
  141. data/app/views/layouts/hr_lite/mailer.html.erb +30 -0
  142. data/app/views/layouts/hr_lite/mailer.text.erb +6 -0
  143. data/app/views/layouts/hr_lite/pdf.html.erb +26 -0
  144. data/config/routes.rb +57 -0
  145. data/db/migrate/20260718190726_create_hr_lite_kudos.rb +14 -0
  146. data/db/migrate/20260718190727_create_hr_lite_kudo_mentions.rb +12 -0
  147. data/db/migrate/20260718190728_create_hr_lite_audit_logs.rb +15 -0
  148. data/db/migrate/20260718192342_create_hr_lite_office_locations.rb +13 -0
  149. data/db/migrate/20260718192343_create_hr_lite_attendance_records.rb +27 -0
  150. data/db/migrate/20260718193045_create_hr_lite_holidays.rb +14 -0
  151. data/db/migrate/20260718193046_create_hr_lite_leave_types.rb +20 -0
  152. data/db/migrate/20260718193047_create_hr_lite_leave_balances.rb +17 -0
  153. data/db/migrate/20260718193048_create_hr_lite_leave_requests.rb +23 -0
  154. data/db/migrate/20260718193049_create_hr_lite_settings.rb +10 -0
  155. data/db/migrate/20260718194534_create_hr_lite_employee_profiles.rb +27 -0
  156. data/db/migrate/20260718194535_create_hr_lite_salary_structures.rb +23 -0
  157. data/db/migrate/20260718194536_create_hr_lite_payroll_runs.rb +20 -0
  158. data/db/migrate/20260718194537_create_hr_lite_salary_slips.rb +28 -0
  159. data/db/migrate/20260718195953_create_hr_lite_appraisals.rb +22 -0
  160. data/db/migrate/20260718195954_create_hr_lite_designation_changes.rb +17 -0
  161. data/lib/generators/hr_lite/install/install_generator.rb +29 -0
  162. data/lib/generators/hr_lite/install/templates/AFTER_INSTALL +35 -0
  163. data/lib/generators/hr_lite/install/templates/initializer.rb +45 -0
  164. data/lib/hr_lite/configuration.rb +60 -0
  165. data/lib/hr_lite/current.rb +7 -0
  166. data/lib/hr_lite/engine.rb +39 -0
  167. data/lib/hr_lite/geo.rb +22 -0
  168. data/lib/hr_lite/mention_parser.rb +18 -0
  169. data/lib/hr_lite/money.rb +33 -0
  170. data/lib/hr_lite/notifications.rb +99 -0
  171. data/lib/hr_lite/seeds.rb +42 -0
  172. data/lib/hr_lite/statutory_rate_card.rb +60 -0
  173. data/lib/hr_lite/version.rb +3 -0
  174. data/lib/hr_lite.rb +104 -0
  175. data/lib/tasks/hr_lite_tasks.rake +8 -0
  176. metadata +250 -0
@@ -0,0 +1,49 @@
1
+ <% content_for(:page_title) { "Career" } %>
2
+ <div class="hrl-page__head">
3
+ <h1 class="hrl-page__title">My career</h1>
4
+ </div>
5
+
6
+ <section class="hrl-card">
7
+ <h2 class="hrl-card__title">Current role</h2>
8
+ <p><strong><%= @profile&.designation.presence || "Not set yet" %></strong>
9
+ <% if @profile %><span class="hrl-muted hrl-small">since <%= (@changes.first&.effective_date || @profile.date_of_joining).strftime("%b %Y") %></span><% end %>
10
+ </p>
11
+ </section>
12
+
13
+ <section class="hrl-card">
14
+ <h2 class="hrl-card__title">Role history</h2>
15
+ <% if @changes.any? %>
16
+ <% @changes.each do |change| %>
17
+ <div class="hrl-feed__item">
18
+ <div class="hrl-feed__body">
19
+ <strong><%= change.to_designation %></strong>
20
+ <% if change.from_designation.present? %><span class="hrl-muted">(from <%= change.from_designation %>)</span><% end %>
21
+ <div class="hrl-feed__meta">
22
+ Effective <%= change.effective_date.strftime("%d %b %Y") %>
23
+ <% if change.note.present? %> · <%= change.note %><% end %>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ <% end %>
28
+ <% else %>
29
+ <p class="hrl-muted hrl-small">No role changes recorded yet.</p>
30
+ <% end %>
31
+ </section>
32
+
33
+ <section class="hrl-card">
34
+ <h2 class="hrl-card__title">Appraisals</h2>
35
+ <% if @appraisals.any? %>
36
+ <% @appraisals.each do |appraisal| %>
37
+ <div class="hrl-feed__item">
38
+ <div class="hrl-feed__body">
39
+ <strong><%= appraisal.period_label %></strong>
40
+ <% if appraisal.rating %><span class="hrl-badge hrl-badge--ok"><%= appraisal.rating %>/5</span><% end %>
41
+ <% if appraisal.outcome != "none" %><span class="hrl-badge"><%= appraisal.outcome.humanize %></span><% end %>
42
+ </div>
43
+ <a class="hrl-btn" href="<%= hr_lite.appraisal_path(appraisal) %>">Read</a>
44
+ </div>
45
+ <% end %>
46
+ <% else %>
47
+ <p class="hrl-muted hrl-small">No shared appraisals yet.</p>
48
+ <% end %>
49
+ </section>
@@ -0,0 +1,22 @@
1
+ <% content_for(:page_title) { "My HR profile" } %>
2
+ <div class="hrl-page__head">
3
+ <h1 class="hrl-page__title">My HR profile</h1>
4
+ </div>
5
+
6
+ <% if @profile %>
7
+ <section class="hrl-card">
8
+ <dl class="hrl-deflist">
9
+ <dt>Employee code</dt><dd><%= @profile.employee_code %></dd>
10
+ <dt>Designation</dt><dd><%= @profile.designation.presence || "—" %></dd>
11
+ <dt>Department</dt><dd><%= @profile.department.presence || "—" %></dd>
12
+ <dt>Joined</dt><dd><%= @profile.date_of_joining.strftime("%d %B %Y") %></dd>
13
+ <dt>Tax regime</dt><dd><%= @profile.tax_regime.humanize %></dd>
14
+ <dt>PAN</dt><dd><%= @profile.masked_pan || "—" %></dd>
15
+ <dt>PF UAN</dt><dd><%= @profile.masked_uan || "—" %></dd>
16
+ <dt>Bank</dt><dd><%= [ @profile.bank_name, @profile.masked_account ].compact.join(" ").presence || "—" %></dd>
17
+ </dl>
18
+ <p class="hrl-small hrl-muted hrl-mt">Something wrong? Ask leadership to correct it — changes are audited.</p>
19
+ </section>
20
+ <% else %>
21
+ <p class="hrl-card hrl-card--empty">Your HR profile has not been set up yet.</p>
22
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <h1><%= @heading %></h1>
2
+ <% if @body.present? %><p><%= @body %></p><% end %>
3
+ <% if @lines.any? %>
4
+ <ul class="lines">
5
+ <% @lines.each do |line| %><li><%= line %></li><% end %>
6
+ </ul>
7
+ <% end %>
8
+ <% if @cta_url %>
9
+ <a class="btn" href="<%= @cta_url %>">Open in HR</a>
10
+ <% end %>
@@ -0,0 +1,10 @@
1
+ <%= @heading %>
2
+ <% if @body.present? %>
3
+ <%= @body %>
4
+ <% end %>
5
+ <% @lines.each do |line| %>
6
+ - <%= line %>
7
+ <% end %>
8
+ <% if @cta_url %>
9
+ <%= @cta_url %>
10
+ <% end %>
@@ -0,0 +1,27 @@
1
+ <h1><%= @heading %></h1>
2
+ <% if @body.present? %><p><%= @body %></p><% end %>
3
+ <% if @lines.any? %>
4
+ <ul class="lines">
5
+ <% @lines.each do |line| %><li><%= line %></li><% end %>
6
+ </ul>
7
+ <% end %>
8
+ <% if @diff.present? %>
9
+ <table class="diff">
10
+ <tr><th>Field</th><th>Change</th></tr>
11
+ <% @diff.each do |attr, change| %>
12
+ <tr>
13
+ <td><%= attr.to_s.humanize %></td>
14
+ <td>
15
+ <% if change.is_a?(Array) %>
16
+ <%= change[0].inspect %> &rarr; <%= change[1].inspect %>
17
+ <% else %>
18
+ <%= change %>
19
+ <% end %>
20
+ </td>
21
+ </tr>
22
+ <% end %>
23
+ </table>
24
+ <% end %>
25
+ <% if @cta_url %>
26
+ <a class="btn" href="<%= @cta_url %>">Open in HR</a>
27
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <%= @heading %>
2
+ <% if @body.present? %>
3
+ <%= @body %>
4
+ <% end %>
5
+ <% @lines.each do |line| %>
6
+ - <%= line %>
7
+ <% end %>
8
+ <% if @diff.present? %>
9
+ Changes:
10
+ <% @diff.each do |attr, change| %>
11
+ - <%= attr.to_s.humanize %>: <%= change.is_a?(Array) ? "#{change[0].inspect} -> #{change[1].inspect}" : change %>
12
+ <% end %>
13
+ <% end %>
14
+ <% if @cta_url %>
15
+ <%= @cta_url %>
16
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <section class="hrl-card">
2
+ <h2 class="hrl-card__title">Next holiday</h2>
3
+ <% holiday = HrLite::Holiday.company_wide.upcoming.first %>
4
+ <% if holiday %>
5
+ <p><strong><%= holiday.name %></strong><br>
6
+ <span class="hrl-muted hrl-small"><%= holiday.date.strftime("%A, %d %B %Y") %>
7
+ (<%= (holiday.date - Date.current).to_i %> days away)</span></p>
8
+ <% else %>
9
+ <p class="hrl-muted hrl-small">No upcoming holidays on the calendar.</p>
10
+ <% end %>
11
+ <a class="hrl-small" href="<%= hr_lite.calendar_path %>">Company calendar</a>
12
+ </section>
@@ -0,0 +1,30 @@
1
+ <% content_for(:page_title) { "Holidays" } %>
2
+ <div class="hrl-page__head">
3
+ <h1 class="hrl-page__title">Holidays <%= @year %></h1>
4
+ <div class="hrl-page__actions">
5
+ <a class="hrl-btn" href="<%= hr_lite.holidays_path(year: @year - 1) %>"><%= @year - 1 %></a>
6
+ <a class="hrl-btn" href="<%= hr_lite.holidays_path(year: @year + 1) %>"><%= @year + 1 %></a>
7
+ <a class="hrl-btn" href="<%= hr_lite.calendar_path %>">Calendar</a>
8
+ </div>
9
+ </div>
10
+
11
+ <section class="hrl-card">
12
+ <% if @holidays.any? %>
13
+ <div class="hrl-table-wrap">
14
+ <table class="hrl-table hrl-table--stack">
15
+ <thead><tr><th>Date</th><th>Holiday</th><th></th></tr></thead>
16
+ <tbody>
17
+ <% @holidays.each do |holiday| %>
18
+ <tr>
19
+ <td data-label="Date"><%= holiday.date.strftime("%a, %d %b") %></td>
20
+ <td data-label="Holiday"><%= holiday.name %></td>
21
+ <td data-label=""><% if holiday.optional %><span class="hrl-badge hrl-badge--muted">Optional</span><% end %></td>
22
+ </tr>
23
+ <% end %>
24
+ </tbody>
25
+ </table>
26
+ </div>
27
+ <% else %>
28
+ <p class="hrl-card--empty">No holidays recorded for <%= @year %>.</p>
29
+ <% end %>
30
+ </section>
@@ -0,0 +1,25 @@
1
+ <div class="hrl-page__head">
2
+ <h1 class="hrl-page__title">Hello, <%= hr_display_name(hr_current_user) %></h1>
3
+ </div>
4
+
5
+ <div class="hrl-grid hrl-grid--2">
6
+ <%= render "hr_lite/attendance/punch_card" %>
7
+ <%= render "hr_lite/leave_requests/balance_chips" %>
8
+ <%= render "hr_lite/holidays/next_holiday" %>
9
+
10
+ <section class="hrl-card">
11
+ <h2 class="hrl-card__title">Latest kudos</h2>
12
+ <% if @latest_kudos.any? %>
13
+ <div class="hrl-feed">
14
+ <%= render partial: "hr_lite/kudos/kudo", collection: @latest_kudos %>
15
+ </div>
16
+ <% else %>
17
+ <p class="hrl-muted hrl-small">No kudos yet — be the first to appreciate a teammate.</p>
18
+ <% end %>
19
+ <a class="hrl-btn hrl-mt" href="<%= hr_lite.kudos_path %>">Give kudos</a>
20
+ </section>
21
+ </div>
22
+
23
+ <% content_for :scripts do %>
24
+ <%= javascript_include_tag "hr_lite/geo_punch", defer: true %>
25
+ <% end %>
@@ -0,0 +1,17 @@
1
+ <div class="hrl-feed__item" id="kudo_<%= kudo.id %>">
2
+ <span class="hrl-avatar" aria-hidden="true"><%= hr_display_name(kudo.giver).first(1).upcase %></span>
3
+ <div class="hrl-feed__body">
4
+ <div class="hrl-row">
5
+ <strong><%= hr_display_name(kudo.giver) %></strong>
6
+ <% if kudo.badge_label %>
7
+ <span class="hrl-badge"><%= kudo.badge_label %></span>
8
+ <% end %>
9
+ <span class="hrl-feed__meta hrl-right"><%= time_ago_in_words(kudo.created_at) %> ago</span>
10
+ </div>
11
+ <p><%= render_kudo_message(kudo) %></p>
12
+ <% if kudo.deletable_by?(hr_current_user) %>
13
+ <%= button_to "Remove", hr_lite.kudo_path(kudo), method: :delete, class: "hrl-btn hrl-btn--danger hrl-small",
14
+ form: { data: { turbo_confirm: "Remove this kudos?" } } %>
15
+ <% end %>
16
+ </div>
17
+ </div>
@@ -0,0 +1,39 @@
1
+ <div class="hrl-page__head">
2
+ <h1 class="hrl-page__title">Kudos wall</h1>
3
+ <p class="hrl-page__sub">Appreciate teammates publicly. Mention people with @ — they get notified.</p>
4
+ </div>
5
+
6
+ <section class="hrl-card">
7
+ <%= form_with model: @kudo, url: hr_lite.kudos_path, local: true do |f| %>
8
+ <%= render "hr_lite/shared/form_errors", record: @kudo %>
9
+ <div class="hrl-field">
10
+ <%= f.label :message, "Say something nice" %>
11
+ <div class="hrl-mention-wrap" data-hrl-mention data-search-url="<%= hr_lite.users_search_path %>">
12
+ <%= f.text_area :message, placeholder: "Type @ to mention a teammate…", maxlength: 1000 %>
13
+ </div>
14
+ </div>
15
+ <div class="hrl-field">
16
+ <%= f.label :badge, "Badge (optional)" %>
17
+ <div class="hrl-chiprow">
18
+ <% HrLite::Kudo::BADGES.each do |key, label| %>
19
+ <label><%= f.radio_button :badge, key %><span><%= label %></span></label>
20
+ <% end %>
21
+ </div>
22
+ </div>
23
+ <div class="hrl-form-actions">
24
+ <%= f.submit "Post kudos", class: "hrl-btn hrl-btn--primary" %>
25
+ </div>
26
+ <% end %>
27
+ </section>
28
+
29
+ <section class="hrl-card">
30
+ <% if @kudos.any? %>
31
+ <div class="hrl-feed">
32
+ <%= render partial: "hr_lite/kudos/kudo", collection: @kudos %>
33
+ </div>
34
+ <% else %>
35
+ <p class="hrl-card--empty">No kudos yet. Start the wall.</p>
36
+ <% end %>
37
+ </section>
38
+
39
+ <%= hrl_pagination %>
@@ -0,0 +1,19 @@
1
+ <% content_for(:page_title) { "Leave balances" } %>
2
+ <div class="hrl-page__head">
3
+ <h1 class="hrl-page__title">Leave balances <%= @year %></h1>
4
+ </div>
5
+
6
+ <div class="hrl-grid hrl-grid--2">
7
+ <% @balances.each do |balance| %>
8
+ <section class="hrl-card">
9
+ <h2 class="hrl-card__title"><%= balance.leave_type.name %></h2>
10
+ <dl class="hrl-deflist">
11
+ <dt>Entitled</dt><dd><%= balance.entitled.to_f %></dd>
12
+ <dt>Used</dt><dd><%= balance.used.to_f %></dd>
13
+ <dt>Available</dt><dd><strong><%= balance.available.to_f %></strong></dd>
14
+ <% if balance.carried_forward.positive? %><dt>Carried forward</dt><dd><%= balance.carried_forward.to_f %></dd><% end %>
15
+ <% if balance.adjustment.nonzero? %><dt>Adjustments</dt><dd><%= balance.adjustment.to_f %> <span class="hrl-muted hrl-small"><%= balance.adjustment_note %></span></dd><% end %>
16
+ </dl>
17
+ </section>
18
+ <% end %>
19
+ </div>
@@ -0,0 +1,17 @@
1
+ <section class="hrl-card">
2
+ <h2 class="hrl-card__title">Leave balance <%= Date.current.year %></h2>
3
+ <% types = HrLite::LeaveType.active.where(paid: true).where.not(annual_quota: nil) %>
4
+ <% if types.any? %>
5
+ <div class="hrl-row">
6
+ <% types.each do |type| %>
7
+ <% balance = HrLite::LeaveBalance.for(hr_current_user, type, Date.current.year) %>
8
+ <span class="hrl-badge" style="background: <%= type.color %>1f; color: <%= type.color %>;">
9
+ <%= type.code %> <%= balance.available.to_f %>/<%= balance.entitled.to_f %>
10
+ </span>
11
+ <% end %>
12
+ </div>
13
+ <a class="hrl-btn hrl-mt" href="<%= hr_lite.new_leave_request_path %>">Apply for leave</a>
14
+ <% else %>
15
+ <p class="hrl-muted hrl-small">No leave types configured yet.</p>
16
+ <% end %>
17
+ </section>
@@ -0,0 +1,37 @@
1
+ <% content_for(:page_title) { "Leaves" } %>
2
+ <div class="hrl-page__head">
3
+ <h1 class="hrl-page__title">My leaves</h1>
4
+ <div class="hrl-page__actions">
5
+ <a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_leave_request_path %>">Apply</a>
6
+ </div>
7
+ </div>
8
+
9
+ <%= render "hr_lite/leave_requests/balance_chips" %>
10
+
11
+ <section class="hrl-card">
12
+ <% if @requests.any? %>
13
+ <div class="hrl-table-wrap">
14
+ <table class="hrl-table hrl-table--stack">
15
+ <thead><tr><th>Type</th><th>Dates</th><th>Days</th><th>Status</th><th></th></tr></thead>
16
+ <tbody>
17
+ <% @requests.each do |request| %>
18
+ <tr>
19
+ <td data-label="Type"><span class="hrl-badge" style="background: <%= request.leave_type.color %>1f; color: <%= request.leave_type.color %>;"><%= request.leave_type.code %></span></td>
20
+ <td data-label="Dates"><%= request.date_range_label %></td>
21
+ <td data-label="Days" class="hrl-num"><%= request.days_count.to_f %></td>
22
+ <td data-label="Status">
23
+ <span class="hrl-badge <%= { "approved" => "hrl-badge--ok", "rejected" => "hrl-badge--bad", "cancelled" => "hrl-badge--muted" }[request.status] %>"><%= request.status.humanize %></span>
24
+ </td>
25
+ <td data-label="">
26
+ <a class="hrl-small" href="<%= hr_lite.leave_request_path(request) %>">View</a>
27
+ </td>
28
+ </tr>
29
+ <% end %>
30
+ </tbody>
31
+ </table>
32
+ </div>
33
+ <%= hrl_pagination %>
34
+ <% else %>
35
+ <p class="hrl-card--empty">No leave requests yet.</p>
36
+ <% end %>
37
+ </section>
@@ -0,0 +1,36 @@
1
+ <% content_for(:page_title) { "Apply for leave" } %>
2
+ <div class="hrl-page__head">
3
+ <h1 class="hrl-page__title">Apply for leave</h1>
4
+ </div>
5
+
6
+ <%= render "hr_lite/leave_requests/balance_chips" %>
7
+
8
+ <section class="hrl-card">
9
+ <%= form_with model: @request, url: hr_lite.leave_requests_path, local: true do |f| %>
10
+ <%= render "hr_lite/shared/form_errors", record: @request %>
11
+ <div class="hrl-field">
12
+ <%= f.label :leave_type_id, "Type" %>
13
+ <%= f.collection_select :leave_type_id, HrLite::LeaveType.active, :id, :name %>
14
+ </div>
15
+ <div class="hrl-field">
16
+ <%= f.label :start_date, "From" %>
17
+ <%= f.date_field :start_date %>
18
+ </div>
19
+ <div class="hrl-field">
20
+ <%= f.label :end_date, "To" %>
21
+ <%= f.date_field :end_date %>
22
+ </div>
23
+ <div class="hrl-field hrl-field--check">
24
+ <%= f.check_box :half_day %>
25
+ <%= f.label :half_day, "Half day (single-day requests only)" %>
26
+ </div>
27
+ <div class="hrl-field">
28
+ <%= f.label :reason, "Reason (optional)" %>
29
+ <%= f.text_area :reason %>
30
+ </div>
31
+ <div class="hrl-form-actions">
32
+ <%= f.submit "Submit request", class: "hrl-btn hrl-btn--primary" %>
33
+ <a class="hrl-btn" href="<%= hr_lite.leave_requests_path %>">Cancel</a>
34
+ </div>
35
+ <% end %>
36
+ </section>
@@ -0,0 +1,25 @@
1
+ <% content_for(:page_title) { "Leave request" } %>
2
+ <div class="hrl-page__head">
3
+ <h1 class="hrl-page__title">Leave request</h1>
4
+ <div class="hrl-page__actions">
5
+ <a class="hrl-btn" href="<%= hr_lite.leave_requests_path %>">Back</a>
6
+ </div>
7
+ </div>
8
+
9
+ <section class="hrl-card">
10
+ <dl class="hrl-deflist">
11
+ <dt>Type</dt><dd><%= @request.leave_type.name %></dd>
12
+ <dt>Dates</dt><dd><%= @request.date_range_label %></dd>
13
+ <dt>Days</dt><dd><%= @request.days_count.to_f %></dd>
14
+ <dt>Status</dt><dd><span class="hrl-badge"><%= @request.status.humanize %></span></dd>
15
+ <% if @request.reason.present? %><dt>Reason</dt><dd><%= @request.reason %></dd><% end %>
16
+ <% if @request.decision_note.present? %><dt>Decision note</dt><dd><%= @request.decision_note %></dd><% end %>
17
+ <% if @request.decided_at %><dt>Decided</dt><dd><%= @request.decided_at.strftime("%d %b %Y") %> by <%= hr_display_name(@request.decided_by) %></dd><% end %>
18
+ </dl>
19
+
20
+ <% if @request.cancellable_by?(hr_current_user) %>
21
+ <%= button_to "Cancel this leave", hr_lite.cancel_leave_request_path(@request), method: :post,
22
+ class: "hrl-btn hrl-btn--danger hrl-mt",
23
+ form: { data: { turbo_confirm: "Cancel this leave request?" } } %>
24
+ <% end %>
25
+ </section>
@@ -0,0 +1,48 @@
1
+ <%# locals: slip, profile — shared by employee show, admin show %>
2
+ <div class="hrl-grid hrl-grid--2">
3
+ <section class="hrl-card">
4
+ <h2 class="hrl-card__title">Earnings</h2>
5
+ <div class="hrl-table-wrap">
6
+ <table class="hrl-table">
7
+ <thead><tr><th>Component</th><th class="hrl-num">Full</th><th class="hrl-num">Earned</th></tr></thead>
8
+ <tbody>
9
+ <% slip.earnings_rows.each do |row| %>
10
+ <tr>
11
+ <td><%= row["label"] %></td>
12
+ <td class="hrl-num"><%= hrl_money(row["full_amount"]) %></td>
13
+ <td class="hrl-num"><%= hrl_money(row["amount"]) %></td>
14
+ </tr>
15
+ <% end %>
16
+ <tr><td><strong>Gross</strong></td><td></td><td class="hrl-num"><strong><%= hrl_money(slip.gross_earnings) %></strong></td></tr>
17
+ </tbody>
18
+ </table>
19
+ </div>
20
+ </section>
21
+
22
+ <section class="hrl-card">
23
+ <h2 class="hrl-card__title">Deductions</h2>
24
+ <div class="hrl-table-wrap">
25
+ <table class="hrl-table">
26
+ <tbody>
27
+ <% slip.deductions_rows.each do |row| %>
28
+ <tr><td><%= row["label"] %></td><td class="hrl-num"><%= hrl_money(row["amount"]) %></td></tr>
29
+ <% end %>
30
+ <tr><td><strong>Total deductions</strong></td><td class="hrl-num"><strong><%= hrl_money(slip.total_deductions) %></strong></td></tr>
31
+ </tbody>
32
+ </table>
33
+ </div>
34
+ </section>
35
+ </div>
36
+
37
+ <section class="hrl-card">
38
+ <dl class="hrl-deflist">
39
+ <dt>Net pay</dt><dd><strong><%= hrl_money(slip.net_pay) %></strong> (<%= hrl_amount_in_words(slip.net_pay) %>)</dd>
40
+ <dt>Days</dt><dd><%= slip.payable_days %> payable / <%= slip.effective_lop_days %> LOP of <%= slip.days_in_month %></dd>
41
+ <% if profile %>
42
+ <dt>Employee code</dt><dd><%= profile.employee_code %></dd>
43
+ <% if profile.masked_uan %><dt>PF UAN</dt><dd><%= profile.masked_uan %></dd><% end %>
44
+ <% if profile.masked_pan %><dt>PAN</dt><dd><%= profile.masked_pan %></dd><% end %>
45
+ <% if profile.masked_account %><dt>Bank</dt><dd><%= profile.bank_name %> <%= profile.masked_account %></dd><% end %>
46
+ <% end %>
47
+ </dl>
48
+ </section>
@@ -0,0 +1,29 @@
1
+ <% content_for(:page_title) { "Salary slips" } %>
2
+ <div class="hrl-page__head">
3
+ <h1 class="hrl-page__title">My salary slips</h1>
4
+ </div>
5
+
6
+ <section class="hrl-card">
7
+ <% if @slips.any? %>
8
+ <div class="hrl-table-wrap">
9
+ <table class="hrl-table hrl-table--stack">
10
+ <thead><tr><th>Period</th><th>Net pay</th><th></th></tr></thead>
11
+ <tbody>
12
+ <% @slips.each do |slip| %>
13
+ <tr>
14
+ <td data-label="Period"><%= slip.period_month.strftime("%B %Y") %></td>
15
+ <td data-label="Net pay" class="hrl-num"><strong><%= hrl_money(slip.net_pay) %></strong></td>
16
+ <td data-label="">
17
+ <a class="hrl-btn" href="<%= hr_lite.salary_slip_path(slip) %>">View</a>
18
+ <a class="hrl-btn" href="<%= hr_lite.salary_slip_path(slip, format: :pdf) %>">PDF</a>
19
+ </td>
20
+ </tr>
21
+ <% end %>
22
+ </tbody>
23
+ </table>
24
+ </div>
25
+ <%= hrl_pagination %>
26
+ <% else %>
27
+ <p class="hrl-card--empty">No published salary slips yet.</p>
28
+ <% end %>
29
+ </section>
@@ -0,0 +1,72 @@
1
+ <% company = HrLite.config.company.call %>
2
+ <div class="pdf-card">
3
+ <div class="pdf-head">
4
+ <div>
5
+ <div class="pdf-company"><%= company[:name] %></div>
6
+ <% if company[:address].present? %><div class="pdf-muted"><%= company[:address] %></div><% end %>
7
+ </div>
8
+ <div style="text-align:right;">
9
+ <h1>Salary slip</h1>
10
+ <div class="pdf-muted"><%= @slip.period_month.strftime("%B %Y") %></div>
11
+ </div>
12
+ </div>
13
+
14
+ <table>
15
+ <tbody>
16
+ <tr>
17
+ <td><strong><%= HrLite.display_name(@slip.user) %></strong>
18
+ <% if @profile&.designation.present? %> · <%= @profile.designation %><% end %></td>
19
+ <td class="num">Employee code: <%= @profile&.employee_code || "—" %></td>
20
+ </tr>
21
+ <tr>
22
+ <td>
23
+ <% if @profile&.department.present? %>Dept: <%= @profile.department %> · <% end %>
24
+ <% if @profile&.date_of_joining %>DOJ: <%= @profile.date_of_joining.strftime("%d %b %Y") %><% end %>
25
+ </td>
26
+ <td class="num">
27
+ <% if @profile&.masked_uan %>UAN: <%= @profile.masked_uan %> · <% end %>
28
+ <% if @profile&.masked_pan %>PAN: <%= @profile.masked_pan %><% end %>
29
+ </td>
30
+ </tr>
31
+ <tr>
32
+ <td>Payable days: <%= @slip.payable_days %> / <%= @slip.days_in_month %> (LOP <%= @slip.effective_lop_days %>)</td>
33
+ <td class="num"><% if @profile&.masked_account %>Bank: <%= @profile.bank_name %> <%= @profile.masked_account %><% end %></td>
34
+ </tr>
35
+ </tbody>
36
+ </table>
37
+
38
+ <div class="cols">
39
+ <div>
40
+ <table>
41
+ <thead><tr><th>Earnings</th><th class="num">Full</th><th class="num">Earned</th></tr></thead>
42
+ <tbody>
43
+ <% @slip.earnings_rows.each do |row| %>
44
+ <tr><td><%= row["label"] %></td><td class="num"><%= row["full_amount"] %></td><td class="num"><%= row["amount"] %></td></tr>
45
+ <% end %>
46
+ <tr class="total"><td>Gross</td><td></td><td class="num"><%= @slip.gross_earnings.to_s("F") %></td></tr>
47
+ </tbody>
48
+ </table>
49
+ </div>
50
+ <div>
51
+ <table>
52
+ <thead><tr><th>Deductions</th><th class="num">Amount</th></tr></thead>
53
+ <tbody>
54
+ <% @slip.deductions_rows.each do |row| %>
55
+ <tr><td><%= row["label"] %></td><td class="num"><%= row["amount"] %></td></tr>
56
+ <% end %>
57
+ <tr class="total"><td>Total deductions</td><td class="num"><%= @slip.total_deductions.to_s("F") %></td></tr>
58
+ </tbody>
59
+ </table>
60
+ </div>
61
+ </div>
62
+
63
+ <div class="pdf-net">
64
+ Net pay: <%= HrLite.config.currency_symbol %><%= @slip.net_pay.to_s("F") %>
65
+ (<%= hrl_amount_in_words(@slip.net_pay) %>)
66
+ </div>
67
+
68
+ <p class="pdf-foot">
69
+ Computer-generated payslip — no signature required. Statutory figures are computed on a
70
+ projection basis; verify with your accountant. Generated <%= Time.current.strftime("%d %b %Y") %>.
71
+ </p>
72
+ </div>
@@ -0,0 +1,12 @@
1
+ <% content_for(:page_title) { "Slip #{@slip.period_month.strftime('%b %Y')}" } %>
2
+ <div class="hrl-page__head">
3
+ <h1 class="hrl-page__title">Salary slip — <%= @slip.period_month.strftime("%B %Y") %></h1>
4
+ <div class="hrl-page__actions">
5
+ <a class="hrl-btn" href="<%= hr_lite.salary_slips_path %>">All slips</a>
6
+ <a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.salary_slip_path(@slip, format: :pdf) %>">Download PDF</a>
7
+ </div>
8
+ </div>
9
+
10
+ <%= render "hr_lite/salary_slips/slip_detail", slip: @slip, profile: @slip.user_profile %>
11
+
12
+ <p class="hrl-small hrl-muted">Computer-generated payslip. Verify statutory figures with your accountant.</p>
@@ -0,0 +1,10 @@
1
+ <% if record.errors.any? %>
2
+ <div class="hrl-errors">
3
+ <strong><%= pluralize(record.errors.count, "problem") %> prevented saving:</strong>
4
+ <ul>
5
+ <% record.errors.full_messages.each do |message| %>
6
+ <li><%= message %></li>
7
+ <% end %>
8
+ </ul>
9
+ </div>
10
+ <% end %>
@@ -0,0 +1,11 @@
1
+ <% if (@total_pages || 1) > 1 %>
2
+ <nav class="hrl-pager" aria-label="Pagination">
3
+ <% if @page > 1 %>
4
+ <%= link_to "Previous", url_for(request.query_parameters.merge(page: @page - 1)), class: "hrl-btn" %>
5
+ <% end %>
6
+ <span class="hrl-pager__info">Page <%= @page %> of <%= @total_pages %> (<%= @total_count %>)</span>
7
+ <% if @page < @total_pages %>
8
+ <%= link_to "Next", url_for(request.query_parameters.merge(page: @page + 1)), class: "hrl-btn" %>
9
+ <% end %>
10
+ </nav>
11
+ <% end %>