hr_lite 0.1.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 (176) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +33 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.md +166 -0
  5. data/Rakefile +3 -0
  6. data/app/assets/javascripts/hr_lite/geo_punch.js +61 -0
  7. data/app/assets/javascripts/hr_lite/mention.js +100 -0
  8. data/app/assets/stylesheets/hr_lite/application.css +15 -0
  9. data/app/assets/stylesheets/hr_lite/hr_lite.css +239 -0
  10. data/app/controllers/hr_lite/admin/appraisals_controller.rb +74 -0
  11. data/app/controllers/hr_lite/admin/attendances_controller.rb +79 -0
  12. data/app/controllers/hr_lite/admin/audit_logs_controller.rb +9 -0
  13. data/app/controllers/hr_lite/admin/base_controller.rb +16 -0
  14. data/app/controllers/hr_lite/admin/designation_changes_controller.rb +33 -0
  15. data/app/controllers/hr_lite/admin/employees_controller.rb +53 -0
  16. data/app/controllers/hr_lite/admin/holidays_controller.rb +70 -0
  17. data/app/controllers/hr_lite/admin/leadership_controller.rb +18 -0
  18. data/app/controllers/hr_lite/admin/leave_balances_controller.rb +41 -0
  19. data/app/controllers/hr_lite/admin/leave_requests_controller.rb +41 -0
  20. data/app/controllers/hr_lite/admin/leave_types_controller.rb +54 -0
  21. data/app/controllers/hr_lite/admin/office_locations_controller.rb +46 -0
  22. data/app/controllers/hr_lite/admin/overview_controller.rb +12 -0
  23. data/app/controllers/hr_lite/admin/payroll_runs_controller.rb +93 -0
  24. data/app/controllers/hr_lite/admin/salary_slips_controller.rb +44 -0
  25. data/app/controllers/hr_lite/admin/salary_structures_controller.rb +49 -0
  26. data/app/controllers/hr_lite/admin/settings_controller.rb +18 -0
  27. data/app/controllers/hr_lite/application_controller.rb +76 -0
  28. data/app/controllers/hr_lite/appraisals_controller.rb +18 -0
  29. data/app/controllers/hr_lite/attendance_controller.rb +37 -0
  30. data/app/controllers/hr_lite/calendar_controller.rb +20 -0
  31. data/app/controllers/hr_lite/career_controller.rb +9 -0
  32. data/app/controllers/hr_lite/employee_profiles_controller.rb +9 -0
  33. data/app/controllers/hr_lite/holidays_controller.rb +9 -0
  34. data/app/controllers/hr_lite/home_controller.rb +7 -0
  35. data/app/controllers/hr_lite/kudos_controller.rb +36 -0
  36. data/app/controllers/hr_lite/leave_balances_controller.rb +11 -0
  37. data/app/controllers/hr_lite/leave_requests_controller.rb +56 -0
  38. data/app/controllers/hr_lite/salary_slips_controller.rb +39 -0
  39. data/app/controllers/hr_lite/users_controller.rb +9 -0
  40. data/app/helpers/hr_lite/application_helper.rb +105 -0
  41. data/app/jobs/hr_lite/application_job.rb +4 -0
  42. data/app/jobs/hr_lite/daily_digest_job.rb +33 -0
  43. data/app/jobs/hr_lite/leave_year_rollover_job.rb +29 -0
  44. data/app/mailers/hr_lite/application_mailer.rb +5 -0
  45. data/app/mailers/hr_lite/event_mailer.rb +33 -0
  46. data/app/models/concerns/hr_lite/audited.rb +70 -0
  47. data/app/models/concerns/hr_lite/encrypted_money.rb +32 -0
  48. data/app/models/hr_lite/application_record.rb +5 -0
  49. data/app/models/hr_lite/appraisal.rb +89 -0
  50. data/app/models/hr_lite/attendance_record.rb +41 -0
  51. data/app/models/hr_lite/audit_log.rb +17 -0
  52. data/app/models/hr_lite/designation_change.rb +52 -0
  53. data/app/models/hr_lite/employee_profile.rb +72 -0
  54. data/app/models/hr_lite/holiday.rb +18 -0
  55. data/app/models/hr_lite/kudo.rb +54 -0
  56. data/app/models/hr_lite/kudo_mention.rb +8 -0
  57. data/app/models/hr_lite/leave_balance.rb +42 -0
  58. data/app/models/hr_lite/leave_request.rb +207 -0
  59. data/app/models/hr_lite/leave_type.rb +22 -0
  60. data/app/models/hr_lite/office_location.rb +21 -0
  61. data/app/models/hr_lite/payroll_run.rb +112 -0
  62. data/app/models/hr_lite/salary_slip.rb +78 -0
  63. data/app/models/hr_lite/salary_structure.rb +49 -0
  64. data/app/models/hr_lite/setting.rb +14 -0
  65. data/app/services/hr_lite/attendance_puncher.rb +103 -0
  66. data/app/services/hr_lite/attendance_summary.rb +98 -0
  67. data/app/services/hr_lite/calculators/esi.rb +25 -0
  68. data/app/services/hr_lite/calculators/pf.rb +32 -0
  69. data/app/services/hr_lite/calculators/professional_tax.rb +20 -0
  70. data/app/services/hr_lite/calculators/proration.rb +30 -0
  71. data/app/services/hr_lite/calculators/tds.rb +74 -0
  72. data/app/services/hr_lite/day_status.rb +49 -0
  73. data/app/services/hr_lite/leave_day_counter.rb +24 -0
  74. data/app/services/hr_lite/overview_query.rb +39 -0
  75. data/app/services/hr_lite/payroll_run_processor.rb +46 -0
  76. data/app/services/hr_lite/pdf_renderer.rb +23 -0
  77. data/app/services/hr_lite/slip_builder.rb +106 -0
  78. data/app/services/hr_lite/working_calendar.rb +41 -0
  79. data/app/views/hr_lite/admin/appraisals/_form.html.erb +29 -0
  80. data/app/views/hr_lite/admin/appraisals/edit.html.erb +6 -0
  81. data/app/views/hr_lite/admin/appraisals/new.html.erb +6 -0
  82. data/app/views/hr_lite/admin/attendances/index.html.erb +49 -0
  83. data/app/views/hr_lite/admin/attendances/show.html.erb +55 -0
  84. data/app/views/hr_lite/admin/audit_logs/index.html.erb +39 -0
  85. data/app/views/hr_lite/admin/designation_changes/new.html.erb +19 -0
  86. data/app/views/hr_lite/admin/employees/_form.html.erb +40 -0
  87. data/app/views/hr_lite/admin/employees/edit.html.erb +3 -0
  88. data/app/views/hr_lite/admin/employees/index.html.erb +40 -0
  89. data/app/views/hr_lite/admin/employees/new.html.erb +3 -0
  90. data/app/views/hr_lite/admin/employees/show.html.erb +75 -0
  91. data/app/views/hr_lite/admin/holidays/index.html.erb +59 -0
  92. data/app/views/hr_lite/admin/leave_balances/index.html.erb +58 -0
  93. data/app/views/hr_lite/admin/leave_requests/index.html.erb +35 -0
  94. data/app/views/hr_lite/admin/leave_requests/show.html.erb +39 -0
  95. data/app/views/hr_lite/admin/leave_types/_form.html.erb +27 -0
  96. data/app/views/hr_lite/admin/leave_types/edit.html.erb +10 -0
  97. data/app/views/hr_lite/admin/leave_types/index.html.erb +39 -0
  98. data/app/views/hr_lite/admin/leave_types/new.html.erb +3 -0
  99. data/app/views/hr_lite/admin/office_locations/_form.html.erb +18 -0
  100. data/app/views/hr_lite/admin/office_locations/edit.html.erb +10 -0
  101. data/app/views/hr_lite/admin/office_locations/index.html.erb +30 -0
  102. data/app/views/hr_lite/admin/office_locations/new.html.erb +3 -0
  103. data/app/views/hr_lite/admin/overview/index.html.erb +72 -0
  104. data/app/views/hr_lite/admin/payroll_runs/index.html.erb +33 -0
  105. data/app/views/hr_lite/admin/payroll_runs/new.html.erb +20 -0
  106. data/app/views/hr_lite/admin/payroll_runs/show.html.erb +76 -0
  107. data/app/views/hr_lite/admin/salary_slips/show.html.erb +42 -0
  108. data/app/views/hr_lite/admin/salary_structures/_form.html.erb +25 -0
  109. data/app/views/hr_lite/admin/salary_structures/edit.html.erb +6 -0
  110. data/app/views/hr_lite/admin/salary_structures/new.html.erb +6 -0
  111. data/app/views/hr_lite/admin/settings/edit.html.erb +18 -0
  112. data/app/views/hr_lite/appraisals/index.html.erb +22 -0
  113. data/app/views/hr_lite/appraisals/show.html.erb +32 -0
  114. data/app/views/hr_lite/attendance/_month_grid.html.erb +41 -0
  115. data/app/views/hr_lite/attendance/_punch_card.html.erb +41 -0
  116. data/app/views/hr_lite/attendance/show.html.erb +22 -0
  117. data/app/views/hr_lite/calendar/show.html.erb +45 -0
  118. data/app/views/hr_lite/career/show.html.erb +49 -0
  119. data/app/views/hr_lite/employee_profiles/show.html.erb +22 -0
  120. data/app/views/hr_lite/event_mailer/event.html.erb +10 -0
  121. data/app/views/hr_lite/event_mailer/event.text.erb +10 -0
  122. data/app/views/hr_lite/event_mailer/leadership.html.erb +27 -0
  123. data/app/views/hr_lite/event_mailer/leadership.text.erb +16 -0
  124. data/app/views/hr_lite/holidays/_next_holiday.html.erb +12 -0
  125. data/app/views/hr_lite/holidays/index.html.erb +30 -0
  126. data/app/views/hr_lite/home/index.html.erb +25 -0
  127. data/app/views/hr_lite/kudos/_kudo.html.erb +17 -0
  128. data/app/views/hr_lite/kudos/index.html.erb +39 -0
  129. data/app/views/hr_lite/leave_balances/index.html.erb +19 -0
  130. data/app/views/hr_lite/leave_requests/_balance_chips.html.erb +17 -0
  131. data/app/views/hr_lite/leave_requests/index.html.erb +37 -0
  132. data/app/views/hr_lite/leave_requests/new.html.erb +36 -0
  133. data/app/views/hr_lite/leave_requests/show.html.erb +25 -0
  134. data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +48 -0
  135. data/app/views/hr_lite/salary_slips/index.html.erb +29 -0
  136. data/app/views/hr_lite/salary_slips/pdf.html.erb +72 -0
  137. data/app/views/hr_lite/salary_slips/show.html.erb +12 -0
  138. data/app/views/hr_lite/shared/_form_errors.html.erb +10 -0
  139. data/app/views/hr_lite/shared/_pagination.html.erb +11 -0
  140. data/app/views/layouts/hr_lite/application.html.erb +56 -0
  141. data/app/views/layouts/hr_lite/mailer.html.erb +30 -0
  142. data/app/views/layouts/hr_lite/mailer.text.erb +6 -0
  143. data/app/views/layouts/hr_lite/pdf.html.erb +26 -0
  144. data/config/routes.rb +57 -0
  145. data/db/migrate/20260718190726_create_hr_lite_kudos.rb +14 -0
  146. data/db/migrate/20260718190727_create_hr_lite_kudo_mentions.rb +12 -0
  147. data/db/migrate/20260718190728_create_hr_lite_audit_logs.rb +15 -0
  148. data/db/migrate/20260718192342_create_hr_lite_office_locations.rb +13 -0
  149. data/db/migrate/20260718192343_create_hr_lite_attendance_records.rb +27 -0
  150. data/db/migrate/20260718193045_create_hr_lite_holidays.rb +14 -0
  151. data/db/migrate/20260718193046_create_hr_lite_leave_types.rb +20 -0
  152. data/db/migrate/20260718193047_create_hr_lite_leave_balances.rb +17 -0
  153. data/db/migrate/20260718193048_create_hr_lite_leave_requests.rb +23 -0
  154. data/db/migrate/20260718193049_create_hr_lite_settings.rb +10 -0
  155. data/db/migrate/20260718194534_create_hr_lite_employee_profiles.rb +27 -0
  156. data/db/migrate/20260718194535_create_hr_lite_salary_structures.rb +23 -0
  157. data/db/migrate/20260718194536_create_hr_lite_payroll_runs.rb +20 -0
  158. data/db/migrate/20260718194537_create_hr_lite_salary_slips.rb +28 -0
  159. data/db/migrate/20260718195953_create_hr_lite_appraisals.rb +22 -0
  160. data/db/migrate/20260718195954_create_hr_lite_designation_changes.rb +17 -0
  161. data/lib/generators/hr_lite/install/install_generator.rb +29 -0
  162. data/lib/generators/hr_lite/install/templates/AFTER_INSTALL +35 -0
  163. data/lib/generators/hr_lite/install/templates/initializer.rb +45 -0
  164. data/lib/hr_lite/configuration.rb +60 -0
  165. data/lib/hr_lite/current.rb +7 -0
  166. data/lib/hr_lite/engine.rb +39 -0
  167. data/lib/hr_lite/geo.rb +22 -0
  168. data/lib/hr_lite/mention_parser.rb +18 -0
  169. data/lib/hr_lite/money.rb +33 -0
  170. data/lib/hr_lite/notifications.rb +99 -0
  171. data/lib/hr_lite/seeds.rb +42 -0
  172. data/lib/hr_lite/statutory_rate_card.rb +60 -0
  173. data/lib/hr_lite/version.rb +3 -0
  174. data/lib/hr_lite.rb +104 -0
  175. data/lib/tasks/hr_lite_tasks.rake +8 -0
  176. metadata +250 -0
