hr_lite 0.54.0 → 0.56.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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +71 -0
  3. data/app/assets/javascripts/hr_lite/confirm.js +9 -5
  4. data/app/assets/javascripts/hr_lite/countdown.js +9 -2
  5. data/app/assets/javascripts/hr_lite/search.js +1 -1
  6. data/app/assets/stylesheets/hr_lite/hr_lite.css +18 -9
  7. data/app/controllers/hr_lite/admin/attendances_controller.rb +10 -3
  8. data/app/controllers/hr_lite/admin/audit_logs_controller.rb +1 -0
  9. data/app/controllers/hr_lite/admin/employees_controller.rb +11 -4
  10. data/app/controllers/hr_lite/admin/expenses_controller.rb +6 -2
  11. data/app/controllers/hr_lite/admin/hr_requests_controller.rb +4 -0
  12. data/app/controllers/hr_lite/admin/leave_requests_controller.rb +4 -1
  13. data/app/controllers/hr_lite/admin/payroll_runs_controller.rb +9 -5
  14. data/app/controllers/hr_lite/admin/reports_controller.rb +3 -2
  15. data/app/controllers/hr_lite/admin/salary_structures_controller.rb +2 -0
  16. data/app/controllers/hr_lite/admin/tax_declarations_controller.rb +13 -3
  17. data/app/controllers/hr_lite/approvals_controller.rb +1 -0
  18. data/app/controllers/hr_lite/home_controller.rb +2 -2
  19. data/app/controllers/hr_lite/regularization_requests_controller.rb +4 -4
  20. data/app/controllers/hr_lite/tax_declarations_controller.rb +9 -5
  21. data/app/helpers/hr_lite/application_helper.rb +20 -14
  22. data/app/jobs/hr_lite/attendance_close_job.rb +1 -1
  23. data/app/jobs/hr_lite/payroll_compute_job.rb +12 -0
  24. data/app/models/concerns/hr_lite/audited.rb +3 -1
  25. data/app/models/concerns/hr_lite/encrypted_money.rb +8 -5
  26. data/app/models/hr_lite/attendance_record.rb +14 -0
  27. data/app/models/hr_lite/benefit_enrolment.rb +3 -1
  28. data/app/models/hr_lite/comp_off_request.rb +2 -0
  29. data/app/models/hr_lite/document.rb +1 -1
  30. data/app/models/hr_lite/expense.rb +13 -5
  31. data/app/models/hr_lite/hr_request.rb +2 -0
  32. data/app/models/hr_lite/kudo.rb +2 -2
  33. data/app/models/hr_lite/leave_request.rb +25 -6
  34. data/app/models/hr_lite/payroll_line_item.rb +8 -0
  35. data/app/models/hr_lite/payroll_run.rb +18 -6
  36. data/app/models/hr_lite/regularization_request.rb +2 -2
  37. data/app/models/hr_lite/resignation.rb +5 -1
  38. data/app/models/hr_lite/role.rb +5 -0
  39. data/app/models/hr_lite/salary_slip.rb +1 -1
  40. data/app/models/hr_lite/tax_declaration.rb +5 -0
  41. data/app/services/hr_lite/attendance_puncher.rb +4 -0
  42. data/app/services/hr_lite/overview_query.rb +0 -12
  43. data/app/services/hr_lite/payroll_run_processor.rb +3 -0
  44. data/app/services/hr_lite/slip_builder.rb +17 -9
  45. data/app/services/hr_lite/work_status.rb +5 -2
  46. data/app/views/hr_lite/admin/assets/index.html.erb +9 -2
  47. data/app/views/hr_lite/admin/attendances/show.html.erb +3 -1
  48. data/app/views/hr_lite/admin/attendances/summary.html.erb +5 -2
  49. data/app/views/hr_lite/admin/audit_logs/index.html.erb +6 -4
  50. data/app/views/hr_lite/admin/ca_reviews/index.html.erb +3 -3
  51. data/app/views/hr_lite/admin/checklists/index.html.erb +3 -3
  52. data/app/views/hr_lite/admin/comp_off_requests/index.html.erb +1 -2
  53. data/app/views/hr_lite/admin/comp_off_requests/show.html.erb +5 -5
  54. data/app/views/hr_lite/admin/designation_changes/new.html.erb +4 -3
  55. data/app/views/hr_lite/admin/documents/index.html.erb +8 -3
  56. data/app/views/hr_lite/admin/documents/show.html.erb +2 -1
  57. data/app/views/hr_lite/admin/employees/_form.html.erb +12 -5
  58. data/app/views/hr_lite/admin/employees/show.html.erb +38 -32
  59. data/app/views/hr_lite/admin/expenses/index.html.erb +4 -3
  60. data/app/views/hr_lite/admin/holidays/index.html.erb +5 -4
  61. data/app/views/hr_lite/admin/hr_requests/index.html.erb +1 -1
  62. data/app/views/hr_lite/admin/hr_requests/show.html.erb +4 -3
  63. data/app/views/hr_lite/admin/leave_balances/index.html.erb +6 -4
  64. data/app/views/hr_lite/admin/leave_requests/index.html.erb +1 -2
  65. data/app/views/hr_lite/admin/leave_requests/show.html.erb +5 -3
  66. data/app/views/hr_lite/admin/leave_types/edit.html.erb +4 -4
  67. data/app/views/hr_lite/admin/leave_types/index.html.erb +3 -3
  68. data/app/views/hr_lite/admin/loans/index.html.erb +2 -2
  69. data/app/views/hr_lite/admin/loans/show.html.erb +8 -12
  70. data/app/views/hr_lite/admin/office_locations/edit.html.erb +4 -4
  71. data/app/views/hr_lite/admin/payroll_runs/index.html.erb +2 -2
  72. data/app/views/hr_lite/admin/payroll_runs/show.html.erb +10 -7
  73. data/app/views/hr_lite/admin/policies/index.html.erb +1 -1
  74. data/app/views/hr_lite/admin/policies/new.html.erb +2 -2
  75. data/app/views/hr_lite/admin/regularization_requests/index.html.erb +3 -4
  76. data/app/views/hr_lite/admin/regularization_requests/show.html.erb +4 -5
  77. data/app/views/hr_lite/admin/reports/index.html.erb +3 -6
  78. data/app/views/hr_lite/admin/reports/show.html.erb +16 -2
  79. data/app/views/hr_lite/admin/roles/_form.html.erb +6 -5
  80. data/app/views/hr_lite/admin/salary_slips/show.html.erb +3 -2
  81. data/app/views/hr_lite/admin/salary_structures/_form.html.erb +1 -0
  82. data/app/views/hr_lite/admin/settings/edit.html.erb +5 -1
  83. data/app/views/hr_lite/admin/statutory_rate_cards/_form.html.erb +3 -3
  84. data/app/views/hr_lite/admin/statutory_rate_cards/index.html.erb +3 -1
  85. data/app/views/hr_lite/admin/tax_declarations/index.html.erb +1 -1
  86. data/app/views/hr_lite/admin/tax_declarations/show.html.erb +2 -0
  87. data/app/views/hr_lite/admin/wfh_requests/index.html.erb +17 -16
  88. data/app/views/hr_lite/admin/wfh_requests/show.html.erb +2 -4
  89. data/app/views/hr_lite/admin/work_locations/index.html.erb +1 -1
  90. data/app/views/hr_lite/admin/work_modes/index.html.erb +1 -1
  91. data/app/views/hr_lite/approvals/index.html.erb +1 -1
  92. data/app/views/hr_lite/attendance/_month_grid.html.erb +9 -9
  93. data/app/views/hr_lite/attendance/_penalties.html.erb +2 -2
  94. data/app/views/hr_lite/attendance/_punch_card.html.erb +2 -4
  95. data/app/views/hr_lite/attendance/_slot.html.erb +9 -6
  96. data/app/views/hr_lite/attendance/_trail.html.erb +1 -1
  97. data/app/views/hr_lite/attendance/_work_mode.html.erb +1 -1
  98. data/app/views/hr_lite/attendance/show.html.erb +4 -4
  99. data/app/views/hr_lite/calendar/show.html.erb +2 -2
  100. data/app/views/hr_lite/comp_off_requests/index.html.erb +1 -1
  101. data/app/views/hr_lite/comp_off_requests/new.html.erb +1 -1
  102. data/app/views/hr_lite/documents/_upload_form.html.erb +1 -1
  103. data/app/views/hr_lite/employee_profiles/show.html.erb +4 -2
  104. data/app/views/hr_lite/home/index.html.erb +2 -2
  105. data/app/views/hr_lite/id_cards/show.html.erb +5 -1
  106. data/app/views/hr_lite/leave_requests/_balance_chips.html.erb +2 -2
  107. data/app/views/hr_lite/leave_requests/new.html.erb +17 -8
  108. data/app/views/hr_lite/leave_requests/show.html.erb +2 -0
  109. data/app/views/hr_lite/org/show.html.erb +1 -1
  110. data/app/views/hr_lite/regularization_requests/index.html.erb +7 -7
  111. data/app/views/hr_lite/regularization_requests/new.html.erb +7 -5
  112. data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +2 -2
  113. data/app/views/hr_lite/salary_slips/pdf.html.erb +1 -1
  114. data/app/views/hr_lite/tax/_computation.html.erb +1 -1
  115. data/app/views/hr_lite/tax_declarations/show.html.erb +4 -4
  116. data/app/views/hr_lite/team/show.html.erb +8 -7
  117. data/app/views/hr_lite/timelines/show.html.erb +1 -0
  118. data/app/views/hr_lite/wfh_requests/index.html.erb +3 -3
  119. data/app/views/hr_lite/work_time/_bars.html.erb +1 -1
  120. data/app/views/hr_lite/work_time/_donut.html.erb +1 -1
  121. data/app/views/hr_lite/work_time/_report.html.erb +14 -8
  122. data/app/views/layouts/hr_lite/application.html.erb +1 -0
  123. data/db/migrate/20261017161441_make_hr_lite_benefit_enrolments_unique_while_open.rb +8 -0
  124. data/db/migrate/20261017161443_add_attendance_record_id_index_to_hr_lite_work_mode_events.rb +5 -0
  125. data/lib/hr_lite/notifications.rb +5 -2
  126. data/lib/hr_lite/version.rb +1 -1
  127. metadata +4 -1
