module Ecom module Core class CrewTime < ApplicationRecord # Time Ranges MORNING = :morning AFTERNOON = :afternoon FULL_DAY = :full_day belongs_to :attendance_sheet_entry belongs_to :revision_to, class_name: 'Ecom::Core::CrewTime', optional: true belongs_to :created_by, class_name: 'Ecom::Core::User' has_one :revision, class_name: 'Ecom::Core::CrewTime', foreign_key: :revision_to_id validates :checkin_time, presence: true, if: :checkout_time validate :time_range_validation, :total_time_validation validates :hours, numericality: { greater_than_or_equal_to: 0 }, allow_nil: true scope :by_attendance, lambda { |id| joins(:attendance_sheet_entry).where(ecom_core_attendance_sheet_entries: { attendance_sheet_id: id }) } scope :revised, ->(revised) { where(revised: revised) } before_save :calculate_hours after_save :compute_total_for_entry # :calculate_total def time_range_validation return unless checkin_time && checkout_time && checkout_time <= checkin_time errors.add(:checkout_time, "can't be less than checkin time.") end def total_time_validation return if checkout_time.nil? || checkin_time.nil? if new_record? & !persisted? & (attendance_sheet_entry.total_hours + compute_hours > 8) || (attendance_sheet_entry.total_hours + compute_hours - computed_hour > 8) errors.add(:attendance_sheet_entry, 'has more than 8 hours') end end def calculate_hours self.hours = if checkout_time.nil? || checkin_time.nil? 0 else compute_hours end end def calculate_total compute_total_for_entry if saved_change_to_hours? end def compute_total_for_entry attendance_sheet_entry.total_hours = Ecom::Core::CrewTime.where( attendance_sheet_entry: attendance_sheet_entry, revised: false ).sum(:hours) attendance_sheet_entry.save end # A method to get the available time ranges at a given point. # We cannot define the range variables as a constant because # the parsing should be done at the exact moment we are about # to do time range calculations to avoid errors caused by date # mismatches def define_range morning = { start: Time.zone.parse('5:00 AM'), finish: Time.zone.parse('9:00 AM') } afternoon = { start: Time.zone.parse('10:00 AM'), finish: Time.zone.parse('2:00 PM') } full_day = { start: Time.zone.parse('5:00 AM'), finish: Time.zone.parse('2:00 PM') } { morning: morning, afternoon: afternoon, full_day: full_day } end # A method to check if checkin and checkout range falls in the morning, # afternoon, or both. def find_range(start, finish) range = define_range if start.before?(range[:morning][:finish]) && finish.before?(range[:afternoon][:start]) :morning elsif start.after?(range[:morning][:finish]) && finish.after?(range[:afternoon][:start]) :afternoon else :full_day end end # A method to adjust time ranges by trimming any time value outside # of the defined morning and afternoon ranges def compute_hours # Reparse time to avoid errors caused by date differences range = define_range start = Time.zone.parse(checkin_time.strftime('%I:%M%p')) finish = Time.zone.parse(checkout_time.strftime('%I:%M%p')) day_part = find_range(start, finish) left = start.before?(range[day_part][:start]) ? range[day_part][:start] : start right = finish.after?(range[day_part][:finish]) ? range[day_part][:finish] : finish time = (right - left) / 1.hour time -= 1 if day_part == FULL_DAY time end # A similar method as `compute_hours` but this one computes hours for # the the currently presisted checkin_time and checkout_time def computed_hour # Reparse time to avoid errors caused by date differences return 0 if checkin_time_was.nil? || checkout_time_was.nil? range = define_range start = Time.zone.parse(checkin_time_was.strftime('%I:%M%p')) finish = Time.zone.parse(checkout_time_was.strftime('%I:%M%p')) day_part = find_range(start, finish) left = start.before?(range[day_part][:start]) ? range[day_part][:start] : start right = finish.after?(range[day_part][:finish]) ? range[day_part][:finish] : finish time = (right - left) / 1.hour time -= 1 if day_part == FULL_DAY time end end end end