require 'harvested' require 'date' module TimeCop class Accountability attr_reader :report_builder, :client, :date DAYS_PER_WEEK = 5 HOURS_PER_WEEK = 32 HOURS_PER_DAY = HOURS_PER_WEEK / DAYS_PER_WEEK QUARTERLY_PERIODS = { q1: [{month: 1, day: 1}, {month: 3, day: 31}], q2: [{month: 4, day: 1}, {month: 6, day: 30}], q3: [{month: 7, day: 1}, {month: 9, day: 30}], q4: [{month: 10, day: 1}, {month: 12, day: 31}] } def initialize(username:, password:, subdomain: 'wildland', date: Date.today, report_builder: nil, email: nil) @client = Harvest.client(username: username, password: password, subdomain: subdomain) @date = date @report_builder = report_builder || ReportBuilder.new( client: client, user: email ? fetch_user(email) : default_user, start_date: start_of_quarter_date, end_date: end_of_quarter_date ) end def account client.account end def fetch_user(email) client.users.find(email) end def default_user account.who_am_i end def date_from_period(period) Date.new(date.year, period[:month], period[:day]) end def select_quarter QUARTERLY_PERIODS.select do |quarter, range| start_of_range = range[0] end_of_range = range[1] start_date_of_range = date_from_period(start_of_range) end_date_of_range = date_from_period(end_of_range) (date >= start_date_of_range) && (date <= end_date_of_range) end end def get_quarter select_quarter.first[1] end def start_of_quarter_date date_from_period(get_quarter[0]) end def end_of_quarter_date date_from_period(get_quarter[1]) end def quarterly_tracked_time_by_user @quarterly_tracked_time_by_user ||= report_builder.generate end def total_quarter_time_tracked quarterly_tracked_time_by_user.map { |t| t.hours }.reduce(:+) end def total_quarter_days (end_of_quarter_date - start_of_quarter_date).to_i end def business_days_between(start_date, end_date) days_between = (end_date - start_date).to_i return 0 unless days_between > 0 # Assuming we need to calculate days from 9th to 25th, 10-23 are covered # by whole weeks, and 24-25 are extra days. # # Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa # 1 2 3 4 5 # 1 2 3 4 5 # 6 7 8 9 10 11 12 # 6 7 8 9 ww ww ww # 13 14 15 16 17 18 19 # ww ww ww ww ww ww ww # 20 21 22 23 24 25 26 # ww ww ww ww ed ed 26 # 27 28 29 30 31 # 27 28 29 30 31 whole_weeks, extra_days = days_between.divmod(7) unless extra_days.zero? # Extra days start from the week day next to start_day, # and end on end_date's week date. The position of the # start date in a week can be either before (the left calendar) # or after (the right one) the end date. # # Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa # 1 2 3 4 5 # 1 2 3 4 5 # 6 7 8 9 10 11 12 # 6 7 8 9 10 11 12 # ## ## ## ## 17 18 19 # 13 14 15 16 ## ## ## # 20 21 22 23 24 25 26 # ## 21 22 23 24 25 26 # 27 28 29 30 31 # 27 28 29 30 31 # # If some of the extra_days fall on a weekend, they need to be subtracted. # In the first case only corner days can be days off, # and in the second case there are indeed two such days. extra_days -= if start_date.next_day.wday <= end_date.wday [start_date.next_day.sunday?, end_date.saturday?].count(true) else 2 end end (whole_weeks * 5) + extra_days end def total_week_days business_days_between(start_of_quarter_date, end_of_quarter_date) end def expected_quarter_hours total_week_days.to_f * HOURS_PER_DAY end def expected_quarter_hours_to_today business_days_between(start_of_quarter_date, date) * HOURS_PER_DAY end def current_hours_delta total_quarter_time_tracked - expected_quarter_hours_to_today end def quarterly_hours_delta expected_quarter_hours - total_quarter_time_tracked end def quarterly_hours_per_business_day_needed if business_days_between(date, end_of_quarter_date) == 0 quarterly_hours_delta else quarterly_hours_delta / business_days_between(date, end_of_quarter_date).to_f end end def print_report puts "Quarter Period: #{start_of_quarter_date} #{end_of_quarter_date}" puts "Current Surplus(+)/Deficit(-): #{current_hours_delta.round(2)}" puts "Quarterly Hour Target: #{expected_quarter_hours.round(2)}" puts "Current Quarterly Charged Hours: #{total_quarter_time_tracked.round(2)}" puts "Total Hours Needed By End Of Quarter: #{quarterly_hours_delta.round(2)}" puts "Hours Per Business Day Average Needed To Reach Goal: #{quarterly_hours_per_business_day_needed.round(2)}" end end end