Skip to content

Instantly share code, notes, and snippets.

@sixtyfive
Created September 18, 2011 19:43
Show Gist options
  • Save sixtyfive/1225469 to your computer and use it in GitHub Desktop.
Save sixtyfive/1225469 to your computer and use it in GitHub Desktop.
XXX marks the spot(s)!
class Activity < ActiveRecord::Base
belongs_to :user
belongs_to :category, :foreign_key => :activity_type_id
SIMPLE_DURATION_CATEGORIES = %w{
project_hour
nppitem_hour
nonclearable_hour
overtime_reduction
office_work
secretarial_work
special_case
comp_time
}
DATERANGE_CATEGORIES = %w{
vacation
unpaid_vacation
sickness
block_release
}
TIMERANGE_CATEGORIES = %w{
childcare_absence
}
def self.time_range(start_date, end_date, user)
# TODO: Out of 11215 activities, the majority have started_on hours
# of 22, 23 or 00. Only 89 activities have different starting hours
# and of those, 8 have 12 as their starting hour. Is it possible that
# those last 89 activities are still not found properly with the below
# code?
find_by_sql(
"SELECT id, user_id, description, duration, activity_type_id,
CASE
WHEN HOUR(started_on) = 22 THEN TIMESTAMPADD(HOUR, 2, started_on)
WHEN HOUR(started_on) = 23 THEN TIMESTAMPADD(HOUR, 1, started_on)
ELSE TIMESTAMP(started_on)
END AS started_on,
CASE
WHEN HOUR(ended_on) = 22 THEN TIMESTAMPADD(HOUR, 2, ended_on)
WHEN HOUR(ended_on) = 23 THEN TIMESTAMPADD(HOUR, 1, ended_on)
ELSE TIMESTAMP(ended_on)
END AS ended_on,
CASE
WHEN HOUR(date) = 22 THEN TIMESTAMPADD(HOUR, 2, date)
WHEN HOUR(date) = 23 THEN TIMESTAMPADD(HOUR, 1, date)
ELSE TIMESTAMP(date)
END AS date
FROM activities
WHERE
user_id = #{user.id}
HAVING
(date BETWEEN '#{start_date}' AND '#{end_date}')
OR
(started_on <= '#{start_date}' AND ended_on >= '#{end_date}')
ORDER BY started_on ASC"
)
end
# There's still a lot of ways other than time_range() to obtain
# a list of activities where the date/time attributes would not
# reliably return good numbers, so we're wrapping these attributes
# here.
%w{started_on, ended_on, date}.each do |attr|
define_method(attr) do
hour = eval("self[:#{attr}].hour")
correction = 0
case hour
when 22 then correction = 2
when 23 then correction = 1
end
log "Adding #{correction} hours to Activity##{id}::#{attr}" if correction != 0
return eval("self[:#{attr}]") + correction.hours
end
end
# XXX - it's still called "duration" only here and calls "partial_duration", which
# is the one I want named "duration_for_timespan" in the new code.
def duration(start_date = nil, end_date = nil)
if (start_date && end_date)
partial_duration(start_date, end_date)
elsif (start_date && !end_date)
partial_duration(start_date.beginning_of_day, start_date.end_of_day)
else
full_duration
end
end
private
def occurs_on_day?(date)
current_date = started_on.to_date
begin
date = date.to_date
if current_date == date
log "Activity##{id} does occur on #{date}"
return true
end
current_date += 1.day
end while current_date <= ended_on.to_date
return false
end
def duration_in_days(start_date = nil, end_date = nil)
# We're trying to cover two scenarios here:
# duration in days for the entire Activity,
# which is the default, and in which case
# no arguments will have been handed to us.
# And duration in das for a given date range
# only.
if start_date && end_date
full_activity = false
else
start_date = started_on
end_date = ended_on
full_activity = true
end
_days = 0
current_date = start_date
begin
# Adaptation of the algorithm implemented in
# Day#is_weekend_day? and Day#is_holiday?
dotw = current_date.day_of_week # TODO: There's a rather difficult problem to solve here:
if ((dotw != 6) && (dotw != 0) && Holiday.find_by_date(current_date).nil?) # an Activity that spans over the weekend might only do so
full_activity ? (_days += 1) : (_days += 1 if occurs_on_day?(current_date)) # because it was entered like that (out of lazyness) or
end # because the user really *did* work during the weekend!
# Add one day until the entire range has been
# spanned and the loop ends.
current_date += 1.day
end while current_date.to_date <= end_date.to_date
log "Activity##{id} spans over #{_days} days"
return _days
end
def full_duration
log "Activity##{id}: full duration requested"
if IMPLE_DURATION_CATEGORIES.include?(category.name)
# Nothing needs to be done here as the user has
# manually entered the duration directly. We can
# just return the database field's content.
log "Duration does not have to be calculated"
return self[:duration]
elsif DATERANGE_CATEGORIES.include?(category.name)
# This is a little more tricky. We know that we
# can only span full days, so we have to count
# just how many days we span, excluding any
# holidays in the process as those are paid for
# by the company anyway.
log "Duration has to be calculated with day-precision"
d = duration_in_days
result = d * user.contract_hours_per_day
log "#{d} * #{user.contract_hours_per_day} = #{result}"
return result
elsif TIMERANGE_CATEGORIES.include?(category.name)
# This is the most complex case and I've honestly
# no idea how to implement it correctly right now.
# For a company that doesn't have glide time it
# would be quite easy as you could just count the
# hours that are within the company's opening hours.
raise NotImplementedError, "Duration has to be calculated with minute-precision"
end
end
# XXX again :)
def partial_duration(start_date, end_date)
log "Activity##{id}: partial duration from #{start_date} to #{end_date} requested"
if SIMPLE_DURATION_CATEGORIES.include?(category.name)
# Should not get used right now. Would only apply
# if the range was within a day, i.e. "how many hours
# of this activity occured between 8 o'clock and 12 o'clock?"
log "Duration does not have to be calculated"
return self[:duration]
elsif DATERANGE_CATEGORIES.include?(category.name)
# Same as without a range having been given to us, but
# a little more complicated yet. See duration_in_days().
log "Duration has to be calculated with day-precision"
d = duration_in_days(start_date, end_date)
result = d * user.contract_hours_per_day
log "#{d} * #{user.contract_hours_per_day} = #{result}"
return result
elsif TIMERANGE_CATEGORIES.include?(category.name)
# Same problem as discussed in the comment for full_duration.
# If that gets implemented, this here should be easy as you
# would just have to shave a bit off around the edges,
# depending on what values are given as parameters.
raise NotImplementedError, "Duration has to be calculated with minute-precision"
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment