Module: HrLite

Defined in:
lib/hr_lite.rb,
lib/hr_lite/geo.rb,
lib/hr_lite/money.rb,
lib/hr_lite/seeds.rb,
lib/hr_lite/engine.rb,
lib/hr_lite/current.rb,
lib/hr_lite/version.rb,
lib/hr_lite/leave_year.rb,
lib/hr_lite/role_seeds.rb,
app/models/hr_lite/kudo.rb,
app/models/hr_lite/loan.rb,
app/models/hr_lite/role.rb,
lib/hr_lite/permissions.rb,
app/models/hr_lite/policy.rb,
lib/hr_lite/configuration.rb,
lib/hr_lite/notifications.rb,
app/models/hr_lite/benefit.rb,
app/models/hr_lite/expense.rb,
app/models/hr_lite/holiday.rb,
app/models/hr_lite/setting.rb,
lib/hr_lite/financial_year.rb,
lib/hr_lite/mention_parser.rb,
app/models/hr_lite/approval.rb,
app/models/hr_lite/document.rb,
app/services/hr_lite/access.rb,
lib/hr_lite/amount_in_words.rb,
lib/hr_lite/statutory_seeds.rb,
app/models/hr_lite/appraisal.rb,
app/models/hr_lite/audit_log.rb,
app/models/hr_lite/hr_request.rb,
app/models/hr_lite/leave_type.rb,
app/models/hr_lite/role_grant.rb,
app/services/hr_lite/team_day.rb,
app/models/hr_lite/payroll_run.rb,
app/models/hr_lite/resignation.rb,
app/models/hr_lite/salary_slip.rb,
app/models/hr_lite/kudo_mention.rb,
app/services/hr_lite/day_status.rb,
lib/hr_lite/statutory_rate_card.rb,
app/jobs/hr_lite/application_job.rb,
app/mailers/hr_lite/event_mailer.rb,
app/models/hr_lite/approval_flow.rb,
app/models/hr_lite/approval_step.rb,
app/models/hr_lite/leave_balance.rb,
app/models/hr_lite/leave_request.rb,
app/jobs/hr_lite/daily_digest_job.rb,
app/models/hr_lite/loan_repayment.rb,
app/services/hr_lite/pdf_renderer.rb,
app/services/hr_lite/slip_builder.rb,
app/models/hr_lite/office_location.rb,
app/models/hr_lite/role_assignment.rb,
app/models/hr_lite/tax_declaration.rb,
app/models/concerns/hr_lite/audited.rb,
app/models/hr_lite/comp_off_request.rb,
app/models/hr_lite/employee_profile.rb,
app/models/hr_lite/expense_category.rb,
app/models/hr_lite/salary_component.rb,
app/models/hr_lite/salary_structure.rb,
app/services/hr_lite/approval_route.rb,
app/services/hr_lite/calculators/pf.rb,
app/services/hr_lite/overview_query.rb,
app/jobs/hr_lite/document_expiry_job.rb,
app/models/hr_lite/attendance_record.rb,
app/models/hr_lite/benefit_enrolment.rb,
app/models/hr_lite/payroll_line_item.rb,
app/services/hr_lite/calculators/esi.rb,
app/services/hr_lite/calculators/tds.rb,
app/models/hr_lite/application_record.rb,
app/models/hr_lite/designation_change.rb,
app/services/hr_lite/working_calendar.rb,
app/controllers/hr_lite/org_controller.rb,
app/helpers/hr_lite/application_helper.rb,
app/mailers/hr_lite/application_mailer.rb,
app/models/concerns/hr_lite/approvable.rb,
app/models/hr_lite/approval_delegation.rb,
app/services/hr_lite/leave_day_counter.rb,
app/controllers/hr_lite/home_controller.rb,
app/controllers/hr_lite/team_controller.rb,
app/jobs/hr_lite/payroll_auto_draft_job.rb,
app/models/hr_lite/tax_declaration_item.rb,
app/services/hr_lite/attendance_puncher.rb,
app/services/hr_lite/attendance_summary.rb,
app/controllers/hr_lite/kudos_controller.rb,
app/controllers/hr_lite/users_controller.rb,
app/jobs/hr_lite/approval_escalation_job.rb,
app/jobs/hr_lite/leave_year_rollover_job.rb,
app/models/hr_lite/professional_tax_slab.rb,
app/controllers/hr_lite/career_controller.rb,
app/models/hr_lite/policy_acknowledgement.rb,
app/models/hr_lite/regularization_request.rb,
app/services/hr_lite/calculators/proration.rb,
app/services/hr_lite/payroll_run_processor.rb,
app/controllers/hr_lite/benefits_controller.rb,
app/controllers/hr_lite/calendar_controller.rb,
app/controllers/hr_lite/expenses_controller.rb,
app/controllers/hr_lite/holidays_controller.rb,
app/controllers/hr_lite/policies_controller.rb,
app/models/concerns/hr_lite/encrypted_money.rb,
app/controllers/hr_lite/approvals_controller.rb,
app/controllers/hr_lite/admin/base_controller.rb,
app/controllers/hr_lite/appraisals_controller.rb,
app/controllers/hr_lite/attendance_controller.rb,
app/models/hr_lite/statutory_rate_card_record.rb,
app/controllers/hr_lite/admin/roles_controller.rb,
app/controllers/hr_lite/application_controller.rb,
app/controllers/hr_lite/hr_requests_controller.rb,
app/controllers/hr_lite/resignations_controller.rb,
app/controllers/hr_lite/salary_slips_controller.rb,
app/controllers/hr_lite/admin/reports_controller.rb,
lib/generators/hr_lite/install/install_generator.rb,
app/controllers/hr_lite/admin/holidays_controller.rb,
app/controllers/hr_lite/admin/overview_controller.rb,
app/controllers/hr_lite/admin/settings_controller.rb,
app/controllers/hr_lite/leave_balances_controller.rb,
app/controllers/hr_lite/leave_requests_controller.rb,
app/services/hr_lite/calculators/professional_tax.rb,
app/controllers/hr_lite/admin/employees_controller.rb,
app/controllers/hr_lite/admin/appraisals_controller.rb,
app/controllers/hr_lite/admin/audit_logs_controller.rb,
app/controllers/hr_lite/admin/leadership_controller.rb,
app/controllers/hr_lite/admin/superadmin_controller.rb,
app/controllers/hr_lite/admin/attendances_controller.rb,
app/controllers/hr_lite/admin/leave_types_controller.rb,
app/controllers/hr_lite/comp_off_requests_controller.rb,
app/controllers/hr_lite/employee_profiles_controller.rb,
app/controllers/hr_lite/admin/payroll_runs_controller.rb,
app/controllers/hr_lite/admin/resignations_controller.rb,
app/controllers/hr_lite/admin/salary_slips_controller.rb,
app/controllers/hr_lite/admin/leave_balances_controller.rb,
app/controllers/hr_lite/admin/leave_requests_controller.rb,
app/controllers/hr_lite/admin/office_locations_controller.rb,
app/controllers/hr_lite/admin/role_assignments_controller.rb,
app/controllers/hr_lite/admin/comp_off_requests_controller.rb,
app/controllers/hr_lite/admin/salary_structures_controller.rb,
app/controllers/hr_lite/regularization_requests_controller.rb,
app/controllers/hr_lite/admin/designation_changes_controller.rb,
app/controllers/hr_lite/admin/statutory_rate_cards_controller.rb,
app/controllers/hr_lite/admin/regularization_requests_controller.rb

