require 'harvested' require 'date' require 'paint' module TimeCop class Accountability attr_reader :report_builder, :client, :date, :today, :hours_per_week DAYS_PER_WEEK = 5.0 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(harvest_client:, date: Date.today, today: Date.today, report_builder: nil, email: nil, hours_per_week: 32) @client = harvest_client @date = date @today = today @hours_per_week = hours_per_week @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 hours_per_day @hours_per_week / DAYS_PER_WEEK 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 active_quarter? quarter_range = (date_from_period(get_quarter[0])...date_from_period(get_quarter[1])) quarter_range.include?(Date.today) 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(0, :+) end def total_quarter_days (end_of_quarter_date - start_of_quarter_date).to_i end def weekdays_left(include_today = false) if include_today weekdays_between(Date.today, end_of_quarter_date) else weekdays_between(Date.today + 1, end_of_quarter_date) end end def weekdays_so_far(include_today = true) if include_today weekdays_between(start_of_quarter_date, Date.today) else weekdays_between(start_of_quarter_date, Date.today - 1) end end def weekdays_between(start_date, end_date) (start_date..end_date).select{|d| (1..5).include?(d.wday)}.size end def total_week_days weekdays_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 weekdays_between(start_of_quarter_date, today < end_of_quarter_date ? Date.today : end_of_quarter_date) * hours_per_day end def average_quarter_hours_per_day if weekdays_so_far(false) > 0 total_quarter_time_tracked / weekdays_so_far(false) else 0 end end def projected_quarter_hours ((total_quarter_time_tracked + (weekdays_left(false) * average_quarter_hours_per_day)) - expected_quarter_hours) 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 weekdays_between(today, end_of_quarter_date) == 0 quarterly_hours_delta else quarterly_hours_delta / weekdays_between(today, end_of_quarter_date).to_f end end def self.paint_number(number) Paint[number, (number >= 0 ? :green : :red)] end def summary_hash { quarter: { start: start_of_quarter_date, end: end_of_quarter_date, weekdays_in_quarter: weekdays_between(@report_builder.start_date, @report_builder.end_date), weekdays_so_far: weekdays_so_far, weekdays_left: weekdays_left }, hours_per_week: @hours_per_week, hours: { charged: total_quarter_time_tracked, needed: expected_quarter_hours, left: quarterly_hours_delta, average_charged: average_quarter_hours_per_day, average_needed: quarterly_hours_per_business_day_needed, current_hours: current_hours_delta, total_projected: projected_quarter_hours } } end def print_report puts "Quarter Period: #{start_of_quarter_date} - #{end_of_quarter_date}" puts "Hours Per Week: #{@hours_per_week}" puts "" puts "Quarterly Hour Target: #{expected_quarter_hours.round(2)}" puts "Quarterly Hours Charged: #{total_quarter_time_tracked.round(2)}" if active_quarter? puts "" puts "Business Days Left In The Quarter (Excluding Today): #{weekdays_left(false)}" puts "" puts "Additional Hours Needed By End Of Quarter: #{quarterly_hours_delta.round(2)}" puts "Average Hours Per Business Day Worked: #{average_quarter_hours_per_day.round(2)}" puts "Hours Per Business Day Average Needed: #{quarterly_hours_per_business_day_needed.round(2)}" end puts"" puts "Quarter Surplus(+)/Deficit(-): #{Accountability.paint_number(current_hours_delta.round(2))}" puts "Projected Surplus(+)/Deficit(-): #{Accountability.paint_number(projected_quarter_hours.round(2))}" if active_quarter? end end end