@@ -0,0 +1,56 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title><%= [ content_for(:page_title), "Earthly HR" ].compact.join(" · ") %></title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
+ <%= csrf_meta_tags %>
7
+ <%= csp_meta_tag %>
8
+ <%= yield :head %>
9
+ <%= stylesheet_link_tag "hr_lite/hr_lite", media: "all" %>
10
+ <% HrLite.config.extra_stylesheets.each do |sheet| %>
11
+ <%= stylesheet_link_tag sheet, media: "all" %>
12
+ <% end %>
13
+ </head>
14
+ <body class="hrl-body">
15
+ <header class="hrl-appbar">
16
+ <div class="hrl-appbar__inner">
17
+ <a class="hrl-appbar__brand" href="<%= hr_lite.root_path %>"><%= HrLite.config.company.call[:name].presence || "HR" %></a>
18
+ <nav class="hrl-nav hrl-nav--top" aria-label="HR">
19
+ <% hrl_nav_items(HrLite::ApplicationHelper::NAV_ITEMS).each do |item| %>
20
+ <%= hrl_nav_link(item) %>
21
+ <% end %>
22
+ <% if hr_admin? || hr_leadership? %>
23
+ <span class="hrl-nav__sep" aria-hidden="true"></span>
24
+ <% hrl_nav_items(HrLite::ApplicationHelper::ADMIN_NAV_ITEMS).each do |item| %>
25
+ <%= hrl_nav_link(item) %>
26
+ <% end %>
27
+ <% end %>
28
+ <% if hr_leadership? %>
29
+ <% hrl_nav_items(HrLite::ApplicationHelper::LEADERSHIP_NAV_ITEMS).each do |item| %>
30
+ <%= hrl_nav_link(item) %>
31
+ <% end %>
32
+ <% end %>
33
+ <% if HrLite.config.back_link %>
34
+ <a class="hrl-nav__link hrl-nav__link--back" href="<%= HrLite.config.back_link[:url] %>"><%= HrLite.config.back_link[:label] %></a>
35
+ <% end %>
36
+ </nav>
37
+ <span class="hrl-appbar__user"><%= hr_display_name(hr_current_user) %></span>
38
+ </div>
39
+ </header>
40
+
41
+ <main class="hrl-main">
42
+ <% if flash[:notice].present? %><div class="hrl-flash hrl-flash--ok" role="status"><%= flash[:notice] %></div><% end %>
43
+ <% if flash[:alert].present? %><div class="hrl-flash hrl-flash--warn" role="alert"><%= flash[:alert] %></div><% end %>
44
+ <%= yield %>
45
+ </main>
46
+
47
+ <nav class="hrl-tabbar" aria-label="HR sections">
48
+ <% hrl_nav_items(HrLite::ApplicationHelper::NAV_ITEMS).first(5).each do |item| %>
49
+ <%= hrl_nav_link(item) %>
50
+ <% end %>
51
+ </nav>
52
+
53
+ <%= javascript_include_tag "hr_lite/mention", defer: true %>
54
+ <%= yield :scripts %>
55
+ </body>
56
+ </html>
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5
+ <style>
6
+ body { margin: 0; background: #f4f5f7; font-family: -apple-system, "Segoe UI", Roboto, Arial, sans-serif; color: #111827; }
7
+ .wrap { max-width: 560px; margin: 0 auto; padding: 24px 16px; }
8
+ .card { background: #ffffff; border: 1px solid #e5e7eb; border-radius: 12px; padding: 24px; }
9
+ .brand { font-size: 13px; color: #6b7280; margin-bottom: 12px; }
10
+ h1 { font-size: 18px; margin: 0 0 12px; }
11
+ p { font-size: 14px; line-height: 1.55; }
12
+ .lines { margin: 16px 0; padding: 0; }
13
+ .lines li { font-size: 14px; margin: 4px 0 4px 18px; }
14
+ .diff { width: 100%; border-collapse: collapse; margin: 16px 0; font-size: 13px; }
15
+ .diff th, .diff td { text-align: left; border-bottom: 1px solid #e5e7eb; padding: 6px 8px; }
16
+ .diff th { color: #6b7280; font-weight: 600; }
17
+ .btn { display: inline-block; background: #2563eb; color: #ffffff !important; padding: 10px 18px; border-radius: 8px; text-decoration: none; font-size: 14px; font-weight: 600; margin-top: 8px; }
18
+ .foot { font-size: 12px; color: #6b7280; margin-top: 16px; }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <div class="wrap">
23
+ <div class="card">
24
+ <div class="brand"><%= HrLite.config.company.call[:name].presence || "HR" %></div>
25
+ <%= yield %>
26
+ <p class="foot">Sent by the HR system. Do not reply to this email.</p>
27
+ </div>
28
+ </div>
29
+ </body>
30
+ </html>
@@ -0,0 +1,6 @@
1
+ <%= HrLite.config.company.call[:name].presence || "HR" %>
2
+
3
+ <%= yield %>
4
+
5
+ --
6
+ Sent by the HR system. Do not reply to this email.
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
5
+ <style>
6
+ body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; color: #111; font-size: 12px; margin: 0; }
7
+ .pdf-card { border: 1px solid #ddd; border-radius: 10px; padding: 18px; margin-bottom: 14px; }
8
+ .pdf-head { display: flex; justify-content: space-between; align-items: flex-start; }
9
+ .pdf-company { font-size: 16px; font-weight: 700; }
10
+ .pdf-muted { color: #666; }
11
+ h1 { font-size: 15px; margin: 0 0 4px; }
12
+ table { width: 100%; border-collapse: collapse; margin: 8px 0; }
13
+ th, td { text-align: left; padding: 6px 8px; border-bottom: 1px solid #e5e5e5; }
14
+ th { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: #666; }
15
+ td.num, th.num { text-align: right; }
16
+ tr.total td { font-weight: 700; border-top: 2px solid #111; }
17
+ .pdf-net { background: #f0f7f2; border-radius: 8px; padding: 12px 16px; font-size: 14px; font-weight: 700; }
18
+ .pdf-foot { font-size: 9px; color: #888; margin-top: 16px; }
19
+ .cols { display: flex; gap: 14px; }
20
+ .cols > div { flex: 1; }
21
+ </style>
22
+ </head>
23
+ <body>
24
+ <%= yield %>
25
+ </body>
26
+ </html>
data/config/routes.rb ADDED
@@ -0,0 +1,57 @@
1
+ HrLite::Engine.routes.draw do
2
+ root "home#index"
3
+
4
+ resources :kudos, only: %i[index create destroy]
5
+ get "users/search", to: "users#search"
6
+
7
+ resource :attendance, only: :show, controller: "attendance" do
8
+ post :check_in
9
+ post :check_out
10
+ end
11
+
12
+ resources :leave_requests, only: %i[index show new create] do
13
+ member { post :cancel }
14
+ end
15
+ resources :leave_balances, only: :index
16
+ resources :holidays, only: :index
17
+ get "calendar", to: "calendar#show"
18
+ resources :salary_slips, only: %i[index show]
19
+ resource :employee_profile, only: :show, path: "profile"
20
+ get "career", to: "career#show"
21
+ resources :appraisals, only: %i[index show]
22
+
23
+ namespace :admin do
24
+ get "overview", to: "overview#index"
25
+ resources :attendances, only: %i[index show update], param: :user_id
26
+ resources :leave_requests, only: %i[index show] do
27
+ member { post :approve; post :reject }
28
+ end
29
+ resources :leave_balances, only: :index do
30
+ collection { post :adjust }
31
+ end
32
+ resources :leave_types, except: :show
33
+ resources :office_locations, except: :show
34
+ resources :holidays, only: %i[index create update destroy] do
35
+ collection { post :bulk_create }
36
+ end
37
+ resources :employees, except: :destroy do
38
+ resources :salary_structures, only: %i[new create edit update]
39
+ resources :appraisals, only: %i[new create edit update destroy] do
40
+ member { post :share }
41
+ end
42
+ resources :designation_changes, only: %i[new create]
43
+ end
44
+ resources :payroll_runs, only: %i[index show new create destroy] do
45
+ member do
46
+ post :compute
47
+ post :finalize
48
+ post :unlock
49
+ post :publish
50
+ get :register
51
+ end
52
+ end
53
+ resources :salary_slips, only: %i[show update]
54
+ resource :setting, only: %i[edit update]
55
+ resources :audit_logs, only: :index
56
+ end
57
+ end
@@ -0,0 +1,14 @@
1
+ class CreateHrLiteKudos < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_kudos do |t|
4
+ # No FK to the host user table — its name is host-specific.
5
+ t.bigint :giver_id, null: false
6
+ t.string :badge
7
+ t.text :message, null: false
8
+
9
+ t.timestamps
10
+ end
11
+ add_index :hr_lite_kudos, :giver_id
12
+ add_index :hr_lite_kudos, :created_at
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ class CreateHrLiteKudoMentions < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_kudo_mentions do |t|
4
+ t.references :kudo, null: false, foreign_key: { to_table: :hr_lite_kudos, on_delete: :cascade }
5
+ t.bigint :user_id, null: false
6
+
7
+ t.timestamps
8
+ end
9
+ add_index :hr_lite_kudo_mentions, [ :kudo_id, :user_id ], unique: true
10
+ add_index :hr_lite_kudo_mentions, :user_id
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ class CreateHrLiteAuditLogs < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_audit_logs do |t|
4
+ t.bigint :actor_id
5
+ t.string :action, null: false
6
+ t.string :subject_type, null: false
7
+ t.bigint :subject_id, null: false
8
+ t.json :audited_changes, null: false, default: {}
9
+
10
+ t.datetime :created_at, null: false
11
+ end
12
+ add_index :hr_lite_audit_logs, [ :subject_type, :subject_id ]
13
+ add_index :hr_lite_audit_logs, :created_at
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ class CreateHrLiteOfficeLocations < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_office_locations do |t|
4
+ t.string :name, null: false
5
+ t.decimal :lat, precision: 9, scale: 6, null: false
6
+ t.decimal :lng, precision: 9, scale: 6, null: false
7
+ t.integer :radius_m, null: false, default: 200
8
+ t.boolean :active, null: false, default: true
9
+
10
+ t.timestamps
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ class CreateHrLiteAttendanceRecords < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_attendance_records do |t|
4
+ t.bigint :user_id, null: false
5
+ t.date :date, null: false
6
+ t.datetime :check_in_at
7
+ t.datetime :check_out_at
8
+ t.decimal :check_in_lat, precision: 9, scale: 6
9
+ t.decimal :check_in_lng, precision: 9, scale: 6
10
+ t.integer :check_in_accuracy_m
11
+ t.decimal :check_out_lat, precision: 9, scale: 6
12
+ t.decimal :check_out_lng, precision: 9, scale: 6
13
+ t.integer :check_out_accuracy_m
14
+ t.string :status, null: false, default: "present"
15
+ t.boolean :flagged, null: false, default: false
16
+ t.string :flag_note
17
+ t.bigint :regularized_by_id
18
+ t.datetime :regularized_at
19
+ t.text :regularization_note
20
+
21
+ t.timestamps
22
+ end
23
+ add_index :hr_lite_attendance_records, [ :user_id, :date ], unique: true
24
+ add_index :hr_lite_attendance_records, :date
25
+ add_index :hr_lite_attendance_records, [ :date, :flagged ]
26
+ end
27
+ end
@@ -0,0 +1,14 @@
1
+ class CreateHrLiteHolidays < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_holidays do |t|
4
+ t.date :date, null: false
5
+ t.string :name, null: false
6
+ # Optional/restricted holidays render on the calendar but do NOT
7
+ # count as working-day exclusions.
8
+ t.boolean :optional, null: false, default: false
9
+
10
+ t.timestamps
11
+ end
12
+ add_index :hr_lite_holidays, :date, unique: true
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ class CreateHrLiteLeaveTypes < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_leave_types do |t|
4
+ t.string :name, null: false
5
+ t.string :code, null: false
6
+ t.string :color, null: false, default: "#0ea5e9"
7
+ t.boolean :paid, null: false, default: true
8
+ # nil quota = unlimited (LWP).
9
+ t.decimal :annual_quota, precision: 4, scale: 1
10
+ t.string :accrual, null: false, default: "yearly_upfront"
11
+ t.decimal :carry_forward_cap, precision: 4, scale: 1, null: false, default: 0
12
+ t.boolean :active, null: false, default: true
13
+ t.integer :position, null: false, default: 0
14
+
15
+ t.timestamps
16
+ end
17
+ add_index :hr_lite_leave_types, :code, unique: true
18
+ add_index :hr_lite_leave_types, :name, unique: true
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ class CreateHrLiteLeaveBalances < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_leave_balances do |t|
4
+ t.bigint :user_id, null: false
5
+ t.references :leave_type, null: false, foreign_key: { to_table: :hr_lite_leave_types }
6
+ t.integer :year, null: false
7
+ # Only carry-in and manual adjustments are materialized; entitlement
8
+ # and usage are computed live so they self-heal.
9
+ t.decimal :carried_forward, precision: 4, scale: 1, null: false, default: 0
10
+ t.decimal :adjustment, precision: 5, scale: 1, null: false, default: 0
11
+ t.string :adjustment_note
12
+
13
+ t.timestamps
14
+ end
15
+ add_index :hr_lite_leave_balances, [ :user_id, :leave_type_id, :year ], unique: true
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ class CreateHrLiteLeaveRequests < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_leave_requests do |t|
4
+ t.bigint :user_id, null: false
5
+ t.references :leave_type, null: false, foreign_key: { to_table: :hr_lite_leave_types }
6
+ t.date :start_date, null: false
7
+ t.date :end_date, null: false
8
+ t.boolean :half_day, null: false, default: false
9
+ t.text :reason
10
+ t.string :status, null: false, default: "pending"
11
+ # Display cache; balance math always recomputes live.
12
+ t.decimal :days_count, precision: 4, scale: 1, null: false
13
+ t.bigint :decided_by_id
14
+ t.datetime :decided_at
15
+ t.text :decision_note
16
+
17
+ t.timestamps
18
+ end
19
+ add_index :hr_lite_leave_requests, [ :user_id, :status ]
20
+ add_index :hr_lite_leave_requests, [ :status, :start_date ]
21
+ add_index :hr_lite_leave_requests, [ :start_date, :end_date ]
22
+ end
23
+ end
@@ -0,0 +1,10 @@
1
+ class CreateHrLiteSettings < ActiveRecord::Migration[8.1]
2
+ def change
3
+ # Singleton row — engine-owned settings (weekend policy for now).
4
+ create_table :hr_lite_settings do |t|
5
+ t.string :weekend_policy, null: false, default: "sat_sun"
6
+
7
+ t.timestamps
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,27 @@
1
+ class CreateHrLiteEmployeeProfiles < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_employee_profiles do |t|
4
+ t.bigint :user_id, null: false
5
+ t.string :employee_code, null: false
6
+ t.string :designation
7
+ t.date :date_of_birth
8
+ t.date :date_of_joining, null: false
9
+ t.date :date_of_exit
10
+ t.string :department
11
+ t.string :work_location
12
+ # Encrypted (Rails AR encryption — text columns):
13
+ t.text :pan_number
14
+ t.text :pf_uan
15
+ t.text :esi_number
16
+ t.text :bank_account_number
17
+ t.text :bank_ifsc
18
+ t.text :declared_annual_deductions
19
+ t.string :bank_name
20
+ t.string :tax_regime, null: false, default: "new"
21
+
22
+ t.timestamps
23
+ end
24
+ add_index :hr_lite_employee_profiles, :user_id, unique: true
25
+ add_index :hr_lite_employee_profiles, :employee_code, unique: true
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ class CreateHrLiteSalaryStructures < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_salary_structures do |t|
4
+ t.bigint :user_id, null: false
5
+ # Always the 1st of a month — revisions take effect from the 1st only.
6
+ t.date :effective_from, null: false
7
+ # Encrypted money (text columns, BigDecimal via EncryptedMoney):
8
+ t.text :basic
9
+ t.text :hra
10
+ t.text :special_allowance
11
+ t.text :other_earnings
12
+ t.boolean :pf_applicable, null: false, default: true
13
+ t.boolean :pf_on_full_basic, null: false, default: false
14
+ t.boolean :esi_applicable, null: false, default: true
15
+ t.string :pt_state, null: false, default: "none"
16
+ t.text :notes
17
+ t.bigint :created_by_id
18
+
19
+ t.timestamps
20
+ end
21
+ add_index :hr_lite_salary_structures, [ :user_id, :effective_from ], unique: true
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ class CreateHrLitePayrollRuns < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_payroll_runs do |t|
4
+ t.date :period_month, null: false
5
+ t.string :status, null: false, default: "draft"
6
+ t.json :warnings, null: false, default: []
7
+ t.datetime :processed_at
8
+ t.datetime :finalized_at
9
+ t.datetime :published_at
10
+ t.bigint :created_by_id
11
+ t.bigint :finalized_by_id
12
+ t.bigint :published_by_id
13
+ t.text :notes
14
+
15
+ t.timestamps
16
+ end
17
+ add_index :hr_lite_payroll_runs, :period_month, unique: true
18
+ add_index :hr_lite_payroll_runs, :status
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ class CreateHrLiteSalarySlips < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_salary_slips do |t|
4
+ t.references :payroll_run, null: false,
5
+ foreign_key: { to_table: :hr_lite_payroll_runs, on_delete: :cascade }
6
+ t.bigint :user_id, null: false
7
+ t.date :period_month, null: false
8
+ t.integer :days_in_month, null: false
9
+ t.decimal :payable_days, precision: 5, scale: 2, null: false
10
+ t.decimal :lop_days, precision: 5, scale: 2, null: false, default: 0
11
+ t.decimal :lop_override, precision: 5, scale: 2
12
+ # Encrypted money / encrypted JSON (text columns):
13
+ t.text :tds_override
14
+ t.text :earnings
15
+ t.text :deductions
16
+ t.text :employer_costs
17
+ t.text :tax_details
18
+ t.text :gross_earnings
19
+ t.text :total_deductions
20
+ t.text :net_pay
21
+ t.datetime :computed_at
22
+
23
+ t.timestamps
24
+ end
25
+ add_index :hr_lite_salary_slips, [ :payroll_run_id, :user_id ], unique: true
26
+ add_index :hr_lite_salary_slips, [ :user_id, :period_month ], unique: true
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ class CreateHrLiteAppraisals < ActiveRecord::Migration[8.1]
2
+ def change
3
+ create_table :hr_lite_appraisals do |t|
4
+ t.bigint :user_id, null: false
5
+ t.bigint :reviewer_id, null: false
6
+ t.date :period_start, null: false
7
+ t.date :period_end, null: false
8
+ t.integer :rating
9
+ t.text :strengths
10
+ t.text :improvements
11
+ t.string :outcome, null: false, default: "none"
12
+ t.date :effective_date
13
+ t.string :new_designation
14
+ t.string :status, null: false, default: "draft"
15
+ t.datetime :shared_at
16
+
17
+ t.timestamps
18
+ end
19
+ add_index :hr_lite_appraisals, [ :user_id, :period_end ]
20
+ add_index :hr_lite_appraisals, :status
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ class CreateHrLiteDesignationChanges < ActiveRecord::Migration[8.1]
2
+ def change
3
+ # Append-only career history.
4
+ create_table :hr_lite_designation_changes do |t|
5
+ t.bigint :user_id, null: false
6
+ t.string :from_designation
7
+ t.string :to_designation, null: false
8
+ t.date :effective_date, null: false
9
+ t.text :note
10
+ t.bigint :appraisal_id
11
+ t.bigint :created_by_id
12
+
13
+ t.timestamps
14
+ end
15
+ add_index :hr_lite_designation_changes, [ :user_id, :effective_date ]
16
+ end
17
+ end
@@ -0,0 +1,29 @@
1
+ require "rails/generators"
2
+ require "rails/generators/base"
3
+
4
+ module HrLite
5
+ module Generators
6
+ # `rails g hr_lite:install` — drops the annotated initializer and prints
7
+ # the remaining wiring steps. With --route it also appends a mount line.
8
+ class InstallGenerator < Rails::Generators::Base
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ class_option :route, type: :boolean, default: false,
12
+ desc: "Append `mount HrLite::Engine => \"/hr\"` to config/routes.rb"
13
+
14
+ def copy_initializer
15
+ template "initializer.rb", "config/initializers/hr_lite.rb"
16
+ end
17
+
18
+ def add_route
19
+ return unless options[:route]
20
+
21
+ route 'mount HrLite::Engine => "/hr", as: :hr_lite'
22
+ end
23
+
24
+ def show_next_steps
25
+ readme "AFTER_INSTALL" if behavior == :invoke
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,35 @@
1
+
2
+ hr_lite is almost ready. Remaining steps:
3
+
4
+ 1. Mount it (skip if you passed --route):
5
+
6
+ # config/routes.rb — a path…
7
+ mount HrLite::Engine => "/hr", as: :hr_lite
8
+ # …or a subdomain:
9
+ constraints subdomain: "hr" do
10
+ mount HrLite::Engine => "/", as: :hr_lite
11
+ end
12
+
13
+ 2. Run the migrations — they load straight from the gem, nothing to copy:
14
+
15
+ bin/rails db:migrate
16
+
17
+ (Prefer copies in your repo? `bin/rails hr_lite:install:migrations`
18
+ first — the engine detects the copies and steps aside.)
19
+
20
+ 3. Seed leave types + fixed national holidays (idempotent, deploy-safe):
21
+
22
+ bin/rails hr_lite:seed
23
+
24
+ 4. Ensure ActiveRecord encryption keys exist (payroll PII is encrypted):
25
+
26
+ bin/rails db:encryption:init # if you don't have keys yet
27
+
28
+ 5. Schedule the recurring jobs in your job scheduler:
29
+
30
+ HrLite::DailyDigestJob # each morning
31
+ HrLite::LeaveYearRolloverJob # Jan 1
32
+
33
+ 6. Review config/initializers/hr_lite.rb — set leadership emails, then
34
+ restart. Docs: https://github.com/kshtzkr/hr_lite/tree/main/docs
35
+
@@ -0,0 +1,45 @@
1
+ # hr_lite — every host integration point in one place.
2
+ # Full reference: https://github.com/kshtzkr/hr_lite/blob/main/docs/CONFIGURATION.md
3
+ HrLite.configure do |c|
4
+ # Inherit your auth stack. MUST be set before boot; restart after changing.
5
+ c.parent_controller = "ApplicationController"
6
+ c.user_class = "User"
7
+ c.current_user_method = :current_user
8
+ c.authenticate_method = :authenticate_user!
9
+
10
+ # Operations tier (team attendance, leave decisions, overview board).
11
+ c.admin_check = ->(user) { user.respond_to?(:admin?) && user.admin? }
12
+
13
+ # Governing tier — ONLY these people change policy, employee profiles,
14
+ # salary structures, payroll and appraisals. Keep it in an env var so
15
+ # changing leadership never needs a deploy.
16
+ c.leadership_emails = ENV.fetch("HR_LEADERSHIP_EMAILS", "").split(",").map(&:strip)
17
+
18
+ # Where the portal is reachable (subdomain or path). Enables email link
19
+ # buttons and HrLite.public_url / HrLite.public_url? for deep links.
20
+ # c.public_url_base = "https://hr.example.com"
21
+
22
+ c.mailer_from = "hr@example.com"
23
+ c.company = -> { { name: "Your Company", address: nil, logo_path: nil } }
24
+ c.time_zone = "Asia/Kolkata"
25
+
26
+ # In-app notifications -> your bell/notification system (optional).
27
+ # c.notify = ->(user:, kind:, title:, body:, path:) {
28
+ # Notifier.notify(user, kind: kind, title: title, body: body,
29
+ # path: HrLite.public_url(path))
30
+ # }
31
+
32
+ # Salary-slip PDFs: plug your renderer, or add `gem "wicked_pdf"` +
33
+ # wkhtmltopdf and the built-in renderer takes over (optional).
34
+ # c.render_pdf = ->(template:, assigns:, cache_key:) { ... }
35
+
36
+ # Mirror promotions into your own user model (optional).
37
+ # c.on_designation_change = ->(user, designation) { user.update!(designation: designation) }
38
+
39
+ # Retheme via CSS variables (optional): create a stylesheet overriding
40
+ # --hrl-* vars and list it here.
41
+ # c.extra_stylesheets = [ "hr_lite_overrides" ]
42
+
43
+ # Escape hatch back to your app in the HR nav (optional).
44
+ # c.back_link = { label: "Back to app", url: "https://app.example.com" }
45
+ end