harvested 0.3.3 → 0.4.0
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.
- data/Gemfile +15 -0
- data/HISTORY +11 -0
- data/README.md +14 -6
- data/Rakefile +13 -31
- data/TODO +2 -0
- data/VERSION +1 -1
- data/examples/user_assignments.rb +1 -1
- data/harvested.gemspec +114 -126
- 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/harvest/api/account.rb +8 -1
- data/lib/harvest/api/base.rb +32 -10
- data/lib/harvest/api/contacts.rb +1 -1
- data/lib/harvest/api/expenses.rb +3 -4
- data/lib/harvest/api/invoice_categories.rb +26 -0
- data/lib/harvest/api/invoices.rb +16 -0
- data/lib/harvest/api/projects.rb +2 -10
- data/lib/harvest/api/reports.rb +13 -16
- data/lib/harvest/api/task_assignments.rb +8 -6
- data/lib/harvest/api/tasks.rb +1 -1
- data/lib/harvest/api/time.rb +13 -13
- data/lib/harvest/api/user_assignments.rb +7 -5
- data/lib/harvest/base.rb +9 -1
- data/lib/harvest/behavior/activatable.rb +2 -2
- data/lib/harvest/behavior/crud.rb +15 -13
- data/lib/harvest/client.rb +18 -13
- data/lib/harvest/contact.rb +13 -11
- data/lib/harvest/errors.rb +6 -4
- data/lib/harvest/expense.rb +40 -14
- data/lib/harvest/expense_category.rb +10 -9
- data/lib/harvest/hardy_client.rb +1 -1
- data/lib/harvest/invoice.rb +103 -0
- data/lib/harvest/invoice_category.rb +18 -0
- data/lib/harvest/line_item.rb +12 -0
- data/lib/harvest/model.rb +120 -0
- data/lib/harvest/project.rb +55 -26
- data/lib/harvest/rate_limit_status.rb +9 -8
- data/lib/harvest/task.rb +17 -14
- data/lib/harvest/task_assignment.rb +27 -22
- data/lib/harvest/time_entry.rb +32 -30
- data/lib/harvest/user.rb +46 -22
- data/lib/harvest/user_assignment.rb +24 -17
- data/lib/harvested.rb +12 -5
- data/spec/functional/account_spec.rb +17 -0
- data/spec/functional/clients_spec.rb +58 -0
- data/spec/functional/errors_spec.rb +22 -0
- data/spec/functional/expenses_spec.rb +84 -0
- data/spec/functional/hardy_client_spec.rb +33 -0
- data/spec/functional/invoice_spec.rb +67 -0
- data/spec/functional/project_spec.rb +50 -0
- data/spec/functional/reporting_spec.rb +80 -0
- data/spec/functional/tasks_spec.rb +88 -0
- data/spec/functional/time_tracking_spec.rb +53 -0
- data/spec/functional/users_spec.rb +102 -0
- data/spec/harvest/base_spec.rb +1 -1
- data/spec/harvest/credentials_spec.rb +1 -1
- data/spec/harvest/expense_category_spec.rb +5 -0
- data/spec/harvest/expense_spec.rb +8 -5
- data/spec/harvest/invoice_spec.rb +47 -0
- data/spec/harvest/project_spec.rb +11 -0
- data/spec/harvest/task_assignment_spec.rb +4 -4
- data/spec/harvest/task_spec.rb +7 -0
- data/spec/harvest/time_entry_spec.rb +11 -10
- data/spec/harvest/user_assignment_spec.rb +3 -3
- data/spec/harvest/user_spec.rb +3 -1
- data/spec/spec_helper.rb +37 -6
- data/{features → spec}/support/harvest_credentials.example.yml +0 -1
- data/spec/support/harvested_helpers.rb +44 -0
- data/spec/support/json_examples.rb +11 -0
- data/spec/test_rubies +5 -0
- metadata +109 -85
- data/.gitignore +0 -28
- data/features/account.feature +0 -7
- data/features/client_contacts.feature +0 -23
- data/features/clients.feature +0 -29
- data/features/errors.feature +0 -25
- data/features/expense_categories.feature +0 -21
- data/features/expenses.feature +0 -55
- data/features/hardy_client.feature +0 -40
- data/features/projects.feature +0 -39
- data/features/reporting.feature +0 -72
- data/features/step_definitions/account_steps.rb +0 -7
- data/features/step_definitions/assignment_steps.rb +0 -100
- data/features/step_definitions/contact_steps.rb +0 -11
- data/features/step_definitions/debug_steps.rb +0 -3
- data/features/step_definitions/error_steps.rb +0 -113
- data/features/step_definitions/expenses_steps.rb +0 -46
- data/features/step_definitions/harvest_steps.rb +0 -8
- data/features/step_definitions/model_steps.rb +0 -90
- data/features/step_definitions/people_steps.rb +0 -4
- data/features/step_definitions/report_steps.rb +0 -91
- data/features/step_definitions/time_entry_steps.rb +0 -40
- data/features/support/env.rb +0 -37
- data/features/support/error_helpers.rb +0 -18
- data/features/support/fixtures/empty_clients.xml +0 -2
- data/features/support/fixtures/over_limit.xml +0 -8
- data/features/support/fixtures/receipt.png +0 -0
- data/features/support/fixtures/under_limit.xml +0 -8
- data/features/support/harvest_helpers.rb +0 -11
- data/features/support/inflections.rb +0 -9
- data/features/task_assignment.feature +0 -69
- data/features/tasks.feature +0 -25
- data/features/time_tracking.feature +0 -29
- data/features/user_assignments.feature +0 -33
- data/features/users.feature +0 -55
- data/lib/harvest/base_model.rb +0 -73
- data/spec/spec.default.opts +0 -1
data/lib/harvest/project.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Harvest
|
2
|
-
|
2
|
+
|
3
3
|
# The model that contains information about a project
|
4
4
|
#
|
5
5
|
# == Fields
|
@@ -22,35 +22,64 @@ module Harvest
|
|
22
22
|
# [+active_task_assignments_count+] (READONLY) the number of active task assignments
|
23
23
|
# [+created_at+] (READONLY) when the project was created
|
24
24
|
# [+updated_at+] (READONLY) when the project was updated
|
25
|
-
class Project <
|
26
|
-
include
|
27
|
-
|
25
|
+
class Project < Hashie::Dash
|
26
|
+
include Harvest::Model
|
27
|
+
|
28
28
|
api_path '/projects'
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
29
|
+
|
30
|
+
property :id
|
31
|
+
property :client_id
|
32
|
+
property :name
|
33
|
+
property :code
|
34
|
+
property :notes
|
35
|
+
property :fees
|
36
|
+
property :active
|
37
|
+
property :billable
|
38
|
+
property :budget
|
39
|
+
property :budget_by
|
40
|
+
property :hourly_rate
|
41
|
+
property :bill_by
|
42
|
+
property :created_at
|
43
|
+
property :updated_at
|
44
|
+
property :notify_when_over_budget
|
45
|
+
property :over_budget_notification_percentage
|
46
|
+
property :show_budget_to_all
|
47
|
+
property :basecamp_id
|
48
|
+
property :highrise_deal_id
|
49
|
+
property :active_task_assignments_count
|
50
|
+
property :over_budget_notified_at
|
51
|
+
property :earliest_record_at
|
52
|
+
property :cost_budget
|
53
|
+
property :cost_budget_include_expenses
|
54
|
+
property :latest_record_at
|
55
|
+
property :estimate_by
|
56
|
+
property :hint_earliest_record_at
|
57
|
+
property :hint_latest_record_at
|
58
|
+
property :active_user_assignments_count
|
59
|
+
property :cache_version
|
60
|
+
property :estimate
|
61
|
+
|
51
62
|
alias_method :active?, :active
|
52
63
|
alias_method :billable?, :billable
|
53
64
|
alias_method :notify_when_over_budget?, :notify_when_over_budget
|
54
65
|
alias_method :show_budget_to_all?, :show_budget_to_all
|
66
|
+
|
67
|
+
def as_json(args = {})
|
68
|
+
super(args).tap do |json|
|
69
|
+
json[json_root].delete("hint_earliest_record_at")
|
70
|
+
json[json_root].delete("hint_latest_record_at")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.parse(json)
|
75
|
+
json = String === json ? JSON.parse(json) : json
|
76
|
+
Array.wrap(json).each do |attrs|
|
77
|
+
# need to cleanup some attributes
|
78
|
+
project_attrs = attrs[json_root] || {}
|
79
|
+
project_attrs["hint_latest_record_at"] = project_attrs.delete("hint-latest-record-at")
|
80
|
+
project_attrs["hint_earliest_record_at"] = project_attrs.delete("hint-earliest-record-at")
|
81
|
+
end
|
82
|
+
super(json)
|
83
|
+
end
|
55
84
|
end
|
56
85
|
end
|
@@ -8,15 +8,16 @@ module Harvest
|
|
8
8
|
# [+timeframe_limit+] The amount of seconds before a rate limit refresh occurs
|
9
9
|
# [+max_calls+] The number of requests you can make within the +timeframe_limit+
|
10
10
|
# [+lockout_seconds+] If you exceed the rate limit, how long you will be locked out from Harvest
|
11
|
-
class RateLimitStatus <
|
12
|
-
include
|
11
|
+
class RateLimitStatus < Hashie::Dash
|
12
|
+
include Harvest::Model
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
14
|
+
skip_json_root true
|
15
|
+
|
16
|
+
property :last_access_at
|
17
|
+
property :count
|
18
|
+
property :timeframe_limit
|
19
|
+
property :max_calls
|
20
|
+
property :lockout_seconds
|
20
21
|
|
21
22
|
# Returns true if the user is over their rate limit
|
22
23
|
# @return [Boolean]
|
data/lib/harvest/task.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module Harvest
|
2
|
-
|
2
|
+
|
3
3
|
# The model that contains information about a task
|
4
4
|
#
|
5
5
|
# == Fields
|
@@ -9,22 +9,25 @@ module Harvest
|
|
9
9
|
# [+deactivated+] whether the task is deactivated
|
10
10
|
# [+hourly_rate+] what the default hourly rate for the task is
|
11
11
|
# [+default?+] whether to add this task to new projects by default
|
12
|
-
class Task <
|
13
|
-
include
|
14
|
-
|
12
|
+
class Task < Hashie::Dash
|
13
|
+
include Harvest::Model
|
14
|
+
|
15
15
|
api_path '/tasks'
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
|
17
|
+
property :id
|
18
|
+
property :name
|
19
|
+
property :billable_by_default
|
20
|
+
property :deactivated
|
21
|
+
property :default_hourly_rate
|
22
|
+
property :is_default
|
23
|
+
property :created_at
|
24
|
+
property :updated_at
|
25
|
+
property :cache_version
|
26
|
+
|
24
27
|
def active?
|
25
28
|
!deactivated
|
26
29
|
end
|
27
|
-
|
28
|
-
alias_method :default?, :
|
30
|
+
|
31
|
+
alias_method :default?, :is_default
|
29
32
|
end
|
30
33
|
end
|
@@ -1,36 +1,41 @@
|
|
1
1
|
module Harvest
|
2
|
-
class TaskAssignment <
|
3
|
-
include
|
2
|
+
class TaskAssignment < Hashie::Dash
|
3
|
+
include Harvest::Model
|
4
|
+
|
5
|
+
property :id
|
6
|
+
property :task_id
|
7
|
+
property :project_id
|
8
|
+
property :billable
|
9
|
+
property :deactivated
|
10
|
+
property :hourly_rate
|
11
|
+
property :budget
|
12
|
+
property :estimate
|
13
|
+
property :created_at
|
14
|
+
property :updated_at
|
4
15
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
element :hourly_rate, Float, :tag => 'hourly-rate'
|
12
|
-
element :budget, Float
|
13
|
-
element :estimate, Float
|
16
|
+
def initialize(args = {})
|
17
|
+
args = args.stringify_keys
|
18
|
+
self.task = args.delete("task") if args["task"]
|
19
|
+
self.project = args.delete("project") if args["project"]
|
20
|
+
super
|
21
|
+
end
|
14
22
|
|
15
23
|
def task=(task)
|
16
|
-
|
24
|
+
self["task_id"] = task.to_i
|
17
25
|
end
|
18
|
-
|
26
|
+
|
19
27
|
def project=(project)
|
20
|
-
|
28
|
+
self["project_id"] = project.to_i
|
21
29
|
end
|
22
|
-
|
30
|
+
|
23
31
|
def active?
|
24
32
|
!deactivated
|
25
33
|
end
|
26
|
-
|
27
|
-
def
|
28
|
-
|
29
|
-
builder.task do |t|
|
30
|
-
t.id(task_id)
|
31
|
-
end
|
34
|
+
|
35
|
+
def task_as_json
|
36
|
+
{"task" => {"id" => task_id}}
|
32
37
|
end
|
33
|
-
|
38
|
+
|
34
39
|
alias_method :billable?, :billable
|
35
40
|
end
|
36
41
|
end
|
data/lib/harvest/time_entry.rb
CHANGED
@@ -1,43 +1,45 @@
|
|
1
1
|
module Harvest
|
2
|
-
class TimeEntry <
|
3
|
-
include
|
2
|
+
class TimeEntry < Hashie::Dash
|
3
|
+
include Harvest::Model
|
4
4
|
|
5
|
-
|
5
|
+
property :id
|
6
|
+
property :client
|
7
|
+
property :hours
|
8
|
+
property :notes
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
property :project_id
|
11
|
+
property :task_id
|
12
|
+
property :project
|
13
|
+
property :task
|
14
|
+
property :spent_at
|
15
|
+
property :created_at
|
16
|
+
property :updated_at
|
17
|
+
property :user_id
|
18
|
+
property :of_user
|
19
|
+
property :is_closed
|
20
|
+
property :is_billed
|
21
|
+
property :timer_started_at
|
22
|
+
property :adjustment_record
|
13
23
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
element :billed, Boolean, :tag => 'is-billed'
|
22
|
-
element :of_user, Integer
|
24
|
+
skip_json_root true
|
25
|
+
|
26
|
+
def initialize(args = {})
|
27
|
+
args = args.stringify_keys
|
28
|
+
self.spent_at = args.delete("spent_at") if args["spent_at"]
|
29
|
+
super
|
30
|
+
end
|
23
31
|
|
24
32
|
def spent_at=(date)
|
25
|
-
|
33
|
+
self["spent_at"] = (String === date ? Time.parse(date) : date)
|
26
34
|
end
|
27
35
|
|
28
|
-
def
|
29
|
-
|
30
|
-
|
31
|
-
r.tag!('notes', notes) if notes
|
32
|
-
r.tag!('hours', hours) if hours
|
33
|
-
r.tag!('project_id', project_id) if project_id
|
34
|
-
r.tag!('task_id', task_id) if task_id
|
35
|
-
r.tag!('spent_at', spent_at) if spent_at
|
36
|
-
r.tag!('of_user', of_user) if of_user
|
36
|
+
def as_json(args = {})
|
37
|
+
super(args).stringify_keys.tap do |hash|
|
38
|
+
hash.update("spent_at" => (spent_at.nil? ? nil : spent_at.to_time.xmlschema))
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
40
|
-
alias_method :closed?, :
|
41
|
-
alias_method :billed?, :
|
42
|
+
alias_method :closed?, :is_closed
|
43
|
+
alias_method :billed?, :is_billed
|
42
44
|
end
|
43
45
|
end
|
data/lib/harvest/user.rb
CHANGED
@@ -9,35 +9,59 @@ module Harvest
|
|
9
9
|
# [+last_name+] the last name for the user
|
10
10
|
# [+telephone+] the telephone for the user
|
11
11
|
# [+department] the department for the user
|
12
|
-
# [+password|password_confirmation+] the password for the user (only used on create.)
|
13
12
|
# [+has_access_to_all_future_projects+] whether the user should be added to future projects by default
|
14
13
|
# [+hourly_rate+] what the default hourly rate for the user is
|
15
14
|
# [+admin?+] whether the user is an admin
|
16
15
|
# [+contractor?+] whether the user is a contractor
|
17
16
|
# [+contractor?+] whether the user is a contractor
|
18
17
|
# [+timezone+] the timezone for the user.
|
19
|
-
class User <
|
20
|
-
include
|
18
|
+
class User < Hashie::Dash
|
19
|
+
include Harvest::Model
|
21
20
|
|
22
21
|
api_path '/people'
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
22
|
+
property :id
|
23
|
+
property :email
|
24
|
+
property :first_name
|
25
|
+
property :last_name
|
26
|
+
property :has_access_to_all_future_projects
|
27
|
+
property :default_hourly_rate
|
28
|
+
property :is_active
|
29
|
+
property :is_admin
|
30
|
+
property :is_contractor
|
31
|
+
property :telephone
|
32
|
+
property :department
|
33
|
+
property :timezone
|
34
|
+
property :password
|
35
|
+
property :first_timer
|
36
|
+
property :wants_newsletter
|
37
|
+
property :preferred_project_status_reports_screen
|
38
|
+
property :preferred_approval_screen
|
39
|
+
property :created_at
|
40
|
+
property :updated_at
|
41
|
+
property :twitter_username
|
42
|
+
property :preferred_entry_method
|
43
|
+
property :default_time_project_id
|
44
|
+
property :default_task_id
|
45
|
+
property :default_expense_category_id
|
46
|
+
property :opensocial_identifier
|
47
|
+
property :duplicate_timesheet_wants_notes
|
48
|
+
property :wants_timesheet_duplication
|
49
|
+
property :cache_version
|
50
|
+
property :email_after_submit
|
51
|
+
property :default_expense_project_id
|
52
|
+
property :identity_url
|
53
|
+
property :timestamp_timers
|
37
54
|
|
38
|
-
alias_method :active?, :
|
39
|
-
alias_method :admin?, :
|
40
|
-
alias_method :contractor?, :
|
55
|
+
alias_method :active?, :is_active
|
56
|
+
alias_method :admin?, :is_admin
|
57
|
+
alias_method :contractor?, :is_contractor
|
58
|
+
|
59
|
+
def initialize(args = {})
|
60
|
+
args = args.stringify_keys
|
61
|
+
args["is_admin"] = args.delete("admin") if args["admin"]
|
62
|
+
self.timezone = args.delete("timezone") if args["timezone"]
|
63
|
+
super
|
64
|
+
end
|
41
65
|
|
42
66
|
# Sets the timezone for the user. This can be done in a variety of ways.
|
43
67
|
#
|
@@ -56,9 +80,9 @@ module Harvest
|
|
56
80
|
when 'pst', 'pdt' then self.timezone = 'america/los_angeles'
|
57
81
|
else
|
58
82
|
if Harvest::Timezones::MAPPING[tz]
|
59
|
-
|
83
|
+
self["timezone"] = Harvest::Timezones::MAPPING[tz]
|
60
84
|
else
|
61
|
-
|
85
|
+
self["timezone"] = timezone
|
62
86
|
end
|
63
87
|
end
|
64
88
|
end
|
@@ -1,34 +1,41 @@
|
|
1
1
|
module Harvest
|
2
|
-
class UserAssignment <
|
3
|
-
include
|
2
|
+
class UserAssignment < Hashie::Dash
|
3
|
+
include Harvest::Model
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
property :id
|
6
|
+
property :user_id
|
7
|
+
property :project_id
|
8
|
+
property :deactivated
|
9
|
+
property :is_project_manager
|
10
|
+
property :hourly_rate
|
11
|
+
property :created_at
|
12
|
+
property :updated_at
|
13
|
+
property :budget
|
14
|
+
property :estimate
|
15
|
+
|
16
|
+
def initialize(args = {})
|
17
|
+
args = args.stringify_keys
|
18
|
+
self.user = args.delete("user") if args["user"]
|
19
|
+
self.project = args.delete("project") if args["project"]
|
20
|
+
super
|
21
|
+
end
|
12
22
|
|
13
23
|
def user=(user)
|
14
|
-
|
24
|
+
self["user_id"] = user.to_i
|
15
25
|
end
|
16
26
|
|
17
27
|
def project=(project)
|
18
|
-
|
28
|
+
self["project_id"] = project.to_i
|
19
29
|
end
|
20
30
|
|
21
31
|
def active?
|
22
32
|
!deactivated
|
23
33
|
end
|
24
34
|
|
25
|
-
def
|
26
|
-
|
27
|
-
builder.user do |t|
|
28
|
-
t.id(user_id)
|
29
|
-
end
|
35
|
+
def user_as_json
|
36
|
+
{"user" => {"id" => user_id}}
|
30
37
|
end
|
31
38
|
|
32
|
-
alias_method :project_manager?, :
|
39
|
+
alias_method :project_manager?, :is_project_manager
|
33
40
|
end
|
34
41
|
end
|