@@ -1,5 +1,6 @@
1
1
  <% content_for(:page_title) { "Timeline — #{hr_display_name(@person)}" } %>
2
2
  <% content_for(:tabrow) { render "hr_lite/shared/person_tabs", user: @person } if params[:user_id] %>
3
+ <% content_for(:back, "Org chart") if params[:user_id] %>
3
4
  <% year_path = ->(y) { @person.id == hr_current_user.id ? hr_lite.timeline_path(year: y) : hr_lite.person_timeline_path(@person.id, year: y) } %>
4
5
  <% name = hr_display_name(@person) %>
5
6
  <div class="hrl-page__head">
@@ -6,14 +6,14 @@
6
6
 
7
7
  <section class="hrl-card">
8
8
  <h2 class="hrl-card__title">Request WFH</h2>
9
- <p class="hrl-small hrl-muted">Pick dates from today up to <%= HrLite::WfhRequest::MAX_DAYS_AHEAD %> days ahead. HR approves or rejects. While a request is waiting you can still check in away from the office; HR sees the reason. A rejected date cannot.</p>
9
+ <p class="hrl-small hrl-muted">Pick dates from today up to <%= HrLite::WfhRequest::MAX_DAYS_AHEAD %> days ahead. Your manager or HR approves or rejects. While a request is waiting you can still check in away from the office; they see the reason. A rejected date cannot.</p>
10
10
  <%= form_with model: @request, url: hr_lite.wfh_requests_path, scope: :wfh_request, local: true do |f| %>
