hr_lite 0.5.2 → 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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +112 -1
  3. data/README.md +67 -31
  4. data/app/controllers/hr_lite/admin/attendances_controller.rb +21 -6
  5. data/app/controllers/hr_lite/admin/base_controller.rb +16 -6
  6. data/app/controllers/hr_lite/admin/comp_off_requests_controller.rb +16 -4
  7. data/app/controllers/hr_lite/admin/leadership_controller.rb +11 -8
  8. data/app/controllers/hr_lite/admin/leave_balances_controller.rb +4 -5
  9. data/app/controllers/hr_lite/admin/leave_requests_controller.rb +21 -5
  10. data/app/controllers/hr_lite/admin/payroll_runs_controller.rb +11 -2
  11. data/app/controllers/hr_lite/admin/regularization_requests_controller.rb +16 -4
  12. data/app/controllers/hr_lite/admin/role_assignments_controller.rb +63 -0
  13. data/app/controllers/hr_lite/admin/roles_controller.rb +73 -0
  14. data/app/controllers/hr_lite/admin/salary_slips_controller.rb +5 -6
  15. data/app/controllers/hr_lite/admin/superadmin_controller.rb +15 -11
  16. data/app/controllers/hr_lite/application_controller.rb +39 -1
  17. data/app/helpers/hr_lite/application_helper.rb +5 -2
  18. data/app/models/hr_lite/audit_log.rb +26 -1
  19. data/app/models/hr_lite/comp_off_request.rb +3 -1
  20. data/app/models/hr_lite/leave_request.rb +27 -4
  21. data/app/models/hr_lite/payroll_run.rb +30 -4
  22. data/app/models/hr_lite/regularization_request.rb +3 -4
  23. data/app/models/hr_lite/role.rb +66 -0
  24. data/app/models/hr_lite/role_assignment.rb +18 -0
  25. data/app/models/hr_lite/role_grant.rb +15 -0
  26. data/app/models/hr_lite/salary_slip.rb +1 -2
  27. data/app/services/hr_lite/access.rb +111 -0
  28. data/app/services/hr_lite/payroll_run_processor.rb +4 -1
  29. data/app/views/hr_lite/admin/roles/_form.html.erb +47 -0
  30. data/app/views/hr_lite/admin/roles/edit.html.erb +53 -0
  31. data/app/views/hr_lite/admin/roles/index.html.erb +49 -0
  32. data/app/views/hr_lite/admin/roles/new.html.erb +3 -0
  33. data/config/routes.rb +3 -0
  34. data/db/migrate/20260817182124_add_status_constraints_and_appraisal_fk_to_hr_lite.rb +63 -0
  35. data/db/migrate/20260817183347_create_hr_lite_roles_and_grants.rb +49 -0
  36. data/db/migrate/20260817183629_seed_hr_lite_roles_from_email_tiers.rb +82 -0
  37. data/lib/generators/hr_lite/install/templates/initializer.rb +13 -7
  38. data/lib/hr_lite/configuration.rb +11 -7
  39. data/lib/hr_lite/current.rb +4 -0
  40. data/lib/hr_lite/financial_year.rb +26 -0
  41. data/lib/hr_lite/permissions.rb +79 -0
  42. data/lib/hr_lite/role_seeds.rb +81 -0
  43. data/lib/hr_lite/statutory_rate_card.rb +45 -5
  44. data/lib/hr_lite/version.rb +1 -1
  45. data/lib/hr_lite.rb +81 -11
  46. data/lib/tasks/hr_lite_tasks.rake +10 -2
  47. metadata +17 -1
data/lib/hr_lite.rb CHANGED
@@ -3,6 +3,9 @@ require "hr_lite/engine"
3
3
  require "hr_lite/configuration"
4
4
  require "hr_lite/current"
5
5
  require "hr_lite/leave_year"
6
+ require "hr_lite/financial_year"
7
+ require "hr_lite/permissions"
8
+ require "hr_lite/role_seeds"
6
9
  require "hr_lite/mention_parser"
7
10
  require "hr_lite/notifications"
8
11
  require "hr_lite/seeds"
@@ -30,34 +33,101 @@ module HrLite
30
33
  config.user_class.constantize
31
34
  end
32
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
+
33
60
  def admin?(user)
34
- user.present? && !!config.admin_check.call(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)
35
65
  end
36
66
 
37
67
  def leadership?(user)
38
- user.present? && !!config.leadership_check.call(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)
39
72
  end
40
73
 
41
74
  def superadmin?(user)
42
- user.present? && !!config.superadmin_check.call(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)
79
+ end
80
+
81
+ # An access list, cleaned. "a@x.com,,b@x.com".split(",") — one stray
82
+ # comma in an ENV var — used to put "" in the list, and a user whose
83
+ # email was blank then MATCHED it and was handed the tier.
84
+ def normalize_email_list(emails)
85
+ Array(emails).map { |e| e.to_s.downcase.strip }.reject(&:empty?)
86
+ end
87
+
88
+ # Whether a user's own email is on a configured access list. A blank
89
+ # email is never on any list, however the list is spelled.
90
+ def email_listed?(user, emails)
91
+ address = user.respond_to?(:email) ? user.email.to_s.downcase.strip : ""
92
+ return false if address.empty?
93
+
94
+ normalize_email_list(emails).include?(address)
43
95
  end
44
96
 
45
97
  # Leadership members resolvable to actual user records (for bell
46
- # notifications). Emails configured but absent from the user table are
47
- # 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.
48
100
  def leadership_users
49
- emails = config.leadership_emails.map { |e| e.to_s.downcase.strip }.reject(&:empty?)
101
+ unless config.legacy_tier_checks
102
+ return users_holding("profile.manage", scope: :all)
103
+ end
104
+
105
+ emails = normalize_email_list(config.leadership_emails)
50
106
  return user_klass.none if emails.empty?
51
107
 
52
108
  user_klass.where("LOWER(#{user_klass.table_name}.email) IN (?)", emails)
53
109
  end
54
110
 
55
- # Every domain event that bells the admins calls this. Loading and
56
- # instantiating the entire users table to run admin_check per row is fine
57
- # at ten staff and silly at a thousand — so it starts from employees_scope
58
- # (which a host narrows to real staff) rather than every row that exists.
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.
59
127
  def admin_users
60
- 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 }
61
131
  end
62
132
 
63
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
- created = HrLite::Seeds.run!
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.5.2
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
@@ -242,6 +252,9 @@ files:
242
252
  - db/migrate/20260807184103_add_live_uniqueness_indexes_to_hr_lite.rb
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
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
245
258
  - lib/generators/hr_lite/install/install_generator.rb
246
259
  - lib/generators/hr_lite/install/templates/AFTER_INSTALL
247
260
  - lib/generators/hr_lite/install/templates/initializer.rb
@@ -250,11 +263,14 @@ files:
250
263
  - lib/hr_lite/configuration.rb
251
264
  - lib/hr_lite/current.rb
252
265
  - lib/hr_lite/engine.rb
266
+ - lib/hr_lite/financial_year.rb
253
267
  - lib/hr_lite/geo.rb
254
268
  - lib/hr_lite/leave_year.rb
255
269
  - lib/hr_lite/mention_parser.rb
256
270
  - lib/hr_lite/money.rb
257
271
  - lib/hr_lite/notifications.rb
272
+ - lib/hr_lite/permissions.rb
273
+ - lib/hr_lite/role_seeds.rb
258
274
  - lib/hr_lite/seeds.rb
259
275
  - lib/hr_lite/statutory_rate_card.rb
260
276
  - lib/hr_lite/version.rb