hr_lite 0.5.0 → 0.5.3

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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +218 -1
  3. data/README.md +312 -99
  4. data/app/assets/javascripts/hr_lite/confirm.js +31 -0
  5. data/app/assets/stylesheets/hr_lite/hr_lite.css +28 -0
  6. data/app/controllers/hr_lite/admin/attendances_controller.rb +19 -5
  7. data/app/controllers/hr_lite/admin/audit_logs_controller.rb +4 -1
  8. data/app/controllers/hr_lite/admin/employees_controller.rb +36 -12
  9. data/app/controllers/hr_lite/admin/leave_balances_controller.rb +19 -13
  10. data/app/controllers/hr_lite/admin/leave_requests_controller.rb +16 -0
  11. data/app/controllers/hr_lite/admin/payroll_runs_controller.rb +11 -2
  12. data/app/controllers/hr_lite/admin/resignations_controller.rb +17 -5
  13. data/app/controllers/hr_lite/admin/salary_slips_controller.rb +22 -8
  14. data/app/controllers/hr_lite/admin/superadmin_controller.rb +5 -0
  15. data/app/controllers/hr_lite/org_controller.rb +6 -1
  16. data/app/controllers/hr_lite/resignations_controller.rb +6 -1
  17. data/app/helpers/hr_lite/application_helper.rb +2 -12
  18. data/app/jobs/hr_lite/application_job.rb +5 -0
  19. data/app/jobs/hr_lite/daily_digest_job.rb +1 -1
  20. data/app/jobs/hr_lite/leave_year_rollover_job.rb +1 -1
  21. data/app/jobs/hr_lite/payroll_auto_draft_job.rb +5 -2
  22. data/app/models/concerns/hr_lite/audited.rb +11 -4
  23. data/app/models/hr_lite/appraisal.rb +9 -1
  24. data/app/models/hr_lite/audit_log.rb +36 -0
  25. data/app/models/hr_lite/comp_off_request.rb +8 -16
  26. data/app/models/hr_lite/designation_change.rb +18 -1
  27. data/app/models/hr_lite/employee_profile.rb +5 -1
  28. data/app/models/hr_lite/leave_balance.rb +51 -5
  29. data/app/models/hr_lite/leave_request.rb +36 -5
  30. data/app/models/hr_lite/leave_type.rb +13 -3
  31. data/app/models/hr_lite/payroll_run.rb +68 -13
  32. data/app/models/hr_lite/regularization_request.rb +11 -4
  33. data/app/models/hr_lite/resignation.rb +27 -0
  34. data/app/models/hr_lite/salary_slip.rb +8 -5
  35. data/app/services/hr_lite/attendance_puncher.rb +8 -1
  36. data/app/services/hr_lite/calculators/esi.rb +7 -4
  37. data/app/services/hr_lite/calculators/professional_tax.rb +5 -1
  38. data/app/services/hr_lite/calculators/tds.rb +8 -1
  39. data/app/services/hr_lite/leave_day_counter.rb +9 -4
  40. data/app/services/hr_lite/overview_query.rb +12 -1
  41. data/app/services/hr_lite/payroll_run_processor.rb +9 -1
  42. data/app/services/hr_lite/slip_builder.rb +54 -8
  43. data/app/views/hr_lite/admin/attendances/index.html.erb +4 -1
  44. data/app/views/hr_lite/admin/comp_off_requests/show.html.erb +19 -5
  45. data/app/views/hr_lite/admin/employees/_form.html.erb +20 -2
  46. data/app/views/hr_lite/admin/employees/show.html.erb +25 -15
  47. data/app/views/hr_lite/admin/leave_requests/show.html.erb +37 -5
  48. data/app/views/hr_lite/admin/overview/index.html.erb +21 -0
  49. data/app/views/hr_lite/admin/payroll_runs/show.html.erb +3 -1
  50. data/app/views/hr_lite/admin/regularization_requests/show.html.erb +18 -4
  51. data/app/views/hr_lite/attendance/_punch_card.html.erb +22 -2
  52. data/app/views/hr_lite/career/show.html.erb +7 -1
  53. data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +5 -1
  54. data/app/views/hr_lite/salary_slips/pdf.html.erb +5 -5
  55. data/app/views/layouts/hr_lite/application.html.erb +42 -4
  56. data/config/routes.rb +1 -1
  57. data/db/migrate/20260807184103_add_live_uniqueness_indexes_to_hr_lite.rb +28 -0
  58. data/db/migrate/20260807184426_add_fy_opening_to_hr_lite_employee_profiles.rb +14 -0
  59. data/db/migrate/20260807184939_add_out_of_window_days_to_hr_lite_salary_slips.rb +12 -0
  60. data/db/migrate/20260817182124_add_status_constraints_and_appraisal_fk_to_hr_lite.rb +63 -0
  61. data/lib/hr_lite/amount_in_words.rb +15 -2
  62. data/lib/hr_lite/configuration.rb +3 -6
  63. data/lib/hr_lite/financial_year.rb +26 -0
  64. data/lib/hr_lite/money.rb +18 -0
  65. data/lib/hr_lite/notifications.rb +23 -8
  66. data/lib/hr_lite/statutory_rate_card.rb +51 -6
  67. data/lib/hr_lite/version.rb +1 -1
  68. data/lib/hr_lite.rb +23 -2
  69. metadata +7 -1
