hr_lite 0.52.0 → 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +5 -2
- data/app/assets/javascripts/hr_lite/emoji_pick.js +4 -4
- data/app/assets/javascripts/hr_lite/vendor/turbo.min.js +28 -0
- data/app/assets/stylesheets/hr_lite/hr_lite.css +7 -6
- data/app/controllers/hr_lite/admin/attendances_controller.rb +2 -0
- data/app/controllers/hr_lite/admin/comp_off_requests_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/documents_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/expenses_controller.rb +13 -1
- data/app/controllers/hr_lite/admin/leave_requests_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/regularization_requests_controller.rb +1 -1
- data/app/controllers/hr_lite/admin/statutory_rate_cards_controller.rb +2 -1
- data/app/controllers/hr_lite/admin/tax_declarations_controller.rb +2 -1
- data/app/controllers/hr_lite/admin/work_locations_controller.rb +1 -1
- data/app/controllers/hr_lite/home_controller.rb +0 -9
- data/app/controllers/hr_lite/loans_controller.rb +1 -1
- data/app/helpers/hr_lite/application_helper.rb +21 -2
- data/app/models/hr_lite/attendance_record.rb +7 -4
- data/app/models/hr_lite/salary_slip.rb +2 -1
- data/app/models/hr_lite/statutory_rate_card_record.rb +6 -0
- data/app/views/hr_lite/admin/attendances/index.html.erb +4 -4
- data/app/views/hr_lite/admin/attendances/show.html.erb +1 -1
- data/app/views/hr_lite/admin/benefits/index.html.erb +10 -4
- data/app/views/hr_lite/admin/comp_off_requests/show.html.erb +1 -1
- data/app/views/hr_lite/admin/documents/index.html.erb +2 -2
- data/app/views/hr_lite/admin/employees/show.html.erb +2 -1
- data/app/views/hr_lite/admin/expense_categories/index.html.erb +1 -1
- data/app/views/hr_lite/admin/expenses/index.html.erb +4 -2
- data/app/views/hr_lite/admin/holidays/index.html.erb +2 -1
- data/app/views/hr_lite/admin/hr_requests/index.html.erb +2 -2
- data/app/views/hr_lite/admin/hr_requests/show.html.erb +1 -1
- data/app/views/hr_lite/admin/leave_requests/show.html.erb +1 -1
- data/app/views/hr_lite/admin/leave_types/index.html.erb +2 -2
- data/app/views/hr_lite/admin/loans/new.html.erb +1 -1
- data/app/views/hr_lite/admin/office_locations/index.html.erb +1 -1
- data/app/views/hr_lite/admin/overview/index.html.erb +10 -31
- data/app/views/hr_lite/admin/payroll_line_items/new.html.erb +2 -1
- data/app/views/hr_lite/admin/payroll_runs/index.html.erb +1 -1
- data/app/views/hr_lite/admin/payroll_runs/show.html.erb +6 -4
- data/app/views/hr_lite/admin/performance_plans/_form.html.erb +2 -1
- data/app/views/hr_lite/admin/policies/index.html.erb +1 -1
- data/app/views/hr_lite/admin/policies/new.html.erb +2 -1
- data/app/views/hr_lite/admin/regularization_requests/show.html.erb +1 -1
- data/app/views/hr_lite/admin/roles/_form.html.erb +2 -1
- data/app/views/hr_lite/admin/roles/edit.html.erb +4 -2
- data/app/views/hr_lite/admin/roles/index.html.erb +1 -1
- data/app/views/hr_lite/admin/statutory_rate_cards/_form.html.erb +11 -7
- data/app/views/hr_lite/admin/tax_declarations/show.html.erb +6 -4
- data/app/views/hr_lite/admin/wfh_requests/show.html.erb +1 -1
- data/app/views/hr_lite/approvals/index.html.erb +4 -3
- data/app/views/hr_lite/attendance/_custom_status.html.erb +7 -9
- data/app/views/hr_lite/attendance/_punch_card.html.erb +8 -0
- data/app/views/hr_lite/attendance/show.html.erb +5 -3
- data/app/views/hr_lite/home/index.html.erb +4 -3
- data/app/views/hr_lite/leave_requests/_balance_chips.html.erb +2 -1
- data/app/views/hr_lite/loans/index.html.erb +1 -1
- data/app/views/hr_lite/resignations/show.html.erb +2 -1
- data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +5 -5
- data/app/views/hr_lite/salary_slips/pdf.html.erb +1 -0
- data/app/views/hr_lite/work_time/_bars.html.erb +4 -3
- data/app/views/layouts/hr_lite/application.html.erb +7 -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/configuration.rb +4 -0
- data/lib/hr_lite/statutory_seeds.rb +1 -1
- data/lib/hr_lite/version.rb +1 -1
- metadata +2 -1
|
@@ -44,7 +44,7 @@ module HrLite
|
|
|
44
44
|
{ label: "Attendance fixes", path: :admin_regularization_requests_path, match: [ "/admin/regularization_requests" ], if: OPS,
|
|
45
45
|
queue: -> { hrl_queue(HrLite::RegularizationRequest.pending, "attendance.manage") } },
|
|
46
46
|
{ label: "WFH", path: :admin_wfh_requests_path, match: [ "/admin/wfh_requests" ], if: OPS,
|
|
47
|
-
queue: -> { hrl_queue(HrLite::WfhRequest.pending, "attendance.manage") } },
|
|
47
|
+
queue: -> { hrl_queue(HrLite::WfhRequest.pending.overlapping(Date.current, Date.current + HrLite::WfhRequest::MAX_DAYS_AHEAD), "attendance.manage") } },
|
|
48
48
|
{ label: "Home locations", path: :admin_work_locations_path, match: [ "/admin/work_locations" ], if: OPS,
|
|
49
49
|
queue: -> { hrl_queue(HrLite::WorkLocation.pending, "attendance.manage") } },
|
|
50
50
|
{ label: "Claims", path: :admin_expenses_path, match: [ "/admin/expenses" ], if: -> { hr_can?("expense.approve", scope: :team) },
|
|
@@ -91,7 +91,8 @@ module HrLite
|
|
|
91
91
|
{ label: "Manage policies", path: :admin_policies_path, match: [ "/admin/policies" ], if: -> { hr_can?("policy.manage", scope: :all) } }
|
|
92
92
|
] },
|
|
93
93
|
{ label: "Admin", divider: true, tabs: [
|
|
94
|
-
{ label: "Settings", path: :admin_leave_types_path, match: [ "/admin/leave_types", "/admin/office_locations", "/admin/holidays", "/admin/
|
|
94
|
+
{ label: "Settings", path: :admin_leave_types_path, match: [ "/admin/leave_types", "/admin/office_locations", "/admin/holidays", "/admin/expense_categories" ], if: LEAD },
|
|
95
|
+
{ label: "HR settings", path: :edit_admin_setting_path, match: [ "/admin/setting" ], if: LEAD },
|
|
95
96
|
{ label: "Roles", path: :admin_roles_path, match: [ "/admin/roles" ], if: -> { hr_can?("role.manage", scope: :all) } },
|
|
96
97
|
{ label: "Statutory rates", path: :admin_statutory_rate_cards_path, match: [ "/admin/statutory_rate_cards" ], if: SUPER },
|
|
97
98
|
{ label: "Reports", path: :admin_reports_path, match: [ "/admin/reports" ], if: -> { hr_can?("expense.approve", scope: :all) || %w[leave.view attendance.view profile.view].any? { |key| hr_can?(key, scope: :team) } } },
|
|
@@ -151,6 +152,24 @@ module HrLite
|
|
|
151
152
|
# What a queue page lets this viewer decide: their reach, never their own requests.
|
|
152
153
|
def hrl_queue(relation, key) = hr_access.scope_relation(relation, key).where.not(user_id: hr_current_user.id)
|
|
153
154
|
|
|
155
|
+
# The Inbox badge's number; Home and the overview board show the same one.
|
|
156
|
+
def hrl_inbox_count
|
|
157
|
+
inbox = hrl_sections.find { |s| s[:label] == "Inbox" }
|
|
158
|
+
inbox ? hrl_waiting(inbox).to_i : 0
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# One queued request in words, and where it is decided.
|
|
162
|
+
def hrl_inbox_item(tab, record)
|
|
163
|
+
case record
|
|
164
|
+
when HrLite::LeaveRequest
|
|
165
|
+
[ "#{record.leave_type.name}, #{record.date_range_label} · #{pluralize(hrl_days(record.days_count), 'day')}", hr_lite.admin_leave_request_path(record) ]
|
|
166
|
+
when HrLite::CompOffRequest then [ "Comp-off for #{record.date_worked.strftime('%a %-d %b')}", hr_lite.admin_comp_off_request_path(record) ]
|
|
167
|
+
when HrLite::RegularizationRequest then [ "Attendance fix, #{record.date.strftime('%a %-d %b')}", hr_lite.admin_regularization_request_path(record) ]
|
|
168
|
+
when HrLite::WfhRequest then [ "WFH, #{record.dates_label}", hr_lite.admin_wfh_request_path(record) ]
|
|
169
|
+
else [ tab[:label], hr_lite.public_send(tab[:path]) ]
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
154
173
|
# Everything waiting in this viewer's queues, oldest first, as [tab, record].
|
|
155
174
|
def hrl_inbox_rows(limit = 50)
|
|
156
175
|
inbox = hrl_sections.find { |s| s[:label] == "Inbox" } or return []
|
|
@@ -57,9 +57,11 @@ module HrLite
|
|
|
57
57
|
EmployeeProfile.day_minutes_for(user_id, on: date) unless HrLite.config.work_hours.nil? || date.saturday?
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
# Hours worked, not the clock: an early start counts.
|
|
60
|
+
# Hours worked, not the clock: an early start counts. The day's penalty
|
|
61
|
+
# minutes count too, the same rule as required_check_out_at, so the card
|
|
62
|
+
# never says "day done" above "short by 20m".
|
|
61
63
|
def short?
|
|
62
|
-
worked_under?(required_minutes)
|
|
64
|
+
worked_under?(required_minutes && required_minutes + penalty_minutes)
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
def below_half_day?
|
|
@@ -70,10 +72,11 @@ module HrLite
|
|
|
70
72
|
def required_check_out_at
|
|
71
73
|
return unless HrLite.config.attendance_slots && check_in_at
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
check_in_at + (EmployeeProfile.day_minutes_for(user_id, on: date) + extra).minutes
|
|
75
|
+
check_in_at + (EmployeeProfile.day_minutes_for(user_id, on: date) + penalty_minutes).minutes
|
|
75
76
|
end
|
|
76
77
|
|
|
78
|
+
def penalty_minutes = AttendancePenalty.active.where(user_id: user_id, date: date).sum(:minutes)
|
|
79
|
+
|
|
77
80
|
def worked_under?(minutes) = !!(minutes && worked_duration && worked_duration < minutes.minutes)
|
|
78
81
|
|
|
79
82
|
# Check in again after checking out: once a day, inside the hours HR sets in
|
|
@@ -61,8 +61,9 @@ module HrLite
|
|
|
61
61
|
row ? Money.d(row["amount"]) : BigDecimal(0)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
# Bump the version when the PDF template changes, or the old layout serves until the cache expires.
|
|
64
65
|
def pdf_cache_key
|
|
65
|
-
[ "
|
|
66
|
+
[ "hr_lite_slip_v2", id, updated_at.to_i, payroll_run.status,
|
|
66
67
|
user_profile&.updated_at.to_i ].join("/")
|
|
67
68
|
end
|
|
68
69
|
|
|
@@ -7,6 +7,9 @@ module HrLite
|
|
|
7
7
|
|
|
8
8
|
self.table_name = "hr_lite_statutory_rate_cards"
|
|
9
9
|
|
|
10
|
+
# Shipped cards count as verified so payroll can auto-approve, but no person checked them.
|
|
11
|
+
SHIPPED_SIGN_OFF = "Shipped defaults, not checked by a person".freeze
|
|
12
|
+
|
|
10
13
|
validates :effective_from, presence: true, uniqueness: true
|
|
11
14
|
validate :effective_from_opens_a_financial_year
|
|
12
15
|
validate :carries_every_figure_payroll_reads
|
|
@@ -22,6 +25,9 @@ module HrLite
|
|
|
22
25
|
|
|
23
26
|
def verified? = verified_by.present? && verified_on.present?
|
|
24
27
|
|
|
28
|
+
# Cards seeded before this read "hr_lite 0.52.0 (official source …)": a version is not a sign-off.
|
|
29
|
+
def verified_by = super&.start_with?("hr_lite ") ? SHIPPED_SIGN_OFF : super
|
|
30
|
+
|
|
25
31
|
# The shape SlipBuilder and the calculators expect: symbol keys and
|
|
26
32
|
# BigDecimal everywhere, because JSON gives back strings and floats and
|
|
27
33
|
# a float has no business in a PF calculation.
|
|
@@ -15,10 +15,8 @@
|
|
|
15
15
|
|
|
16
16
|
<div class="hrl-kpis">
|
|
17
17
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @records.values.count { |r| r.check_in_at } %></div><div class="hrl-kpi__label">Checked in</div></div>
|
|
18
|
-
<%#
|
|
19
|
-
|
|
20
|
-
quietly stopped adding up to the headcount. %>
|
|
21
|
-
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @employees.size - @records.values.count { |r| r.check_in_at } %></div><div class="hrl-kpi__label">No punch</div></div>
|
|
18
|
+
<%# Expected in and not checked in: people on leave, a holiday or a weekly off are not counted. %>
|
|
19
|
+
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @employees.count { |u| @days[u.id]&.kind == :absent } %></div><div class="hrl-kpi__label">No punch</div></div>
|
|
22
20
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @flagged_count %></div><div class="hrl-kpi__label">Flagged</div></div>
|
|
23
21
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @records.values.count { |r| r.check_in_at && r.check_out_at.nil? } %></div><div class="hrl-kpi__label">Still in</div></div>
|
|
24
22
|
</div>
|
|
@@ -71,6 +69,8 @@
|
|
|
71
69
|
<span class="hrl-badge hrl-badge--bad">Flagged</span> <span class="hrl-small"><%= record.flag_note %></span>
|
|
72
70
|
<% elsif record&.check_in_at %>
|
|
73
71
|
<span class="hrl-badge hrl-badge--ok"><%= record.status.humanize %><%= " (#{record.half_day_part} half)" if record.half_day_part %></span>
|
|
72
|
+
<% elsif @days[employee.id] %>
|
|
73
|
+
<%= hrl_team_status(@days[employee.id]) %>
|
|
74
74
|
<% else %>
|
|
75
75
|
<span class="hrl-badge hrl-badge--muted">No punch</span>
|
|
76
76
|
<% end %>
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
</div>
|
|
81
81
|
<div class="hrl-field">
|
|
82
82
|
<%= f.label :regularization_note, "Why is this being fixed? (required)" %>
|
|
83
|
-
<%= f.text_area :regularization_note %>
|
|
83
|
+
<%= f.text_area :regularization_note, required: true %>
|
|
84
84
|
</div>
|
|
85
85
|
<div class="hrl-form-actions">
|
|
86
86
|
<%= f.submit "Save fix", class: "hrl-btn hrl-btn--primary" %>
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
<%= form_with url: hr_lite.enrol_admin_benefit_path(benefit), method: :post, local: true do %>
|
|
21
21
|
<div class="hrl-field">
|
|
22
22
|
<%= label_tag "enrol_user_#{benefit.id}", "Employee" %>
|
|
23
|
-
<%= select_tag :user_id, options_for_select(people), id: "enrol_user_#{benefit.id}"
|
|
23
|
+
<%= select_tag :user_id, options_for_select(people), id: "enrol_user_#{benefit.id}", include_blank: "Choose…", required: true,
|
|
24
|
+
data: { hrl_filter: "Type a name to filter" } %>
|
|
24
25
|
</div>
|
|
25
26
|
<div class="hrl-field">
|
|
26
27
|
<%= label_tag "enrol_on_#{benefit.id}", "Covered from" %>
|
|
@@ -49,10 +50,15 @@
|
|
|
49
50
|
<td data-label="Since"><%= enrolment.enrolled_on.strftime("%d %b %Y") %></td>
|
|
50
51
|
<td data-label="Dependants" class="hrl-num"><%= enrolment.dependants %></td>
|
|
51
52
|
<td data-label="">
|
|
52
|
-
|
|
53
|
+
<% if enrolment.ended_on %>
|
|
54
|
+
<p class="hrl-small">Cover ends <%= enrolment.ended_on.strftime("%d %b %Y") %></p>
|
|
55
|
+
<% end %>
|
|
56
|
+
<%= form_with url: hr_lite.unenrol_admin_benefit_path(enrolment.benefit), method: :post, class: "hrl-row",
|
|
57
|
+
data: { turbo_confirm: "End #{enrolment.benefit.name} for #{hr_display_name(enrolment.user)} on the date picked?" } do %>
|
|
53
58
|
<%= hidden_field_tag :user_id, enrolment.user_id %>
|
|
54
|
-
<%=
|
|
55
|
-
<%=
|
|
59
|
+
<%= label_tag "ended-on-#{enrolment.id}", "Last day of cover", class: "hrl-small" %>
|
|
60
|
+
<%= date_field_tag :ended_on, enrolment.ended_on || Date.current, id: "ended-on-#{enrolment.id}", class: "hrl-input" %>
|
|
61
|
+
<%= submit_tag enrolment.ended_on ? "Change end" : "End cover", class: "hrl-btn" %>
|
|
56
62
|
<% end %>
|
|
57
63
|
</td>
|
|
58
64
|
</tr>
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
<%= form_with url: hr_lite.reject_admin_comp_off_request_path(@request), method: :post, local: true do |f| %>
|
|
52
52
|
<div class="hrl-field">
|
|
53
53
|
<%= f.label :decision_note, "Reason (required)", for: "reject_note" %>
|
|
54
|
-
<%= f.text_area :decision_note, id: "reject_note" %>
|
|
54
|
+
<%= f.text_area :decision_note, id: "reject_note", required: true %>
|
|
55
55
|
</div>
|
|
56
56
|
<div class="hrl-form-actions">
|
|
57
57
|
<%= f.button "Reject", type: :submit, class: "hrl-btn hrl-btn--danger" %>
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
</td>
|
|
36
36
|
<td data-label="File">
|
|
37
37
|
<% if doc.readable_by?(hr_current_user) && doc.file.attached? %>
|
|
38
|
-
<a class="hrl-
|
|
38
|
+
<a class="hrl-btn" href="<%= hr_lite.download_document_path(doc) %>">Open</a>
|
|
39
39
|
<% else %>
|
|
40
40
|
<span class="hrl-small hrl-muted">Not yours to open</span>
|
|
41
41
|
<% end %>
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
<%= button_to "Verify", hr_lite.verify_admin_document_path(doc),
|
|
45
45
|
method: :post, class: "hrl-btn hrl-btn--primary" %>
|
|
46
46
|
<%= form_with url: hr_lite.reject_admin_document_path(doc), method: :post, local: true do %>
|
|
47
|
-
<%= text_field_tag :note, nil, placeholder: "What is wrong with it", class: "hrl-input" %>
|
|
47
|
+
<%= text_field_tag :note, nil, placeholder: "What is wrong with it", class: "hrl-input", required: true %>
|
|
48
48
|
<%= submit_tag "Reject", class: "hrl-btn" %>
|
|
49
49
|
<% end %>
|
|
50
50
|
</td>
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
<dt>Proposed last day</dt><dd><%= @pending_resignation.proposed_last_day.strftime("%d %B %Y") %></dd>
|
|
31
31
|
<% if @pending_resignation.reason.present? %><dt>Reason</dt><dd><%= @pending_resignation.reason %></dd><% end %>
|
|
32
32
|
</dl>
|
|
33
|
-
<%= form_with url: hr_lite.accept_admin_resignation_path(@pending_resignation), method: :post, local: true
|
|
33
|
+
<%= form_with url: hr_lite.accept_admin_resignation_path(@pending_resignation), method: :post, local: true,
|
|
34
|
+
data: { turbo_confirm: "Accept #{hr_display_name(@profile.user)}'s resignation? Payroll and attendance end on the last day you set." } do %>
|
|
34
35
|
<div class="hrl-field">
|
|
35
36
|
<label for="res_last_day">Confirm last working day</label>
|
|
36
37
|
<input type="date" id="res_last_day" name="last_day" class="hrl-input"
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<td data-label="Monthly cap" class="hrl-num"><%= category.uncapped? ? "No cap" : hrl_money(category.monthly_cap) %></td>
|
|
19
19
|
<td data-label="Receipt"><%= category.receipt_required ? "Required" : "Optional" %></td>
|
|
20
20
|
<td data-label="Active"><%= category.active ? "Yes" : "No" %></td>
|
|
21
|
-
<td data-label=""><a class="hrl-
|
|
21
|
+
<td data-label=""><a class="hrl-btn" href="<%= hr_lite.edit_admin_expense_category_path(category) %>">Edit</a></td>
|
|
22
22
|
</tr>
|
|
23
23
|
<% end %>
|
|
24
24
|
</tbody>
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
<div class="hrl-small hrl-muted">
|
|
27
27
|
<%= expense.description %> · <%= expense.spent_on.strftime("%d %b %Y") %>
|
|
28
28
|
</div>
|
|
29
|
+
<% if expense.receipt.attached? %><a class="hrl-card__link" href="<%= hr_lite.receipt_admin_expense_path(expense) %>">Open receipt</a><% end %>
|
|
29
30
|
</td>
|
|
30
31
|
<td data-label="Amount" class="hrl-num"><%= hrl_money(expense.amount) %></td>
|
|
31
32
|
<td data-label="">
|
|
@@ -33,8 +34,9 @@
|
|
|
33
34
|
<%= form_with url: hr_lite.approve_admin_expense_path(expense), method: :post do %>
|
|
34
35
|
<%= submit_tag "Approve", class: "hrl-btn hrl-btn--primary" %>
|
|
35
36
|
<% end %>
|
|
36
|
-
<%= form_with url: hr_lite.reject_admin_expense_path(expense), method: :post do %>
|
|
37
|
-
<%=
|
|
37
|
+
<%= form_with url: hr_lite.reject_admin_expense_path(expense), method: :post, class: "hrl-row hrl-mt" do %>
|
|
38
|
+
<%= label_tag "reject-expense-#{expense.id}", "Reason to reject", class: "hrl-sr-only" %>
|
|
39
|
+
<%= text_field_tag :decision_note, nil, id: "reject-expense-#{expense.id}", placeholder: "Reason to reject", required: true, class: "hrl-input" %>
|
|
38
40
|
<%= submit_tag "Reject", class: "hrl-btn" %>
|
|
39
41
|
<% end %>
|
|
40
42
|
<% elsif expense.approved? %>
|
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
<%= select_tag :office_location_id, options_for_select(@offices), include_blank: "All offices", id: "bulk-office", data: { hrl_filter: ("Type an office" if @offices.size > 5) } %></div>
|
|
59
59
|
<% end %>
|
|
60
60
|
<div class="hrl-field">
|
|
61
|
-
<
|
|
61
|
+
<label for="bulk-lines">One holiday a line: date, name</label>
|
|
62
|
+
<textarea name="lines" id="bulk-lines" class="hrl-input" rows="6" placeholder="2026-11-09, Diwali 2026-03-04, Holi"></textarea>
|
|
62
63
|
</div>
|
|
63
64
|
<div class="hrl-form-actions">
|
|
64
65
|
<button type="submit" class="hrl-btn hrl-btn--primary">Add all</button>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<% @requests.each do |request| %>
|
|
22
22
|
<tr>
|
|
23
23
|
<td data-label="Request">
|
|
24
|
-
<a href="<%= hr_lite.admin_hr_request_path(request) %>"><%= request.subject %></a>
|
|
24
|
+
<a class="hrl-card__link" href="<%= hr_lite.admin_hr_request_path(request) %>"><%= request.subject %></a>
|
|
25
25
|
<div class="hrl-small hrl-muted"><%= request.category_label %></div>
|
|
26
26
|
</td>
|
|
27
27
|
<td data-label="Who"><%= hr_display_name(request.user) %></td>
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<%= request.assigned_to ? hr_display_name(request.assigned_to) : "—" %>
|
|
30
30
|
</td>
|
|
31
31
|
<td data-label="">
|
|
32
|
-
<a class="hrl-
|
|
32
|
+
<a class="hrl-btn" href="<%= hr_lite.admin_hr_request_path(request) %>">Open</a>
|
|
33
33
|
</td>
|
|
34
34
|
</tr>
|
|
35
35
|
<% end %>
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
<%= form_with url: hr_lite.resolve_admin_hr_request_path(@request), method: :post do %>
|
|
22
22
|
<div class="hrl-field">
|
|
23
23
|
<%= label_tag :resolution, "What you did" %>
|
|
24
|
-
<%= text_area_tag :resolution, nil, rows: 4 %>
|
|
24
|
+
<%= text_area_tag :resolution, nil, rows: 4, required: true %>
|
|
25
25
|
</div>
|
|
26
26
|
<div class="hrl-form-actions">
|
|
27
27
|
<%= submit_tag "Send answer", class: "hrl-btn hrl-btn--primary" %>
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
<%= form_with url: hr_lite.reject_admin_leave_request_path(@request), method: :post, local: true do %>
|
|
77
77
|
<div class="hrl-field">
|
|
78
78
|
<label for="reject_note">Reason (required)</label>
|
|
79
|
-
<textarea name="decision_note" id="reject_note" class="hrl-input"></textarea>
|
|
79
|
+
<textarea name="decision_note" id="reject_note" class="hrl-input" required></textarea>
|
|
80
80
|
</div>
|
|
81
81
|
<div class="hrl-form-actions">
|
|
82
82
|
<button class="hrl-btn hrl-btn--danger" type="submit">Reject</button>
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
<td data-label="Carry cap" class="hrl-num"><%= type.carry_forward_cap.to_f %></td>
|
|
24
24
|
<td data-label="Paid"><%= type.paid ? "Yes" : "No" %></td>
|
|
25
25
|
<td data-label="">
|
|
26
|
-
<a class="hrl-
|
|
26
|
+
<a class="hrl-btn" href="<%= hr_lite.edit_admin_leave_type_path(type) %>">Edit</a>
|
|
27
27
|
</td>
|
|
28
28
|
</tr>
|
|
29
29
|
<% end %>
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
<a class="hrl-btn" href="<%= hr_lite.admin_office_locations_path %>">Office locations</a>
|
|
37
37
|
<a class="hrl-btn" href="<%= hr_lite.admin_holidays_path %>">Holidays</a>
|
|
38
38
|
<a class="hrl-btn" href="<%= hr_lite.admin_expense_categories_path %>">Expense categories</a>
|
|
39
|
-
<a class="hrl-btn" href="<%= hr_lite.edit_admin_setting_path %>">
|
|
39
|
+
<a class="hrl-btn" href="<%= hr_lite.edit_admin_setting_path %>">HR settings</a>
|
|
40
40
|
</div>
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<%= f.label :user_id, "Employee" %>
|
|
14
14
|
<%= f.select :user_id, options_for_select(HrLite.employees.map { |u| [ hr_display_name(u), u.id ] },
|
|
15
15
|
@loan.user_id),
|
|
16
|
-
{}, data: { hrl_filter: "Type a name to filter" } %>
|
|
16
|
+
{ include_blank: "Choose…" }, required: true, data: { hrl_filter: "Type a name to filter" } %>
|
|
17
17
|
</div>
|
|
18
18
|
<div class="hrl-field">
|
|
19
19
|
<%= f.label :principal, "Amount lent (₹)" %>
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
<td data-label="Coordinates"><%= office.lat %>, <%= office.lng %></td>
|
|
19
19
|
<td data-label="Radius" class="hrl-num"><%= office.radius_m %> m</td>
|
|
20
20
|
<td data-label="Active"><%= office.active ? "Yes" : "No" %></td>
|
|
21
|
-
<td data-label=""><a class="hrl-
|
|
21
|
+
<td data-label=""><a class="hrl-btn" href="<%= hr_lite.edit_admin_office_location_path(office) %>">Edit</a></td>
|
|
22
22
|
</tr>
|
|
23
23
|
<% end %>
|
|
24
24
|
</tbody>
|
|
@@ -3,50 +3,29 @@
|
|
|
3
3
|
<h1 class="hrl-page__title">HR overview</h1>
|
|
4
4
|
</div>
|
|
5
5
|
|
|
6
|
+
<%# The Inbox badge's count and rows, so the board never counts what the Inbox hides. %>
|
|
7
|
+
<% pending = hrl_inbox_count %>
|
|
6
8
|
<div class="hrl-kpis">
|
|
7
|
-
<div class="hrl-kpi"><div class="hrl-kpi__value"><%=
|
|
9
|
+
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= pending %></div><div class="hrl-kpi__label">Pending approvals</div></div>
|
|
8
10
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @kpis[:on_leave] %></div><div class="hrl-kpi__label">On leave today</div></div>
|
|
9
11
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @kpis[:flagged] %></div><div class="hrl-kpi__label">Flagged punches</div></div>
|
|
10
12
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @kpis[:missing_checkout] %></div><div class="hrl-kpi__label">Missing checkouts</div></div>
|
|
11
13
|
</div>
|
|
12
14
|
|
|
13
|
-
<% if @
|
|
15
|
+
<% if pending.zero? && @kpis.except(:pending).values.all?(&:zero?) %>
|
|
14
16
|
<p class="hrl-card hrl-card--empty">All present and accounted for.</p>
|
|
15
17
|
<% else %>
|
|
16
|
-
<% if
|
|
18
|
+
<% if pending.positive? %>
|
|
17
19
|
<section class="hrl-card">
|
|
18
20
|
<h2 class="hrl-card__title">Pending approvals</h2>
|
|
19
|
-
<%
|
|
21
|
+
<% hrl_inbox_rows(HrLite::Admin::OverviewController::SECTION_CAP).each do |tab, request| %>
|
|
22
|
+
<% what, href = hrl_inbox_item(tab, request) %>
|
|
20
23
|
<div class="hrl-feed__item">
|
|
21
|
-
<div class="hrl-feed__body">
|
|
22
|
-
|
|
23
|
-
— <%= request.leave_type.name %>, <%= request.date_range_label %> (<%= hrl_days(request.days_count) %> d)
|
|
24
|
-
</div>
|
|
25
|
-
<a class="hrl-btn" href="<%= hr_lite.admin_leave_request_path(request) %>">Decide</a>
|
|
26
|
-
</div>
|
|
27
|
-
<% end %>
|
|
28
|
-
<%# Comp-off and regularization are approvals too. Listing leave alone
|
|
29
|
-
left both queues invisible on the board that exists to surface work. %>
|
|
30
|
-
<% @query.pending_comp_offs.limit(HrLite::Admin::OverviewController::SECTION_CAP).each do |request| %>
|
|
31
|
-
<div class="hrl-feed__item">
|
|
32
|
-
<div class="hrl-feed__body">
|
|
33
|
-
<strong><%= hr_display_name(request.user) %></strong>
|
|
34
|
-
— comp-off for <%= request.date_worked.strftime("%d %b") %>
|
|
35
|
-
(<%= hrl_days(request.credit_days) %> d)
|
|
36
|
-
</div>
|
|
37
|
-
<a class="hrl-btn" href="<%= hr_lite.admin_comp_off_request_path(request) %>">Decide</a>
|
|
38
|
-
</div>
|
|
39
|
-
<% end %>
|
|
40
|
-
<% @query.pending_regularizations.limit(HrLite::Admin::OverviewController::SECTION_CAP).each do |request| %>
|
|
41
|
-
<div class="hrl-feed__item">
|
|
42
|
-
<div class="hrl-feed__body">
|
|
43
|
-
<strong><%= hr_display_name(request.user) %></strong>
|
|
44
|
-
— attendance fix for <%= request.date.strftime("%d %b") %>
|
|
45
|
-
</div>
|
|
46
|
-
<a class="hrl-btn" href="<%= hr_lite.admin_regularization_request_path(request) %>">Decide</a>
|
|
24
|
+
<div class="hrl-feed__body"><strong><%= hr_display_name(request.user) %></strong> — <%= what %></div>
|
|
25
|
+
<a class="hrl-btn" href="<%= href %>">Decide</a>
|
|
47
26
|
</div>
|
|
48
27
|
<% end %>
|
|
49
|
-
<a class="hrl-card__link" href="<%= hr_lite.
|
|
28
|
+
<a class="hrl-card__link" href="<%= hr_lite.approvals_path %>">All requests</a>
|
|
50
29
|
</section>
|
|
51
30
|
<% end %>
|
|
52
31
|
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
<div class="hrl-field">
|
|
14
14
|
<%= f.label :user_id, "Employee" %>
|
|
15
15
|
<%= f.select :user_id, options_for_select(HrLite.employees.map { |u| [ hr_display_name(u), u.id ] },
|
|
16
|
-
@item.user_id)
|
|
16
|
+
@item.user_id),
|
|
17
|
+
{ include_blank: "Choose…" }, required: true, data: { hrl_filter: "Type a name to filter" } %>
|
|
17
18
|
</div>
|
|
18
19
|
<div class="hrl-field">
|
|
19
20
|
<%= f.label :component_id, "Item" %>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="hrl-page__head">
|
|
3
3
|
<h1 class="hrl-page__title">Payroll runs</h1>
|
|
4
4
|
<div class="hrl-page__actions">
|
|
5
|
-
|
|
5
|
+
<% if hr_can?("payroll.manage", scope: :all) %><a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_admin_payroll_run_path %>">New run</a><% end %>
|
|
6
6
|
</div>
|
|
7
7
|
</div>
|
|
8
8
|
|
|
@@ -7,11 +7,11 @@
|
|
|
7
7
|
</div>
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
|
-
<div class="hrl-kpis">
|
|
10
|
+
<div class="hrl-kpis hrl-kpis--money">
|
|
11
11
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= @slips.size %></div><div class="hrl-kpi__label">Slips</div></div>
|
|
12
12
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_money(@run.total_gross) %></div><div class="hrl-kpi__label">Gross</div></div>
|
|
13
13
|
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_money(@run.total_net) %></div><div class="hrl-kpi__label">Net payout</div></div>
|
|
14
|
-
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_money(@run.total_employer_cost) %></div><div class="hrl-kpi__label">Employer
|
|
14
|
+
<div class="hrl-kpi"><div class="hrl-kpi__value"><%= hrl_money(@run.total_employer_cost) %></div><div class="hrl-kpi__label">Employer contributions</div></div>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
17
|
<% if @run.warnings.any? %>
|
|
@@ -29,12 +29,14 @@
|
|
|
29
29
|
<% manager = hr_can?("payroll.manage", scope: :all) %>
|
|
30
30
|
<% if manager && (@run.draft? || @run.review? || @run.processing?) %>
|
|
31
31
|
<%= button_to @run.draft? ? "Compute" : "Recompute", hr_lite.compute_admin_payroll_run_path(@run),
|
|
32
|
-
method: :post, class: "hrl-btn"
|
|
32
|
+
method: :post, class: "hrl-btn",
|
|
33
|
+
form: { data: { turbo_confirm: ("Recompute #{@run.label}? This clears #{hr_display_name(@run.first_approved_by)}'s approval." if @run.first_approved_by) } } %>
|
|
33
34
|
<% end %>
|
|
34
35
|
<% if @run.review? && hr_can?("payroll.approve", scope: :all) && @run.first_approved_by_id != hr_current_user.id %>
|
|
35
36
|
<%= button_to @run.first_approved_by_id ? "Approve and publish" : "Approve", hr_lite.approve_admin_payroll_run_path(@run),
|
|
36
37
|
method: :post, class: "hrl-btn hrl-btn--primary",
|
|
37
|
-
form: { data: { turbo_confirm: @run.first_approved_by_id ? "Approve #{@run.label}? This finalizes the run and publishes the slips." : "Approve #{@run.label}?"
|
|
38
|
+
form: { data: { turbo_confirm: [ @run.first_approved_by_id ? "Approve #{@run.label}? This finalizes the run and publishes the slips." : "Approve #{@run.label}?",
|
|
39
|
+
*("Blocking warnings: #{@run.blocking_warnings.join('; ')}" if @run.blocking_warnings.any?) ].join("\n\n") } } %>
|
|
38
40
|
<% end %>
|
|
39
41
|
<% if manager && @run.finalized? %>
|
|
40
42
|
<%= button_to "Unlock", hr_lite.unlock_admin_payroll_run_path(@run), method: :post, class: "hrl-btn" %>
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
<div class="hrl-field"><%= f.label :outcome_note, "Outcome note" %><%= f.text_area :outcome_note, rows: 3 %></div>
|
|
23
23
|
<% end %>
|
|
24
24
|
<div class="hrl-form-actions">
|
|
25
|
-
<%= f.submit plan.persisted? ? "Save" : "Start PIP", class: "hrl-btn hrl-btn--primary"
|
|
25
|
+
<%= f.submit plan.persisted? ? "Save" : "Start PIP", class: "hrl-btn hrl-btn--primary",
|
|
26
|
+
data: { turbo_confirm: ("Save? Passed or failed closes the plan for good and tells the employee." if plan.persisted?) } %>
|
|
26
27
|
<a class="hrl-btn" href="<%= hr_lite.admin_performance_plans_path %>">Cancel</a>
|
|
27
28
|
</div>
|
|
28
29
|
<% end %>
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<tbody>
|
|
15
15
|
<% @policies.each do |policy| %>
|
|
16
16
|
<tr>
|
|
17
|
-
<td data-label="Title"><%= policy.title
|
|
17
|
+
<td data-label="Title"><a class="hrl-card__link" href="<%= hr_lite.policy_path(policy) %>"><%= policy.title %></a> <span class="hrl-badge hrl-badge--muted">v<%= policy.version %></span></td>
|
|
18
18
|
<td data-label="Kind"><%= policy.announcement? ? "Announcement" : "Policy" %></td>
|
|
19
19
|
<td data-label="In force"><%= policy.effective_from.strftime("%d %b %Y") %></td>
|
|
20
20
|
<td data-label="Still to acknowledge"><%= policy.announcement? ? "—" : policy.outstanding_for.count %></td>
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
<%= f.label :acknowledgement_required, "Policy — everyone must acknowledge it (untick for an announcement)" %>
|
|
16
16
|
</div>
|
|
17
17
|
<div class="hrl-form-actions">
|
|
18
|
-
<%= f.submit "Publish", class: "hrl-btn hrl-btn--primary"
|
|
18
|
+
<%= f.submit "Publish", class: "hrl-btn hrl-btn--primary",
|
|
19
|
+
data: { turbo_confirm: "Publish and notify #{pluralize(HrLite.active_employees.size, 'person')} by email?" } %>
|
|
19
20
|
<a class="hrl-btn" href="<%= hr_lite.admin_policies_path %>">Cancel</a>
|
|
20
21
|
</div>
|
|
21
22
|
<% end %>
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
<%= form_with url: hr_lite.reject_admin_regularization_request_path(@request), method: :post, local: true do |f| %>
|
|
55
55
|
<div class="hrl-field">
|
|
56
56
|
<%= f.label :decision_note, "Reason (required)", for: "reject_note" %>
|
|
57
|
-
<%= f.text_area :decision_note, id: "reject_note" %>
|
|
57
|
+
<%= f.text_area :decision_note, id: "reject_note", required: true %>
|
|
58
58
|
</div>
|
|
59
59
|
<div class="hrl-form-actions">
|
|
60
60
|
<%= f.button "Reject", type: :submit, class: "hrl-btn hrl-btn--danger" %>
|
|
@@ -40,7 +40,8 @@
|
|
|
40
40
|
<% end %>
|
|
41
41
|
|
|
42
42
|
<div class="hrl-form-actions">
|
|
43
|
-
<%= f.submit "Save", class: "hrl-btn hrl-btn--primary"
|
|
43
|
+
<%= f.submit "Save", class: "hrl-btn hrl-btn--primary",
|
|
44
|
+
data: { turbo_confirm: ("Save? Everyone holding #{role.name} gets these permissions at once." if role.persisted?) } %>
|
|
44
45
|
<a class="hrl-btn" href="<%= hr_lite.admin_roles_path %>">Cancel</a>
|
|
45
46
|
</div>
|
|
46
47
|
<% end %>
|
|
@@ -38,13 +38,15 @@
|
|
|
38
38
|
</div>
|
|
39
39
|
<% end %>
|
|
40
40
|
|
|
41
|
-
<%= form_with url: hr_lite.admin_role_assignments_path(@role), method: :post, local: true
|
|
41
|
+
<%= form_with url: hr_lite.admin_role_assignments_path(@role), method: :post, local: true,
|
|
42
|
+
data: { turbo_confirm: "Give the #{@role.name} role to the person you picked? Their access changes at once." } do %>
|
|
42
43
|
<div class="hrl-field">
|
|
43
44
|
<%= label_tag :user_id, "Add somebody to this role" %>
|
|
44
45
|
<% held = @role.role_assignments.map(&:user_id) %>
|
|
45
46
|
<%= select_tag :user_id,
|
|
46
47
|
options_for_select(HrLite.employees.reject { |u| held.include?(u.id) }
|
|
47
|
-
.map { |u| [ hr_display_name(u), u.id ] })
|
|
48
|
+
.map { |u| [ hr_display_name(u), u.id ] }),
|
|
49
|
+
include_blank: "Choose…", required: true, data: { hrl_filter: "Type a name to filter" } %>
|
|
48
50
|
</div>
|
|
49
51
|
<div class="hrl-form-actions">
|
|
50
52
|
<%= submit_tag "Add", class: "hrl-btn hrl-btn--primary" %>
|
|
@@ -68,16 +68,20 @@
|
|
|
68
68
|
the open-ended top rate.
|
|
69
69
|
</p>
|
|
70
70
|
<% slabs = Array(table["slabs"]) %>
|
|
71
|
-
<% (
|
|
72
|
-
|
|
71
|
+
<% num = ->(v) { v && number_with_precision(v, precision: 4, strip_insignificant_zeros: true) } %>
|
|
72
|
+
<div class="hrl-slabs">
|
|
73
|
+
<span class="hrl-small" aria-hidden="true">From ₹</span>
|
|
74
|
+
<span class="hrl-small" aria-hidden="true">Up to ₹</span>
|
|
75
|
+
<span class="hrl-small" aria-hidden="true">Rate (0.05 = 5%)</span>
|
|
76
|
+
<% (slabs + [ nil, nil ]).each_with_index do |row, index| %>
|
|
73
77
|
<%= text_field_tag "statutory_rate_card[income_tax][#{regime}][slabs][#{index}][from]",
|
|
74
|
-
row&.first,
|
|
78
|
+
num.(row&.first), class: "hrl-input", inputmode: "decimal", "aria-label": "Band #{index + 1} from ₹" %>
|
|
75
79
|
<%= text_field_tag "statutory_rate_card[income_tax][#{regime}][slabs][#{index}][to]",
|
|
76
|
-
row&.at(1),
|
|
80
|
+
num.(row&.at(1)), class: "hrl-input", inputmode: "decimal", "aria-label": "Band #{index + 1} up to ₹ (blank = top)" %>
|
|
77
81
|
<%= text_field_tag "statutory_rate_card[income_tax][#{regime}][slabs][#{index}][rate]",
|
|
78
|
-
row&.last,
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
num.(row&.last), class: "hrl-input", inputmode: "decimal", "aria-label": "Band #{index + 1} rate" %>
|
|
83
|
+
<% end %>
|
|
84
|
+
</div>
|
|
81
85
|
</fieldset>
|
|
82
86
|
<% end %>
|
|
83
87
|
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
<% end %>
|
|
49
49
|
<div class="hrl-form-actions">
|
|
50
50
|
<%= f.submit "Record amounts", class: "hrl-btn" %>
|
|
51
|
+
<%= f.submit "Record and verify", name: "verify", class: "hrl-btn" %>
|
|
51
52
|
</div>
|
|
52
53
|
</section>
|
|
53
54
|
<% end %>
|
|
@@ -55,16 +56,17 @@
|
|
|
55
56
|
<section class="hrl-card">
|
|
56
57
|
<h2 class="hrl-card__title">Decide</h2>
|
|
57
58
|
<div class="hrl-row">
|
|
59
|
+
<% unproven = @declaration.tax_declaration_items.reject { |item| item.proofs.attached? }.map { |item| item.section_label.split(" — ").first } %>
|
|
58
60
|
<%= button_to "Accept all and verify", hr_lite.accept_all_admin_tax_declaration_path(@declaration),
|
|
59
|
-
method: :post, class: "hrl-btn hrl-btn--primary"
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
method: :post, class: "hrl-btn hrl-btn--primary",
|
|
62
|
+
form: { data: { turbo_confirm: [ "Accept every claimed amount and verify? The employee can no longer edit it.",
|
|
63
|
+
("No proof attached: #{unproven.to_sentence}." if unproven.any?) ].compact.join(" ") } } %>
|
|
62
64
|
</div>
|
|
63
65
|
<%= form_with url: hr_lite.reject_admin_tax_declaration_path(@declaration),
|
|
64
66
|
method: :post, local: true do %>
|
|
65
67
|
<div class="hrl-field">
|
|
66
68
|
<%= label_tag :note, "What is missing (required to send back)" %>
|
|
67
|
-
<%= text_field_tag :note %>
|
|
69
|
+
<%= text_field_tag :note, nil, class: "hrl-input" %>
|
|
68
70
|
</div>
|
|
69
71
|
<%= submit_tag "Send back", class: "hrl-btn" %>
|
|
70
72
|
<% end %>
|