forecasted 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.gitignore +39 -0
- data/.rspec +2 -0
- data/.ruby-version +1 -0
- data/.travis.yml +16 -0
- data/Gemfile +19 -0
- data/HISTORY.md +19 -0
- data/MIT-LICENSE +20 -0
- data/README.md +109 -0
- data/Rakefile +22 -0
- data/examples/basics.rb +35 -0
- data/examples/clear_account.rb +28 -0
- data/examples/project_create_script.rb +93 -0
- data/examples/task_assignments.rb +27 -0
- data/examples/user_assignments.rb +24 -0
- data/forecasted.gemspec +25 -0
- data/lib/ext/array.rb +52 -0
- data/lib/ext/date.rb +9 -0
- data/lib/ext/hash.rb +17 -0
- data/lib/ext/time.rb +5 -0
- data/lib/forecast/aggregate.rb +8 -0
- data/lib/forecast/api/account.rb +22 -0
- data/lib/forecast/api/aggregates.rb +20 -0
- data/lib/forecast/api/assignments.rb +63 -0
- data/lib/forecast/api/base.rb +68 -0
- data/lib/forecast/api/clients.rb +10 -0
- data/lib/forecast/api/expense_categories.rb +9 -0
- data/lib/forecast/api/expenses.rb +27 -0
- data/lib/forecast/api/invoice_categories.rb +26 -0
- data/lib/forecast/api/invoice_messages.rb +75 -0
- data/lib/forecast/api/invoice_payments.rb +31 -0
- data/lib/forecast/api/invoices.rb +35 -0
- data/lib/forecast/api/milestones.rb +21 -0
- data/lib/forecast/api/projects.rb +23 -0
- data/lib/forecast/api/reports.rb +53 -0
- data/lib/forecast/api/tasks.rb +36 -0
- data/lib/forecast/api/time.rb +48 -0
- data/lib/forecast/api/user_assignments.rb +34 -0
- data/lib/forecast/api/users.rb +21 -0
- data/lib/forecast/assignment.rb +7 -0
- data/lib/forecast/base.rb +111 -0
- data/lib/forecast/behavior/activatable.rb +31 -0
- data/lib/forecast/behavior/crud.rb +75 -0
- data/lib/forecast/client.rb +22 -0
- data/lib/forecast/credentials.rb +42 -0
- data/lib/forecast/errors.rb +26 -0
- data/lib/forecast/expense.rb +27 -0
- data/lib/forecast/expense_category.rb +10 -0
- data/lib/forecast/hardy_client.rb +80 -0
- data/lib/forecast/invoice.rb +107 -0
- data/lib/forecast/invoice_category.rb +9 -0
- data/lib/forecast/invoice_message.rb +8 -0
- data/lib/forecast/invoice_payment.rb +8 -0
- data/lib/forecast/line_item.rb +4 -0
- data/lib/forecast/model.rb +154 -0
- data/lib/forecast/project.rb +37 -0
- data/lib/forecast/rate_limit_status.rb +23 -0
- data/lib/forecast/task.rb +22 -0
- data/lib/forecast/time_entry.rb +25 -0
- data/lib/forecast/timezones.rb +130 -0
- data/lib/forecast/trackable_project.rb +38 -0
- data/lib/forecast/user_assignment.rb +30 -0
- data/lib/forecast/version.rb +3 -0
- data/lib/forecasted.rb +87 -0
- data/spec/factories.rb +17 -0
- data/spec/forecast/base_spec.rb +11 -0
- data/spec/functional/aggregates_spec.rb +64 -0
- data/spec/functional/assignments_spec.rb +131 -0
- data/spec/functional/errors_spec.rb +22 -0
- data/spec/functional/hardy_client_spec.rb +33 -0
- data/spec/functional/milestones_spec.rb +82 -0
- data/spec/functional/people_spec.rb +85 -0
- data/spec/functional/project_spec.rb +41 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/support/forecast_credentials.example.yml +6 -0
- data/spec/support/forecasted_helpers.rb +66 -0
- data/spec/support/json_examples.rb +9 -0
- metadata +189 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'business_time'
|
2
|
+
|
3
|
+
module Forecast
|
4
|
+
|
5
|
+
FORECAST_DATE_FORMAT = "%Y-%m-%d"
|
6
|
+
|
7
|
+
module API
|
8
|
+
class Assignments < Base
|
9
|
+
api_model Forecast::Assignment
|
10
|
+
include Forecast::Behavior::Crud
|
11
|
+
|
12
|
+
def by_project(project_id, query_options={})
|
13
|
+
query = {project_id: project_id}.merge(query_options)
|
14
|
+
self.all(query)
|
15
|
+
end
|
16
|
+
|
17
|
+
def sum_allocation_seconds(query)
|
18
|
+
axs = self.all(query)
|
19
|
+
|
20
|
+
total_time = 0
|
21
|
+
|
22
|
+
axs.each do |x|
|
23
|
+
start_date = Date.strptime(x.start_date, FORECAST_DATE_FORMAT)
|
24
|
+
end_date = Date.strptime(x.end_date, FORECAST_DATE_FORMAT)
|
25
|
+
|
26
|
+
num_days = start_date.business_days_until(end_date) + 1
|
27
|
+
|
28
|
+
total_time += (1.0 * x.allocation * num_days)
|
29
|
+
end
|
30
|
+
|
31
|
+
return total_time
|
32
|
+
end
|
33
|
+
|
34
|
+
def last_by_project(project_id)
|
35
|
+
x = self.by_project(project_id)
|
36
|
+
self.last_by_date(x)
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def create(*) ; raise "not implemented" ; end
|
41
|
+
def update(*) ; raise "not implemented" ; end
|
42
|
+
def delete(*) ; raise "not implemented" ; end
|
43
|
+
|
44
|
+
|
45
|
+
def last_by_date(array_hm)
|
46
|
+
array_hm.sort_by do |x|
|
47
|
+
Date.strptime(x.end_date, FORECAST_DATE_FORMAT)
|
48
|
+
end.last
|
49
|
+
end
|
50
|
+
|
51
|
+
def business_days_between(date1, date2)
|
52
|
+
business_days = 0
|
53
|
+
date = date2
|
54
|
+
while date > date1
|
55
|
+
business_days = business_days + 1 unless date.saturday? or date.sunday?
|
56
|
+
date = date - 1.day
|
57
|
+
end
|
58
|
+
business_days
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Forecast
|
2
|
+
module API
|
3
|
+
class Base
|
4
|
+
attr_reader :credentials
|
5
|
+
|
6
|
+
def initialize(credentials)
|
7
|
+
@credentials = credentials
|
8
|
+
end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def api_model(klass)
|
12
|
+
class_eval <<-END
|
13
|
+
def api_model
|
14
|
+
#{klass}
|
15
|
+
end
|
16
|
+
END
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
def request(method, credentials, path, options = {})
|
22
|
+
params = {
|
23
|
+
path: path,
|
24
|
+
options: options,
|
25
|
+
method: method
|
26
|
+
}
|
27
|
+
|
28
|
+
httparty_options = {
|
29
|
+
query: options[:query],
|
30
|
+
body: options[:body],
|
31
|
+
format: :plain,
|
32
|
+
headers: {
|
33
|
+
"Accept" => "application/json",
|
34
|
+
"Content-Type" => "application/json; charset=utf-8",
|
35
|
+
"User-Agent" => "Forecasted/#{Forecast::VERSION}"
|
36
|
+
}.update(options[:headers] || {})
|
37
|
+
}
|
38
|
+
|
39
|
+
credentials.set_authentication(httparty_options)
|
40
|
+
response = HTTParty.send(method, "#{credentials.host}#{path}", httparty_options)
|
41
|
+
params[:response] = response.inspect.to_s
|
42
|
+
|
43
|
+
case response.code
|
44
|
+
when 200..201
|
45
|
+
response
|
46
|
+
when 400
|
47
|
+
raise Forecast::BadRequest.new(response, params)
|
48
|
+
when 401
|
49
|
+
raise Forecast::AuthenticationFailed.new(response, params)
|
50
|
+
when 404
|
51
|
+
raise Forecast::NotFound.new(response, params, "Do you have sufficient privileges?")
|
52
|
+
when 500
|
53
|
+
raise Forecast::ServerError.new(response, params)
|
54
|
+
when 502
|
55
|
+
raise Forecast::Unavailable.new(response, params)
|
56
|
+
when 503
|
57
|
+
raise Forecast::RateLimited.new(response, params)
|
58
|
+
else
|
59
|
+
raise Forecast::InformHarvest.new(response, params)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def of_user_query(user)
|
64
|
+
query = user.nil? ? {} : {"of_user" => user.to_i}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Harvest
|
2
|
+
module API
|
3
|
+
class Expenses < Base
|
4
|
+
api_model Harvest::Expense
|
5
|
+
|
6
|
+
include Harvest::Behavior::Crud
|
7
|
+
|
8
|
+
def all(date = ::Time.now, user = nil)
|
9
|
+
date = ::Time.parse(date) if String === date
|
10
|
+
response = request(:get, credentials, "#{api_model.api_path}/#{date.yday}/#{date.year}", :query => of_user_query(user))
|
11
|
+
api_model.parse(response.parsed_response)
|
12
|
+
end
|
13
|
+
|
14
|
+
# This is currently broken, but will come back to it
|
15
|
+
def attach(expense, filename, receipt)
|
16
|
+
body = ""
|
17
|
+
body << "------------------------------b7edea381b46\r\n"
|
18
|
+
body << %Q{Content-Disposition: form-data; name="expense[receipt]"; filename="#{filename}"\r\n}
|
19
|
+
body << "Content-Type: image/png\r\n"
|
20
|
+
body << "\r\n#{receipt.read}\r\n"
|
21
|
+
body << "------------------------------b7edea381b46\r\n"
|
22
|
+
|
23
|
+
request(:post, credentials, "#{api_model.api_path}/#{expense.to_i}/receipt", :headers => {'Content-Type' => 'multipart/form-data; boundary=------------------------------b7edea381b46'}, :body => body)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Harvest
|
2
|
+
module API
|
3
|
+
class InvoiceCategories < Base
|
4
|
+
api_model Harvest::InvoiceCategory
|
5
|
+
include Harvest::Behavior::Crud
|
6
|
+
|
7
|
+
def find(*)
|
8
|
+
raise "find is unsupported for InvoiceCategories"
|
9
|
+
end
|
10
|
+
|
11
|
+
def create(model)
|
12
|
+
model = api_model.wrap(model)
|
13
|
+
response = request(:post, credentials, "#{api_model.api_path}", :body => model.to_json)
|
14
|
+
id = response.headers["location"].match(/\/.*\/(\d+)/)[1]
|
15
|
+
all.detect {|c| c.id == id.to_i }
|
16
|
+
end
|
17
|
+
|
18
|
+
def update(model, user = nil)
|
19
|
+
model = api_model.wrap(model)
|
20
|
+
request(:put, credentials, "#{api_model.api_path}/#{model.to_i}", :body => model.to_json, :query => of_user_query(user))
|
21
|
+
all.detect {|c| c.id == model.id }
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Harvest
|
2
|
+
module API
|
3
|
+
class InvoiceMessages < Base
|
4
|
+
api_model Harvest::InvoiceMessage
|
5
|
+
include Harvest::Behavior::Crud
|
6
|
+
|
7
|
+
def all(invoice)
|
8
|
+
response = request(:get, credentials, "/invoices/#{invoice.to_i}/messages")
|
9
|
+
api_model.parse(response.parsed_response)
|
10
|
+
end
|
11
|
+
|
12
|
+
def find(invoice, message)
|
13
|
+
response = request(:get, credentials, "/invoices/#{invoice.to_i}/messages/#{message.to_i}")
|
14
|
+
api_model.parse(response.parsed_response).first
|
15
|
+
end
|
16
|
+
|
17
|
+
def create(message)
|
18
|
+
message = api_model.wrap(message)
|
19
|
+
response = request(:post, credentials, "/invoices/#{message.invoice_id}/messages", :body => message.to_json)
|
20
|
+
id = response.headers["location"].match(/\/.*\/(\d+)\/.*\/(\d+)/)[2]
|
21
|
+
find(message.invoice_id, id)
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(message)
|
25
|
+
request(:delete, credentials, "/invoices/#{message.invoice_id}/messages/#{message.to_i}")
|
26
|
+
message.id
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create a message for marking an invoice as sent.
|
30
|
+
#
|
31
|
+
# @param [Harvest::InvoiceMessage] The message you want to send
|
32
|
+
# @return [Harvest::InvoiceMessage] The sent message
|
33
|
+
def mark_as_sent(message)
|
34
|
+
send_status_message(message, 'mark_as_sent')
|
35
|
+
end
|
36
|
+
|
37
|
+
# Create a message and mark an open invoice as closed (writing an invoice off)
|
38
|
+
#
|
39
|
+
# @param [Harvest::InvoiceMessage] The message you want to send
|
40
|
+
# @return [Harvest::InvoiceMessage] The sent message
|
41
|
+
def mark_as_closed(message)
|
42
|
+
send_status_message(message, 'mark_as_closed')
|
43
|
+
end
|
44
|
+
|
45
|
+
# Create a message and mark a closed (written-off) invoice as open
|
46
|
+
#
|
47
|
+
# @param [Harvest::InvoiceMessage] The message you want to send
|
48
|
+
# @return [Harvest::InvoiceMessage] The sent message
|
49
|
+
def re_open(message)
|
50
|
+
send_status_message(message, 're_open')
|
51
|
+
end
|
52
|
+
|
53
|
+
# Create a message for marking an open invoice as draft
|
54
|
+
#
|
55
|
+
# @param [Harvest::InvoiceMessage] The message you want to send
|
56
|
+
# @return [Harvest::InvoiceMessage] The sent message
|
57
|
+
def mark_as_draft(message)
|
58
|
+
send_status_message(message, 'mark_as_draft')
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def send_status_message(message, action)
|
64
|
+
message = api_model.wrap(message)
|
65
|
+
response = request( :post,
|
66
|
+
credentials,
|
67
|
+
"/invoices/#{message.invoice_id}/messages/#{action}",
|
68
|
+
:body => message.to_json
|
69
|
+
)
|
70
|
+
message
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Harvest
|
2
|
+
module API
|
3
|
+
class InvoicePayments < Base
|
4
|
+
api_model Harvest::InvoicePayment
|
5
|
+
include Harvest::Behavior::Crud
|
6
|
+
|
7
|
+
def all(invoice)
|
8
|
+
response = request(:get, credentials, "/invoices/#{invoice.to_i}/payments")
|
9
|
+
api_model.parse(response.parsed_response)
|
10
|
+
end
|
11
|
+
|
12
|
+
def find(invoice, payment)
|
13
|
+
response = request(:get, credentials, "/invoices/#{invoice.to_i}/payments/#{payment.to_i}")
|
14
|
+
api_model.parse(response.parsed_response).first
|
15
|
+
end
|
16
|
+
|
17
|
+
def create(payment)
|
18
|
+
payment = api_model.wrap(payment)
|
19
|
+
response = request(:post, credentials, "/invoices/#{payment.invoice_id}/payments", :body => payment.to_json)
|
20
|
+
id = response.headers["location"].match(/\/.*\/(\d+)\/.*\/(\d+)/)[2]
|
21
|
+
find(payment.invoice_id, id)
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(payment)
|
25
|
+
request(:delete, credentials, "/invoices/#{payment.invoice_id}/payments/#{payment.to_i}")
|
26
|
+
payment.id
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Harvest
|
2
|
+
module API
|
3
|
+
class Invoices < Base
|
4
|
+
api_model Harvest::Invoice
|
5
|
+
include Harvest::Behavior::Crud
|
6
|
+
|
7
|
+
# == Retrieves invoices
|
8
|
+
#
|
9
|
+
# == Available options
|
10
|
+
# - :status - invoices by status
|
11
|
+
# - :page
|
12
|
+
# - :updated_since
|
13
|
+
# - :timeframe (must be a nested hash with :to and :from)
|
14
|
+
#
|
15
|
+
# @overload all()
|
16
|
+
# @overload all(options)
|
17
|
+
# @param [Hash] filtering options
|
18
|
+
#
|
19
|
+
# @return [Array<Harvest::Invoice>] an array of invoices
|
20
|
+
def all(options = {})
|
21
|
+
query = {}
|
22
|
+
query[:status] = options[:status] if options[:status]
|
23
|
+
query[:page] = options[:page] if options[:page]
|
24
|
+
query[:updated_since] = options[:updated_since] if options[:updated_since]
|
25
|
+
if options[:timeframe]
|
26
|
+
query[:from] = options[:timeframe][:from]
|
27
|
+
query[:to] = options[:timeframe][:to]
|
28
|
+
end
|
29
|
+
|
30
|
+
response = request(:get, credentials, "/invoices", :query => query)
|
31
|
+
api_model.parse(response.parsed_response)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Forest
|
2
|
+
module API
|
3
|
+
class Milestones < Base
|
4
|
+
|
5
|
+
# renamed from contant
|
6
|
+
# api_model Harvest::Contact
|
7
|
+
|
8
|
+
# include Harvest::Behavior::Crud
|
9
|
+
|
10
|
+
# def all(client_id = nil)
|
11
|
+
# response = if client_id
|
12
|
+
# request(:get, credentials, "/clients/#{client_id}/contacts")
|
13
|
+
# else
|
14
|
+
# request(:get, credentials, "/contacts")
|
15
|
+
# end
|
16
|
+
|
17
|
+
# api_model.parse(response.parsed_response)
|
18
|
+
# end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Forecast
|
2
|
+
module API
|
3
|
+
class Projects < Base
|
4
|
+
api_model Forecast::Project
|
5
|
+
|
6
|
+
include Forecast::Behavior::Crud
|
7
|
+
|
8
|
+
def find_by_harvest_id(harvest_project_id)
|
9
|
+
selected = self.all.select{|x| x.harvest_id == harvest_project_id}
|
10
|
+
|
11
|
+
if selected.size > 1
|
12
|
+
raise "Forecasted::Error - more than 1 harvest_id forecast project"
|
13
|
+
else
|
14
|
+
return selected.first
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def create ; raise "not implemented" ; end
|
19
|
+
def update ; raise "not implemented" ; end
|
20
|
+
def delete ; raise "not implemented" ; end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Harvest
|
2
|
+
module API
|
3
|
+
class Reports < Base
|
4
|
+
|
5
|
+
TIME_FORMAT = '%Y%m%d'
|
6
|
+
|
7
|
+
def time_by_project(project, start_date, end_date, options = {})
|
8
|
+
query = { from: start_date.strftime(TIME_FORMAT), to: end_date.strftime(TIME_FORMAT) }
|
9
|
+
query[:user_id] = options.delete(:user).to_i if options[:user]
|
10
|
+
query[:billable] = (options.delete(:billable) ? "yes" : "no") unless options[:billable].nil?
|
11
|
+
query[:updated_since] = options.delete(:updated_since).to_s if options[:updated_since]
|
12
|
+
query.update(options)
|
13
|
+
|
14
|
+
response = request(:get, credentials, "/projects/#{project.to_i}/entries", query: query)
|
15
|
+
Harvest::TimeEntry.parse(JSON.parse(response.body).map {|h| h["day_entry"]})
|
16
|
+
end
|
17
|
+
|
18
|
+
def time_by_user(user, start_date, end_date, options = {})
|
19
|
+
query = { from: start_date.strftime(TIME_FORMAT), to: end_date.strftime(TIME_FORMAT) }
|
20
|
+
query[:project_id] = options.delete(:project).to_i if options[:project]
|
21
|
+
query[:billable] = (options.delete(:billable) ? "yes" : "no") unless options[:billable].nil?
|
22
|
+
query[:updated_since] = options.delete(:updated_since).to_s if options[:updated_since]
|
23
|
+
query.update(options)
|
24
|
+
|
25
|
+
response = request(:get, credentials, "/people/#{user.to_i}/entries", query: query)
|
26
|
+
Harvest::TimeEntry.parse(JSON.parse(response.body).map {|h| h["day_entry"]})
|
27
|
+
end
|
28
|
+
|
29
|
+
def expenses_by_user(user, start_date, end_date, options = {})
|
30
|
+
query = { from: start_date.strftime(TIME_FORMAT), to: end_date.strftime(TIME_FORMAT) }
|
31
|
+
query[:updated_since] = options.delete(:updated_since).to_s if options[:updated_since]
|
32
|
+
query.update(options)
|
33
|
+
|
34
|
+
response = request(:get, credentials, "/people/#{user.to_i}/expenses", query: query)
|
35
|
+
Harvest::Expense.parse(response.parsed_response)
|
36
|
+
end
|
37
|
+
|
38
|
+
def expenses_by_project(project, start_date, end_date, options = {})
|
39
|
+
query = { from: start_date.strftime(TIME_FORMAT), to: end_date.strftime(TIME_FORMAT) }
|
40
|
+
query[:updated_since] = options.delete(:updated_since).to_s if options[:updated_since]
|
41
|
+
query.update(options)
|
42
|
+
|
43
|
+
response = request(:get, credentials, "/projects/#{project.to_i}/expenses", query: query)
|
44
|
+
Harvest::Expense.parse(response.parsed_response)
|
45
|
+
end
|
46
|
+
|
47
|
+
def projects_by_client(client)
|
48
|
+
response = request(:get, credentials, "/projects?client=#{client.to_i}")
|
49
|
+
Harvest::Project.parse(response.parsed_response)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Harvest
|
2
|
+
module API
|
3
|
+
class Tasks < Base
|
4
|
+
api_model Harvest::Task
|
5
|
+
|
6
|
+
include Harvest::Behavior::Crud
|
7
|
+
|
8
|
+
# Deactivating tasks is not yet supported by the Harvest API.
|
9
|
+
|
10
|
+
# Deactivates the task. Does nothing if the task is already deactivated
|
11
|
+
#
|
12
|
+
# @param [Harvest::Task] task the task you want to deactivate
|
13
|
+
# @return [Harvest::Task] the deactivated task
|
14
|
+
#def deactivate(task)
|
15
|
+
# if task.active?
|
16
|
+
# request(:post, credentials, "#{api_model.api_path}/#{task.to_i}/deactivate", :headers => {'Content-Length' => '0'})
|
17
|
+
# task.active = false
|
18
|
+
# end
|
19
|
+
# task
|
20
|
+
#end
|
21
|
+
|
22
|
+
# Activates the task. Does nothing if the task is already activated
|
23
|
+
#
|
24
|
+
# @param [Harvest::Task] task the task you want to activate
|
25
|
+
# @return [Harvest::Task] the activated task
|
26
|
+
def activate(task)
|
27
|
+
if !task.active?
|
28
|
+
request(:post, credentials, "#{api_model.api_path}/#{task.to_i}/activate", :headers => {'Content-Length' => '0'})
|
29
|
+
task.active = true
|
30
|
+
end
|
31
|
+
task
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Harvest
|
2
|
+
module API
|
3
|
+
class Time < Base
|
4
|
+
|
5
|
+
def find(id, user = nil)
|
6
|
+
response = request(:get, credentials, "/daily/show/#{id.to_i}", :query => of_user_query(user))
|
7
|
+
Harvest::TimeEntry.parse(response.parsed_response).first
|
8
|
+
end
|
9
|
+
|
10
|
+
def all(date = ::Time.now, user = nil)
|
11
|
+
Harvest::TimeEntry.parse(daily(date, user)["day_entries"])
|
12
|
+
end
|
13
|
+
|
14
|
+
def trackable_projects(date = ::Time.now, user = nil)
|
15
|
+
Harvest::TrackableProject.parse(daily(date, user)["projects"])
|
16
|
+
end
|
17
|
+
|
18
|
+
def toggle(id, user = nil)
|
19
|
+
response = request(:get, credentials, "/daily/timer/#{id}", :query => of_user_query(user))
|
20
|
+
Harvest::TimeEntry.parse(response.parsed_response).first
|
21
|
+
end
|
22
|
+
|
23
|
+
def create(entry, user = nil)
|
24
|
+
response = request(:post, credentials, '/daily/add', :body => entry.to_json, :query => of_user_query(user))
|
25
|
+
Harvest::TimeEntry.parse(response.parsed_response).first
|
26
|
+
end
|
27
|
+
|
28
|
+
def update(entry, user = nil)
|
29
|
+
request(:put, credentials, "/daily/update/#{entry.to_i}", :body => entry.to_json, :query => of_user_query(user))
|
30
|
+
find(entry.id, user)
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete(entry, user = nil)
|
34
|
+
request(:delete, credentials, "/daily/delete/#{entry.to_i}", :query => of_user_query(user))
|
35
|
+
entry.id
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def daily(date, user)
|
42
|
+
date = ::Time.parse(date) if String === date
|
43
|
+
response = request(:get, credentials, "/daily/#{date.yday}/#{date.year}", :query => of_user_query(user))
|
44
|
+
JSON.parse(response.body)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Harvest
|
2
|
+
module API
|
3
|
+
class UserAssignments < Base
|
4
|
+
|
5
|
+
def all(project)
|
6
|
+
response = request(:get, credentials, "/projects/#{project.to_i}/user_assignments")
|
7
|
+
Harvest::UserAssignment.parse(response.parsed_response)
|
8
|
+
end
|
9
|
+
|
10
|
+
def find(project, id)
|
11
|
+
response = request(:get, credentials, "/projects/#{project.to_i}/user_assignments/#{id}")
|
12
|
+
Harvest::UserAssignment.parse(response.parsed_response).first
|
13
|
+
end
|
14
|
+
|
15
|
+
def create(user_assignment)
|
16
|
+
user_assignment = Harvest::UserAssignment.wrap(user_assignment)
|
17
|
+
response = request(:post, credentials, "/projects/#{user_assignment.project_id}/user_assignments", :body => user_assignment.user_as_json.to_json)
|
18
|
+
id = response.headers["location"].match(/\/.*\/(\d+)\/.*\/(\d+)/)[2]
|
19
|
+
find(user_assignment.project_id, id)
|
20
|
+
end
|
21
|
+
|
22
|
+
def update(user_assignment)
|
23
|
+
user_assignment = Harvest::UserAssignment.wrap(user_assignment)
|
24
|
+
request(:put, credentials, "/projects/#{user_assignment.project_id}/user_assignments/#{user_assignment.id}", :body => user_assignment.to_json)
|
25
|
+
find(user_assignment.project_id, user_assignment.id)
|
26
|
+
end
|
27
|
+
|
28
|
+
def delete(user_assignment)
|
29
|
+
request(:delete, credentials, "/projects/#{user_assignment.project_id}/user_assignments/#{user_assignment.to_i}")
|
30
|
+
user_assignment.id
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Harvest
|
2
|
+
module API
|
3
|
+
class Users < Base
|
4
|
+
api_model Harvest::User
|
5
|
+
|
6
|
+
include Harvest::Behavior::Crud
|
7
|
+
include Harvest::Behavior::Activatable
|
8
|
+
|
9
|
+
# Triggers Harvest to reset the user's password and sends them an email to change it.
|
10
|
+
# @overload reset_password(id)
|
11
|
+
# @param [Integer] id the id of the user you want to reset the password for
|
12
|
+
# @overload reset_password(user)
|
13
|
+
# @param [Harvest::User] user the user you want to reset the password for
|
14
|
+
# @return [Harvest::User] the user you passed in
|
15
|
+
def reset_password(user)
|
16
|
+
request(:post, credentials, "#{api_model.api_path}/#{user.to_i}/reset_password")
|
17
|
+
user
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|