@@ -0,0 +1,63 @@
1
+ class AddStatusConstraintsAndAppraisalFkToHrLite < ActiveRecord::Migration[8.1]
2
+ # Every status column in the engine is a plain string validated only in
3
+ # Ruby, so anything that skips validations — update_column, update_all,
4
+ # insert_all, a console fix, a future migration — can write a value no
5
+ # screen renders and no transition accepts. These constraints put the
6
+ # allowed set somewhere it cannot be bypassed.
7
+ #
8
+ # Deliberately in step with each model's own STATUSES array: adding a
9
+ # status now means writing a migration, which is the point.
10
+ STATUSES = {
11
+ hr_lite_attendance_records: %w[present half_day],
12
+ hr_lite_leave_requests: %w[pending approved rejected cancelled],
13
+ hr_lite_comp_off_requests: %w[pending approved rejected cancelled],
14
+ hr_lite_regularization_requests: %w[pending approved rejected cancelled],
15
+ hr_lite_resignations: %w[pending accepted withdrawn],
16
+ hr_lite_appraisals: %w[draft shared],
17
+ hr_lite_payroll_runs: %w[draft processing review finalized published]
18
+ }.freeze
19
+
20
+ def up
21
+ # On an install that already holds an off-list value the constraint build
22
+ # fails with a bare integrity error naming neither the row nor the value.
23
+ # Say what is actually in the way instead — this runs on live data.
24
+ offenders = STATUSES.filter_map do |table, statuses|
25
+ found = select_values(
26
+ "SELECT DISTINCT status FROM #{table} WHERE status NOT IN (#{quoted(statuses)})"
27
+ )
28
+ "#{table}: #{found.join(', ')}" if found.any?
29
+ end
30
+
31
+ if offenders.any?
32
+ raise ActiveRecord::IrreversibleMigration,
33
+ "Cannot constrain status columns — these rows hold values no model " \
34
+ "declares. Correct them first:\n #{offenders.join("\n ")}"
35
+ end
36
+
37
+ STATUSES.each do |table, statuses|
38
+ add_check_constraint table, status_sql(statuses), name: constraint_name(table)
39
+ end
40
+
41
+ # has_one :designation_change, dependent: :nullify — with neither an index
42
+ # nor a foreign key. Destroying an appraisal scanned the whole table to
43
+ # find its promotion row, and nothing stopped that row from being left
44
+ # pointing at an appraisal that no longer exists.
45
+ add_index :hr_lite_designation_changes, :appraisal_id
46
+ add_foreign_key :hr_lite_designation_changes, :hr_lite_appraisals,
47
+ column: :appraisal_id, on_delete: :nullify
48
+ end
49
+
50
+ def down
51
+ remove_foreign_key :hr_lite_designation_changes, column: :appraisal_id
52
+ remove_index :hr_lite_designation_changes, :appraisal_id
53
+ STATUSES.each_key { |table| remove_check_constraint table, name: constraint_name(table) }
54
+ end
55
+
56
+ private
57
+
58
+ def status_sql(statuses) = "status IN (#{quoted(statuses)})"
59
+
60
+ def quoted(statuses) = statuses.map { |s| connection.quote(s) }.join(", ")
61
+
62
+ def constraint_name(table) = "#{table}_status_check"
63
+ end
@@ -14,15 +14,28 @@ module HrLite
14
14
  rupees = Money.round_rupee(amount).to_i
