hr_lite 0.13.0 → 0.15.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 +86 -1
- data/app/controllers/hr_lite/admin/assets_controller.rb +53 -0
- data/app/controllers/hr_lite/admin/checklists_controller.rb +32 -0
- data/app/controllers/hr_lite/admin/documents_controller.rb +63 -0
- data/app/controllers/hr_lite/admin/employees_controller.rb +4 -0
- data/app/controllers/hr_lite/admin/expenses_controller.rb +45 -0
- data/app/controllers/hr_lite/admin/hr_requests_controller.rb +38 -0
- data/app/controllers/hr_lite/admin/loans_controller.rb +59 -0
- data/app/controllers/hr_lite/admin/tax_declarations_controller.rb +75 -0
- data/app/controllers/hr_lite/documents_controller.rb +69 -0
- data/app/controllers/hr_lite/loans_controller.rb +11 -0
- data/app/controllers/hr_lite/tax_declarations_controller.rb +100 -0
- data/app/helpers/hr_lite/application_helper.rb +14 -4
- data/app/models/hr_lite/approval_flow.rb +1 -1
- data/app/models/hr_lite/asset.rb +57 -0
- data/app/models/hr_lite/asset_assignment.rb +24 -0
- data/app/models/hr_lite/checklist_item.rb +43 -0
- data/app/models/hr_lite/checklist_template.rb +50 -0
- data/app/models/hr_lite/employee_profile.rb +13 -0
- data/app/models/hr_lite/tax_declaration.rb +4 -1
- data/app/views/hr_lite/admin/assets/index.html.erb +59 -0
- data/app/views/hr_lite/admin/assets/new.html.erb +23 -0
- data/app/views/hr_lite/admin/checklists/index.html.erb +50 -0
- data/app/views/hr_lite/admin/documents/index.html.erb +89 -0
- data/app/views/hr_lite/admin/documents/show.html.erb +42 -0
- data/app/views/hr_lite/admin/expenses/index.html.erb +54 -0
- data/app/views/hr_lite/admin/hr_requests/index.html.erb +41 -0
- data/app/views/hr_lite/admin/hr_requests/show.html.erb +47 -0
- data/app/views/hr_lite/admin/loans/index.html.erb +43 -0
- data/app/views/hr_lite/admin/loans/new.html.erb +39 -0
- data/app/views/hr_lite/admin/loans/show.html.erb +52 -0
- data/app/views/hr_lite/admin/tax_declarations/index.html.erb +37 -0
- data/app/views/hr_lite/admin/tax_declarations/show.html.erb +54 -0
- data/app/views/hr_lite/documents/index.html.erb +84 -0
- data/app/views/hr_lite/loans/index.html.erb +52 -0
- data/app/views/hr_lite/tax_declarations/show.html.erb +68 -0
- data/config/routes.rb +29 -0
- data/db/migrate/20260818010401_create_hr_lite_assets_and_onboarding.rb +76 -0
- data/lib/hr_lite/permissions.rb +3 -0
- data/lib/hr_lite/role_seeds.rb +5 -3
- data/lib/hr_lite/version.rb +1 -1
- data/lib/tasks/hr_lite_tasks.rake +2 -1
- metadata +32 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<% content_for(:page_title) { @request.subject } %>
|
|
2
|
+
<div class="hrl-page__head"><h1 class="hrl-page__title"><%= @request.subject %></h1></div>
|
|
3
|
+
|
|
4
|
+
<section class="hrl-card">
|
|
5
|
+
<p class="hrl-small hrl-muted">
|
|
6
|
+
<%= @request.category_label %> · <%= hr_display_name(@request.user) %> ·
|
|
7
|
+
<%= @request.created_at.to_date.strftime("%d %b %Y") %> ·
|
|
8
|
+
<%= hrl_request_status_badge(@request.status) %>
|
|
9
|
+
</p>
|
|
10
|
+
<% if @request.body.present? %>
|
|
11
|
+
<div class="hrl-prose"><%= simple_format(@request.body) %></div>
|
|
12
|
+
<% end %>
|
|
13
|
+
</section>
|
|
14
|
+
|
|
15
|
+
<% unless @request.resolved? || @request.closed? || @request.cancelled? %>
|
|
16
|
+
<section class="hrl-card">
|
|
17
|
+
<h2 class="hrl-card__title">Answer</h2>
|
|
18
|
+
<%= form_with url: hr_lite.resolve_admin_hr_request_path(@request), method: :post do %>
|
|
19
|
+
<div class="hrl-field">
|
|
20
|
+
<%= label_tag :resolution, "What you did" %>
|
|
21
|
+
<%= text_area_tag :resolution, nil, rows: 4 %>
|
|
22
|
+
</div>
|
|
23
|
+
<div class="hrl-form-actions">
|
|
24
|
+
<%= submit_tag "Send answer", class: "hrl-btn hrl-btn--primary" %>
|
|
25
|
+
</div>
|
|
26
|
+
<% end %>
|
|
27
|
+
|
|
28
|
+
<%= form_with url: hr_lite.assign_admin_hr_request_path(@request), method: :post do %>
|
|
29
|
+
<div class="hrl-field">
|
|
30
|
+
<%= label_tag :assignee_id, "Or hand it to somebody" %>
|
|
31
|
+
<%= select_tag :assignee_id,
|
|
32
|
+
options_for_select(HrLite.users_holding("hr_request.manage")
|
|
33
|
+
.map { |u| [ hr_display_name(u), u.id ] }) %>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="hrl-form-actions">
|
|
36
|
+
<%= submit_tag "Assign", class: "hrl-btn" %>
|
|
37
|
+
</div>
|
|
38
|
+
<% end %>
|
|
39
|
+
</section>
|
|
40
|
+
<% end %>
|
|
41
|
+
|
|
42
|
+
<% if @request.resolution.present? %>
|
|
43
|
+
<section class="hrl-card">
|
|
44
|
+
<h2 class="hrl-card__title">Answered</h2>
|
|
45
|
+
<div class="hrl-prose"><%= simple_format(@request.resolution) %></div>
|
|
46
|
+
</section>
|
|
47
|
+
<% end %>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Loans" } %>
|
|
2
|
+
<div class="hrl-page__head">
|
|
3
|
+
<h1 class="hrl-page__title">Loans & advances</h1>
|
|
4
|
+
<div class="hrl-page__actions">
|
|
5
|
+
<a class="hrl-btn hrl-btn--primary" href="<%= hr_lite.new_admin_loan_path %>">Record a loan</a>
|
|
6
|
+
</div>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<section class="hrl-card">
|
|
10
|
+
<p>Outstanding across every active loan: <strong><%= hrl_money(@outstanding) %></strong></p>
|
|
11
|
+
<div class="hrl-row">
|
|
12
|
+
<% HrLite::Loan::STATUSES.each do |status| %>
|
|
13
|
+
<a class="hrl-btn <%= "hrl-btn--primary" if @status == status %>"
|
|
14
|
+
href="<%= hr_lite.admin_loans_path(status: status) %>"><%= status.humanize %></a>
|
|
15
|
+
<% end %>
|
|
16
|
+
</div>
|
|
17
|
+
</section>
|
|
18
|
+
|
|
19
|
+
<section class="hrl-card">
|
|
20
|
+
<% if @loans.empty? %>
|
|
21
|
+
<p class="hrl-muted">Nothing <%= @status %>.</p>
|
|
22
|
+
<% else %>
|
|
23
|
+
<div class="hrl-table-wrap">
|
|
24
|
+
<table class="hrl-table hrl-table--stack">
|
|
25
|
+
<thead><tr><th>Person</th><th>Principal</th><th>Instalment</th><th>Outstanding</th><th></th></tr></thead>
|
|
26
|
+
<tbody>
|
|
27
|
+
<% @loans.each do |loan| %>
|
|
28
|
+
<tr>
|
|
29
|
+
<td data-label="Person"><%= hr_display_name(loan.user) %></td>
|
|
30
|
+
<td data-label="Principal" class="hrl-num"><%= hrl_money(loan.principal) %></td>
|
|
31
|
+
<td data-label="Instalment" class="hrl-num"><%= hrl_money(loan.monthly_instalment) %></td>
|
|
32
|
+
<td data-label="Outstanding" class="hrl-num"><%= hrl_money(loan.outstanding) %></td>
|
|
33
|
+
<td data-label="">
|
|
34
|
+
<a class="hrl-small" href="<%= hr_lite.admin_loan_path(loan) %>">Open</a>
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
<% end %>
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
<%= hrl_pagination %>
|
|
43
|
+
</section>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Record a loan" } %>
|
|
2
|
+
<div class="hrl-page__head"><h1 class="hrl-page__title">Record a loan</h1></div>
|
|
3
|
+
|
|
4
|
+
<section class="hrl-card">
|
|
5
|
+
<p class="hrl-muted hrl-small">
|
|
6
|
+
The instalment comes out of the payslip automatically from the month it
|
|
7
|
+
starts, and stops on its own when the balance runs out — the last
|
|
8
|
+
instalment is whatever is left, not a full one.
|
|
9
|
+
</p>
|
|
10
|
+
<%= form_with model: @loan, url: hr_lite.admin_loans_path, method: :post, local: true do |f| %>
|
|
11
|
+
<%= render "hr_lite/shared/form_errors", record: @loan %>
|
|
12
|
+
<div class="hrl-field">
|
|
13
|
+
<%= f.label :user_id, "Employee" %>
|
|
14
|
+
<%= f.select :user_id, options_for_select(HrLite.employees.map { |u| [ hr_display_name(u), u.id ] },
|
|
15
|
+
@loan.user_id) %>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="hrl-field">
|
|
18
|
+
<%= f.label :principal, "Amount lent (₹)" %>
|
|
19
|
+
<%= f.text_field :principal, value: @loan.principal&.to_s("F"), inputmode: "decimal" %>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="hrl-field">
|
|
22
|
+
<%= f.label :monthly_instalment, "Monthly deduction (₹)" %>
|
|
23
|
+
<%= f.text_field :monthly_instalment, value: @loan.monthly_instalment&.to_s("F"),
|
|
24
|
+
inputmode: "decimal" %>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="hrl-field">
|
|
27
|
+
<%= f.label :starts_on, "First deduction in" %>
|
|
28
|
+
<%= f.date_field :starts_on %>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="hrl-field">
|
|
31
|
+
<%= f.label :reason, "What it is for (optional)" %>
|
|
32
|
+
<%= f.text_field :reason %>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="hrl-form-actions">
|
|
35
|
+
<%= f.submit "Record", class: "hrl-btn hrl-btn--primary" %>
|
|
36
|
+
<a class="hrl-btn" href="<%= hr_lite.admin_loans_path %>">Cancel</a>
|
|
37
|
+
</div>
|
|
38
|
+
<% end %>
|
|
39
|
+
</section>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<% content_for(:page_title) { hr_display_name(@loan.user) } %>
|
|
2
|
+
<div class="hrl-page__head">
|
|
3
|
+
<h1 class="hrl-page__title"><%= hr_display_name(@loan.user) %> — <%= hrl_money(@loan.principal) %></h1>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<section class="hrl-card">
|
|
7
|
+
<div class="hrl-table-wrap">
|
|
8
|
+
<table class="hrl-table hrl-table--stack">
|
|
9
|
+
<tbody>
|
|
10
|
+
<tr><td data-label="Status"><%= @loan.status.humanize %></td></tr>
|
|
11
|
+
<tr><td data-label="Monthly instalment"><%= hrl_money(@loan.monthly_instalment) %></td></tr>
|
|
12
|
+
<tr><td data-label="Starts"><%= @loan.starts_on.strftime("%b %Y") %></td></tr>
|
|
13
|
+
<tr><td data-label="Repaid"><%= hrl_money(@loan.repaid) %></td></tr>
|
|
14
|
+
<tr><td data-label="Outstanding"><strong><%= hrl_money(@loan.outstanding) %></strong></td></tr>
|
|
15
|
+
<% if @loan.reason.present? %><tr><td data-label="Reason"><%= @loan.reason %></td></tr><% end %>
|
|
16
|
+
</tbody>
|
|
17
|
+
</table>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<% if @loan.active? %>
|
|
21
|
+
<div class="hrl-row">
|
|
22
|
+
<%= button_to "Close (stop deductions)", hr_lite.close_admin_loan_path(@loan),
|
|
23
|
+
method: :post, class: "hrl-btn",
|
|
24
|
+
form: { data: { turbo_confirm: "Stop deducting? The outstanding balance stays on record." } } %>
|
|
25
|
+
<%= button_to "Cancel", hr_lite.cancel_admin_loan_path(@loan),
|
|
26
|
+
method: :post, class: "hrl-btn hrl-btn--danger",
|
|
27
|
+
form: { data: { turbo_confirm: "Cancel this loan entirely?" } } %>
|
|
28
|
+
</div>
|
|
29
|
+
<% end %>
|
|
30
|
+
</section>
|
|
31
|
+
|
|
32
|
+
<section class="hrl-card">
|
|
33
|
+
<h2 class="hrl-card__title">Deducted so far</h2>
|
|
34
|
+
<% repayments = @loan.loan_repayments.sort_by(&:period_month).reverse %>
|
|
35
|
+
<% if repayments.empty? %>
|
|
36
|
+
<p class="hrl-muted">Nothing deducted yet.</p>
|
|
37
|
+
<% else %>
|
|
38
|
+
<div class="hrl-table-wrap">
|
|
39
|
+
<table class="hrl-table hrl-table--stack">
|
|
40
|
+
<thead><tr><th>Month</th><th>Amount</th></tr></thead>
|
|
41
|
+
<tbody>
|
|
42
|
+
<% repayments.each do |r| %>
|
|
43
|
+
<tr>
|
|
44
|
+
<td data-label="Month"><%= r.period_month.strftime("%b %Y") %></td>
|
|
45
|
+
<td data-label="Amount" class="hrl-num"><%= hrl_money(r.amount) %></td>
|
|
46
|
+
</tr>
|
|
47
|
+
<% end %>
|
|
48
|
+
</tbody>
|
|
49
|
+
</table>
|
|
50
|
+
</div>
|
|
51
|
+
<% end %>
|
|
52
|
+
</section>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Tax declarations" } %>
|
|
2
|
+
<div class="hrl-page__head"><h1 class="hrl-page__title">Tax declarations</h1></div>
|
|
3
|
+
|
|
4
|
+
<section class="hrl-card">
|
|
5
|
+
<div class="hrl-row">
|
|
6
|
+
<% HrLite::TaxDeclaration::STATUSES.each do |status| %>
|
|
7
|
+
<a class="hrl-btn <%= "hrl-btn--primary" if @status == status %>"
|
|
8
|
+
href="<%= hr_lite.admin_tax_declarations_path(status: status) %>"><%= status.humanize %></a>
|
|
9
|
+
<% end %>
|
|
10
|
+
</div>
|
|
11
|
+
</section>
|
|
12
|
+
|
|
13
|
+
<section class="hrl-card">
|
|
14
|
+
<% if @declarations.empty? %>
|
|
15
|
+
<p class="hrl-muted">Nothing <%= @status %>.</p>
|
|
16
|
+
<% else %>
|
|
17
|
+
<div class="hrl-table-wrap">
|
|
18
|
+
<table class="hrl-table hrl-table--stack">
|
|
19
|
+
<thead><tr><th>Person</th><th>Year</th><th>Regime</th><th>Claimed</th><th></th></tr></thead>
|
|
20
|
+
<tbody>
|
|
21
|
+
<% @declarations.each do |declaration| %>
|
|
22
|
+
<tr>
|
|
23
|
+
<td data-label="Person"><%= hr_display_name(declaration.user) %></td>
|
|
24
|
+
<td data-label="Year"><%= declaration.label %></td>
|
|
25
|
+
<td data-label="Regime"><%= declaration.regime.humanize %></td>
|
|
26
|
+
<td data-label="Claimed" class="hrl-num"><%= hrl_money(declaration.declared_total) %></td>
|
|
27
|
+
<td data-label="">
|
|
28
|
+
<a class="hrl-small" href="<%= hr_lite.admin_tax_declaration_path(declaration) %>">Check</a>
|
|
29
|
+
</td>
|
|
30
|
+
</tr>
|
|
31
|
+
<% end %>
|
|
32
|
+
</tbody>
|
|
33
|
+
</table>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
36
|
+
<%= hrl_pagination %>
|
|
37
|
+
</section>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<% content_for(:page_title) { hr_display_name(@declaration.user) } %>
|
|
2
|
+
<div class="hrl-page__head">
|
|
3
|
+
<h1 class="hrl-page__title">
|
|
4
|
+
<%= hr_display_name(@declaration.user) %> — <%= @declaration.label %>
|
|
5
|
+
</h1>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<section class="hrl-card">
|
|
9
|
+
<p>
|
|
10
|
+
Regime: <strong><%= @declaration.regime.humanize %></strong> ·
|
|
11
|
+
Status: <%= hrl_request_status_badge(@declaration.status == "verified" ? "approved" : @declaration.status) %>
|
|
12
|
+
</p>
|
|
13
|
+
<p class="hrl-muted hrl-small">
|
|
14
|
+
Until this is verified, payroll deducts against what was CLAIMED — making
|
|
15
|
+
somebody overpay tax all year because paperwork is slow is its own kind of
|
|
16
|
+
wrong. Once verified, only the amounts you enter below count.
|
|
17
|
+
</p>
|
|
18
|
+
</section>
|
|
19
|
+
|
|
20
|
+
<%= form_with model: @declaration, url: hr_lite.admin_tax_declaration_path(@declaration),
|
|
21
|
+
method: :patch, scope: :tax_declaration, local: true do |f| %>
|
|
22
|
+
<section class="hrl-card">
|
|
23
|
+
<h2 class="hrl-card__title">What the proof supports</h2>
|
|
24
|
+
<%= f.fields_for :tax_declaration_items do |item| %>
|
|
25
|
+
<div class="hrl-field">
|
|
26
|
+
<%= item.label :verified_amount, item.object.section_label %>
|
|
27
|
+
<span class="hrl-small hrl-muted">
|
|
28
|
+
Claimed <%= hrl_money(item.object.declared_amount) %>
|
|
29
|
+
<%= "— #{item.object.label}" if item.object.label.present? %>
|
|
30
|
+
</span>
|
|
31
|
+
<%= item.text_field :verified_amount,
|
|
32
|
+
value: item.object.verified_amount&.to_s("F"), inputmode: "decimal",
|
|
33
|
+
placeholder: "Verified amount" %>
|
|
34
|
+
</div>
|
|
35
|
+
<% end %>
|
|
36
|
+
<div class="hrl-form-actions">
|
|
37
|
+
<%= f.submit "Record amounts", class: "hrl-btn" %>
|
|
38
|
+
</div>
|
|
39
|
+
</section>
|
|
40
|
+
<% end %>
|
|
41
|
+
|
|
42
|
+
<section class="hrl-card">
|
|
43
|
+
<h2 class="hrl-card__title">Decide</h2>
|
|
44
|
+
<%= button_to "Verify", hr_lite.verify_admin_tax_declaration_path(@declaration),
|
|
45
|
+
method: :post, class: "hrl-btn hrl-btn--primary" %>
|
|
46
|
+
<%= form_with url: hr_lite.reject_admin_tax_declaration_path(@declaration),
|
|
47
|
+
method: :post, local: true do %>
|
|
48
|
+
<div class="hrl-field">
|
|
49
|
+
<%= label_tag :note, "What is missing (required to send back)" %>
|
|
50
|
+
<%= text_field_tag :note %>
|
|
51
|
+
</div>
|
|
52
|
+
<%= submit_tag "Send back", class: "hrl-btn" %>
|
|
53
|
+
<% end %>
|
|
54
|
+
</section>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<% content_for(:page_title) { "My documents" } %>
|
|
2
|
+
<div class="hrl-page__head"><h1 class="hrl-page__title">My documents</h1></div>
|
|
3
|
+
|
|
4
|
+
<% if @expiring.any? %>
|
|
5
|
+
<section class="hrl-card hrl-card--warn">
|
|
6
|
+
<h2 class="hrl-card__title">Expiring soon</h2>
|
|
7
|
+
<ul>
|
|
8
|
+
<% @expiring.each do |doc| %>
|
|
9
|
+
<li><%= doc.title %> — <%= doc.expired? ? "expired" : "expires" %>
|
|
10
|
+
<%= doc.expires_on.strftime("%d %b %Y") %></li>
|
|
11
|
+
<% end %>
|
|
12
|
+
</ul>
|
|
13
|
+
</section>
|
|
14
|
+
<% end %>
|
|
15
|
+
|
|
16
|
+
<section class="hrl-card">
|
|
17
|
+
<h2 class="hrl-card__title">Upload a document</h2>
|
|
18
|
+
<%= form_with model: @document, url: hr_lite.documents_path, method: :post, local: true do |f| %>
|
|
19
|
+
<%= render "hr_lite/shared/form_errors", record: @document %>
|
|
20
|
+
<div class="hrl-field">
|
|
21
|
+
<%= f.label :category %>
|
|
22
|
+
<%= f.select :category,
|
|
23
|
+
options_for_select(HrLite::Document::SENSITIVE_CATEGORIES.map { |c| [ c.humanize, c ] } +
|
|
24
|
+
[ [ "Offer letter", "offer_letter" ], [ "Certificate", "certificate" ],
|
|
25
|
+
[ "Other", "other" ] ], @document.category) %>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="hrl-field"><%= f.label :title %><%= f.text_field :title %></div>
|
|
28
|
+
<div class="hrl-field">
|
|
29
|
+
<%= f.label :reference_number, "Document number (optional)" %>
|
|
30
|
+
<%= f.text_field :reference_number %>
|
|
31
|
+
</div>
|
|
32
|
+
<div class="hrl-field">
|
|
33
|
+
<%= f.label :expires_on, "Expires on (optional — we will remind you)" %>
|
|
34
|
+
<%= f.date_field :expires_on %>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="hrl-field">
|
|
37
|
+
<%= f.label :file, "File (PDF or image, up to 10 MB)" %>
|
|
38
|
+
<%= f.file_field :file %>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="hrl-form-actions"><%= f.submit "Upload", class: "hrl-btn hrl-btn--primary" %></div>
|
|
41
|
+
<% end %>
|
|
42
|
+
</section>
|
|
43
|
+
|
|
44
|
+
<section class="hrl-card">
|
|
45
|
+
<h2 class="hrl-card__title">On file</h2>
|
|
46
|
+
<% if @documents.empty? %>
|
|
47
|
+
<p class="hrl-muted">Nothing uploaded yet.</p>
|
|
48
|
+
<% else %>
|
|
49
|
+
<div class="hrl-table-wrap">
|
|
50
|
+
<table class="hrl-table hrl-table--stack">
|
|
51
|
+
<thead><tr><th>Document</th><th>Expires</th><th>Checked</th><th></th></tr></thead>
|
|
52
|
+
<tbody>
|
|
53
|
+
<% @documents.each do |doc| %>
|
|
54
|
+
<tr>
|
|
55
|
+
<td data-label="Document">
|
|
56
|
+
<strong><%= doc.title %></strong>
|
|
57
|
+
<div class="hrl-small hrl-muted"><%= doc.category.humanize %></div>
|
|
58
|
+
</td>
|
|
59
|
+
<td data-label="Expires">
|
|
60
|
+
<%= doc.expires_on ? doc.expires_on.strftime("%d %b %Y") : "—" %>
|
|
61
|
+
</td>
|
|
62
|
+
<td data-label="Checked">
|
|
63
|
+
<%= hrl_request_status_badge(doc.verification == "verified" ? "approved" : doc.verification) %>
|
|
64
|
+
<% if doc.verification_note.present? %>
|
|
65
|
+
<div class="hrl-small hrl-muted"><%= doc.verification_note %></div>
|
|
66
|
+
<% end %>
|
|
67
|
+
</td>
|
|
68
|
+
<td data-label="">
|
|
69
|
+
<% if doc.file.attached? %>
|
|
70
|
+
<a class="hrl-small" href="<%= hr_lite.download_document_path(doc) %>">Open</a>
|
|
71
|
+
<% end %>
|
|
72
|
+
<% unless doc.verified? %>
|
|
73
|
+
<%= button_to "Remove", hr_lite.document_path(doc), method: :delete,
|
|
74
|
+
class: "hrl-btn",
|
|
75
|
+
form: { data: { turbo_confirm: "Remove #{doc.title}?" } } %>
|
|
76
|
+
<% end %>
|
|
77
|
+
</td>
|
|
78
|
+
</tr>
|
|
79
|
+
<% end %>
|
|
80
|
+
</tbody>
|
|
81
|
+
</table>
|
|
82
|
+
</div>
|
|
83
|
+
<% end %>
|
|
84
|
+
</section>
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<% content_for(:page_title) { "My loans" } %>
|
|
2
|
+
<div class="hrl-page__head"><h1 class="hrl-page__title">Loans & advances</h1></div>
|
|
3
|
+
|
|
4
|
+
<% if @loans.empty? %>
|
|
5
|
+
<section class="hrl-card"><p class="hrl-muted">You have no loans or salary advances.</p></section>
|
|
6
|
+
<% else %>
|
|
7
|
+
<section class="hrl-card">
|
|
8
|
+
<p>Outstanding across everything: <strong><%= hrl_money(@outstanding) %></strong></p>
|
|
9
|
+
</section>
|
|
10
|
+
|
|
11
|
+
<% @loans.each do |loan| %>
|
|
12
|
+
<section class="hrl-card">
|
|
13
|
+
<h2 class="hrl-card__title">
|
|
14
|
+
<%= hrl_money(loan.principal) %>
|
|
15
|
+
<span class="hrl-badge <%= "hrl-badge--muted" unless loan.active? %>"><%= loan.status.humanize %></span>
|
|
16
|
+
</h2>
|
|
17
|
+
<% if loan.reason.present? %><p class="hrl-muted"><%= loan.reason %></p><% end %>
|
|
18
|
+
<div class="hrl-table-wrap">
|
|
19
|
+
<table class="hrl-table hrl-table--stack">
|
|
20
|
+
<tbody>
|
|
21
|
+
<tr><td data-label="Monthly instalment"><%= hrl_money(loan.monthly_instalment) %></td></tr>
|
|
22
|
+
<tr><td data-label="Repaid so far"><%= hrl_money(loan.repaid) %></td></tr>
|
|
23
|
+
<tr><td data-label="Outstanding"><strong><%= hrl_money(loan.outstanding) %></strong></td></tr>
|
|
24
|
+
<tr>
|
|
25
|
+
<td data-label="Next deduction">
|
|
26
|
+
<% amount = loan.instalment_for(@next_month) %>
|
|
27
|
+
<%= amount.positive? ? "#{hrl_money(amount)} in #{@next_month.strftime('%b %Y')}" : "—" %>
|
|
28
|
+
</td>
|
|
29
|
+
</tr>
|
|
30
|
+
</tbody>
|
|
31
|
+
</table>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<% if loan.loan_repayments.any? %>
|
|
35
|
+
<h3 class="hrl-card__title">Already deducted</h3>
|
|
36
|
+
<div class="hrl-table-wrap">
|
|
37
|
+
<table class="hrl-table hrl-table--stack">
|
|
38
|
+
<thead><tr><th>Month</th><th>Amount</th></tr></thead>
|
|
39
|
+
<tbody>
|
|
40
|
+
<% loan.loan_repayments.sort_by(&:period_month).reverse.each do |r| %>
|
|
41
|
+
<tr>
|
|
42
|
+
<td data-label="Month"><%= r.period_month.strftime("%b %Y") %></td>
|
|
43
|
+
<td data-label="Amount" class="hrl-num"><%= hrl_money(r.amount) %></td>
|
|
44
|
+
</tr>
|
|
45
|
+
<% end %>
|
|
46
|
+
</tbody>
|
|
47
|
+
</table>
|
|
48
|
+
</div>
|
|
49
|
+
<% end %>
|
|
50
|
+
</section>
|
|
51
|
+
<% end %>
|
|
52
|
+
<% end %>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Tax declaration" } %>
|
|
2
|
+
<div class="hrl-page__head">
|
|
3
|
+
<h1 class="hrl-page__title">Tax declaration — <%= @declaration.label %></h1>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<section class="hrl-card">
|
|
7
|
+
<p class="hrl-muted hrl-small">
|
|
8
|
+
Your whole year's TDS is projected from these numbers, so a declaration
|
|
9
|
+
filed in April spreads the tax evenly instead of landing it in March.
|
|
10
|
+
Claim what you intend to invest; HR records what the proof supports when
|
|
11
|
+
you hand it over.
|
|
12
|
+
</p>
|
|
13
|
+
<p>
|
|
14
|
+
Status: <%= hrl_request_status_badge(@declaration.status == "verified" ? "approved" : @declaration.status) %>
|
|
15
|
+
<% if @declaration.note.present? %>
|
|
16
|
+
<span class="hrl-small hrl-muted">— <%= @declaration.note %></span>
|
|
17
|
+
<% end %>
|
|
18
|
+
</p>
|
|
19
|
+
</section>
|
|
20
|
+
|
|
21
|
+
<%= form_with model: @declaration, url: hr_lite.tax_declaration_path, method: :patch,
|
|
22
|
+
scope: :tax_declaration, local: true do |f| %>
|
|
23
|
+
<%= render "hr_lite/shared/form_errors", record: @declaration %>
|
|
24
|
+
|
|
25
|
+
<section class="hrl-card">
|
|
26
|
+
<div class="hrl-field">
|
|
27
|
+
<%= f.label :regime, "Tax regime" %>
|
|
28
|
+
<%= f.select :regime, [ [ "New regime (lower rates, no deductions)", "new" ],
|
|
29
|
+
[ "Old regime (deductions apply)", "old" ] ] %>
|
|
30
|
+
<span class="hrl-small hrl-muted">
|
|
31
|
+
The deductions below only reduce tax under the old regime.
|
|
32
|
+
</span>
|
|
33
|
+
</div>
|
|
34
|
+
</section>
|
|
35
|
+
|
|
36
|
+
<section class="hrl-card">
|
|
37
|
+
<h2 class="hrl-card__title">What you are claiming</h2>
|
|
38
|
+
<%= f.fields_for :tax_declaration_items do |item| %>
|
|
39
|
+
<div class="hrl-field">
|
|
40
|
+
<%= item.hidden_field :section %>
|
|
41
|
+
<%= item.label :declared_amount, item.object.section_label %>
|
|
42
|
+
<%= item.text_field :declared_amount,
|
|
43
|
+
value: item.object.declared_amount&.to_s("F"), inputmode: "decimal" %>
|
|
44
|
+
<%= item.text_field :label, placeholder: "What it is (optional)" %>
|
|
45
|
+
<% if item.object.persisted? && item.object.verified_amount %>
|
|
46
|
+
<span class="hrl-small hrl-muted">
|
|
47
|
+
HR verified <%= hrl_money(item.object.verified_amount) %>
|
|
48
|
+
</span>
|
|
49
|
+
<% end %>
|
|
50
|
+
</div>
|
|
51
|
+
<% end %>
|
|
52
|
+
<p>Total claimed: <strong><%= hrl_money(@declaration.declared_total) %></strong></p>
|
|
53
|
+
<div class="hrl-form-actions">
|
|
54
|
+
<%= f.submit "Save draft", class: "hrl-btn hrl-btn--primary" %>
|
|
55
|
+
</div>
|
|
56
|
+
</section>
|
|
57
|
+
<% end %>
|
|
58
|
+
|
|
59
|
+
<% if @declaration.persisted? && (@declaration.draft? || @declaration.rejected?) %>
|
|
60
|
+
<section class="hrl-card">
|
|
61
|
+
<p class="hrl-muted hrl-small">
|
|
62
|
+
Submitting locks it for HR to check. They can send it back if the proof
|
|
63
|
+
does not match.
|
|
64
|
+
</p>
|
|
65
|
+
<%= button_to "Submit to HR", hr_lite.submit_tax_declaration_path,
|
|
66
|
+
method: :post, class: "hrl-btn hrl-btn--primary" %>
|
|
67
|
+
</section>
|
|
68
|
+
<% end %>
|
data/config/routes.rb
CHANGED
|
@@ -30,6 +30,13 @@ HrLite::Engine.routes.draw do
|
|
|
30
30
|
member { post :acknowledge }
|
|
31
31
|
end
|
|
32
32
|
resources :benefits, only: :index
|
|
33
|
+
resources :documents, only: %i[index create destroy] do
|
|
34
|
+
member { get :download }
|
|
35
|
+
end
|
|
36
|
+
resource :tax_declaration, only: %i[show update], controller: "tax_declarations" do
|
|
37
|
+
post :submit
|
|
38
|
+
end
|
|
39
|
+
resources :loans, only: :index
|
|
33
40
|
get "team", to: "team#show"
|
|
34
41
|
get "org", to: "org#show"
|
|
35
42
|
resources :holidays, only: :index
|
|
@@ -61,7 +68,29 @@ HrLite::Engine.routes.draw do
|
|
|
61
68
|
resources :assignments, only: %i[create destroy], controller: "role_assignments"
|
|
62
69
|
end
|
|
63
70
|
resources :statutory_rate_cards, only: %i[index new create edit update]
|
|
71
|
+
resources :documents, only: :index do
|
|
72
|
+
member { post :verify; post :reject }
|
|
73
|
+
end
|
|
74
|
+
get "documents/for/:user_id", to: "documents#show", as: :employee_documents
|
|
75
|
+
resources :tax_declarations, only: %i[index show update] do
|
|
76
|
+
member { post :verify; post :reject }
|
|
77
|
+
end
|
|
78
|
+
resources :loans, only: %i[index new create show] do
|
|
79
|
+
member { post :close; post :cancel }
|
|
80
|
+
end
|
|
64
81
|
resources :reports, only: %i[index show]
|
|
82
|
+
resources :expenses, only: :index do
|
|
83
|
+
member { post :approve; post :reject; post :reimburse }
|
|
84
|
+
end
|
|
85
|
+
resources :hr_requests, only: %i[index show] do
|
|
86
|
+
member { post :assign; post :resolve }
|
|
87
|
+
end
|
|
88
|
+
resources :assets, only: %i[index new create] do
|
|
89
|
+
member { post :assign; post :take_back }
|
|
90
|
+
end
|
|
91
|
+
resources :checklists, only: :index do
|
|
92
|
+
member { post :complete; post :reopen }
|
|
93
|
+
end
|
|
65
94
|
resources :leave_types, except: :show
|
|
66
95
|
resources :office_locations, except: :show
|
|
67
96
|
resources :holidays, only: %i[index create update destroy] do
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
class CreateHrLiteAssetsAndOnboarding < ActiveRecord::Migration[8.1]
|
|
2
|
+
# The two ends of employment that were still manual: the laptop somebody is
|
|
3
|
+
# given on day one, and the checklist nobody remembers on their last.
|
|
4
|
+
#
|
|
5
|
+
# An asset that is never returned is a cost nobody notices, and access that
|
|
6
|
+
# is never revoked is a security problem that outlives the employment.
|
|
7
|
+
def change
|
|
8
|
+
create_table :hr_lite_assets do |t|
|
|
9
|
+
t.string :name, null: false
|
|
10
|
+
# laptop | phone | sim | id_card | accessory | vehicle | other
|
|
11
|
+
t.string :category, null: false, default: "other"
|
|
12
|
+
t.string :serial_number
|
|
13
|
+
t.date :purchased_on
|
|
14
|
+
# available | assigned | returned | lost | damaged | retired
|
|
15
|
+
t.string :status, null: false, default: "available"
|
|
16
|
+
t.text :notes
|
|
17
|
+
t.timestamps
|
|
18
|
+
end
|
|
19
|
+
add_index :hr_lite_assets, :status
|
|
20
|
+
add_index :hr_lite_assets, :serial_number, unique: true,
|
|
21
|
+
where: "serial_number IS NOT NULL"
|
|
22
|
+
add_check_constraint :hr_lite_assets,
|
|
23
|
+
"status IN ('available', 'assigned', 'returned', 'lost', 'damaged', 'retired')",
|
|
24
|
+
name: "hr_lite_assets_status_check"
|
|
25
|
+
|
|
26
|
+
# Append-only: who had it, when, and what state it came back in. Editing
|
|
27
|
+
# an assignment away is how a missing laptop stops being anybody's.
|
|
28
|
+
create_table :hr_lite_asset_assignments do |t|
|
|
29
|
+
t.references :asset, null: false, index: true,
|
|
30
|
+
foreign_key: { to_table: :hr_lite_assets, on_delete: :cascade }
|
|
31
|
+
t.bigint :user_id, null: false
|
|
32
|
+
t.date :assigned_on, null: false
|
|
33
|
+
t.date :returned_on
|
|
34
|
+
t.string :condition_note
|
|
35
|
+
t.bigint :assigned_by_id
|
|
36
|
+
t.timestamps
|
|
37
|
+
end
|
|
38
|
+
add_index :hr_lite_asset_assignments, %i[user_id returned_on]
|
|
39
|
+
# One live holder at a time — two open assignments means the asset is in
|
|
40
|
+
# two places, which it is not.
|
|
41
|
+
add_index :hr_lite_asset_assignments, :asset_id,
|
|
42
|
+
unique: true, where: "returned_on IS NULL",
|
|
43
|
+
name: "index_hr_lite_asset_assignments_one_live_per_asset"
|
|
44
|
+
|
|
45
|
+
create_table :hr_lite_checklist_templates do |t|
|
|
46
|
+
# onboarding | offboarding
|
|
47
|
+
t.string :kind, null: false
|
|
48
|
+
t.string :title, null: false
|
|
49
|
+
t.integer :position, null: false, default: 0
|
|
50
|
+
# Which permission the person who ticks it should hold — IT hands back
|
|
51
|
+
# a laptop, HR collects a document, and neither should be the other.
|
|
52
|
+
t.string :owner_permission
|
|
53
|
+
# Days from the joining or leaving date this is due.
|
|
54
|
+
t.integer :due_offset_days, null: false, default: 0
|
|
55
|
+
t.boolean :active, null: false, default: true
|
|
56
|
+
t.timestamps
|
|
57
|
+
end
|
|
58
|
+
add_index :hr_lite_checklist_templates, %i[kind position]
|
|
59
|
+
add_check_constraint :hr_lite_checklist_templates, "kind IN ('onboarding', 'offboarding')",
|
|
60
|
+
name: "hr_lite_checklist_templates_kind_check"
|
|
61
|
+
|
|
62
|
+
create_table :hr_lite_checklist_items do |t|
|
|
63
|
+
t.bigint :user_id, null: false
|
|
64
|
+
t.references :template, index: true,
|
|
65
|
+
foreign_key: { to_table: :hr_lite_checklist_templates, on_delete: :nullify }
|
|
66
|
+
t.string :kind, null: false
|
|
67
|
+
t.string :title, null: false
|
|
68
|
+
t.date :due_on
|
|
69
|
+
t.datetime :completed_at
|
|
70
|
+
t.bigint :completed_by_id
|
|
71
|
+
t.string :note
|
|
72
|
+
t.timestamps
|
|
73
|
+
end
|
|
74
|
+
add_index :hr_lite_checklist_items, %i[user_id kind completed_at]
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/hr_lite/permissions.rb
CHANGED
|
@@ -56,6 +56,9 @@ module HrLite
|
|
|
56
56
|
"hr_request.manage" => [ "Help desk", "Answer and assign HR requests" ],
|
|
57
57
|
"policy.view" => [ "Policies", "Read published policies" ],
|
|
58
58
|
"policy.manage" => [ "Policies", "Write, publish and re-issue policies" ],
|
|
59
|
+
"asset.view" => [ "Assets", "See company assets and who holds them" ],
|
|
60
|
+
"asset.manage" => [ "Assets", "Assign and take back company assets" ],
|
|
61
|
+
"checklist.manage" => [ "Lifecycle", "Work through joining and leaving checklists" ],
|
|
59
62
|
"settings.manage" => [ "Administration", "Change company settings, offices and policy" ],
|
|
60
63
|
"audit.view" => [ "Administration", "Read the audit trail" ],
|
|
61
64
|
"audit.view_money" => [ "Administration", "Read audit rows about pay, appraisals and promotions" ],
|
data/lib/hr_lite/role_seeds.rb
CHANGED
|
@@ -19,7 +19,7 @@ module HrLite
|
|
|
19
19
|
"profile.view" => "self", "appraisal.view" => "self",
|
|
20
20
|
"resignation.view" => "self", "document.view" => "self", "tax.view" => "self",
|
|
21
21
|
"expense.claim" => "self", "benefit.view" => "self",
|
|
22
|
-
"hr_request.raise" => "self", "policy.view" => "all"
|
|
22
|
+
"hr_request.raise" => "self", "policy.view" => "all", "asset.view" => "self"
|
|
23
23
|
}
|
|
24
24
|
},
|
|
25
25
|
Role::MANAGER => {
|
|
@@ -40,7 +40,8 @@ module HrLite
|
|
|
40
40
|
"appraisal.view" => "self", "resignation.view" => "all",
|
|
41
41
|
"document.view" => "all", "tax.view" => "self",
|
|
42
42
|
"expense.claim" => "self", "benefit.view" => "all", "benefit.manage" => "all",
|
|
43
|
-
"hr_request.raise" => "self", "hr_request.manage" => "all", "policy.view" => "all"
|
|
43
|
+
"hr_request.raise" => "self", "hr_request.manage" => "all", "policy.view" => "all",
|
|
44
|
+
"asset.view" => "all", "asset.manage" => "all", "checklist.manage" => "all"
|
|
44
45
|
}
|
|
45
46
|
},
|
|
46
47
|
Role::FINANCE => {
|
|
@@ -65,7 +66,8 @@ module HrLite
|
|
|
65
66
|
"resignation.view" => "all", "resignation.manage" => "all",
|
|
66
67
|
"settings.manage" => "all", "audit.view" => "all", "payroll.view" => "self",
|
|
67
68
|
"document.view" => "all", "expense.approve" => "all", "benefit.manage" => "all",
|
|
68
|
-
"hr_request.manage" => "all", "policy.view" => "all", "policy.manage" => "all"
|
|
69
|
+
"hr_request.manage" => "all", "policy.view" => "all", "policy.manage" => "all",
|
|
70
|
+
"asset.view" => "all", "asset.manage" => "all", "checklist.manage" => "all"
|
|
69
71
|
}
|
|
70
72
|
},
|
|
71
73
|
Role::SUPER_ADMIN => {
|
data/lib/hr_lite/version.rb
CHANGED