forecasted 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|