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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +73 -0
- data/README.md +5 -2
- data/app/assets/javascripts/hr_lite/confirm.js +9 -5
- data/app/assets/javascripts/hr_lite/countdown.js +9 -2
- data/app/assets/javascripts/hr_lite/emoji_pick.js +4 -4
- data/app/assets/javascripts/hr_lite/search.js +1 -1
- data/app/assets/stylesheets/hr_lite/hr_lite.css +24 -14
- data/app/controllers/hr_lite/admin/attendances_controller.rb +12 -3
- data/app/controllers/hr_lite/admin/audit_logs_controller.rb +1 -0
- data/app/controllers/hr_lite/admin/comp_off_requests_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/documents_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/employees_controller.rb +11 -4
- data/app/controllers/hr_lite/admin/expenses_controller.rb +17 -1
- data/app/controllers/hr_lite/admin/hr_requests_controller.rb +4 -0
- data/app/controllers/hr_lite/admin/leave_requests_controller.rb +5 -2
- data/app/controllers/hr_lite/admin/payroll_runs_controller.rb +9 -5
- data/app/controllers/hr_lite/admin/regularization_requests_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/reports_controller.rb +3 -2
- data/app/controllers/hr_lite/admin/salary_structures_controller.rb +2 -0
- data/app/controllers/hr_lite/admin/statutory_rate_cards_controller.rb +2 -1
- data/app/controllers/hr_lite/admin/tax_declarations_controller.rb +15 -4
- data/app/controllers/hr_lite/admin/work_locations_controller.rb +1 -1
- data/app/controllers/hr_lite/approvals_controller.rb +1 -0
- data/app/controllers/hr_lite/home_controller.rb +2 -11
- data/app/controllers/hr_lite/loans_controller.rb +1 -1
- data/app/controllers/hr_lite/regularization_requests_controller.rb +4 -4
- data/app/controllers/hr_lite/tax_declarations_controller.rb +9 -5
- data/app/helpers/hr_lite/application_helper.rb +34 -9
- data/app/jobs/hr_lite/attendance_close_job.rb +1 -1
- data/app/jobs/hr_lite/payroll_compute_job.rb +12 -0
- data/app/models/concerns/hr_lite/audited.rb +3 -1
- data/app/models/concerns/hr_lite/encrypted_money.rb +8 -5
- data/app/models/hr_lite/attendance_record.rb +21 -4
- data/app/models/hr_lite/comp_off_request.rb +2 -0
- data/app/models/hr_lite/document.rb +1 -1
- data/app/models/hr_lite/expense.rb +3 -0
- data/app/models/hr_lite/hr_request.rb +2 -0
- data/app/models/hr_lite/kudo.rb +2 -2
- data/app/models/hr_lite/leave_request.rb +25 -6
- data/app/models/hr_lite/payroll_line_item.rb +8 -0
- data/app/models/hr_lite/payroll_run.rb +15 -6
- data/app/models/hr_lite/regularization_request.rb +2 -2
- data/app/models/hr_lite/resignation.rb +5 -1
- data/app/models/hr_lite/role.rb +5 -0
- data/app/models/hr_lite/salary_slip.rb +2 -1
- data/app/models/hr_lite/statutory_rate_card_record.rb +6 -0
- data/app/models/hr_lite/tax_declaration.rb +5 -0
- data/app/services/hr_lite/attendance_puncher.rb +4 -0
- data/app/services/hr_lite/overview_query.rb +0 -12
- data/app/services/hr_lite/work_status.rb +5 -2
- data/app/views/hr_lite/admin/assets/index.html.erb +9 -2
- data/app/views/hr_lite/admin/attendances/index.html.erb +4 -4
- data/app/views/hr_lite/admin/attendances/show.html.erb +4 -2
- data/app/views/hr_lite/admin/attendances/summary.html.erb +5 -2
- data/app/views/hr_lite/admin/audit_logs/index.html.erb +6 -4
- data/app/views/hr_lite/admin/benefits/index.html.erb +10 -4
- data/app/views/hr_lite/admin/ca_reviews/index.html.erb +3 -3
- data/app/views/hr_lite/admin/checklists/index.html.erb +3 -3
- data/app/views/hr_lite/admin/comp_off_requests/index.html.erb +1 -2
- data/app/views/hr_lite/admin/comp_off_requests/show.html.erb +6 -6
- data/app/views/hr_lite/admin/designation_changes/new.html.erb +4 -3
- data/app/views/hr_lite/admin/documents/index.html.erb +9 -4
- data/app/views/hr_lite/admin/documents/show.html.erb +1 -0
- data/app/views/hr_lite/admin/employees/_form.html.erb +12 -5
- data/app/views/hr_lite/admin/employees/show.html.erb +40 -33
- data/app/views/hr_lite/admin/expense_categories/index.html.erb +1 -1
- data/app/views/hr_lite/admin/expenses/index.html.erb +7 -4
- data/app/views/hr_lite/admin/holidays/index.html.erb +7 -5
- data/app/views/hr_lite/admin/hr_requests/index.html.erb +3 -3
- data/app/views/hr_lite/admin/hr_requests/show.html.erb +5 -4
- data/app/views/hr_lite/admin/leave_balances/index.html.erb +6 -4
- data/app/views/hr_lite/admin/leave_requests/index.html.erb +1 -2
- data/app/views/hr_lite/admin/leave_requests/show.html.erb +6 -4
- data/app/views/hr_lite/admin/leave_types/edit.html.erb +4 -4
- data/app/views/hr_lite/admin/leave_types/index.html.erb +5 -5
- data/app/views/hr_lite/admin/loans/index.html.erb +1 -1
- data/app/views/hr_lite/admin/loans/new.html.erb +1 -1
- data/app/views/hr_lite/admin/loans/show.html.erb +8 -12
- data/app/views/hr_lite/admin/office_locations/edit.html.erb +4 -4
- data/app/views/hr_lite/admin/office_locations/index.html.erb +1 -1
- data/app/views/hr_lite/admin/overview/index.html.erb +10 -31
- data/app/views/hr_lite/admin/payroll_line_items/new.html.erb +2 -1
- data/app/views/hr_lite/admin/payroll_runs/index.html.erb +3 -3
- data/app/views/hr_lite/admin/payroll_runs/show.html.erb +15 -10
- data/app/views/hr_lite/admin/performance_plans/_form.html.erb +2 -1
- data/app/views/hr_lite/admin/policies/index.html.erb +2 -2
- data/app/views/hr_lite/admin/policies/new.html.erb +4 -3
- data/app/views/hr_lite/admin/regularization_requests/index.html.erb +3 -4
- data/app/views/hr_lite/admin/regularization_requests/show.html.erb +5 -6
- data/app/views/hr_lite/admin/reports/index.html.erb +3 -6
- data/app/views/hr_lite/admin/reports/show.html.erb +16 -2
- data/app/views/hr_lite/admin/roles/_form.html.erb +8 -6
- data/app/views/hr_lite/admin/roles/edit.html.erb +4 -2
- data/app/views/hr_lite/admin/roles/index.html.erb +1 -1
- data/app/views/hr_lite/admin/salary_slips/show.html.erb +3 -2
- data/app/views/hr_lite/admin/salary_structures/_form.html.erb +1 -0
- data/app/views/hr_lite/admin/settings/edit.html.erb +5 -1
- data/app/views/hr_lite/admin/statutory_rate_cards/_form.html.erb +14 -10
- data/app/views/hr_lite/admin/statutory_rate_cards/index.html.erb +3 -1
- data/app/views/hr_lite/admin/tax_declarations/index.html.erb +1 -1
- data/app/views/hr_lite/admin/tax_declarations/show.html.erb +8 -4
- data/app/views/hr_lite/admin/wfh_requests/index.html.erb +17 -16
- data/app/views/hr_lite/admin/wfh_requests/show.html.erb +3 -5
- data/app/views/hr_lite/admin/work_locations/index.html.erb +1 -1
- data/app/views/hr_lite/admin/work_modes/index.html.erb +1 -1
- data/app/views/hr_lite/approvals/index.html.erb +5 -4
- data/app/views/hr_lite/attendance/_custom_status.html.erb +7 -9
- data/app/views/hr_lite/attendance/_month_grid.html.erb +9 -9
- data/app/views/hr_lite/attendance/_penalties.html.erb +2 -2
- data/app/views/hr_lite/attendance/_punch_card.html.erb +10 -4
- data/app/views/hr_lite/attendance/_slot.html.erb +9 -6
- data/app/views/hr_lite/attendance/_trail.html.erb +1 -1
- data/app/views/hr_lite/attendance/_work_mode.html.erb +1 -1
- data/app/views/hr_lite/attendance/show.html.erb +7 -5
- data/app/views/hr_lite/calendar/show.html.erb +2 -2
- data/app/views/hr_lite/comp_off_requests/index.html.erb +1 -1
- data/app/views/hr_lite/comp_off_requests/new.html.erb +1 -1
- data/app/views/hr_lite/documents/_upload_form.html.erb +1 -1
- data/app/views/hr_lite/employee_profiles/show.html.erb +4 -2
- data/app/views/hr_lite/home/index.html.erb +6 -5
- data/app/views/hr_lite/id_cards/show.html.erb +5 -1
- data/app/views/hr_lite/leave_requests/_balance_chips.html.erb +3 -2
- data/app/views/hr_lite/leave_requests/new.html.erb +17 -8
- data/app/views/hr_lite/leave_requests/show.html.erb +2 -0
- data/app/views/hr_lite/loans/index.html.erb +1 -1
- data/app/views/hr_lite/org/show.html.erb +1 -1
- data/app/views/hr_lite/regularization_requests/index.html.erb +7 -7
- data/app/views/hr_lite/regularization_requests/new.html.erb +7 -5
- data/app/views/hr_lite/resignations/show.html.erb +2 -1
- data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +7 -7
- data/app/views/hr_lite/salary_slips/pdf.html.erb +2 -1
- data/app/views/hr_lite/tax/_computation.html.erb +1 -1
- data/app/views/hr_lite/tax_declarations/show.html.erb +4 -4
- data/app/views/hr_lite/team/show.html.erb +8 -7
- data/app/views/hr_lite/timelines/show.html.erb +1 -0
- data/app/views/hr_lite/wfh_requests/index.html.erb +3 -3
- data/app/views/hr_lite/work_time/_bars.html.erb +5 -4
- data/app/views/hr_lite/work_time/_donut.html.erb +1 -1
- data/app/views/hr_lite/work_time/_report.html.erb +14 -8
- data/app/views/layouts/hr_lite/application.html.erb +1 -0
- data/app/views/layouts/hr_lite/mailer.html.erb +10 -2
- data/app/views/layouts/hr_lite/mailer.text.erb +4 -1
- data/config/routes.rb +1 -1
- data/lib/hr_lite/amount_in_words.rb +7 -5
- data/lib/hr_lite/notifications.rb +5 -2
- data/lib/hr_lite/statutory_seeds.rb +1 -1
- data/lib/hr_lite/version.rb +1 -1
- metadata +2 -1
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
<table class="hrl-table hrl-tax-working__table">
|
|
35
35
|
<tbody>
|
|
36
36
|
<tr><td>Salary paid so far this year</td><td class="hrl-num"><%= hrl_rupees(tax.paid_gross) %></td></tr>
|
|
37
|
-
<tr><td>Expected for the rest of the year (<%= hrl_rupees(tax.structure.monthly_gross) %> × <%= s.months_left %>)</td><td class="hrl-num"><%= hrl_rupees(tax.projected_gross) %></td></tr>
|
|
37
|
+
<tr><td>Expected for the rest of the year, before unpaid days (<%= hrl_rupees(tax.structure.monthly_gross) %> × <%= s.months_left %>)</td><td class="hrl-num"><%= hrl_rupees(tax.projected_gross) %></td></tr>
|
|
38
38
|
<tr class="hrl-tax-working__total"><td>Gross salary for the year</td><td class="hrl-num"><%= hrl_rupees(s.gross) %></td></tr>
|
|
39
39
|
<% if s.hra_exemption.positive? %><tr><td>Less HRA exemption (from rent)</td><td class="hrl-num">− <%= hrl_rupees(s.hra_exemption) %></td></tr><% end %>
|
|
40
40
|
<tr><td>Less standard deduction</td><td class="hrl-num">− <%= hrl_rupees(s.standard_deduction) %></td></tr>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<% content_for(:page_title) { "Tax" } %>
|
|
2
2
|
<% submittable = @declaration.persisted? && (@declaration.draft? || @declaration.rejected?) %>
|
|
3
|
-
<% locked = @declaration.verified? %>
|
|
3
|
+
<% locked = @declaration.verified? || @declaration.submitted? %>
|
|
4
4
|
<div class="hrl-page__head">
|
|
5
5
|
<h1 class="hrl-page__title">Tax — <%= @declaration.label %></h1>
|
|
6
6
|
</div>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
<section class="hrl-card">
|
|
15
15
|
<p>
|
|
16
|
-
Declaration:
|
|
16
|
+
Declaration: <% if @declaration.verified? %><span class="hrl-badge hrl-badge--ok">Verified</span><% elsif @declaration.rejected? %><span class="hrl-badge hrl-badge--bad">Sent back</span><% else %><%= hrl_request_status_badge(@declaration.status) %><% end %>
|
|
17
17
|
<% if @declaration.note.present? %><span class="hrl-small hrl-muted">— <%= @declaration.note %></span><% end %>
|
|
18
18
|
</p>
|
|
19
19
|
<fieldset class="hrl-field" <%= "disabled" if locked %>>
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
</div>
|
|
27
27
|
<% end %>
|
|
28
28
|
</fieldset>
|
|
29
|
-
</
|
|
29
|
+
</section>
|
|
30
30
|
|
|
31
31
|
<details class="hrl-card" <%= "open" if @declaration.regime != "new" || @declaration.errors.any? %>>
|
|
32
32
|
<summary class="hrl-btn">Investments and rent (old regime only)</summary>
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
<% if submittable %><button type="submit" name="submit_to_hr" value="1" class="hrl-btn hrl-btn--primary">Submit to HR</button><% end %>
|
|
70
70
|
</div>
|
|
71
71
|
<% else %>
|
|
72
|
-
<p class="hrl-small hrl-muted"
|
|
72
|
+
<p class="hrl-small hrl-muted"><%= @declaration.verified? ? "HR has verified this year's declaration." : "Sent to HR to check the proof." %> To change anything, ask HR through Help › Ask HR.</p>
|
|
73
73
|
<% end %>
|
|
74
74
|
<% end %>
|
|
75
75
|
<% content_for(:scripts) { javascript_include_tag "hr_lite/tax_preview", defer: true } %>
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
<% content_for(:page_title) { "Team" } %>
|
|
1
|
+
<% content_for(:page_title) { "Team board" } %>
|
|
2
2
|
<div class="hrl-page__head">
|
|
3
|
-
<h1 class="hrl-page__title">Team — <%= @date == Date.current ? "today" : @date.strftime("%A, %d %B") %></h1>
|
|
3
|
+
<h1 class="hrl-page__title">Team board — <%= @date == Date.current ? "today" : @date.strftime("%A, %d %B") %></h1>
|
|
4
4
|
<div class="hrl-page__actions">
|
|
5
|
-
<a class="hrl-btn"
|
|
5
|
+
<a class="hrl-btn" href="<%= hr_lite.team_path(date: (@date - 1).strftime("%Y-%m-%d")) %>"><span aria-hidden="true">‹</span> <%= @date == Date.current ? "Yesterday" : "Previous day" %></a>
|
|
6
6
|
<% unless @date == Date.current %>
|
|
7
7
|
<a class="hrl-btn" href="<%= hr_lite.team_path %>">Today</a>
|
|
8
8
|
<% end %>
|
|
9
9
|
<% if @date < Date.current %>
|
|
10
|
-
<a class="hrl-btn"
|
|
10
|
+
<a class="hrl-btn" href="<%= hr_lite.team_path(date: (@date + 1).strftime("%Y-%m-%d")) %>">Next day <span aria-hidden="true">›</span></a>
|
|
11
11
|
<% end %>
|
|
12
12
|
</div>
|
|
13
13
|
</div>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= kpis[:checked_in] %></div><div class="hrl-kpi__label">Checked in</div></div>
|
|
18
18
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= kpis[:on_leave] %></div><div class="hrl-kpi__label">On leave</div></div>
|
|
19
19
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= kpis[:not_in] %></div><div class="hrl-kpi__label">Not in</div></div>
|
|
20
|
-
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @board.rows.size %></div><div class="hrl-kpi__label">
|
|
20
|
+
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @board.rows.size %></div><div class="hrl-kpi__label">People</div></div>
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
23
|
<% if @statuses %>
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
<span class="hrl-badge hrl-badge--muted">+<%= row.profile.buffer_minutes / 60 %>h buffer · <%= HrLite::EmployeeProfile::BUFFER_REASONS[row.profile.buffer_reason] %></span><% end %></span>
|
|
49
49
|
</td>
|
|
50
50
|
<% if (st = @statuses&.dig(row.user.id)) %>
|
|
51
|
-
|
|
51
|
+
<%# A derived status (offline, leave, holiday) repeats the Status cell; phone cards skip it. %>
|
|
52
|
+
<td data-label="Now" class="<%= 'hrl-cell--void' unless st.primary.event || st.custom %>"><span class="hrl-modes"><%= hrl_status_icons(st) %></span>
|
|
52
53
|
<span class="hrl-small"><%= st.primary.place || st.primary.label %><% if st.overlay %> · <%= st.overlay.label %><% end %>
|
|
53
54
|
<% if (till = st.overlay&.until || (st.primary.until if st.primary.until.respond_to?(:hour))) %> · until <%= till.strftime("%H:%M") %><% end %></span>
|
|
54
55
|
<% if st.updated_at %><span class="hrl-small hrl-muted"> · since <%= st.updated_at.strftime("%H:%M") %></span><% end %>
|
|
@@ -66,5 +67,5 @@
|
|
|
66
67
|
<p class="hrl-card--empty">Nobody to show<%= @rows ? " for these filters" : " for this date" %>.</p>
|
|
67
68
|
<% end %>
|
|
68
69
|
<p class="hrl-small hrl-muted hrl-mt">Forgot a punch yourself?
|
|
69
|
-
<a class="hrl-card__link" href="<%= hr_lite.new_regularization_request_path %>">
|
|
70
|
+
<a class="hrl-card__link" href="<%= hr_lite.new_regularization_request_path %>">Fix a day</a></p>
|
|
70
71
|
</section>
|
|
@@ -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;
|
|
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
|
|
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,16 +2,17 @@
|
|
|
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 =
|
|
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>
|
|
9
9
|
<div class="hrl-chart-scroll">
|
|
10
|
-
<svg class="hrl-bars" viewBox="0 0 <%= buckets.size * (w + gap) %> <%= h + 14 %>" role="img"
|
|
10
|
+
<svg class="hrl-bars" style="--hrl-bars-n: <%= buckets.size %>" viewBox="0 0 <%= buckets.size * (w + gap) %> <%= h + 14 %>" role="img"
|
|
11
11
|
aria-label="Time per <%= period.bucket %> stacked by work mode; tallest bar <%= hrl_duration(max) %>; details in the table">
|
|
12
12
|
<% buckets.each_with_index do |(key, modes), i| %>
|
|
13
13
|
<% x = i * (w + gap); y = h %>
|
|
14
|
-
<a href="<%= path.(period: period.bucket == :day ? "day" : (period.bucket == :week ? "week" : "month"), date: key) %>"
|
|
14
|
+
<a href="<%= path.(period: period.bucket == :day ? "day" : (period.bucket == :week ? "week" : "month"), date: key) %>"
|
|
15
|
+
aria-label="<%= key.strftime(period.bucket == :day ? "%a %-d %b" : fmt) %>: <%= hrl_duration(totals[key]) %>">
|
|
15
16
|
<% modes.except("offline").sort.each do |mode, secs| %>
|
|
16
17
|
<% next unless secs.positive? %>
|
|
17
18
|
<% bh = secs / max * h; y -= bh %>
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
</svg>
|
|
29
30
|
</div>
|
|
30
31
|
<details class="hrl-small">
|
|
31
|
-
<summary>Show as a table</summary>
|
|
32
|
+
<summary class="hrl-card__link">Show as a table</summary>
|
|
32
33
|
<table class="hrl-table">
|
|
33
34
|
<thead><tr><th><%= period.bucket.to_s.capitalize %></th><th class="hrl-num">Working (breaks incl.)</th><th class="hrl-num">Of which breaks</th><th class="hrl-num">Total</th></tr></thead>
|
|
34
35
|
<tbody>
|
|
@@ -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 %>">›</a>
|
|
21
21
|
<% end %>
|
|
22
22
|
</div>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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 ],
|
|
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">
|
|
@@ -21,9 +21,17 @@
|
|
|
21
21
|
<body>
|
|
22
22
|
<div class="wrap">
|
|
23
23
|
<div class="card">
|
|
24
|
-
|
|
24
|
+
<% company = HrLite.config.company.call %>
|
|
25
|
+
<%# Mail clients strip data: URIs, so this is the host's absolute https logo, not logo_url. %>
|
|
26
|
+
<% if company[:email_logo_url].present? %>
|
|
27
|
+
<img src="<%= company[:email_logo_url] %>" alt="<%= company[:name] %>" height="40" style="display:block; height:40px; width:auto; border:0; margin-bottom:12px;">
|
|
28
|
+
<% end %>
|
|
29
|
+
<div class="brand"><%= company[:name].presence || "HR" %></div>
|
|
25
30
|
<%= yield %>
|
|
26
|
-
<p class="foot">
|
|
31
|
+
<p class="foot">
|
|
32
|
+
Sent by the HR system of <%= company[:name].presence || "your company" %>. Do not reply to this email.
|
|
33
|
+
<% if (contact = company.values_at(:phone, :email, :website).compact_blank).any? %><br>Questions: <%= contact.join(" · ") %><% end %>
|
|
34
|
+
</p>
|
|
27
35
|
</div>
|
|
28
36
|
</div>
|
|
29
37
|
</body>
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
<% company = HrLite.config.company.call %>
|
|
2
|
+
<%= company[:name].presence || "HR" %>
|
|
2
3
|
|
|
3
4
|
<%= yield %>
|
|
4
5
|
|
|
5
6
|
--
|
|
6
7
|
Sent by the HR system. Do not reply to this email.
|
|
8
|
+
<% if (contact = company.values_at(:phone, :email, :website).compact_blank).any? %>Questions: <%= contact.join(" · ") %>
|
|
9
|
+
<% end %>
|
data/config/routes.rb
CHANGED
|
@@ -115,7 +115,7 @@ HrLite::Engine.routes.draw do
|
|
|
115
115
|
end
|
|
116
116
|
resources :reports, only: %i[index show]
|
|
117
117
|
resources :expenses, only: :index do
|
|
118
|
-
member { post :approve; post :reject; post :reimburse }
|
|
118
|
+
member { post :approve; post :reject; post :reimburse; get :receipt }
|
|
119
119
|
end
|
|
120
120
|
resources :hr_requests, only: %i[index show] do
|
|
121
121
|
member { post :assign; post :resolve }
|
|
@@ -10,15 +10,17 @@ module HrLite
|
|
|
10
10
|
|
|
11
11
|
module_function
|
|
12
12
|
|
|
13
|
+
# Paise are spelled too: a slip showing ₹99,612.91 must not say "six hundred thirteen".
|
|
13
14
|
def words(amount)
|
|
14
|
-
|
|
15
|
-
return "Zero rupees" if
|
|
15
|
+
paise = (Money.d(amount) * 100).round(0, BigDecimal::ROUND_HALF_UP).to_i
|
|
16
|
+
return "Zero rupees" if paise.zero?
|
|
16
17
|
|
|
18
|
+
rupees, cents = paise.abs.divmod(100)
|
|
17
19
|
# A negative amount rendered as bare " rupees": no group was >= its
|
|
18
20
|
# divisor, so nothing was ever appended.
|
|
19
|
-
sentence = groups(rupees
|
|
20
|
-
sentence = "minus #{sentence}" if
|
|
21
|
-
"#{sentence.capitalize} rupees"
|
|
21
|
+
sentence = rupees.zero? ? "zero" : groups(rupees).join(" ")
|
|
22
|
+
sentence = "minus #{sentence}" if paise.negative?
|
|
23
|
+
"#{sentence.capitalize} rupees#{" and #{two_digit(cents)} paise" if cents.positive?}"
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
def groups(rupees)
|
|
@@ -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
|
|
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 =
|
|
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}")
|
|
@@ -22,7 +22,7 @@ module HrLite
|
|
|
22
22
|
income_tax: card[:income_tax].transform_values { |regime| stringify_regime(regime) },
|
|
23
23
|
notes: StatutoryRateCard::SOURCES[effective_from] ||
|
|
24
24
|
"Shipped with hr_lite #{HrLite::VERSION}. Confirm with your accountant, then record who verified it.",
|
|
25
|
-
verified_by: (StatutoryRateCard::SOURCES.key?(effective_from)
|
|
25
|
+
verified_by: (StatutoryRateCardRecord::SHIPPED_SIGN_OFF if StatutoryRateCard::SOURCES.key?(effective_from)),
|
|
26
26
|
verified_on: (Date.current if StatutoryRateCard::SOURCES.key?(effective_from))
|
|
27
27
|
)
|
|
28
28
|
"rate card FY #{FinancialYear.label(effective_from)}"
|
data/lib/hr_lite/version.rb
CHANGED
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.
|
|
4
|
+
version: 0.55.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
|