11
11
  <%= render "hr_lite/shared/form_errors", record: @request %>
12
12
  <% min, max = @window.first.iso8601, @window.last.iso8601 %>
13
13
  <div class="hrl-field"><%= f.label :start_date, "From" %><%= f.date_field :start_date, min: min, max: max, required: true %></div>
14
14
  <div class="hrl-field"><%= f.label :end_date, "To (same day for one day)" %><%= f.date_field :end_date, min: min, max: max, required: true %></div>
15
15
  <div class="hrl-field"><%= f.label :reason, "Why do you need WFH?" %><%= f.text_area :reason, rows: 3, required: true %></div>
16
- <div class="hrl-form-actions"><%= f.submit "Send to HR", class: "hrl-btn hrl-btn--primary" %></div>
16
+ <div class="hrl-form-actions"><%= f.submit "Send for approval", class: "hrl-btn hrl-btn--primary" %></div>
17
17
  <% end %>
18
18
  </section>
19
19
 
@@ -27,7 +27,7 @@
27
27
  <% code, word, mod = marks[@day_status[date]] %>
28
28
  <div class="<%= class_names("hrl-mgrid__day", "hrl-mgrid__day--#{mod}": mod, "hrl-mgrid__day--today": date == Date.current) %>"
29
29
  role="group" aria-label="<%= date.strftime('%-d %b') %><%= ", #{word}" if word %>">
