this repo has no description
1#!/usr/bin/env ruby
2
3# Cloud City needs 1200 hours to be in the black on employing me. With 10 weeks
4# of PTO and 2 weeks of vacation per year, that means I need to bill an average
5# of 30 hours per week. To reach 30 billable hours, I need to average 6 hours
6# per workday.
7
8# Unfortunately, Cloud City doesn't always provide billable work. As a result,
9# the only way to track that I have figured out that makes sense is to assume
10# 30 logged hours per week, both billed and unbilled, as the ultimate goal.
11
12# 1365 hours worked/40 weeks is 34.125 hours/week or 6.825 hours/day
13YEARLY_HOURS = 1365.to_f
14EXPECTED_HOURS_PER_DAY = 6.825
15
16require "bundler/inline"
17gemfile do
18 source "https://rubygems.org"
19 gem "http"
20 gem "pry"
21end
22
23
24def harvest_req(url)
25 @acct ||= %x(security find-internet-password -s "ccd.harvestapp.com")[/"acct"<blob>="(.*)"/, 1] || ""
26 @pass ||= %x(security find-internet-password -s "ccd.harvestapp.com" -w).chomp
27
28 if @acct.empty? || @pass.empty?
29 puts "security add-internet-password -s 'ccd.harvestapp.com' -a '202855' -w"
30 abort "Add your Harvest personal access token to the keychain as an internet password!"
31 end
32
33 HTTP.auth("Bearer #{@pass}").
34 headers("Harvest-Account-ID" => @acct).
35 headers("Content-Type" => "application/json").
36 get(url).parse
37end
38
39def api_url(path)
40 "https://api.harvestapp.com/api/v2/#{path}"
41end
42
43def time_entries(user, year_start, year_end)
44 entries = []
45 page = 1
46
47 while page
48 next_page = api_url("time_entries.json?user_id=#{user}&page=#{page}&per_page=100" +
49 "&from=#{year_start.iso8601}&to=#{year_end.iso8601}")
50 response = harvest_req(next_page)
51 entries.push(*response["time_entries"])
52 page = response["next_page"]
53 end
54
55 entries
56end
57
58today = Date.today
59year_end = Date.new(today.year, 12, 31)
60remaining_weekdays = (today..year_end).reject{|d| [0,6].include?(d.wday) }.size
61year_start = Date.new(today.year, 1, 1)
62
63user = harvest_req(api_url("users/me"))["id"]
64report = time_entries(user, year_start, year_end)
65
66# We've added a Harvest "project" to track PTO. It's not getting used super
67# consistently, but there are at least a few entries in there, so let's make
68# sure any hours entered as PTO will not be counted as worked time, either
69# billed or unbilled.
70PTO_PROJECT_ID = 11169073
71report.reject!{|e| e["project"]["id"] == PTO_PROJECT_ID }
72
73hours_logged = report.map{|e| e["hours"] }.inject(0, :+)
74hours_billed = report.select{|e| e["billable"] }.map{|e| e["hours"] }.inject(0, :+)
75hours_unbilled = report.reject{|e| e["billable"] }.map{|e| e["hours"] }.inject(0, :+)
76remaining_hours = YEARLY_HOURS - hours_billed - hours_unbilled
77remaining_work_days = remaining_hours / EXPECTED_HOURS_PER_DAY
78remaining_days_off = (remaining_weekdays - remaining_work_days).round(2)
79
80puts "In calendar year #{today.year}, you have:"
81puts "#{hours_logged.round} hours logged"
82puts "#{hours_billed.round} hours of billed work"
83puts "#{hours_unbilled.round} hours of unbilled work"
84puts "#{remaining_work_days.round} expected workdays left"
85puts "#{remaining_days_off.round} days of PTO left"