Defined Under Namespace

Modules: Admin, AmountInWords, ApplicationHelper, Approvable, AttendanceSummary, Audited, Calculators, EncryptedMoney, FinancialYear, Generators, Geo, LeaveDayCounter, LeaveYear, MentionParser, Money, Notifications, PdfRenderer, Permissions, RoleSeeds, Seeds, StatutoryRateCard, StatutorySeeds Classes: Access, ApplicationController, ApplicationJob, ApplicationMailer, ApplicationRecord, Appraisal, AppraisalsController, Approval, ApprovalDelegation, ApprovalEscalationJob, ApprovalFlow, ApprovalRoute, ApprovalStep, ApprovalsController, AttendanceController, AttendancePuncher, AttendanceRecord, AuditLog, Benefit, BenefitEnrolment, BenefitsController, CalendarController, CareerController, CompOffRequest, CompOffRequestsController, Configuration, Current, DailyDigestJob, DayStatus, DesignationChange, Document, DocumentExpiryJob, EmployeeProfile, EmployeeProfilesController, Engine, EventMailer, Expense, ExpenseCategory, ExpensesController, Holiday, HolidaysController, HomeController, HrRequest, HrRequestsController, Kudo, KudoMention, KudosController, LeaveBalance, LeaveBalancesController, LeaveRequest, LeaveRequestsController, LeaveType, LeaveYearRolloverJob, Loan, LoanRepayment, OfficeLocation, OrgController, OverviewQuery, PayrollAutoDraftJob, PayrollLineItem, PayrollRun, PayrollRunProcessor, PoliciesController, Policy, PolicyAcknowledgement, ProfessionalTaxSlab, RegularizationRequest, RegularizationRequestsController, Resignation, ResignationsController, Role, RoleAssignment, RoleGrant, SalaryComponent, SalarySlip, SalarySlipsController, SalaryStructure, Setting, SlipBuilder, StatutoryRateCardRecord, TaxDeclaration, TaxDeclarationItem, TeamController, TeamDay, UsersController, WorkingCalendar

