Class: WorkSchedule

Inherits:
ApplicationRecord show all
Includes:
Models::Auditable
Defined in:
app/models/work_schedule.rb

Overview

== Schema Information

Table name: work_schedules
Database name: primary

id :bigint not null, primary key
amount :decimal(, )
approved_by :integer
approved_on :date
created_by :integer
effective_date :date
expiry_date :date
notes :text
state :string default("pending"), not null
created_at :datetime not null
updated_at :datetime not null
employee_id :integer

Indexes

index_work_schedules_on_approved_by (approved_by)
index_work_schedules_on_approved_on (approved_on)
index_work_schedules_on_created_by (created_by)
index_work_schedules_on_effective_date (effective_date)
index_work_schedules_on_employee_id (employee_id)
index_work_schedules_on_expiry_date (expiry_date)
index_work_schedules_on_state (state)

Foreign Keys

fk_rails_... (approved_by => parties.id)
fk_rails_... (created_by => parties.id)

Constant Summary

Constants included from Models::Auditable

Models::Auditable::ALWAYS_IGNORED

Instance Attribute Summary collapse

Belongs to collapse

Methods included from Models::Auditable

#updater

Has many collapse

Has one collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Models::Auditable

#all_skipped_columns, #audit_reference_data, #should_not_save_version, #stamp_record

Methods inherited from ApplicationRecord

ransackable_associations, ransackable_attributes, ransackable_scopes, ransortable_attributes, #to_relation

Methods included from Models::EventPublishable

#publish_event

Instance Attribute Details

#amountObject (readonly)



44
# File 'app/models/work_schedule.rb', line 44

validates :amount, numericality: { greater_than: 0 }, presence: true

#effective_dateObject (readonly)



43
# File 'app/models/work_schedule.rb', line 43

validates :employee_id, :effective_date, presence: true

#employee_idObject (readonly)



43
# File 'app/models/work_schedule.rb', line 43

validates :employee_id, :effective_date, presence: true

#skip_approvalObject

validate :validate_approved_schedules_limit, on: :create



53
54
55
# File 'app/models/work_schedule.rb', line 53

def skip_approval
  @skip_approval
end

Class Method Details

.effective_nowActiveRecord::Relation<WorkSchedule>

A relation of WorkSchedules that are effective now. Active Record Scope

Returns:

See Also:



59
60
61
62
63
64
65
66
67
# File 'app/models/work_schedule.rb', line 59

scope :effective_now, ->(exclude_schedule_id = nil) {
  where(
    state: 'approved',
    effective_date: ..Date.current,
    expiry_date: [nil, Date.current..]
  )
  .where.not(id: exclude_schedule_id)
  .order(effective_date: :desc)
}

.effective_onActiveRecord::Relation<WorkSchedule>

A relation of WorkSchedules that are effective on. Active Record Scope

Returns:

See Also:



68
69
70
# File 'app/models/work_schedule.rb', line 68

scope :effective_on, ->(date) {
  where('effective_date <= ? AND (expiry_date IS NULL OR expiry_date >= ?)', date, date).where(state: 'approved')
}

Instance Method Details

#after_approved_schedule_tasksObject



142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'app/models/work_schedule.rb', line 142

def after_approved_schedule_tasks
  # Expire the current effective schedule if applicable
  if effective_date >= Date.today
    current_schedule = employee.work_schedules.effective_now(id).first
    if current_schedule.present?
      current_schedule.update(expiry_date: effective_date)
      if current_schedule.expiry_date == Date.today
        current_schedule.expire!
      end
    end
  end
  # Send notification email
  send_approved_email
end

#auto_assign_time_off_policyObject



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'app/models/work_schedule.rb', line 165

def auto_assign_time_off_policy
  # Calculate total hours for the schedule
  total_hours = amount

  # Find policies matching the total hours and the employee's hire date
  applicable_policies = TimeOffPolicy.where(hours_per_week: total_hours)
  
  # Check if any policy matches the criteria
  if applicable_policies.empty?
    errors.add(:base, "No time-off policy created for #{total_hours} hours per week. Please contact HW Team or HR to create a new Policy for this work schedule.")
    raise ActiveRecord::RecordInvalid, self
  end

  # Filter by hire date and tenure criteria
  tenure = ((effective_date - employee.employee_record.hire_date) / 365.25).floor
  selected_policy = applicable_policies.find do |policy|
    tenure >= policy.min_tenure && (policy.max_tenure.nil? || tenure < policy.max_tenure)
  end

  # If no matching policy is found
  unless selected_policy
    errors.add(:base, "No time-off policy matches the tenure criteria for #{total_hours} hours per week.")
    raise ActiveRecord::RecordInvalid, self
  end

  # Check if the same policy is already assigned
  existing_assignment = employee.time_off_policy_assignments.find_by(
    time_off_policy_id: selected_policy.id,
  )

  # If already assigned, return early
  return if existing_assignment

  # Assign the policy
  employee.time_off_policy_assignments.create!(
    time_off_policy: selected_policy,
    work_schedule_id: id,
    accrual_start_date: effective_date
  )
