hr_lite 0.14.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 +54 -1
- data/app/controllers/hr_lite/admin/documents_controller.rb +63 -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 +9 -3
- data/app/models/hr_lite/approval_flow.rb +1 -1
- data/app/models/hr_lite/tax_declaration.rb +4 -1
- 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/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 +17 -0
- data/lib/hr_lite/version.rb +1 -1
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ec0aa9dccb203487f12a0e30cbbae7567da32b9589af4046fe86795c9a3cb5a4
|
|
4
|
+
data.tar.gz: 0e5b02a62ea2925115f71279dd9772f12a103b6cc17fb6d192f0225a3c95504e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e1877020700a05db3f7a5a4a715be0c1e8e7f4ad447d3c42eaf6d17b60e0252ef68968eb58f431e7373e43d3452bae468d6e211eac64c3f4e9ca71703920d412
|
|
7
|
+
data.tar.gz: 28d5a185fd3af1373efb3b2db1e3d53d8b64e66ec676a7cfed01c15ffbb8ed2a07b0a921d4351ac6afca41db18e683ae9ad984e99b5ffbf6eeac32209e108af0
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,58 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.15.0] - 2026-08-18
|
|
11
|
+
|
|
12
|
+
The screens for three features that shipped as tables nobody could reach. No
|
|
13
|
+
migration — the data layer was already there.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- **Documents, tax declarations and loans had no UI at all.** 0.13.0 and
|
|
18
|
+
0.14.0 shipped their models, tables, payroll integration and an expiry job,
|
|
19
|
+
and every one of them passed its specs — but there was no controller, no
|
|
20
|
+
route and no view. An employee could not upload a passport, HR could not
|
|
21
|
+
verify a PAN, and nobody could file an 80C declaration. Loans at least
|
|
22
|
+
reached a payslip; the other two were unreachable rows.
|
|
23
|
+
- **A tax declaration 500ed when a real browser saved it.** The form posts a
|
|
24
|
+
line for every section, because that is what a list of claimable things
|
|
25
|
+
looks like, and the blank ones violated a NOT NULL amount. The specs had
|
|
26
|
+
only ever posted the lines they filled in. Blank sections are now dropped,
|
|
27
|
+
and clearing an existing amount withdraws that claim.
|
|
28
|
+
- `TaxDeclaration` and `ApprovalFlow` were missing `inverse_of` on their
|
|
29
|
+
nested associations. The foreign key is not derived from the class name, so
|
|
30
|
+
a nested item on an unsaved parent could not see it — every first save
|
|
31
|
+
failed with "declaration must exist".
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **Documents.** Employees upload, see what is expiring and remove anything
|
|
36
|
+
not yet verified; HR sees what is waiting, whose file is missing which
|
|
37
|
+
category, and verifies or sends one back with a reason. Downloads go
|
|
38
|
+
through the row's own `readable_by?` and every one is audited — a file
|
|
39
|
+
leaving the building is the event worth recording.
|
|
40
|
+
- **Tax declarations.** A section-by-section claim the employee files
|
|
41
|
+
themselves, replacing an admin typing one lump sum into the profile. HR
|
|
42
|
+
records what the proof actually supported, which is often less than the
|
|
43
|
+
claim. Until it is verified payroll deducts against the CLAIM: making
|
|
44
|
+
somebody overpay tax all year because paperwork is slow is its own kind of
|
|
45
|
+
wrong.
|
|
46
|
+
- **Loans.** Employees see what they owe and what comes out of the next
|
|
47
|
+
payslip; the money tier records a loan, closes it, or cancels one that has
|
|
48
|
+
not been deducted from yet. Cancelling after an instalment has been taken
|
|
49
|
+
is refused — that money has already left somebody's salary.
|
|
50
|
+
- Nav entries for all three, employee and admin.
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
- Verifying a document requires being able to OPEN it. Attesting to a scan
|
|
55
|
+
you are not allowed to look at is not verification, and it keeps an
|
|
56
|
+
Aadhaar with the money tier rather than with whoever happens to run HR.
|
|
57
|
+
- The spec suite deletes its sqlite file before every run. The role and
|
|
58
|
+
rate-card migrations SEED rows, and seeds never overwrite what exists —
|
|
59
|
+
right in production, wrong in a test database, where a leftover file kept
|
|
60
|
+
yesterday's definitions and a change to `RoleSeeds` silently did not apply.
|
|
61
|
+
|
|
10
62
|
## [0.14.0] - 2026-08-18
|
|
11
63
|
|
|
12
64
|
The admin half, plus the two ends of employment that were still manual.
|
|
@@ -793,7 +845,8 @@ Initial release.
|
|
|
793
845
|
bus with per-event channel matrix (bell, email, leadership email/bell),
|
|
794
846
|
daily leadership digest and an append-only audit trail.
|
|
795
847
|
|
|
796
|
-
[Unreleased]: https://github.com/kshtzkr/hr_lite/compare/v0.
|
|
848
|
+
[Unreleased]: https://github.com/kshtzkr/hr_lite/compare/v0.15.0...HEAD
|
|
849
|
+
[0.15.0]: https://github.com/kshtzkr/hr_lite/compare/v0.14.0...v0.15.0
|
|
797
850
|
[0.14.0]: https://github.com/kshtzkr/hr_lite/compare/v0.13.0...v0.14.0
|
|
798
851
|
[0.13.0]: https://github.com/kshtzkr/hr_lite/compare/v0.12.0...v0.13.0
|
|
799
852
|
[0.12.0]: https://github.com/kshtzkr/hr_lite/compare/v0.11.0...v0.12.0
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module HrLite
|
|
2
|
+
module Admin
|
|
3
|
+
# HR's side: whose documents are missing, and verifying the ones that
|
|
4
|
+
# arrived. Reaching a person's list is `document.view`; opening the file
|
|
5
|
+
# still goes through the row's own visibility.
|
|
6
|
+
class DocumentsController < BaseController
|
|
7
|
+
skip_before_action :require_operations_access!
|
|
8
|
+
before_action :require_document_access!
|
|
9
|
+
|
|
10
|
+
def index
|
|
11
|
+
@category = params[:category].presence
|
|
12
|
+
scope = hr_scope(Document.includes(:user).recent_first, "document.view")
|
|
13
|
+
scope = scope.where(category: @category) if @category
|
|
14
|
+
@pending = scope.where(verification: "pending").limit(50).to_a
|
|
15
|
+
@documents = paginate(scope)
|
|
16
|
+
@categories = Document.distinct.order(:category).pluck(:category)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def show
|
|
20
|
+
@employee = HrLite.user_klass.find(params[:user_id])
|
|
21
|
+
hr_require_reach!("document.view", @employee)
|
|
22
|
+
@documents = Document.for_user(@employee).recent_first
|
|
23
|
+
@missing = Document::SENSITIVE_CATEGORIES - @documents.map(&:category)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def verify
|
|
27
|
+
document = find_manageable
|
|
28
|
+
document.verify!(actor: hr_current_user, note: params[:note])
|
|
29
|
+
redirect_back fallback_location: admin_documents_path,
|
|
30
|
+
notice: "#{document.title} verified."
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def reject
|
|
34
|
+
document = find_manageable
|
|
35
|
+
note = params[:note].to_s.strip
|
|
36
|
+
if note.blank?
|
|
37
|
+
return redirect_back fallback_location: admin_documents_path,
|
|
38
|
+
alert: "Say what is wrong with it — the employee has to re-upload."
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
document.reject!(actor: hr_current_user, note: note)
|
|
42
|
+
redirect_back fallback_location: admin_documents_path, notice: "#{document.title} rejected."
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def require_document_access!
|
|
48
|
+
hr_require_permission!("document.view", scope: :team)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# You may verify a document you can OPEN. Attesting to a scan you are
|
|
52
|
+
# not allowed to look at is not verification, and the row's visibility
|
|
53
|
+
# is what keeps an Aadhaar with the money tier rather than with
|
|
54
|
+
# whoever happens to run HR.
|
|
55
|
+
def find_manageable
|
|
56
|
+
document = Document.find(params[:id])
|
|
57
|
+
raise ActiveRecord::RecordNotFound unless document.readable_by?(hr_current_user)
|
|
58
|
+
|
|
59
|
+
document
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module HrLite
|
|
2
|
+
module Admin
|
|
3
|
+
# Loans and salary advances. Money tier: the instalment lands as a
|
|
4
|
+
# deduction on a payslip, so whoever sets it is setting somebody's pay.
|
|
5
|
+
class LoansController < SuperadminController
|
|
6
|
+
def index
|
|
7
|
+
@status = params[:status].presence_in(Loan::STATUSES) || "active"
|
|
8
|
+
@loans = paginate(Loan.includes(:user).where(status: @status).order(starts_on: :desc))
|
|
9
|
+
@outstanding = Loan.active.includes(:loan_repayments).sum(BigDecimal(0), &:outstanding)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def new
|
|
13
|
+
@loan = Loan.new(starts_on: Date.current.next_month.beginning_of_month)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create
|
|
17
|
+
@loan = Loan.new(loan_params.merge(approved_by_id: hr_current_user.id))
|
|
18
|
+
if @loan.save
|
|
19
|
+
redirect_to admin_loans_path,
|
|
20
|
+
notice: "Loan recorded for #{HrLite.display_name(@loan.user)} — " \
|
|
21
|
+
"deductions start #{@loan.starts_on.strftime('%b %Y')}."
|
|
22
|
+
else
|
|
23
|
+
render :new, status: :unprocessable_entity
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def show
|
|
28
|
+
@loan = Loan.includes(:loan_repayments).find(params[:id])
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Closing stops the deduction. It does NOT delete the repayments —
|
|
32
|
+
# those are on published payslips and are what the outstanding balance
|
|
33
|
+
# is derived from.
|
|
34
|
+
def close
|
|
35
|
+
loan = Loan.find(params[:id])
|
|
36
|
+
loan.update!(status: "closed")
|
|
37
|
+
redirect_to admin_loans_path, notice: "Closed — no further deductions."
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def cancel
|
|
41
|
+
loan = Loan.find(params[:id])
|
|
42
|
+
if loan.loan_repayments.exists?
|
|
43
|
+
return redirect_to admin_loan_path(loan),
|
|
44
|
+
alert: "Instalments have already been deducted. Close it instead — " \
|
|
45
|
+
"cancelling would deny money that has left somebody's salary."
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
loan.update!(status: "cancelled")
|
|
49
|
+
redirect_to admin_loans_path, notice: "Cancelled before any deduction."
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def loan_params
|
|
55
|
+
params.require(:loan).permit(:user_id, :principal, :monthly_instalment, :starts_on, :reason)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
module HrLite
|
|
2
|
+
module Admin
|
|
3
|
+
# HR checking the proof behind a declaration, line by line. The verified
|
|
4
|
+
# amount is what payroll deducts against once this is done, so it is
|
|
5
|
+
# entered per section rather than accepted wholesale.
|
|
6
|
+
class TaxDeclarationsController < BaseController
|
|
7
|
+
skip_before_action :require_operations_access!
|
|
8
|
+
before_action :require_tax_access!
|
|
9
|
+
|
|
10
|
+
def index
|
|
11
|
+
@status = params[:status].presence_in(TaxDeclaration::STATUSES) || "submitted"
|
|
12
|
+
@declarations = paginate(
|
|
13
|
+
hr_scope(TaxDeclaration.includes(:user, :tax_declaration_items), "tax.view")
|
|
14
|
+
.where(status: @status).order(created_at: :desc)
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def show
|
|
19
|
+
@declaration = find_visible
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update
|
|
23
|
+
@declaration = find_manageable
|
|
24
|
+
# Recording what the proof supported is a separate act from deciding
|
|
25
|
+
# — an amount can be corrected while the decision is still pending.
|
|
26
|
+
if @declaration.update(verification_params)
|
|
27
|
+
redirect_to admin_tax_declaration_path(@declaration), notice: "Amounts recorded."
|
|
28
|
+
else
|
|
29
|
+
render :show, status: :unprocessable_entity
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def verify
|
|
34
|
+
declaration = find_manageable
|
|
35
|
+
declaration.verify!(actor: hr_current_user, note: params[:note])
|
|
36
|
+
redirect_to admin_tax_declarations_path, notice: "Verified — payroll will use the checked amounts."
|
|
37
|
+
rescue ActiveRecord::RecordInvalid
|
|
38
|
+
redirect_to admin_tax_declaration_path(declaration), alert: "Only a submitted declaration can be verified."
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def reject
|
|
42
|
+
declaration = find_manageable
|
|
43
|
+
declaration.reject!(actor: hr_current_user, note: params[:note].to_s.strip)
|
|
44
|
+
redirect_to admin_tax_declarations_path, notice: "Sent back to the employee."
|
|
45
|
+
rescue ArgumentError
|
|
46
|
+
redirect_to admin_tax_declaration_path(declaration),
|
|
47
|
+
alert: "Say what is missing — they have to know what to fix."
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def require_tax_access!
|
|
53
|
+
hr_require_permission!("tax.view", scope: :all)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def find_visible
|
|
57
|
+
declaration = TaxDeclaration.includes(:tax_declaration_items).find(params[:id])
|
|
58
|
+
hr_require_reach!("tax.view", declaration.user)
|
|
59
|
+
declaration
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def find_manageable
|
|
63
|
+
declaration = TaxDeclaration.find(params[:id])
|
|
64
|
+
hr_require_reach!("tax.manage", declaration.user)
|
|
65
|
+
declaration
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def verification_params
|
|
69
|
+
params.require(:tax_declaration).permit(
|
|
70
|
+
tax_declaration_items_attributes: %i[id verified_amount]
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module HrLite
|
|
2
|
+
# An employee's own documents, and the download itself.
|
|
3
|
+
#
|
|
4
|
+
# The FILE is the sensitive part, so every read goes through
|
|
5
|
+
# `Document#readable_by?` rather than through the scope that found the row.
|
|
6
|
+
# A passport is not a payslip: HR can see that one exists without being
|
|
7
|
+
# handed the scan.
|
|
8
|
+
class DocumentsController < ApplicationController
|
|
9
|
+
def index
|
|
10
|
+
@documents = Document.for_user(hr_current_user).recent_first
|
|
11
|
+
@document = Document.new
|
|
12
|
+
@expiring = @documents.select { |d| d.expires_on && d.expires_on <= 60.days.from_now.to_date }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def create
|
|
16
|
+
@document = Document.new(document_params)
|
|
17
|
+
@document.user_id = hr_current_user.id
|
|
18
|
+
@document.uploaded_by_id = hr_current_user.id
|
|
19
|
+
|
|
20
|
+
if @document.save
|
|
21
|
+
redirect_to documents_path, notice: "#{@document.title} uploaded."
|
|
22
|
+
else
|
|
23
|
+
@documents = Document.for_user(hr_current_user).recent_first
|
|
24
|
+
@expiring = []
|
|
25
|
+
render :index, status: :unprocessable_entity
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def destroy
|
|
30
|
+
document = Document.for_user(hr_current_user).find(params[:id])
|
|
31
|
+
# A verified identity document is evidence HR acted on; removing it
|
|
32
|
+
# quietly would leave their verification pointing at nothing.
|
|
33
|
+
if document.verified?
|
|
34
|
+
return redirect_to documents_path, status: :see_other,
|
|
35
|
+
alert: "#{document.title} has been verified — ask HR to replace it."
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
document.destroy!
|
|
39
|
+
redirect_to documents_path, notice: "#{document.title} removed.", status: :see_other
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The one place a file leaves the building. Scoped by nothing — the
|
|
43
|
+
# permission check is the row's own, because a document may be readable
|
|
44
|
+
# by its owner, by HR, or by nobody but the money tier.
|
|
45
|
+
def download
|
|
46
|
+
document = Document.find(params[:id])
|
|
47
|
+
unless document.readable_by?(hr_current_user)
|
|
48
|
+
raise ActiveRecord::RecordNotFound
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
AuditLog.record!(
|
|
52
|
+
action: "document.downloaded", subject: document, actor: hr_current_user,
|
|
53
|
+
changes: { "title" => document.title, "category" => document.category,
|
|
54
|
+
"owner" => HrLite.display_name(document.user) }
|
|
55
|
+
)
|
|
56
|
+
# rails_blob_path, not url_for: an engine controller has no default
|
|
57
|
+
# host and url_for(attachment) raises without one.
|
|
58
|
+
redirect_to Rails.application.routes.url_helpers.rails_blob_path(
|
|
59
|
+
document.file, disposition: "attachment", only_path: true
|
|
60
|
+
), allow_other_host: false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def document_params
|
|
66
|
+
params.require(:document).permit(:category, :title, :reference_number, :issued_on, :expires_on, :file)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module HrLite
|
|
2
|
+
# What an employee owes and what is coming out of the next payslip. Read
|
|
3
|
+
# only: a loan is agreed with the company, not self-served.
|
|
4
|
+
class LoansController < ApplicationController
|
|
5
|
+
def index
|
|
6
|
+
@loans = Loan.where(user_id: hr_current_user.id).order(starts_on: :desc)
|
|
7
|
+
@outstanding = @loans.sum(BigDecimal(0), &:outstanding)
|
|
8
|
+
@next_month = Date.current.next_month.beginning_of_month
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module HrLite
|
|
2
|
+
# The employee's own declaration for a financial year. Replaces an admin
|
|
3
|
+
# typing one lump sum into the profile: the whole year's TDS is projected
|
|
4
|
+
# from this, so the person whose money it is fills it in.
|
|
5
|
+
class TaxDeclarationsController < ApplicationController
|
|
6
|
+
def show
|
|
7
|
+
@declaration = current_declaration || build_declaration
|
|
8
|
+
@regime = @declaration.regime
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def update
|
|
12
|
+
# A bare record, not the form's scaffold: `build_declaration` fills in
|
|
13
|
+
# one blank line per section so the form is a list rather than an empty
|
|
14
|
+
# box, and saving those blanks fails a NOT NULL amount.
|
|
15
|
+
@declaration = current_declaration ||
|
|
16
|
+
TaxDeclaration.new(user_id: hr_current_user.id,
|
|
17
|
+
financial_year: financial_year, regime: employee_regime)
|
|
18
|
+
# A verified declaration is what payroll is deducting against; changing
|
|
19
|
+
# it would silently re-open a decision HR has already made.
|
|
20
|
+
if @declaration.verified?
|
|
21
|
+
return redirect_to tax_declaration_path,
|
|
22
|
+
alert: "This year's declaration is verified. Ask HR to reopen it."
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if @declaration.update(declaration_params)
|
|
26
|
+
redirect_to tax_declaration_path, notice: "Saved."
|
|
27
|
+
else
|
|
28
|
+
# Re-render needs the full set of lines back, or the form loses the
|
|
29
|
+
# sections the person had not filled in yet.
|
|
30
|
+
fill_missing_sections(@declaration)
|
|
31
|
+
render :show, status: :unprocessable_entity
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def submit
|
|
36
|
+
@declaration = current_declaration
|
|
37
|
+
return redirect_to tax_declaration_path, alert: "Nothing to submit yet." if @declaration.nil?
|
|
38
|
+
|
|
39
|
+
@declaration.submit!(actor: hr_current_user)
|
|
40
|
+
redirect_to tax_declaration_path, notice: "Submitted — HR will check the proof."
|
|
41
|
+
rescue ActiveRecord::RecordInvalid
|
|
42
|
+
redirect_to tax_declaration_path, alert: "Only a draft can be submitted."
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
def financial_year
|
|
48
|
+
FinancialYear.start_for(params[:financial_year].present? ? Date.parse(params[:financial_year]) : Date.current)
|
|
49
|
+
rescue Date::Error
|
|
50
|
+
FinancialYear.start_for(Date.current)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def current_declaration
|
|
54
|
+
TaxDeclaration.find_by(user_id: hr_current_user.id, financial_year: financial_year)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def build_declaration
|
|
58
|
+
declaration = TaxDeclaration.new(user_id: hr_current_user.id, financial_year: financial_year,
|
|
59
|
+
regime: employee_regime)
|
|
60
|
+
# One blank line per section, so the form is a list of the things a
|
|
61
|
+
# person can actually claim rather than an empty box and a plus button.
|
|
62
|
+
fill_missing_sections(declaration)
|
|
63
|
+
declaration
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Every section gets a line, whether or not it has been claimed yet.
|
|
67
|
+
def fill_missing_sections(declaration)
|
|
68
|
+
present = declaration.tax_declaration_items.map(&:section)
|
|
69
|
+
(TaxDeclarationItem::SECTIONS - present).each do |section|
|
|
70
|
+
declaration.tax_declaration_items.build(section: section)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def employee_regime
|
|
75
|
+
EmployeeProfile.find_by(user_id: hr_current_user.id)&.tax_regime.presence || "new"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The form posts a line for every section, because that is what a list of
|
|
79
|
+
# claimable things looks like. Most of them are blank — a section with no
|
|
80
|
+
# amount is not a claim, and saving it violates a NOT NULL amount.
|
|
81
|
+
# An EXISTING line blanked out is a claim being withdrawn, so it is
|
|
82
|
+
# destroyed rather than skipped.
|
|
83
|
+
def declaration_params
|
|
84
|
+
permitted = params.require(:tax_declaration).permit(
|
|
85
|
+
:regime,
|
|
86
|
+
tax_declaration_items_attributes: %i[id section label declared_amount _destroy]
|
|
87
|
+
)
|
|
88
|
+
items = permitted[:tax_declaration_items_attributes]
|
|
89
|
+
return permitted if items.blank?
|
|
90
|
+
|
|
91
|
+
permitted[:tax_declaration_items_attributes] = items.to_h.filter_map { |key, row|
|
|
92
|
+
next [ key, row.merge("_destroy" => "1") ] if row["declared_amount"].blank? && row["id"].present?
|
|
93
|
+
next if row["declared_amount"].blank?
|
|
94
|
+
|
|
95
|
+
[ key, row ]
|
|
96
|
+
}.to_h
|
|
97
|
+
permitted
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -19,7 +19,10 @@ module HrLite
|
|
|
19
19
|
{ label: "Org", path: :org_path, match: [ "/org" ] },
|
|
20
20
|
{ label: "Kudos", path: :kudos_path, match: [ "/kudos" ] },
|
|
21
21
|
{ label: "Slips", path: :salary_slips_path, match: [ "/salary_slips" ] },
|
|
22
|
-
{ label: "Career", path: :career_path, match: [ "/career", "/appraisals", "/profile" ] }
|
|
22
|
+
{ label: "Career", path: :career_path, match: [ "/career", "/appraisals", "/profile" ] },
|
|
23
|
+
{ label: "Documents", path: :documents_path, match: [ "/documents" ] },
|
|
24
|
+
{ label: "Tax", path: :tax_declaration_path, match: [ "/tax_declaration" ] },
|
|
25
|
+
{ label: "Loans", path: :loans_path, match: [ "/loans" ] }
|
|
23
26
|
].freeze
|
|
24
27
|
|
|
25
28
|
ADMIN_NAV_ITEMS = [
|
|
@@ -36,7 +39,8 @@ module HrLite
|
|
|
36
39
|
LEADERSHIP_NAV_ITEMS = [
|
|
37
40
|
{ label: "Employees", path: :admin_employees_path, match: [ "/admin/employees" ] },
|
|
38
41
|
{ label: "Settings", path: :admin_leave_types_path, match: [ "/admin/leave_types", "/admin/office_locations", "/admin/holidays", "/admin/setting" ] },
|
|
39
|
-
{ label: "Audit", path: :admin_audit_logs_path, match: [ "/admin/audit_logs" ] }
|
|
42
|
+
{ label: "Audit", path: :admin_audit_logs_path, match: [ "/admin/audit_logs" ] },
|
|
43
|
+
{ label: "Documents", path: :admin_documents_path, match: [ "/admin/documents" ] }
|
|
40
44
|
].freeze
|
|
41
45
|
|
|
42
46
|
# Money screens, plus role management: granting somebody payroll is the
|
|
@@ -45,7 +49,9 @@ module HrLite
|
|
|
45
49
|
SUPERADMIN_NAV_ITEMS = [
|
|
46
50
|
{ label: "Payroll", path: :admin_payroll_runs_path, match: [ "/admin/payroll_runs", "/admin/salary_slips" ] },
|
|
47
51
|
{ label: "Roles", path: :admin_roles_path, match: [ "/admin/roles" ] },
|
|
48
|
-
{ label: "Rates", path: :admin_statutory_rate_cards_path, match: [ "/admin/statutory_rate_cards" ] }
|
|
52
|
+
{ label: "Rates", path: :admin_statutory_rate_cards_path, match: [ "/admin/statutory_rate_cards" ] },
|
|
53
|
+
{ label: "Tax", path: :admin_tax_declarations_path, match: [ "/admin/tax_declarations" ] },
|
|
54
|
+
{ label: "Loans", path: :admin_loans_path, match: [ "/admin/loans" ] }
|
|
49
55
|
].freeze
|
|
50
56
|
|
|
51
57
|
# Only items whose routes exist yet (the nav grows with each phase).
|
|
@@ -4,7 +4,7 @@ module HrLite
|
|
|
4
4
|
class ApprovalFlow < ApplicationRecord
|
|
5
5
|
include Audited
|
|
6
6
|
|
|
7
|
-
has_many :approval_steps, -> { order(:position) },
|
|
7
|
+
has_many :approval_steps, -> { order(:position) }, inverse_of: :flow,
|
|
8
8
|
class_name: "HrLite::ApprovalStep", foreign_key: :flow_id, dependent: :destroy
|
|
9
9
|
accepts_nested_attributes_for :approval_steps, allow_destroy: true
|
|
10
10
|
|
|
@@ -11,8 +11,11 @@ module HrLite
|
|
|
11
11
|
STATUSES = %w[draft submitted verified rejected].freeze
|
|
12
12
|
REGIMES = %w[new old].freeze
|
|
13
13
|
|
|
14
|
+
# inverse_of is load-bearing, not decoration: the foreign key is not
|
|
15
|
+
# derived from the class name, so without it a nested item on an UNSAVED
|
|
16
|
+
# declaration cannot see its parent and fails "declaration must exist".
|
|
14
17
|
has_many :tax_declaration_items, -> { order(:section) },
|
|
15
|
-
class_name: "HrLite::TaxDeclarationItem",
|
|
18
|
+
class_name: "HrLite::TaxDeclarationItem", inverse_of: :declaration,
|
|
16
19
|
foreign_key: :declaration_id, dependent: :destroy
|
|
17
20
|
accepts_nested_attributes_for :tax_declaration_items, allow_destroy: true
|
|
18
21
|
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<% content_for(:page_title) { "Documents" } %>
|
|
2
|
+
<div class="hrl-page__head"><h1 class="hrl-page__title">Documents</h1></div>
|
|
3
|
+
|
|
4
|
+
<section class="hrl-card">
|
|
5
|
+
<p class="hrl-muted hrl-small">
|
|
6
|
+
Seeing that a document exists and opening it are different things — a
|
|
7
|
+
passport is visible here, but the file itself only opens for whoever its
|
|
8
|
+
visibility allows.
|
|
9
|
+
</p>
|
|
10
|
+
<div class="hrl-row">
|
|
11
|
+
<a class="hrl-btn <%= "hrl-btn--primary" if @category.nil? %>"
|
|
12
|
+
href="<%= hr_lite.admin_documents_path %>">All</a>
|
|
13
|
+
<% @categories.each do |category| %>
|
|
14
|
+
<a class="hrl-btn <%= "hrl-btn--primary" if @category == category %>"
|
|
15
|
+
href="<%= hr_lite.admin_documents_path(category: category) %>"><%= category.humanize %></a>
|
|
16
|
+
<% end %>
|
|
17
|
+
</div>
|
|
18
|
+
</section>
|
|
19
|
+
|
|
20
|
+
<section class="hrl-card">
|
|
21
|
+
<h2 class="hrl-card__title">Waiting to be checked (<%= @pending.size %>)</h2>
|
|
22
|
+
<% if @pending.empty? %>
|
|
23
|
+
<p class="hrl-muted">Nothing pending.</p>
|
|
24
|
+
<% else %>
|
|
25
|
+
<div class="hrl-table-wrap">
|
|
26
|
+
<table class="hrl-table hrl-table--stack">
|
|
27
|
+
<thead><tr><th>Person</th><th>Document</th><th>File</th><th></th></tr></thead>
|
|
28
|
+
<tbody>
|
|
29
|
+
<% @pending.each do |doc| %>
|
|
30
|
+
<tr>
|
|
31
|
+
<td data-label="Person"><%= hr_display_name(doc.user) %></td>
|
|
32
|
+
<td data-label="Document">
|
|
33
|
+
<%= doc.title %>
|
|
34
|
+
<div class="hrl-small hrl-muted"><%= doc.category.humanize %></div>
|
|
35
|
+
</td>
|
|
36
|
+
<td data-label="File">
|
|
37
|
+
<% if doc.readable_by?(hr_current_user) && doc.file.attached? %>
|
|
38
|
+
<a class="hrl-small" href="<%= hr_lite.download_document_path(doc) %>">Open</a>
|
|
39
|
+
<% else %>
|
|
40
|
+
<span class="hrl-small hrl-muted">Not yours to open</span>
|
|
41
|
+
<% end %>
|
|
42
|
+
</td>
|
|
43
|
+
<td data-label="">
|
|
44
|
+
<%= button_to "Verify", hr_lite.verify_admin_document_path(doc),
|
|
45
|
+
method: :post, class: "hrl-btn hrl-btn--primary" %>
|
|
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" %>
|
|
48
|
+
<%= submit_tag "Reject", class: "hrl-btn" %>
|
|
49
|
+
<% end %>
|
|
50
|
+
</td>
|
|
51
|
+
</tr>
|
|
52
|
+
<% end %>
|
|
53
|
+
</tbody>
|
|
54
|
+
</table>
|
|
55
|
+
</div>
|
|
56
|
+
<% end %>
|
|
57
|
+
</section>
|
|
58
|
+
|
|
59
|
+
<section class="hrl-card">
|
|
60
|
+
<h2 class="hrl-card__title">Everything on file</h2>
|
|
61
|
+
<div class="hrl-table-wrap">
|
|
62
|
+
<table class="hrl-table hrl-table--stack">
|
|
63
|
+
<thead><tr><th>Person</th><th>Document</th><th>Expires</th><th>Checked</th></tr></thead>
|
|
64
|
+
<tbody>
|
|
65
|
+
<% @documents.each do |doc| %>
|
|
66
|
+
<tr>
|
|
67
|
+
<td data-label="Person">
|
|
68
|
+
<a href="<%= hr_lite.admin_employee_documents_path(user_id: doc.user_id) %>">
|
|
69
|
+
<%= hr_display_name(doc.user) %>
|
|
70
|
+
</a>
|
|
71
|
+
</td>
|
|
72
|
+
<td data-label="Document"><%= doc.title %></td>
|
|
73
|
+
<td data-label="Expires">
|
|
74
|
+
<% if doc.expires_on %>
|
|
75
|
+
<span class="<%= "hrl-badge hrl-badge--bad" if doc.expired? %>">
|
|
76
|
+
<%= doc.expires_on.strftime("%d %b %Y") %>
|
|
77
|
+
</span>
|
|
78
|
+
<% else %>—<% end %>
|
|
79
|
+
</td>
|
|
80
|
+
<td data-label="Checked">
|
|
81
|
+
<%= hrl_request_status_badge(doc.verification == "verified" ? "approved" : doc.verification) %>
|
|
82
|
+
</td>
|
|
83
|
+
</tr>
|
|
84
|
+
<% end %>
|
|
85
|
+
</tbody>
|
|
86
|
+
</table>
|
|
87
|
+
</div>
|
|
88
|
+
<%= hrl_pagination %>
|
|
89
|
+
</section>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<% content_for(:page_title) { hr_display_name(@employee) } %>
|
|
2
|
+
<div class="hrl-page__head">
|
|
3
|
+
<h1 class="hrl-page__title"><%= hr_display_name(@employee) %> — documents</h1>
|
|
4
|
+
</div>
|
|
5
|
+
|
|
6
|
+
<% if @missing.any? %>
|
|
7
|
+
<section class="hrl-card hrl-card--warn">
|
|
8
|
+
<h2 class="hrl-card__title">Not on file</h2>
|
|
9
|
+
<p><%= @missing.map(&:humanize).to_sentence %></p>
|
|
10
|
+
</section>
|
|
11
|
+
<% end %>
|
|
12
|
+
|
|
13
|
+
<section class="hrl-card">
|
|
14
|
+
<% if @documents.empty? %>
|
|
15
|
+
<p class="hrl-muted">Nothing uploaded.</p>
|
|
16
|
+
<% else %>
|
|
17
|
+
<div class="hrl-table-wrap">
|
|
18
|
+
<table class="hrl-table hrl-table--stack">
|
|
19
|
+
<thead><tr><th>Document</th><th>Expires</th><th>Checked</th><th></th></tr></thead>
|
|
20
|
+
<tbody>
|
|
21
|
+
<% @documents.each do |doc| %>
|
|
22
|
+
<tr>
|
|
23
|
+
<td data-label="Document">
|
|
24
|
+
<strong><%= doc.title %></strong>
|
|
25
|
+
<div class="hrl-small hrl-muted"><%= doc.category.humanize %></div>
|
|
26
|
+
</td>
|
|
27
|
+
<td data-label="Expires"><%= doc.expires_on&.strftime("%d %b %Y") || "—" %></td>
|
|
28
|
+
<td data-label="Checked">
|
|
29
|
+
<%= hrl_request_status_badge(doc.verification == "verified" ? "approved" : doc.verification) %>
|
|
30
|
+
</td>
|
|
31
|
+
<td data-label="">
|
|
32
|
+
<% if doc.readable_by?(hr_current_user) && doc.file.attached? %>
|
|
33
|
+
<a class="hrl-small" href="<%= hr_lite.download_document_path(doc) %>">Open</a>
|
|
34
|
+
<% end %>
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
<% end %>
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
</section>
|
|
@@ -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,6 +68,16 @@ 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]
|
|
65
82
|
resources :expenses, only: :index do
|
|
66
83
|
member { post :approve; post :reject; post :reimburse }
|
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.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kshitiz sinha
|
|
@@ -63,6 +63,7 @@ files:
|
|
|
63
63
|
- app/controllers/hr_lite/admin/checklists_controller.rb
|
|
64
64
|
- app/controllers/hr_lite/admin/comp_off_requests_controller.rb
|
|
65
65
|
- app/controllers/hr_lite/admin/designation_changes_controller.rb
|
|
66
|
+
- app/controllers/hr_lite/admin/documents_controller.rb
|
|
66
67
|
- app/controllers/hr_lite/admin/employees_controller.rb
|
|
67
68
|
- app/controllers/hr_lite/admin/expenses_controller.rb
|
|
68
69
|
- app/controllers/hr_lite/admin/holidays_controller.rb
|
|
@@ -71,6 +72,7 @@ files:
|
|
|
71
72
|
- app/controllers/hr_lite/admin/leave_balances_controller.rb
|
|
72
73
|
- app/controllers/hr_lite/admin/leave_requests_controller.rb
|
|
73
74
|
- app/controllers/hr_lite/admin/leave_types_controller.rb
|
|
75
|
+
- app/controllers/hr_lite/admin/loans_controller.rb
|
|
74
76
|
- app/controllers/hr_lite/admin/office_locations_controller.rb
|
|
75
77
|
- app/controllers/hr_lite/admin/overview_controller.rb
|
|
76
78
|
- app/controllers/hr_lite/admin/payroll_runs_controller.rb
|
|
@@ -84,6 +86,7 @@ files:
|
|
|
84
86
|
- app/controllers/hr_lite/admin/settings_controller.rb
|
|
85
87
|
- app/controllers/hr_lite/admin/statutory_rate_cards_controller.rb
|
|
86
88
|
- app/controllers/hr_lite/admin/superadmin_controller.rb
|
|
89
|
+
- app/controllers/hr_lite/admin/tax_declarations_controller.rb
|
|
87
90
|
- app/controllers/hr_lite/application_controller.rb
|
|
88
91
|
- app/controllers/hr_lite/appraisals_controller.rb
|
|
89
92
|
- app/controllers/hr_lite/approvals_controller.rb
|
|
@@ -92,6 +95,7 @@ files:
|
|
|
92
95
|
- app/controllers/hr_lite/calendar_controller.rb
|
|
93
96
|
- app/controllers/hr_lite/career_controller.rb
|
|
94
97
|
- app/controllers/hr_lite/comp_off_requests_controller.rb
|
|
98
|
+
- app/controllers/hr_lite/documents_controller.rb
|
|
95
99
|
- app/controllers/hr_lite/employee_profiles_controller.rb
|
|
96
100
|
- app/controllers/hr_lite/expenses_controller.rb
|
|
97
101
|
- app/controllers/hr_lite/holidays_controller.rb
|
|
@@ -100,11 +104,13 @@ files:
|
|
|
100
104
|
- app/controllers/hr_lite/kudos_controller.rb
|
|
101
105
|
- app/controllers/hr_lite/leave_balances_controller.rb
|
|
102
106
|
- app/controllers/hr_lite/leave_requests_controller.rb
|
|
107
|
+
- app/controllers/hr_lite/loans_controller.rb
|
|
103
108
|
- app/controllers/hr_lite/org_controller.rb
|
|
104
109
|
- app/controllers/hr_lite/policies_controller.rb
|
|
105
110
|
- app/controllers/hr_lite/regularization_requests_controller.rb
|
|
106
111
|
- app/controllers/hr_lite/resignations_controller.rb
|
|
107
112
|
- app/controllers/hr_lite/salary_slips_controller.rb
|
|
113
|
+
- app/controllers/hr_lite/tax_declarations_controller.rb
|
|
108
114
|
- app/controllers/hr_lite/team_controller.rb
|
|
109
115
|
- app/controllers/hr_lite/users_controller.rb
|
|
110
116
|
- app/helpers/hr_lite/application_helper.rb
|
|
@@ -195,6 +201,8 @@ files:
|
|
|
195
201
|
- app/views/hr_lite/admin/comp_off_requests/index.html.erb
|
|
196
202
|
- app/views/hr_lite/admin/comp_off_requests/show.html.erb
|
|
197
203
|
- app/views/hr_lite/admin/designation_changes/new.html.erb
|
|
204
|
+
- app/views/hr_lite/admin/documents/index.html.erb
|
|
205
|
+
- app/views/hr_lite/admin/documents/show.html.erb
|
|
198
206
|
- app/views/hr_lite/admin/employees/_form.html.erb
|
|
199
207
|
- app/views/hr_lite/admin/employees/edit.html.erb
|
|
200
208
|
- app/views/hr_lite/admin/employees/index.html.erb
|
|
@@ -211,6 +219,9 @@ files:
|
|
|
211
219
|
- app/views/hr_lite/admin/leave_types/edit.html.erb
|
|
212
220
|
- app/views/hr_lite/admin/leave_types/index.html.erb
|
|
213
221
|
- app/views/hr_lite/admin/leave_types/new.html.erb
|
|
222
|
+
- app/views/hr_lite/admin/loans/index.html.erb
|
|
223
|
+
- app/views/hr_lite/admin/loans/new.html.erb
|
|
224
|
+
- app/views/hr_lite/admin/loans/show.html.erb
|
|
214
225
|
- app/views/hr_lite/admin/office_locations/_form.html.erb
|
|
215
226
|
- app/views/hr_lite/admin/office_locations/edit.html.erb
|
|
216
227
|
- app/views/hr_lite/admin/office_locations/index.html.erb
|
|
@@ -237,6 +248,8 @@ files:
|
|
|
237
248
|
- app/views/hr_lite/admin/statutory_rate_cards/edit.html.erb
|
|
238
249
|
- app/views/hr_lite/admin/statutory_rate_cards/index.html.erb
|
|
239
250
|
- app/views/hr_lite/admin/statutory_rate_cards/new.html.erb
|
|
251
|
+
- app/views/hr_lite/admin/tax_declarations/index.html.erb
|
|
252
|
+
- app/views/hr_lite/admin/tax_declarations/show.html.erb
|
|
240
253
|
- app/views/hr_lite/appraisals/index.html.erb
|
|
241
254
|
- app/views/hr_lite/appraisals/show.html.erb
|
|
242
255
|
- app/views/hr_lite/approvals/index.html.erb
|
|
@@ -248,6 +261,7 @@ files:
|
|
|
248
261
|
- app/views/hr_lite/career/show.html.erb
|
|
249
262
|
- app/views/hr_lite/comp_off_requests/index.html.erb
|
|
250
263
|
- app/views/hr_lite/comp_off_requests/new.html.erb
|
|
264
|
+
- app/views/hr_lite/documents/index.html.erb
|
|
251
265
|
- app/views/hr_lite/employee_profiles/show.html.erb
|
|
252
266
|
- app/views/hr_lite/event_mailer/event.html.erb
|
|
253
267
|
- app/views/hr_lite/event_mailer/event.text.erb
|
|
@@ -268,6 +282,7 @@ files:
|
|
|
268
282
|
- app/views/hr_lite/leave_requests/index.html.erb
|
|
269
283
|
- app/views/hr_lite/leave_requests/new.html.erb
|
|
270
284
|
- app/views/hr_lite/leave_requests/show.html.erb
|
|
285
|
+
- app/views/hr_lite/loans/index.html.erb
|
|
271
286
|
- app/views/hr_lite/org/_node.html.erb
|
|
272
287
|
- app/views/hr_lite/org/show.html.erb
|
|
273
288
|
- app/views/hr_lite/policies/index.html.erb
|
|
@@ -281,6 +296,7 @@ files:
|
|
|
281
296
|
- app/views/hr_lite/salary_slips/show.html.erb
|
|
282
297
|
- app/views/hr_lite/shared/_form_errors.html.erb
|
|
283
298
|
- app/views/hr_lite/shared/_pagination.html.erb
|
|
299
|
+
- app/views/hr_lite/tax_declarations/show.html.erb
|
|
284
300
|
- app/views/hr_lite/team/show.html.erb
|
|
285
301
|
- app/views/layouts/hr_lite/application.html.erb
|
|
286
302
|
- app/views/layouts/hr_lite/mailer.html.erb
|