Constant Summary collapse

VERSION =
"0.13.0"

Class Method Summary collapse

Class Method Details

.access_for(user) ⇒ Object



52
# File 'lib/hr_lite.rb', line 52

def access_for(user) = Access.for(user)

.active_employees(on: Date.current) ⇒ Object

employees minus anyone whose profile says they have exited — user accounts outlive employment (slip access), broadcasts must not.



119
120
121
122
# File 'lib/hr_lite.rb', line 119

def active_employees(on: Date.current)
  exits = HrLite::EmployeeProfile.where.not(date_of_exit: nil).pluck(:user_id, :date_of_exit).to_h
  employees.select { |u| exits[u.id].nil? || exits[u.id] >= on }
end

.admin?(user) ⇒ Boolean

--- tier predicates ----------------------------------------------------

Shorthands over the permissions, kept because views, hosts and the notification fan-out all read better asking "is this person leadership" than "do they hold profile.manage at all scope". They are ROLES all the way down; the pre-0.6.0 lambdas no longer decide anything.

Returns:

  • (Boolean)


61
62
63
64
65
# File 'lib/hr_lite.rb', line 61

def admin?(user)
  return false if user.blank?

  can?(user, "leave.approve", scope: :all) || can?(user, "attendance.manage", scope: :all)
end

.admin_usersObject

Every domain event that bells the admins calls this. One query over the grant tables — it used to instantiate every employee and ask each one.



107
108
109
# File 'lib/hr_lite.rb', line 107

def admin_users
  users_holding("leave.approve", scope: :all).sort_by { |u| display_name(u).downcase }
end

.can?(user, key, scope: :self) ⇒ Boolean

The question every gate now asks. scope: is what the CALLER needs; a holder with a wider scope satisfies it.

Returns:

  • (Boolean)


41
42
43
# File 'lib/hr_lite.rb', line 41

def can?(user, key, scope: :self)
  Access.for(user).can?(key, scope: scope)
end

.configObject



20
21
22
# File 'lib/hr_lite.rb', line 20

def config
  @config ||= Configuration.new
end

.config=(configuration) ⇒ Object

Test-only escape hatch: swap the whole configuration object.



29
30
31
# File 'lib/hr_lite.rb', line 29

def config=(configuration)
  @config = configuration
end

.configure {|config| ... } ⇒ Object

Yields:



24
25
26
# File 'lib/hr_lite.rb', line 24

def configure
  yield config
end

.default_mentionable_users(query) ⇒ Object

Default @mention source: name/email prefix match on the host user table. Uses LOWER(...) LIKE so it works on sqlite and postgres alike; hosts with pg_trgm or scopes of their own override config.mentionable_users.



95
96
97
98
99
100
101
102
103
# File 'lib/hr_lite/configuration.rb', line 95

def default_mentionable_users(query)
  q = "%#{ActiveRecord::Base.sanitize_sql_like(query.to_s.downcase)}%"
  klass = user_klass
  columns = %w[name email].select { |c| klass.column_names.include?(c) }
  return klass.none if columns.empty?

  where_sql = columns.map { |c| "LOWER(#{klass.table_name}.#{c}) LIKE :q" }.join(" OR ")
  klass.where(where_sql, q: q).order(:id).limit(8)
end

.display_name(user) ⇒ Object



124
125
126
127
128
129
130
131
132
133
# File 'lib/hr_lite.rb', line 124

def display_name(user)
  return "" if user.nil?

  [ config.display_name_method, :display_name, :name, :email ].each do |m|
    next unless m && user.respond_to?(m)
    value = user.public_send(m).presence
    return value if value
  end
  "User ##{user.id}"
