hr_lite 0.5.3 → 0.6.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 +51 -1
- data/README.md +67 -31
- data/app/controllers/hr_lite/admin/attendances_controller.rb +18 -2
- data/app/controllers/hr_lite/admin/base_controller.rb +16 -6
- data/app/controllers/hr_lite/admin/comp_off_requests_controller.rb +16 -4
- data/app/controllers/hr_lite/admin/leadership_controller.rb +11 -8
- data/app/controllers/hr_lite/admin/leave_requests_controller.rb +21 -5
- data/app/controllers/hr_lite/admin/regularization_requests_controller.rb +16 -4
- data/app/controllers/hr_lite/admin/role_assignments_controller.rb +63 -0
- data/app/controllers/hr_lite/admin/roles_controller.rb +73 -0
- data/app/controllers/hr_lite/admin/superadmin_controller.rb +15 -11
- data/app/controllers/hr_lite/application_controller.rb +39 -1
- data/app/helpers/hr_lite/application_helper.rb +5 -2
- data/app/models/hr_lite/role.rb +66 -0
- data/app/models/hr_lite/role_assignment.rb +18 -0
- data/app/models/hr_lite/role_grant.rb +15 -0
- data/app/services/hr_lite/access.rb +111 -0
- data/app/views/hr_lite/admin/roles/_form.html.erb +47 -0
- data/app/views/hr_lite/admin/roles/edit.html.erb +53 -0
- data/app/views/hr_lite/admin/roles/index.html.erb +49 -0
- data/app/views/hr_lite/admin/roles/new.html.erb +3 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20260817183347_create_hr_lite_roles_and_grants.rb +49 -0
- data/db/migrate/20260817183629_seed_hr_lite_roles_from_email_tiers.rb +82 -0
- data/lib/generators/hr_lite/install/templates/initializer.rb +13 -7
- data/lib/hr_lite/configuration.rb +8 -1
- data/lib/hr_lite/current.rb +4 -0
- data/lib/hr_lite/permissions.rb +79 -0
- data/lib/hr_lite/role_seeds.rb +81 -0
- data/lib/hr_lite/version.rb +1 -1
- data/lib/hr_lite.rb +63 -10
- data/lib/tasks/hr_lite_tasks.rake +10 -2
- metadata +15 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module HrLite
|
|
2
|
+
# The whole vocabulary of the engine's authorization, in one place.
|
|
3
|
+
#
|
|
4
|
+
# A permission is a KEY plus a SCOPE. The key says what the action is
|
|
5
|
+
# ("approve leave"); the scope says whose rows it reaches:
|
|
6
|
+
#
|
|
7
|
+
# self — only this person's own records
|
|
8
|
+
# team — the people who report to them (EmployeeProfile#manager_id)
|
|
9
|
+
# all — everyone in the company
|
|
10
|
+
#
|
|
11
|
+
# That is why there is no `leave.approve_own` / `leave.approve_team` /
|
|
12
|
+
# `leave.approve_all` triple: one key with three possible scopes says the
|
|
13
|
+
# same thing without three times the surface to get wrong.
|
|
14
|
+
#
|
|
15
|
+
# Keys are DATA — a role grants them from the database. They are declared
|
|
16
|
+
# here so that a typo in a controller is a boot-time failure rather than a
|
|
17
|
+
# permission that silently never matches.
|
|
18
|
+
module Permissions
|
|
19
|
+
SCOPES = %i[self team all].freeze
|
|
20
|
+
|
|
21
|
+
# Ordered weakest to strongest. `all` satisfies a `team` requirement,
|
|
22
|
+
# `team` satisfies `self`; never the other way round.
|
|
23
|
+
SCOPE_RANK = { self: 0, team: 1, all: 2 }.freeze
|
|
24
|
+
|
|
25
|
+
# key => [ group, description ]. The group is what the roles screen
|
|
26
|
+
# renders as a heading; the description is the sentence beside the
|
|
27
|
+
# checkbox, so it is written for the person granting it.
|
|
28
|
+
REGISTRY = {
|
|
29
|
+
"profile.view" => [ "People", "See employee profiles" ],
|
|
30
|
+
"profile.manage" => [ "People", "Create and edit employee profiles, onboard and offboard" ],
|
|
31
|
+
"attendance.view" => [ "Attendance", "See attendance records" ],
|
|
32
|
+
"attendance.manage" => [ "Attendance", "Correct punches and decide regularization tickets" ],
|
|
33
|
+
"leave.request" => [ "Leave", "Apply for leave and comp-off" ],
|
|
34
|
+
"leave.view" => [ "Leave", "See leave requests and balances" ],
|
|
35
|
+
"leave.approve" => [ "Leave", "Approve, reject and cancel leave and comp-off" ],
|
|
36
|
+
"leave.manage" => [ "Leave", "Adjust balances, configure leave types and holidays" ],
|
|
37
|
+
"payroll.view" => [ "Payroll", "See payroll runs and salary slips" ],
|
|
38
|
+
"payroll.manage" => [ "Payroll", "Create, compute, finalize, unlock and publish payroll" ],
|
|
39
|
+
"payroll.export" => [ "Payroll", "Download the payout register, including bank details" ],
|
|
40
|
+
"salary.view" => [ "Payroll", "See salary structures" ],
|
|
41
|
+
"salary.manage" => [ "Payroll", "Set and revise salary structures" ],
|
|
42
|
+
"appraisal.view" => [ "Growth", "See appraisals" ],
|
|
43
|
+
"appraisal.manage" => [ "Growth", "Write, share and act on appraisals and promotions" ],
|
|
44
|
+
"resignation.view" => [ "Lifecycle", "See resignations" ],
|
|
45
|
+
"resignation.manage" => [ "Lifecycle", "Accept resignations and set the last working day" ],
|
|
46
|
+
"settings.manage" => [ "Administration", "Change company settings, offices and policy" ],
|
|
47
|
+
"audit.view" => [ "Administration", "Read the audit trail" ],
|
|
48
|
+
"audit.view_money" => [ "Administration", "Read audit rows about pay, appraisals and promotions" ],
|
|
49
|
+
"role.manage" => [ "Administration", "Create roles and grant permissions" ]
|
|
50
|
+
}.freeze
|
|
51
|
+
|
|
52
|
+
KEYS = REGISTRY.keys.freeze
|
|
53
|
+
|
|
54
|
+
def self.valid?(key) = REGISTRY.key?(key.to_s)
|
|
55
|
+
|
|
56
|
+
# Raises rather than returning false: an unknown key in a controller is a
|
|
57
|
+
# typo that would otherwise read as "nobody may do this", which fails in
|
|
58
|
+
# the safe direction but silently, and is then very hard to find.
|
|
59
|
+
def self.validate!(key)
|
|
60
|
+
return key.to_s if valid?(key)
|
|
61
|
+
|
|
62
|
+
raise ArgumentError, "Unknown HrLite permission #{key.inspect}. " \
|
|
63
|
+
"Declare it in HrLite::Permissions::REGISTRY first."
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.group(key) = REGISTRY.fetch(key.to_s).first
|
|
67
|
+
def self.description(key) = REGISTRY.fetch(key.to_s).last
|
|
68
|
+
|
|
69
|
+
def self.grouped
|
|
70
|
+
REGISTRY.group_by { |_key, (group, _)| group }
|
|
71
|
+
.transform_values { |pairs| pairs.map(&:first) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Does `held` satisfy a requirement for `needed`?
|
|
75
|
+
def self.scope_covers?(held, needed)
|
|
76
|
+
SCOPE_RANK.fetch(held.to_sym) >= SCOPE_RANK.fetch(needed.to_sym)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module HrLite
|
|
2
|
+
# The six roles an install starts with. They are a STARTING POINT, not a
|
|
3
|
+
# ladder in code: an install is expected to edit the grants, and `roles:seed`
|
|
4
|
+
# never overwrites a role that already exists.
|
|
5
|
+
#
|
|
6
|
+
# Read the scopes as the interesting part. Manager and HR hold the same
|
|
7
|
+
# leave.approve key — the difference between them is `team` and `all`, which
|
|
8
|
+
# is the whole reason scope lives on the grant.
|
|
9
|
+
module RoleSeeds
|
|
10
|
+
# A method, not a constant: the keys are Role constants, and this file is
|
|
11
|
+
# required while the gem loads, long before Active Record models exist.
|
|
12
|
+
def self.definitions
|
|
13
|
+
{
|
|
14
|
+
Role::EMPLOYEE => {
|
|
15
|
+
description: "Self-service only: own attendance, leave, documents and payslips.",
|
|
16
|
+
grants: {
|
|
17
|
+
"leave.request" => "self", "leave.view" => "self",
|
|
18
|
+
"attendance.view" => "self", "payroll.view" => "self",
|
|
19
|
+
"profile.view" => "self", "appraisal.view" => "self",
|
|
20
|
+
"resignation.view" => "self"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
Role::MANAGER => {
|
|
24
|
+
description: "Everything an employee has, plus their reports' attendance and leave.",
|
|
25
|
+
grants: {
|
|
26
|
+
"leave.request" => "self", "leave.view" => "team", "leave.approve" => "team",
|
|
27
|
+
"attendance.view" => "team", "attendance.manage" => "team",
|
|
28
|
+
"payroll.view" => "self", "profile.view" => "team",
|
|
29
|
+
"appraisal.view" => "self", "resignation.view" => "self"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
Role::HR => {
|
|
33
|
+
description: "Day-to-day operations for everyone: attendance, leave, holidays, tickets.",
|
|
34
|
+
grants: {
|
|
35
|
+
"leave.request" => "self", "leave.view" => "all", "leave.approve" => "all",
|
|
36
|
+
"leave.manage" => "all", "attendance.view" => "all", "attendance.manage" => "all",
|
|
37
|
+
"profile.view" => "all", "payroll.view" => "self",
|
|
38
|
+
"appraisal.view" => "self", "resignation.view" => "all"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
Role::FINANCE => {
|
|
42
|
+
description: "Payroll and pay data. No authority over people or policy.",
|
|
43
|
+
grants: {
|
|
44
|
+
"leave.request" => "self", "leave.view" => "self",
|
|
45
|
+
"attendance.view" => "all", "profile.view" => "all",
|
|
46
|
+
"payroll.view" => "all", "payroll.manage" => "all", "payroll.export" => "all",
|
|
47
|
+
"salary.view" => "all", "salary.manage" => "all",
|
|
48
|
+
"audit.view" => "all", "audit.view_money" => "all"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
Role::LEADERSHIP => {
|
|
52
|
+
description: "People and policy for everyone — deliberately NOT pay.",
|
|
53
|
+
grants: {
|
|
54
|
+
"leave.request" => "self", "leave.view" => "all", "leave.approve" => "all",
|
|
55
|
+
"leave.manage" => "all", "attendance.view" => "all", "attendance.manage" => "all",
|
|
56
|
+
"profile.view" => "all", "profile.manage" => "all",
|
|
57
|
+
"resignation.view" => "all", "resignation.manage" => "all",
|
|
58
|
+
"settings.manage" => "all", "audit.view" => "all", "payroll.view" => "self"
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
Role::SUPER_ADMIN => {
|
|
62
|
+
description: "Everything, including pay, appraisals and who holds which role.",
|
|
63
|
+
grants: Permissions::KEYS.to_h { |key| [ key, "all" ] }
|
|
64
|
+
}
|
|
65
|
+
}.freeze
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Creates any role that does not exist yet and leaves every existing one
|
|
69
|
+
# exactly as the install has tuned it — the same contract as the leave-type
|
|
70
|
+
# seed. Returns the names it created.
|
|
71
|
+
def self.call
|
|
72
|
+
definitions.filter_map do |name, definition|
|
|
73
|
+
next if Role.exists?(name: name)
|
|
74
|
+
|
|
75
|
+
role = Role.create!(name: name, description: definition[:description], system: true)
|
|
76
|
+
role.replace_grants!(definition[:grants])
|
|
77
|
+
name
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
data/lib/hr_lite/version.rb
CHANGED
data/lib/hr_lite.rb
CHANGED
|
@@ -4,6 +4,8 @@ require "hr_lite/configuration"
|
|
|
4
4
|
require "hr_lite/current"
|
|
5
5
|
require "hr_lite/leave_year"
|
|
6
6
|
require "hr_lite/financial_year"
|
|
7
|
+
require "hr_lite/permissions"
|
|
8
|
+
require "hr_lite/role_seeds"
|
|
7
9
|
require "hr_lite/mention_parser"
|
|
8
10
|
require "hr_lite/notifications"
|
|
9
11
|
require "hr_lite/seeds"
|
|
@@ -31,16 +33,49 @@ module HrLite
|
|
|
31
33
|
config.user_class.constantize
|
|
32
34
|
end
|
|
33
35
|
|
|
36
|
+
# --- authorization -----------------------------------------------------
|
|
37
|
+
|
|
38
|
+
# The question every gate now asks. `scope:` is what the CALLER needs;
|
|
39
|
+
# a holder with a wider scope satisfies it.
|
|
40
|
+
def can?(user, key, scope: :self)
|
|
41
|
+
Access.for(user).can?(key, scope: scope)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Whether `user` may exercise `key` over `subject`'s rows. This is the
|
|
45
|
+
# question that did not exist before roles, and its absence is why any
|
|
46
|
+
# admin could approve anybody's leave.
|
|
47
|
+
def reaches?(user, key, subject)
|
|
48
|
+
Access.for(user).reaches?(key, subject)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def access_for(user) = Access.for(user)
|
|
52
|
+
|
|
53
|
+
# --- legacy tier predicates --------------------------------------------
|
|
54
|
+
#
|
|
55
|
+
# Kept because views, hosts and the notification fan-out all call them.
|
|
56
|
+
# They are now READ OFF ROLES rather than off a mutable email column; the
|
|
57
|
+
# configured lambdas survive only as an explicit opt-in for a host that
|
|
58
|
+
# has not migrated yet (see Configuration#legacy_tier_checks).
|
|
59
|
+
|
|
34
60
|
def admin?(user)
|
|
35
|
-
user.
|
|
61
|
+
return false if user.blank?
|
|
62
|
+
return !!config.admin_check.call(user) if config.legacy_tier_checks
|
|
63
|
+
|
|
64
|
+
can?(user, "leave.approve", scope: :all) || can?(user, "attendance.manage", scope: :all)
|
|
36
65
|
end
|
|
37
66
|
|
|
38
67
|
def leadership?(user)
|
|
39
|
-
user.
|
|
68
|
+
return false if user.blank?
|
|
69
|
+
return !!config.leadership_check.call(user) if config.legacy_tier_checks
|
|
70
|
+
|
|
71
|
+
can?(user, "profile.manage", scope: :all)
|
|
40
72
|
end
|
|
41
73
|
|
|
42
74
|
def superadmin?(user)
|
|
43
|
-
user.
|
|
75
|
+
return false if user.blank?
|
|
76
|
+
return !!config.superadmin_check.call(user) if config.legacy_tier_checks
|
|
77
|
+
|
|
78
|
+
can?(user, "payroll.manage", scope: :all)
|
|
44
79
|
end
|
|
45
80
|
|
|
46
81
|
# An access list, cleaned. "a@x.com,,b@x.com".split(",") — one stray
|
|
@@ -60,21 +95,39 @@ module HrLite
|
|
|
60
95
|
end
|
|
61
96
|
|
|
62
97
|
# Leadership members resolvable to actual user records (for bell
|
|
63
|
-
# notifications).
|
|
64
|
-
# still reachable by email — see Notifications.
|
|
98
|
+
# notifications). On a legacy host, emails configured but absent from the
|
|
99
|
+
# user table are still reachable by email — see Notifications.
|
|
65
100
|
def leadership_users
|
|
101
|
+
unless config.legacy_tier_checks
|
|
102
|
+
return users_holding("profile.manage", scope: :all)
|
|
103
|
+
end
|
|
104
|
+
|
|
66
105
|
emails = normalize_email_list(config.leadership_emails)
|
|
67
106
|
return user_klass.none if emails.empty?
|
|
68
107
|
|
|
69
108
|
user_klass.where("LOWER(#{user_klass.table_name}.email) IN (?)", emails)
|
|
70
109
|
end
|
|
71
110
|
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
|
|
75
|
-
|
|
111
|
+
# Everyone whose roles grant `key` at `scope` or wider. One query over
|
|
112
|
+
# the grant tables rather than instantiating every user and asking.
|
|
113
|
+
def users_holding(key, scope: :all)
|
|
114
|
+
wide = Permissions::SCOPE_RANK.select { |_, rank| rank >= Permissions::SCOPE_RANK.fetch(scope) }
|
|
115
|
+
ids = RoleAssignment.joins(role: :role_grants)
|
|
116
|
+
.where(hr_lite_role_grants: {
|
|
117
|
+
permission_key: Permissions.validate!(key), scope: wide.keys.map(&:to_s)
|
|
118
|
+
})
|
|
119
|
+
.distinct.pluck(:user_id)
|
|
120
|
+
user_klass.where(id: ids)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Every domain event that bells the admins calls this. On roles it is one
|
|
124
|
+
# query over the grant tables; on a legacy host it still has to run the
|
|
125
|
+
# host's lambda per row, which is why it starts from employees_scope
|
|
126
|
+
# (narrowed to real staff) rather than every row that exists.
|
|
76
127
|
def admin_users
|
|
77
|
-
employees.select { |u| admin?(u) }
|
|
128
|
+
return employees.select { |u| admin?(u) } if config.legacy_tier_checks
|
|
129
|
+
|
|
130
|
+
users_holding("leave.approve", scope: :all).sort_by { |u| display_name(u).downcase }
|
|
78
131
|
end
|
|
79
132
|
|
|
80
133
|
# Everyone HR tracks (host-overridable to exclude bots/test accounts),
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
namespace :hr_lite do
|
|
2
|
-
desc "Idempotently seed HR reference data (leave types, national holidays)"
|
|
2
|
+
desc "Idempotently seed HR reference data (leave types, national holidays, roles)"
|
|
3
3
|
task seed: :environment do
|
|
4
4
|
require "hr_lite/seeds"
|
|
5
|
-
|
|
5
|
+
require "hr_lite/role_seeds"
|
|
6
|
+
created = HrLite::Seeds.run! + HrLite::RoleSeeds.call
|
|
6
7
|
puts created.any? ? "hr_lite:seed created: #{created.join(', ')}" : "hr_lite:seed — nothing to do"
|
|
7
8
|
end
|
|
9
|
+
|
|
10
|
+
desc "Idempotently seed the built-in roles only (never overwrites a tuned role)"
|
|
11
|
+
task roles: :environment do
|
|
12
|
+
require "hr_lite/role_seeds"
|
|
13
|
+
created = HrLite::RoleSeeds.call
|
|
14
|
+
puts created.any? ? "hr_lite:roles created: #{created.join(', ')}" : "hr_lite:roles — nothing to do"
|
|
15
|
+
end
|
|
8
16
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hr_lite
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- kshitiz sinha
|
|
@@ -72,6 +72,8 @@ files:
|
|
|
72
72
|
- app/controllers/hr_lite/admin/payroll_runs_controller.rb
|
|
73
73
|
- app/controllers/hr_lite/admin/regularization_requests_controller.rb
|
|
74
74
|
- app/controllers/hr_lite/admin/resignations_controller.rb
|
|
75
|
+
- app/controllers/hr_lite/admin/role_assignments_controller.rb
|
|
76
|
+
- app/controllers/hr_lite/admin/roles_controller.rb
|
|
75
77
|
- app/controllers/hr_lite/admin/salary_slips_controller.rb
|
|
76
78
|
- app/controllers/hr_lite/admin/salary_structures_controller.rb
|
|
77
79
|
- app/controllers/hr_lite/admin/settings_controller.rb
|
|
@@ -120,9 +122,13 @@ files:
|
|
|
120
122
|
- app/models/hr_lite/payroll_run.rb
|
|
121
123
|
- app/models/hr_lite/regularization_request.rb
|
|
122
124
|
- app/models/hr_lite/resignation.rb
|
|
125
|
+
- app/models/hr_lite/role.rb
|
|
126
|
+
- app/models/hr_lite/role_assignment.rb
|
|
127
|
+
- app/models/hr_lite/role_grant.rb
|
|
123
128
|
- app/models/hr_lite/salary_slip.rb
|
|
124
129
|
- app/models/hr_lite/salary_structure.rb
|
|
125
130
|
- app/models/hr_lite/setting.rb
|
|
131
|
+
- app/services/hr_lite/access.rb
|
|
126
132
|
- app/services/hr_lite/attendance_puncher.rb
|
|
127
133
|
- app/services/hr_lite/attendance_summary.rb
|
|
128
134
|
- app/services/hr_lite/calculators/esi.rb
|
|
@@ -170,6 +176,10 @@ files:
|
|
|
170
176
|
- app/views/hr_lite/admin/payroll_runs/show.html.erb
|
|
171
177
|
- app/views/hr_lite/admin/regularization_requests/index.html.erb
|
|
172
178
|
- app/views/hr_lite/admin/regularization_requests/show.html.erb
|
|
179
|
+
- app/views/hr_lite/admin/roles/_form.html.erb
|
|
180
|
+
- app/views/hr_lite/admin/roles/edit.html.erb
|
|
181
|
+
- app/views/hr_lite/admin/roles/index.html.erb
|
|
182
|
+
- app/views/hr_lite/admin/roles/new.html.erb
|
|
173
183
|
- app/views/hr_lite/admin/salary_slips/show.html.erb
|
|
174
184
|
- app/views/hr_lite/admin/salary_structures/_form.html.erb
|
|
175
185
|
- app/views/hr_lite/admin/salary_structures/edit.html.erb
|
|
@@ -243,6 +253,8 @@ files:
|
|
|
243
253
|
- db/migrate/20260807184426_add_fy_opening_to_hr_lite_employee_profiles.rb
|
|
244
254
|
- db/migrate/20260807184939_add_out_of_window_days_to_hr_lite_salary_slips.rb
|
|
245
255
|
- db/migrate/20260817182124_add_status_constraints_and_appraisal_fk_to_hr_lite.rb
|
|
256
|
+
- db/migrate/20260817183347_create_hr_lite_roles_and_grants.rb
|
|
257
|
+
- db/migrate/20260817183629_seed_hr_lite_roles_from_email_tiers.rb
|
|
246
258
|
- lib/generators/hr_lite/install/install_generator.rb
|
|
247
259
|
- lib/generators/hr_lite/install/templates/AFTER_INSTALL
|
|
248
260
|
- lib/generators/hr_lite/install/templates/initializer.rb
|
|
@@ -257,6 +269,8 @@ files:
|
|
|
257
269
|
- lib/hr_lite/mention_parser.rb
|
|
258
270
|
- lib/hr_lite/money.rb
|
|
259
271
|
- lib/hr_lite/notifications.rb
|
|
272
|
+
- lib/hr_lite/permissions.rb
|
|
273
|
+
- lib/hr_lite/role_seeds.rb
|
|
260
274
|
- lib/hr_lite/seeds.rb
|
|
261
275
|
- lib/hr_lite/statutory_rate_card.rb
|
|
262
276
|
- lib/hr_lite/version.rb
|