15
15
  return "Zero rupees" if rupees.zero?
16
16
 
17
+ # A negative amount rendered as bare " rupees": no group was >= its
18
+ # divisor, so nothing was ever appended.
19
+ sentence = groups(rupees.abs).join(" ")
20
+ sentence = "minus #{sentence}" if rupees.negative?
21
+ "#{sentence.capitalize} rupees"
22
+ end
23
+
24
+ def groups(rupees)
17
25
  parts = []
18
26
  SCALES.each do |divisor, label|
19
27
  next unless rupees >= divisor
20
28
 
21
- parts << "#{two_digit(rupees / divisor)} #{label}"
29
+ # The crore group is unbounded — every later group is reduced below
30
+ # 100 by the preceding modulo, but this one is not, and two_digit
31
+ # only knows 0..99. Recurse for anything above a hundred crore.
32
+ count = rupees / divisor
33
+ spelled = count > 99 ? groups(count).join(" ") : two_digit(count)
34
+ parts << "#{spelled} #{label}"
22
35
  rupees %= divisor
23
36
  end
24
37
  parts << two_digit(rupees) if rupees.positive?
25
- "#{parts.join(' ').capitalize} rupees"
38
+ parts
26
39
  end
27
40
 
28
41
  def two_digit(number)
@@ -46,16 +46,13 @@ module HrLite
46
46
  @currency_symbol = "₹"
47
47
  @on_designation_change = ->(user, designation) { }
48
48
  @leadership_emails = []
49
- @leadership_check = ->(user) do
50
- emails = HrLite.config.leadership_emails.map { |e| e.to_s.downcase.strip }
51
- emails.include?(user.email.to_s.downcase)
52
- end
49
+ @leadership_check = ->(user) { HrLite.email_listed?(user, HrLite.config.leadership_emails) }
53
50
  # Money tier: salary structures, payroll, slips, appraisals. Empty
54
51
  # list means "same as leadership" (pre-0.5.0 behaviour).
55
52
  @superadmin_emails = []
56
53
  @superadmin_check = ->(user) do
57
- emails = HrLite.config.superadmin_emails.map { |e| e.to_s.downcase.strip }.reject(&:empty?)
58
- emails.empty? ? HrLite.leadership?(user) : emails.include?(user.email.to_s.downcase)
54
+ list = HrLite.normalize_email_list(HrLite.config.superadmin_emails)
55
+ list.empty? ? HrLite.leadership?(user) : HrLite.email_listed?(user, list)
59
56
  end
60
57
  @extra_stylesheets = [] # host stylesheets linked AFTER hr_lite.css (CSS-var overrides)
61
58
  @mailer_from = "hr@example.com"
@@ -0,0 +1,26 @@
1
+ module HrLite
2
+ # The Indian financial year runs 1 April – 31 March. Three places worked
3
+ # this out for themselves (the slip's year-to-date sums, the TDS projector
4
+ # and the rate card); it lives here once so they cannot drift apart.
5
+ module FinancialYear
6
+ START_MONTH = 4
7
+
8
+ # First day of the FY containing `date`. March 2027 belongs to the year
9
+ # that opened on 1 April 2026.
10
+ def self.start_for(date)
11
+ year = date.month >= START_MONTH ? date.year : date.year - 1
12
+ Date.new(year, START_MONTH, 1)
13
+ end
14
+
15
+ # "2026-27" — for warnings and screens, never for arithmetic.
16
+ def self.label(date)
17
+ start = start_for(date)
18
+ "#{start.year}-#{format('%02d', (start.year + 1) % 100)}"
19
+ end
20
+
21
+ # True when `date` falls in an earlier FY than `other`.
22
+ def self.before?(date, other)
23
+ start_for(date) < start_for(other)
24
+ end
25
+ end
26
+ end
data/lib/hr_lite/money.rb CHANGED
@@ -29,5 +29,23 @@ module HrLite
29
29
  def round_to_10(value)