end

.employeesObject

Everyone HR tracks (host-overridable to exclude bots/test accounts), sorted by display name for team screens.



113
114
115
# File 'lib/hr_lite.rb', line 113

def employees
  config.employees_scope.call.sort_by { |u| display_name(u).downcase }
end

.leadership?(user) ⇒ Boolean

Returns:

  • (Boolean)


67
68
69
70
71
# File 'lib/hr_lite.rb', line 67

def leadership?(user)
  return false if user.blank?

  can?(user, "profile.manage", scope: :all)
end

.leadership_usersObject

Leadership members resolvable to actual user records (for bell notifications).



89
90
91
# File 'lib/hr_lite.rb', line 89

def leadership_users
  users_holding("profile.manage", scope: :all)
end

.normalize_email_list(emails) ⇒ Object

An access list, cleaned — read now only by the 0.6.0 upgrade migration. One stray comma in an ENV var ("a@x.com,,b@x.com") used to put "" in the list, and a user whose email was blank then MATCHED it and was handed the tier. Kept honest here so the migration cannot repeat that.



83
84
85
# File 'lib/hr_lite.rb', line 83

def normalize_email_list(emails)
  Array(emails).map { |e| e.to_s.downcase.strip }.reject(&:empty?)
end

.notify(user:, kind:, title:, body: nil, path: nil) ⇒ Object

Host bell hook. Never raises — a notification must not break the domain action that triggered it.



162
163
164
165
166
167
# File 'lib/hr_lite.rb', line 162

def notify(user:, kind:, title:, body: nil, path: nil)
  config.notify.call(user: user, kind: kind, title: title, body: body, path: path)
rescue => e
  Rails.logger.error("[hr_lite] notify failed: #{e.class}: #{e.message}")
  nil
end

.public_url(path = "/") ⇒ Object

Absolute public URL for an engine-relative path, from config.public_url_base. Nil when no base is configured — callers (and emails) then simply carry no link. Hosts use this to build bell deep-links and profile links without re-deriving the HR host.



139
140
141
142
143
144
# File 'lib/hr_lite.rb', line 139

def public_url(path = "/")
  base = config.public_url_base.to_s.chomp("/")
  return nil if base.empty?

  "#{base}#{path}"
end

.public_url?(candidate) ⇒ Boolean

True when the given absolute URL points at the configured public HR host — the allowlist check for hosts that follow stored notification links (an open-redirect guard stays intact on their side).

Returns:

  • (Boolean)


149
150
151
152
153
154
155
156
157
158
# File 'lib/hr_lite.rb', line 149

def public_url?(candidate)
  base = public_url
  return false if base.nil?

  candidate_uri = URI.parse(candidate.to_s)
  base_uri = URI.parse(base)
  %w[http https].include?(candidate_uri.scheme) && candidate_uri.host == base_uri.host
rescue URI::InvalidURIError
  false
end

.reaches?(user, key, subject) ⇒ Boolean

Whether user may exercise key over subject's rows. This is the question that did not exist before roles, and its absence is why any admin could approve anybody's leave.

Returns:

  • (Boolean)


48
49
50
# File 'lib/hr_lite.rb', line 48

def reaches?(user, key, subject)
  Access.for(user).reaches?(key, subject)
end

.superadmin?(user) ⇒ Boolean

Returns:

  • (Boolean)


73
74
75
76
77
# File 'lib/hr_lite.rb', line 73

def superadmin?(user)
  return false if user.blank?

  can?(user, "payroll.manage", scope: :all)
end

.user_klassObject



33
34
35
# File 'lib/hr_lite.rb', line 33

def user_klass
  config.user_class.constantize
end

.users_holding(key, scope: :all) ⇒ Object

Everyone whose roles grant key at scope or wider. One query over the grant tables rather than instantiating every user and asking.



95
96
97
98
99
100
101
102
103
# File 'lib/hr_lite.rb', line 95

def users_holding(key, scope: :all)
  wide = Permissions::SCOPE_RANK.select { |_, rank| rank >= Permissions::SCOPE_RANK.fetch(scope) }
  ids = RoleAssignment.joins(role: :role_grants)
                      .where(hr_lite_role_grants: {
                        permission_key: Permissions.validate!(key), scope: wide.keys.map(&:to_s)
                      })
                      .distinct.pluck(:user_id)
  user_klass.where(id: ids)
end