end

#before_approved_schedule_tasksObject

def send_udpate_email
InternalMailer.update_work_schedule(self).deliver_now
end



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'app/models/work_schedule.rb', line 126

def before_approved_schedule_tasks
  ActiveRecord::Base.transaction do
    self.approved_on = Date.today

    # Auto-assign a time-off policy
    auto_assign_time_off_policy
    return true
  rescue ActiveRecord::RecordInvalid => e
    errors.add(:base, "Time-off policy assignment failed: #{e.message}")
    false # Halt the state transition
  rescue StandardError => e
    errors.add(:base, "Unexpected error during approval: #{e.message}")
    false # Halt the state transition
  end
end

#changes_summaryObject



222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'app/models/work_schedule.rb', line 222

def changes_summary
  summary = {}
  # Track changes on the main work schedule attributes
  saved_changes.each do |attribute, values|
    old_value, new_value = values
    summary[attribute] = { old: old_value, new: new_value }
  end

  # Track changes on each work schedule day
  work_schedule_days.each do |day|
    day_changes = {}
    day.saved_changes.each do |attribute, values|
      old_value, new_value = values
      day_changes[attribute] = { old: old_value, new: new_value }
    end
    summary[day] = day_changes unless day_changes.empty?
  end

  summary
end

#check_approval_requirementObject



110
111
112
113
114
115
116
# File 'app/models/work_schedule.rb', line 110

def check_approval_requirement
  if skip_approval.to_b
    approve
  else
    send_create_email
  end
end

#creatorEmployee

Returns:

See Also:



38
# File 'app/models/work_schedule.rb', line 38

belongs_to  :creator, class_name: 'Employee', foreign_key: 'created_by'

#employeeEmployee

Returns:

See Also:



37
# File 'app/models/work_schedule.rb', line 37

belongs_to  :employee

#is_current?Boolean

Returns:

  • (Boolean)


161
162
163
# File 'app/models/work_schedule.rb', line 161

def is_current?
  approved? && effective_date <= Date.today && (expiry_date.nil? || expiry_date > Date.today)
end

#parentObject



106
107
108
# File 'app/models/work_schedule.rb', line 106

def parent
  employee
end

#send_approved_emailObject



157
158
159
# File 'app/models/work_schedule.rb', line 157

def send_approved_email
  InternalMailer.new_work_schedule_state(self).deliver_now
end

#send_create_emailObject



118
119
120
# File 'app/models/work_schedule.rb', line 118

def send_create_email
  InternalMailer.new_work_schedule_request(self).deliver_now
end

#time_off_policyTimeOffPolicy



41
# File 'app/models/work_schedule.rb', line 41

has_one     :time_off_policy, through: :time_off_policy_assignment

#time_off_policy_assignmentTimeOffPolicyAssignment



40
# File 'app/models/work_schedule.rb', line 40

has_one     :time_off_policy_assignment, dependent: :destroy

#to_detailed_jsonObject



243
244
245
246
247
248
249
250
251
252
253
# File 'app/models/work_schedule.rb', line 243

def to_detailed_json
  as_json(
    only: [:id, :employee_id, :amount, :effective_date, :expiry_date, :state, :notes],
    include: {
      work_schedule_days: {
        only: [:day_of_week, :hours],
        methods: [:day_name, :formatted_start_time, :formatted_end_time, :formatted_lunch_times]
      }
    }
  )
end

#update_policy_assignmentObject



206
207
208
209
210
211
212
213
214
215
216
# File 'app/models/work_schedule.rb', line 206

def update_policy_assignment
  # if work_schedule_days_changed?
  #   employee.time_off_policy_assignments.find_by(work_schedule_id: id)&.destroy
  # end

  if saved_change_to_effective_date? || work_schedule_days_changed?
    # Recalculate and reassign the policy
    employee.time_off_policy_assignments.find_by(work_schedule_id: id)&.destroy
    auto_assign_time_off_policy
  end
end

#work_schedule_daysActiveRecord::Relation<WorkScheduleDay>

Returns:

See Also:



39
# File 'app/models/work_schedule.rb', line 39

has_many    :work_schedule_days, dependent: :destroy

#work_schedule_days_changed?Boolean

Returns:

  • (Boolean)


218
219
220
# File 'app/models/work_schedule.rb', line 218

def work_schedule_days_changed?
  work_schedule_days.any? { |day| day.saved_changes.present? }
end