30
- <span class="hrl-mgrid__num"><%= date.day %></span>
30
+ <span class="hrl-mgrid__num"><%= date.day == 1 ? date.strftime("%-d %b") : date.day %></span>
31
31
  <% if code %><span class="hrl-mgrid__tag"><%= code %></span><% end %>
32
32
  </div>
33
33
  <% end %>
@@ -2,7 +2,7 @@
2
2
  <% buckets = result.by_bucket
3
3
  totals = buckets.transform_values { |m| m.except("offline").values.sum }
4
4
  max = [ totals.values.max.to_f, 1 ].max
5
- w = 12; gap = 4; h = 100
5
+ w = 12; gap = 8; h = 100 # gap 8 units: bars ~11px apart on screen
6
6
  fmt = { day: "%-d", week: "%-d %b", month: "%b" }[period.bucket] %>
7
7
  <section class="hrl-card">
8
8
  <h2 class="hrl-card__title">By <%= period.bucket %></h2>
@@ -29,7 +29,7 @@
29
29
  </tbody>
30
30
  </table>
31
31
  </div>
32
- <p class="hrl-small hrl-muted">Shares are of tracked time (<%= hrl_duration(total) %>: working, breaks and out of office). Offline (<%= hrl_duration(result.offline) %>) is not included.</p>
32
+ <p class="hrl-small hrl-muted">Shares are of tracked time (<%= hrl_duration(total) %>: working, breaks and out of office). Offline (<%= hrl_duration(result.offline) %>) is not included. Stretches: how many separate times each mode ran.</p>
33
33
  <% else %>
34
34
  <p class="hrl-card--empty">Only offline time recorded.</p>
35
35
  <% end %>
@@ -20,12 +20,16 @@
20
20
  <a class="hrl-btn" href="<%= path.(period: period.kind, date: period.shift(1)) %>" aria-label="Next <%= period.kind %>">&rsaquo;</a>
21
21
  <% end %>
22
22
  </div>