30
30
  (d(value) / 10).round(0, BigDecimal::ROUND_HALF_UP) * 10
31
31
  end
32
+
33
+ # Indian digit grouping: 12,34,567 — not the western 1,234,567. It lives
34
+ # here rather than only in the view helper because the slip PDF is
35
+ # rendered by HOST code (config.render_pdf), where engine helpers are out
36
+ # of scope — which is why the PDF printed a bare "75000.0" next to the
37
+ # web slip's "₹75,000.00".
38
+ def format(amount)
39
+ return "—" if amount.nil?
40
+
41
+ whole, fraction = round2(amount).to_s("F").split(".")
42
+ sign = whole.start_with?("-") ? "-" : ""
43
+ whole = whole.delete_prefix("-")
44
+ if whole.length > 3
45
+ head = whole[0..-4].reverse.scan(/\d{1,2}/).join(",").reverse
46
+ whole = "#{head},#{whole[-3..]}"
47
+ end
48
+ "#{sign}#{HrLite.config.currency_symbol}#{whole}.#{fraction.ljust(2, '0')}"
49
+ end
32
50
  end
33
51
  end
@@ -49,17 +49,27 @@ module HrLite
49
49
  DEFAULT_MATRIX.merge(HrLite.config.notification_matrix || {})
50
50
  end
51
51
 
52
- def publish(event, title:, body: nil, path: nil, bell_to: [], email_to: [], lines: [], diff: nil, link_url: nil)
52
+ # `leadership:` overrides title/body/path for the leadership copies of an
53
+ # event. Some events are written in the second person and point at an
54
+ # employee-scoped path ("Your appraisal has been shared", /appraisals/7):
55
+ # fanned out verbatim, leadership read them as being about themselves and
56
+ # followed a link their own scope 404s or resolves to their own record.
57
+ def publish(event, title:, body: nil, path: nil, bell_to: [], email_to: [], lines: [],
58
+ diff: nil, link_url: nil, leadership: {})
53
59
  row = matrix[event.to_s]
54
60
  unless row
55
61
  Rails.logger.warn("[hr_lite] unknown notification event #{event}")
56
62
  return
57
63
  end
58
64
 
65
+ lead_title = leadership[:title] || title
66
+ lead_body = leadership.key?(:body) ? leadership[:body] : body
67
+ lead_path = leadership[:path] || path
68
+
59
69
  deliver_bells(event, row, bell_to, title, body, path)
60
70
  deliver_emails(row, email_to, title, body, path, lines, link_url)
61
- deliver_leadership_email(event, row, title, body, path, lines, diff)
62
- deliver_leadership_bells(event, row, bell_to, title, body, path)
71
+ deliver_leadership_email(event, row, lead_title, lead_body, lead_path, lines, diff)
72
+ deliver_leadership_bells(event, row, bell_to, lead_title, lead_body, lead_path)
63
73
  nil
64
74
  end
65
75
 
@@ -76,15 +86,20 @@ module HrLite
76
86
  def deliver_emails(row, email_to, title, body, path, lines, link_url = nil)
77
87
  return unless row[:email]
78
88
 
89
+ # Rescued PER RECIPIENT. Wrapping the whole loop meant one bad address
90
+ # or a single enqueue failure silently dropped everyone after it — on
91
+ # a team-wide leave notice, most of the team.
79
92
  Array(email_to).compact.uniq.each do |user|
80
93
  next if user.email.blank?
81
94
 
82
- EventMailer.event(to: user.email, subject: title, heading: title,
83
- body: body, lines: lines, path: path,
84
- link_url: link_url).deliver_later
95
+ begin
96
+ EventMailer.event(to: user.email, subject: title, heading: title,
97
+ body: body, lines: lines, path: path,
98
+ link_url: link_url).deliver_later
99
+ rescue => e
100
+ Rails.logger.error("[hr_lite] event email failed: #{e.class}: #{e.message}")
101
+ end
85
102
  end
86
- rescue => e
87
- Rails.logger.error("[hr_lite] event email failed: #{e.class}: #{e.message}")
88
103
  end
89
104
 
90
105
  def deliver_leadership_email(event, row, title, body, path, lines, diff)
@@ -3,9 +3,13 @@ module HrLite
3
3
  # is a one-hash edit that gets code review and a spec diff, never an
