Created
September 18, 2011 19:43
-
-
Save sixtyfive/1225469 to your computer and use it in GitHub Desktop.
XXX marks the spot(s)!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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