23
- <%= form_with url: path.({}), method: :get, local: true, class: "hrl-row" do %>
24
- <input type="hidden" name="period" value="custom">
25
- <% (local_assigns[:keep] || {}).each do |k, v| %><input type="hidden" name="<%= k %>" value="<%= v %>"><% end %>
26
- <div class="hrl-field"><label for="wt-from">From</label><%= date_field_tag :from, period.from, max: Date.current, id: "wt-from" %></div>
27
- <div class="hrl-field"><label for="wt-to">To</label><%= date_field_tag :to, period.to, max: Date.current, id: "wt-to" %></div>
28
- <div class="hrl-form-actions"><button type="submit" class="hrl-btn">Show range</button></div>
23
+ <%# One picker on show: the date range stays folded unless it is what was asked for. %>
24
+ <%= tag.details open: params[:period] == "custom" do %>
25
+ <summary class="hrl-card__link">Pick your own dates</summary>
26
+ <%= form_with url: path.({}), method: :get, local: true, class: "hrl-row" do %>
27
+ <input type="hidden" name="period" value="custom">
28
+ <% (local_assigns[:keep] || {}).each do |k, v| %><input type="hidden" name="<%= k %>" value="<%= v %>"><% end %>
29
+ <div class="hrl-field"><label for="wt-from">From</label><%= date_field_tag :from, period.from, max: Date.current, id: "wt-from" %></div>
30
+ <div class="hrl-field"><label for="wt-to">To</label><%= date_field_tag :to, period.to, max: Date.current, id: "wt-to" %></div>
31
+ <div class="hrl-form-actions"><button type="submit" class="hrl-btn">Show range</button></div>
32
+ <% end %>
29
33
  <% end %>
30
34
  </section>
31
35
 
@@ -35,7 +39,8 @@
35
39
  <% office = result.by_mode["office"]; wfh = result.by_mode["wfh"]; field = result.by_mode["travelling"] + result.by_mode["outside_meeting"] %>
36
40
  <div class="hrl-kpis">
37
41
  <% [ [ "Attendance (check-in to check-out)", result.attendance ], [ "Working time (breaks included)", result.working + result.breaks ], [ "Of which breaks", result.breaks ],
38
- [ "In office", office ], [ "Work from home", wfh ], [ "Travel and client meetings", field ], [ "Out of office", result.other ] ].each do |kpi_label, secs| %>
42
+ [ "In office", office ], [ "Work from home", wfh ], [ "Travel and client meetings", field ],
43
+ [ "Meetings and busy", result.by_mode["meeting"] + result.by_mode["busy"] ], [ "Out of office", result.other ] ].each do |kpi_label, secs| %>
39
44
  <div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_duration(secs) %></div><div class="hrl-kpi__label"><%= kpi_label %></div></div>
40
45
  <% end %>
41
46
  </div>
@@ -62,11 +67,12 @@
62
67
  <% if records.any? %>
63
68
  <section class="hrl-card">
64
69
  <h2 class="hrl-card__title">Check-ins</h2>
70
+ <% all_punches = HrLite::AttendancePunch.where(user_id: records.map(&:user_id).uniq, date: records.map(&:date)).order(:at, :id).group_by { |p| [ p.user_id, p.date ] } # one query, not one a day %>
65
71
  <% records.each do |r| %>
66
72
  <div class="hrl-feed__item hrl-small"><div class="hrl-feed__body">
67
73
  <% if names %><strong><%= names[r.user_id] %></strong> · <% end %>
68
74
  <%= r.date.strftime("%a %-d %b") %><% if r.work_mode_label %> · <%= r.work_mode_label %><% end %>
69
- <%= render "hr_lite/attendance/trail", record: r %>
75
+ <%= render "hr_lite/attendance/trail", record: r, punches: all_punches.fetch([ r.user_id, r.date ], []) %>
70
76
  </div></div>
71
77
  <% end %>
72
78
  </section>
@@ -117,6 +117,7 @@
117
117
  <%= javascript_include_tag "hr_lite/select_filter", defer: true %>
118
118
  <%= javascript_include_tag "hr_lite/search", defer: true %>
119
119
  <dialog class="hrl-search" data-hrl-search aria-label="Search">
120
+ <form method="dialog" class="hrl-search__close"><button class="hrl-btn" type="submit">Close</button></form>
120
121
  <form action="<%= hr_lite.search_path %>" method="get" role="search">
121
122
  <label class="hrl-sr-only" for="hrl-search-q">Search screens and people</label>
122
123
  <input id="hrl-search-q" class="hrl-input" type="search" name="q" placeholder="Search screens and people" autocomplete="off">
@@ -0,0 +1,8 @@
1
+ class MakeHrLiteBenefitEnrolmentsUniqueWhileOpen < ActiveRecord::Migration[8.1]
2
+ # One open enrolment per person per plan; ended ones stay as history.
3
+ def change
4
+ remove_index :hr_lite_benefit_enrolments, %i[benefit_id user_id], unique: true, if_exists: true
5
+ add_index :hr_lite_benefit_enrolments, %i[benefit_id user_id], unique: true, where: "ended_on IS NULL",
6
+ name: "index_hr_lite_benefit_enrolments_one_open", if_not_exists: true
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class AddAttendanceRecordIdIndexToHrLiteWorkModeEvents < ActiveRecord::Migration[8.1]
2
+ def change
3
+ add_index :hr_lite_work_mode_events, :attendance_record_id, if_not_exists: true
4
+ end
5
+ end
@@ -131,14 +131,17 @@ module HrLite
131
131
  # Rescued PER RECIPIENT. Wrapping the whole loop meant one bad address
132
132
  # or a single enqueue failure silently dropped everyone after it — on
133
133
  # a team-wide leave notice, most of the team.
134
- Array(email_to).compact.uniq.each do |user|
134
+ users = Array(email_to).compact.uniq
135
+ # One query for everyone's Busy/Meeting end, not one per recipient.
136
+ held_until = HrLite.config.office_geofence ? WorkStatus.do_not_disturb_for(users.map(&:id)) : {}
137
+ users.each do |user|
135
138
  next if user.email.blank?
136
139
 
137
140
  begin
138
141
  mail = EventMailer.event(to: user.email, subject: title, heading: title,
139
142
  body: body, lines: lines, path: path, link_url: link_url)
140
143
  # Busy or in a meeting: the email waits until that ends.
141
- held = WorkStatus.do_not_disturb_until(user.id) if HrLite.config.office_geofence
144
+ held = held_until[user.id]
142
145
  held ? mail.deliver_later(wait_until: held) : mail.deliver_later
143
146
  rescue => e
144
147
  Rails.logger.error("[hr_lite] event email failed: #{e.class}: #{e.message}")
@@ -1,3 +1,3 @@
1
1
  module HrLite
2
- VERSION = "0.54.0"
2
+ VERSION = "0.56.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hr_lite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.54.0
4
+ version: 0.56.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - kshitiz sinha
@@ -171,6 +171,7 @@ files:
171
171
  - app/jobs/hr_lite/leave_year_rollover_job.rb
172
172
  - app/jobs/hr_lite/payroll_auto_approve_job.rb
173
173
  - app/jobs/hr_lite/payroll_auto_draft_job.rb
174
+ - app/jobs/hr_lite/payroll_compute_job.rb
174
175
  - app/jobs/hr_lite/slips_ready_job.rb
175
176
  - app/jobs/hr_lite/work_mode_expire_job.rb
176
177
  - app/mailers/hr_lite/application_mailer.rb
@@ -504,6 +505,8 @@ files:
504
505
  - db/migrate/20261016090000_add_penalty_leave_type_to_hr_lite_settings.rb
505
506
  - db/migrate/20261016091000_create_hr_lite_penalty_removals.rb
506
507
  - db/migrate/20261016100000_add_hours_allowance_repeat_to_hr_lite_employee_profiles.rb
508
+ - db/migrate/20261017161441_make_hr_lite_benefit_enrolments_unique_while_open.rb
509
+ - db/migrate/20261017161443_add_attendance_record_id_index_to_hr_lite_work_mode_events.rb
507
510
  - lib/generators/hr_lite/install/install_generator.rb
508
511
  - lib/generators/hr_lite/install/templates/AFTER_INSTALL
509
512
  - lib/generators/hr_lite/install/templates/initializer.rb