4
4
  # inline-constant hunt.
5
5
  #
6
- # VERIFY WITH A CA before the first run of any new financial year; the
7
- # FY 2026-27 card ships with FY 2025-26 figures pending confirmation of
8
- # the Feb 2026 Finance Act.
6
+ # VERIFY WITH A CA before the first run of any new financial year. Adding
7
+ # a year is one new dated entry in CARDS; nothing else changes.
8
+ #
9
+ # When no card exists for a run's financial year the lookup falls back to
10
+ # the newest one it has — payroll cannot simply stop every April — but
11
+ # `warning_for` says so on the run, and `PayrollRunProcessor` puts that
12
+ # sentence in front of every other warning until a card is added.
9
13
  module StatutoryRateCard
10
14
  def self.r(value) = BigDecimal(value.to_s)
11
15
  private_class_method :r
@@ -24,11 +28,16 @@ module HrLite
24
28
  "none" => [],
25
29
  "uttar_pradesh" => [],
26
30
  "uttarakhand" => [],
27
- "karnataka" => [ { above: r("24999"), monthly: r("200") } ]
31
+ # Inclusive lower bounds. `above: 24999` with a strict `>` taxed a
32
+ # prorated gross of ₹24,999.50, which is below the real threshold.
33
+ "karnataka" => [ { from: r("25000"), monthly: r("200") } ]
28
34
  },
