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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +218 -1
- data/README.md +312 -99
- data/app/assets/javascripts/hr_lite/confirm.js +31 -0
- data/app/assets/stylesheets/hr_lite/hr_lite.css +28 -0
- data/app/controllers/hr_lite/admin/attendances_controller.rb +19 -5
- data/app/controllers/hr_lite/admin/audit_logs_controller.rb +4 -1
- data/app/controllers/hr_lite/admin/employees_controller.rb +36 -12
- data/app/controllers/hr_lite/admin/leave_balances_controller.rb +19 -13
- data/app/controllers/hr_lite/admin/leave_requests_controller.rb +16 -0
- data/app/controllers/hr_lite/admin/payroll_runs_controller.rb +11 -2
- data/app/controllers/hr_lite/admin/resignations_controller.rb +17 -5
- data/app/controllers/hr_lite/admin/salary_slips_controller.rb +22 -8
- data/app/controllers/hr_lite/admin/superadmin_controller.rb +5 -0
- data/app/controllers/hr_lite/org_controller.rb +6 -1
- data/app/controllers/hr_lite/resignations_controller.rb +6 -1
- data/app/helpers/hr_lite/application_helper.rb +2 -12
- data/app/jobs/hr_lite/application_job.rb +5 -0
- data/app/jobs/hr_lite/daily_digest_job.rb +1 -1
- data/app/jobs/hr_lite/leave_year_rollover_job.rb +1 -1
- data/app/jobs/hr_lite/payroll_auto_draft_job.rb +5 -2
- data/app/models/concerns/hr_lite/audited.rb +11 -4
- data/app/models/hr_lite/appraisal.rb +9 -1
- data/app/models/hr_lite/audit_log.rb +36 -0
- data/app/models/hr_lite/comp_off_request.rb +8 -16
- data/app/models/hr_lite/designation_change.rb +18 -1
- data/app/models/hr_lite/employee_profile.rb +5 -1
- data/app/models/hr_lite/leave_balance.rb +51 -5
- data/app/models/hr_lite/leave_request.rb +36 -5
- data/app/models/hr_lite/leave_type.rb +13 -3
- data/app/models/hr_lite/payroll_run.rb +68 -13
- data/app/models/hr_lite/regularization_request.rb +11 -4
- data/app/models/hr_lite/resignation.rb +27 -0
- data/app/models/hr_lite/salary_slip.rb +8 -5
- data/app/services/hr_lite/attendance_puncher.rb +8 -1
- data/app/services/hr_lite/calculators/esi.rb +7 -4
- data/app/services/hr_lite/calculators/professional_tax.rb +5 -1
- data/app/services/hr_lite/calculators/tds.rb +8 -1
- data/app/services/hr_lite/leave_day_counter.rb +9 -4
- data/app/services/hr_lite/overview_query.rb +12 -1
- data/app/services/hr_lite/payroll_run_processor.rb +9 -1
- data/app/services/hr_lite/slip_builder.rb +54 -8
- data/app/views/hr_lite/admin/attendances/index.html.erb +4 -1
- data/app/views/hr_lite/admin/comp_off_requests/show.html.erb +19 -5
- data/app/views/hr_lite/admin/employees/_form.html.erb +20 -2
- data/app/views/hr_lite/admin/employees/show.html.erb +25 -15
- data/app/views/hr_lite/admin/leave_requests/show.html.erb +37 -5
- data/app/views/hr_lite/admin/overview/index.html.erb +21 -0
- data/app/views/hr_lite/admin/payroll_runs/show.html.erb +3 -1
- data/app/views/hr_lite/admin/regularization_requests/show.html.erb +18 -4
- data/app/views/hr_lite/attendance/_punch_card.html.erb +22 -2
- data/app/views/hr_lite/career/show.html.erb +7 -1
- data/app/views/hr_lite/salary_slips/_slip_detail.html.erb +5 -1
- data/app/views/hr_lite/salary_slips/pdf.html.erb +5 -5
- data/app/views/layouts/hr_lite/application.html.erb +42 -4
- data/config/routes.rb +1 -1
- data/db/migrate/20260807184103_add_live_uniqueness_indexes_to_hr_lite.rb +28 -0
- data/db/migrate/20260807184426_add_fy_opening_to_hr_lite_employee_profiles.rb +14 -0
- data/db/migrate/20260807184939_add_out_of_window_days_to_hr_lite_salary_slips.rb +12 -0
- data/db/migrate/20260817182124_add_status_constraints_and_appraisal_fk_to_hr_lite.rb +63 -0
- data/lib/hr_lite/amount_in_words.rb +15 -2
- data/lib/hr_lite/configuration.rb +3 -6
- data/lib/hr_lite/financial_year.rb +26 -0
- data/lib/hr_lite/money.rb +18 -0
- data/lib/hr_lite/notifications.rb +23 -8
- data/lib/hr_lite/statutory_rate_card.rb +51 -6
- data/lib/hr_lite/version.rb +1 -1
- data/lib/hr_lite.rb +23 -2
- 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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
|
|
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,
|
|
62
|
-
deliver_leadership_bells(event, row, bell_to,
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
7
|
-
#
|
|
8
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
data/lib/hr_lite/version.rb
CHANGED
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
|
|
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
|
-
|
|
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.
|
|
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
|