29
35
  income_tax: {
30
36
  "new" => {
31
37
  standard_deduction: r("75000"), rebate_cap: r("1200000"), cess_rate: r("0.04"),
38
+ # §115BAC carries marginal relief just above the rebate cap; the
39
+ # old regime does not, so this is a per-regime flag.
40
+ marginal_relief: true,
32
41
  slabs: [
33
42
  [ r("0"), r("400000"), r("0") ],
34
43
  [ r("400000"), r("800000"), r("0.05") ],
@@ -53,8 +62,44 @@ module HrLite
53
62
  }.freeze
54
63
 
55
64
  def self.for(period_month)
56
- effective = CARDS.keys.sort.reverse.find { |date| date <= period_month }
57
- effective ? CARDS[effective] : CARDS[CARDS.keys.min]
65
+ CARDS[effective_date_for(period_month)]
66
+ end
67
+
68
+ # Which card a run will actually use. A month older than every card
69
+ # borrows the earliest one — see `predates_cards?`, which says so.
70
+ def self.effective_date_for(period_month)
71
+ CARDS.keys.sort.reverse.find { |date| date <= period_month } || CARDS.keys.min
72
+ end
73
+
74
+ # The card in force is from an EARLIER financial year than the run. The
75
+ # lookup still returns figures — it has to, or payroll would stop dead
76
+ # every April — so the run carries the warning instead.
77
+ def self.stale_for?(period_month)
78
+ FinancialYear.before?(effective_date_for(period_month), period_month)
79
+ end
80
+
81
+ # The run is older than every card we ship, so it is being computed on
82
+ # rates that had not been announced yet.
83
+ def self.predates_cards?(period_month)
84
+ period_month < CARDS.keys.min
85
+ end
86
+
87
+ # One sentence naming both the run's FY and the card's, or nil when they
88
+ # match. Rendered verbatim in the run's warnings list.
89
+ def self.warning_for(period_month)
90
+ card_fy = FinancialYear.label(effective_date_for(period_month))
91
+ run_fy = FinancialYear.label(period_month)
92
+
93
+ if predates_cards?(period_month)
94
+ "Payroll for FY #{run_fy} is being computed on the FY #{card_fy} " \
95
+ "statutory card — no card ships for a year that early. PF, ESI, PT " \
96
+ "and TDS on this run are not the rates that applied. Verify with your CA."
97
+ elsif stale_for?(period_month)
98
+ "Payroll for FY #{run_fy} is being computed on the FY #{card_fy} " \
99
+ "statutory card — no card ships for FY #{run_fy} yet. PF, ESI, PT and " \
100
+ "TDS on this run use last year's rates. Add a CA-verified card for " \
101
+ "FY #{run_fy} before publishing."
102
+ end
58
103
  end
59
104
  end
60
105
  end
@@ -1,3 +1,3 @@
1
1
  module HrLite
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.3"
3
3
  end
data/lib/hr_lite.rb CHANGED
@@ -3,6 +3,7 @@ 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"
6
7
  require "hr_lite/mention_parser"
7
8
  require "hr_lite/notifications"
8
9
  require "hr_lite/seeds"
@@ -42,18 +43,38 @@ module HrLite
42
43
  user.present? && !!config.superadmin_check.call(user)
43
44
  end
44
45
 
46
+ # An access list, cleaned. "a@x.com,,b@x.com".split(",") — one stray
47
+ # comma in an ENV var — used to put "" in the list, and a user whose
48
+ # email was blank then MATCHED it and was handed the tier.
49
+ def normalize_email_list(emails)
50
+ Array(emails).map { |e| e.to_s.downcase.strip }.reject(&:empty?)
51
+ end
52
+
53
+ # Whether a user's own email is on a configured access list. A blank
54
+ # email is never on any list, however the list is spelled.
55
+ def email_listed?(user, emails)
56
+ address = user.respond_to?(:email) ? user.email.to_s.downcase.strip : ""
57
+ return false if address.empty?
58
+
59
+ normalize_email_list(emails).include?(address)
60
+ end
61
+
45
62
  # Leadership members resolvable to actual user records (for bell
46
63
  # notifications). Emails configured but absent from the user table are
47
64
  # still reachable by email — see Notifications.
48
65
  def leadership_users
49
- emails = config.leadership_emails.map { |e| e.to_s.downcase.strip }.reject(&:empty?)
66
+ emails = normalize_email_list(config.leadership_emails)
50
67
  return user_klass.none if emails.empty?
51
68
 
52
69
  user_klass.where("LOWER(#{user_klass.table_name}.email) IN (?)", emails)
53
70
  end
54
71
 
72
+ # Every domain event that bells the admins calls this. Loading and
73
+ # instantiating the entire users table to run admin_check per row is fine
74
+ # at ten staff and silly at a thousand — so it starts from employees_scope
75
+ # (which a host narrows to real staff) rather than every row that exists.
55
76
  def admin_users
56
- user_klass.all.select { |u| admin?(u) }
77
+ employees.select { |u| admin?(u) }
57
78
  end
58
79
 
59
80
  # Everyone HR tracks (host-overridable to exclude bots/test accounts),
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.0
4
+ version: 0.5.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - kshitiz sinha
@@ -50,6 +50,7 @@ files:
50
50
  - MIT-LICENSE
51
51
  - README.md
52
52
  - Rakefile
53
+ - app/assets/javascripts/hr_lite/confirm.js
53
54
  - app/assets/javascripts/hr_lite/geo_punch.js
54
55
  - app/assets/javascripts/hr_lite/mention.js
55
56
  - app/assets/stylesheets/hr_lite/application.css
@@ -238,6 +239,10 @@ files:
238
239
  - db/migrate/20260719104317_create_hr_lite_regularization_requests.rb
239
240
  - db/migrate/20260719162154_add_manager_to_hr_lite_employee_profiles.rb
240
241
  - db/migrate/20260720094454_add_employee_code_prefix_to_hr_lite_settings.rb
242
+ - db/migrate/20260807184103_add_live_uniqueness_indexes_to_hr_lite.rb
243
+ - db/migrate/20260807184426_add_fy_opening_to_hr_lite_employee_profiles.rb
244
+ - db/migrate/20260807184939_add_out_of_window_days_to_hr_lite_salary_slips.rb
245
+ - db/migrate/20260817182124_add_status_constraints_and_appraisal_fk_to_hr_lite.rb
241
246
  - lib/generators/hr_lite/install/install_generator.rb
242
247
  - lib/generators/hr_lite/install/templates/AFTER_INSTALL
243
248
  - lib/generators/hr_lite/install/templates/initializer.rb
@@ -246,6 +251,7 @@ files:
246
251
  - lib/hr_lite/configuration.rb
247
252
  - lib/hr_lite/current.rb
248
253
  - lib/hr_lite/engine.rb
254
+ - lib/hr_lite/financial_year.rb
249
255
  - lib/hr_lite/geo.rb
250
256
  - lib/hr_lite/leave_year.rb
251
257
  - lib/hr_lite/mention_parser.rb