quilted-harvested 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/.document +3 -0
  2. data/.gitignore +25 -0
  3. data/HISTORY +19 -0
  4. data/MIT-LICENSE +20 -0
  5. data/README.md +68 -0
  6. data/Rakefile +25 -0
  7. data/VERSION +1 -0
  8. data/examples/basics.rb +35 -0
  9. data/examples/clear_account.rb +28 -0
  10. data/examples/task_assignments.rb +27 -0
  11. data/examples/user_assignments.rb +24 -0
  12. data/features/account.feature +7 -0
  13. data/features/client_contacts.feature +23 -0
  14. data/features/clients.feature +29 -0
  15. data/features/errors.feature +25 -0
  16. data/features/expense_categories.feature +21 -0
  17. data/features/expenses.feature +55 -0
  18. data/features/hardy_client.feature +40 -0
  19. data/features/projects.feature +39 -0
  20. data/features/reporting.feature +72 -0
  21. data/features/step_definitions/account_steps.rb +7 -0
  22. data/features/step_definitions/assignment_steps.rb +100 -0
  23. data/features/step_definitions/contact_steps.rb +11 -0
  24. data/features/step_definitions/debug_steps.rb +3 -0
  25. data/features/step_definitions/error_steps.rb +113 -0
  26. data/features/step_definitions/expenses_steps.rb +46 -0
  27. data/features/step_definitions/harvest_steps.rb +8 -0
  28. data/features/step_definitions/model_steps.rb +90 -0
  29. data/features/step_definitions/people_steps.rb +4 -0
  30. data/features/step_definitions/report_steps.rb +91 -0
  31. data/features/step_definitions/time_entry_steps.rb +40 -0
  32. data/features/support/env.rb +37 -0
  33. data/features/support/error_helpers.rb +18 -0
  34. data/features/support/fixtures/empty_clients.xml +2 -0
  35. data/features/support/fixtures/over_limit.xml +8 -0
  36. data/features/support/fixtures/receipt.png +0 -0
  37. data/features/support/fixtures/under_limit.xml +8 -0
  38. data/features/support/harvest_credentials.example.yml +4 -0
  39. data/features/support/harvest_helpers.rb +11 -0
  40. data/features/support/inflections.rb +9 -0
  41. data/features/task_assignment.feature +69 -0
  42. data/features/tasks.feature +25 -0
  43. data/features/time_tracking.feature +29 -0
  44. data/features/user_assignments.feature +33 -0
  45. data/features/users.feature +55 -0
  46. data/harvested.gemspec +159 -0
  47. data/lib/harvest/api/account.rb +15 -0
  48. data/lib/harvest/api/base.rb +42 -0
  49. data/lib/harvest/api/clients.rb +10 -0
  50. data/lib/harvest/api/contacts.rb +19 -0
  51. data/lib/harvest/api/expense_categories.rb +9 -0
  52. data/lib/harvest/api/expenses.rb +28 -0
  53. data/lib/harvest/api/projects.rb +54 -0
  54. data/lib/harvest/api/reports.rb +39 -0
  55. data/lib/harvest/api/task_assignments.rb +32 -0
  56. data/lib/harvest/api/tasks.rb +9 -0
  57. data/lib/harvest/api/time.rb +32 -0
  58. data/lib/harvest/api/user_assignments.rb +32 -0
  59. data/lib/harvest/api/users.rb +21 -0
  60. data/lib/harvest/base.rb +258 -0
  61. data/lib/harvest/base_model.rb +73 -0
  62. data/lib/harvest/behavior/activatable.rb +31 -0
  63. data/lib/harvest/behavior/crud.rb +57 -0
  64. data/lib/harvest/client.rb +30 -0
  65. data/lib/harvest/contact.rb +29 -0
  66. data/lib/harvest/credentials.rb +21 -0
  67. data/lib/harvest/errors.rb +23 -0
  68. data/lib/harvest/expense.rb +19 -0
  69. data/lib/harvest/expense_category.rb +18 -0
  70. data/lib/harvest/hardy_client.rb +80 -0
  71. data/lib/harvest/project.rb +56 -0
  72. data/lib/harvest/rate_limit_status.rb +28 -0
  73. data/lib/harvest/task.rb +30 -0
  74. data/lib/harvest/task_assignment.rb +34 -0
  75. data/lib/harvest/time_entry.rb +42 -0
  76. data/lib/harvest/timezones.rb +149 -0
  77. data/lib/harvest/user.rb +66 -0
  78. data/lib/harvest/user_assignment.rb +34 -0
  79. data/lib/harvested.rb +62 -0
  80. data/spec/harvest/base_spec.rb +9 -0
  81. data/spec/harvest/credentials_spec.rb +22 -0
  82. data/spec/harvest/expense_spec.rb +15 -0
  83. data/spec/harvest/task_assignment_spec.rb +10 -0
  84. data/spec/harvest/time_entry_spec.rb +22 -0
  85. data/spec/harvest/user_assignment_spec.rb +10 -0
  86. data/spec/harvest/user_spec.rb +32 -0
  87. data/spec/spec.default.opts +1 -0
  88. data/spec/spec_helper.rb +10 -0
  89. metadata +264 -0
@@ -0,0 +1,69 @@
1
+ @clean
2
+ Feature: Task Assignments
3
+
4
+ Scenario: Adding and Updating a Task on a Project
5
+ Given I am using the credentials from "./support/harvest_credentials.yml"
6
+ When I create a client with the following:
7
+ | name | Client Projects |
8
+ | details | Building API Widgets across the country |
9
+ When I create a project for the client "Client Projects" with the following:
10
+ | name | Test Project |
11
+ | active | true |
12
+ | notes | project to test the api |
13
+ Then there should be a project "Test Project"
14
+ When I create a task with the following:
15
+ | name | Test Task |
16
+ | billable | true |
17
+ | deactivated | false |
18
+ | hourly_rate | 120 |
19
+ Then there should be a task "Test Task"
20
+ When I assign the task "Test Task" to the project "Test Project"
21
+ Then the task "Test Task" should be assigned to the project "Test Project"
22
+ When I update the task "Test Task" for the project "Test Project" with the following:
23
+ | hourly_rate | 75 |
24
+ | billable | false |
25
+ Then the task "Test Task" for the project "Test Project" should have the following attributes:
26
+ | hourly_rate | 75.0 |
27
+ | billable? | false |
28
+ | active? | true |
29
+
30
+ Scenario: Removing Tasks on a project
31
+ Given I am using the credentials from "./support/harvest_credentials.yml"
32
+ When I create a client with the following:
33
+ | name | Client Projects |
34
+ | details | Building API Widgets across the country |
35
+ When I create a project for the client "Client Projects" with the following:
36
+ | name | Test Project |
37
+ | active | true |
38
+ | notes | project to test the api |
39
+ Then there should be a project "Test Project"
40
+ When I create a task with the following:
41
+ | name | Test Task1 |
42
+ When I create a task with the following:
43
+ | name | Test Task2 |
44
+ Then there should be a task "Test Task1"
45
+ Then there should be a task "Test Task2"
46
+ When I assign the task "Test Task1" to the project "Test Project"
47
+ When I assign the task "Test Task2" to the project "Test Project"
48
+ Then the task "Test Task1" should be assigned to the project "Test Project"
49
+ Then the task "Test Task2" should be assigned to the project "Test Project"
50
+ When I remove the task "Test Task1" from the project "Test Project"
51
+ Then the task "Test Task1" should not be assigned to the project "Test Project"
52
+ When I try to remove the task "Test Task2" from the project "Test Project"
53
+ Then a 400 error should be raised
54
+
55
+ Scenario: Removing a task that has recorded hours
56
+
57
+ Scenario: Creating a task and immediately assigning it
58
+ Given I am using the credentials from "./support/harvest_credentials.yml"
59
+ When I create a client with the following:
60
+ | name | Client Projects |
61
+ | details | Building API Widgets across the country |
62
+ When I create a project for the client "Client Projects" with the following:
63
+ | name | Test Project |
64
+ | active | true |
65
+ | notes | project to test the api |
66
+ Then there should be a project "Test Project"
67
+ When I create and assign a task "Created Task" to the project "Test Project"
68
+ Then there should be a task "Created Task"
69
+ Then the task "Created Task" should be assigned to the project "Test Project"
@@ -0,0 +1,25 @@
1
+ @clean
2
+ Feature: Managing Tasks
3
+
4
+ Scenario: Adding, Updating, and Removing a Task
5
+ Given I am using the credentials from "./support/harvest_credentials.yml"
6
+ When I create a task with the following:
7
+ | name | Test Task |
8
+ | billable | true |
9
+ | deactivated | false |
10
+ | hourly_rate | 120 |
11
+ Then there should be a task "Test Task"
12
+ When I update the task "Test Task" with the following:
13
+ | name | Updated Task |
14
+ | hourly_rate | 100.0 |
15
+ | deactivated | true |
16
+ | billable | false |
17
+ | default | true |
18
+ Then the task "Updated Task" should have the following attributes:
19
+ | hourly_rate | 100.0 |
20
+ | deactivated | true |
21
+ | active? | false |
22
+ | billable | false |
23
+ | default? | true |
24
+ When I delete the task "Updated Task"
25
+ Then there should not be a task "Updated Task"
@@ -0,0 +1,29 @@
1
+ @clean
2
+ Feature: Time Tracking
3
+
4
+ Scenario: Adding, Updating, and Deleting Time Entries
5
+ Given I am using the credentials from "./support/harvest_credentials.yml"
6
+ And I create a client with the following:
7
+ | name | Time Client |
8
+ | details | Client to track time against |
9
+ And I create a project for the client "Time Client" with the following:
10
+ | name | Time Project |
11
+ | active | true |
12
+ And I create a task with the following:
13
+ | name | Time Task |
14
+ | billable | true |
15
+ | hourly_rate | 120 |
16
+ And I assign the task "Time Task" to the project "Time Project"
17
+ When I create a time entry for the project "Time Project" and the task "Time Task" with the following:
18
+ | notes | Test api support |
19
+ | hours | 3.0 |
20
+ | spent_at | 12/28/2009 |
21
+ Then there should be a time entry "Test api support" on "12/28/2009"
22
+ When I update the time entry "Test api support" on "12/28/2009" with the following:
23
+ | hours | 4.0 |
24
+ Then the time entry "Test api support" on "12/28/2009" should have the following attributes:
25
+ | hours | 4.0 |
26
+ When I delete the time entry "Test api support" on "12/28/2009"
27
+ Then there should not be a time entry "Test api support" on "12/28/2009"
28
+
29
+ Scenario: Toggling Timers
@@ -0,0 +1,33 @@
1
+ @clean
2
+ Feature: User Assignments
3
+
4
+ Scenario: Adding and Updating a User on a Project
5
+ Given I am using the credentials from "./support/harvest_credentials.yml"
6
+ When I create a client with the following:
7
+ | name | Client Projects |
8
+ | details | Building API Widgets across the country |
9
+ When I create a project for the client "Client Projects" with the following:
10
+ | name | Test Project |
11
+ | active | true |
12
+ | notes | project to test the api |
13
+ Then there should be a project "Test Project"
14
+ When I create a user with the following:
15
+ | first_name | Edgar |
16
+ | last_name | Ruth |
17
+ | email | edgar@ruth.com |
18
+ | password | mypassword |
19
+ | password_confirmation | mypassword |
20
+ Then there should be a user "edgar@ruth.com"
21
+ When I assign the user "edgar@ruth.com" to the project "Test Project"
22
+ Then the user "edgar@ruth.com" should be assigned to the project "Test Project"
23
+ When I update the user "edgar@ruth.com" on the project "Test Project" with the following:
24
+ | hourly_rate | 50.0 |
25
+ | project_manager | true |
26
+ Then the user "edgar@ruth.com" on the project "Test Project" should have the following attributes:
27
+ | active? | true |
28
+ | hourly_rate | 50.0 |
29
+ | project_manager | true |
30
+ When I remove the user "edgar@ruth.com" from the project "Test Project"
31
+ Then the user "edgar@ruth.com" should not be assigned to the project "Test Project"
32
+
33
+ Scenario: Removing a user from a project that has recorded hours
@@ -0,0 +1,55 @@
1
+ @clean
2
+ Feature: Managing People
3
+
4
+ Scenario: Adding and Removing a User
5
+ Given I am using the credentials from "./support/harvest_credentials.yml"
6
+ When I create a user with the following:
7
+ | first_name | Edgar |
8
+ | last_name | Ruth |
9
+ | email | edgar@ruth.com |
10
+ | password | mypassword |
11
+ | password_confirmation | mypassword |
12
+ | timezone | cst |
13
+ | admin | false |
14
+ | telephone | 444-4444 |
15
+ Then there should be a user "edgar@ruth.com"
16
+ When I update the user "edgar@ruth.com" with the following:
17
+ | first_name | Jonah |
18
+ | timezone | pst |
19
+ Then the user "edgar@ruth.com" should have the following attributes:
20
+ | first_name | Jonah |
21
+ | timezone | Pacific Time (US & Canada) |
22
+ When I delete the user "edgar@ruth.com"
23
+ Then there should not be a user "edgar@ruth.com"
24
+
25
+ Scenario: Activating and Deactivating a User
26
+ Given I am using the credentials from "./support/harvest_credentials.yml"
27
+ Then I create a user with the following:
28
+ | first_name | Simon |
29
+ | last_name | Steel |
30
+ | email | simon@steel.com |
31
+ | password | mypassword |
32
+ | password_confirmation | mypassword |
33
+ | timezone | cst |
34
+ | admin | false |
35
+ | telephone | 444-4444 |
36
+ Then the user "simon@steel.com" should be activated
37
+ When I deactivate the user "simon@steel.com"
38
+ Then the user "simon@steel.com" should be deactivated
39
+ When I activate the user "simon@steel.com"
40
+ Then the user "simon@steel.com" should be activated
41
+ Then I delete the user "simon@steel.com"
42
+
43
+ Scenario: Resetting a User's password
44
+ Given I am using the credentials from "./support/harvest_credentials.yml"
45
+ Then I create a user with the following:
46
+ | first_name | Edgar |
47
+ | last_name | Ruth |
48
+ | email | edgar@ruth.com |
49
+ | password | mypassword |
50
+ | password_confirmation | mypassword |
51
+ | timezone | cst |
52
+ | admin | false |
53
+ | telephone | 444-4444 |
54
+ Then there should be a user "edgar@ruth.com"
55
+ Then I reset the password of "edgar@ruth.com"
data/harvested.gemspec ADDED
@@ -0,0 +1,159 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{harvested}
8
+ s.version = "0.3.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Zach Moazeni, Michelle Moon Lee (updated)"]
12
+ s.date = %q{2010-10-20}
13
+ s.description = %q{Harvested wraps the Harvest API concisely without the use of Rails dependencies. More information about the Harvest API can be found on their website (http://www.getharvest.com/api). For support hit up the Mailing List (http://groups.google.com/group/harvested)}
14
+ s.email = %q{michelle@quilted.coop}
15
+ s.extra_rdoc_files = [
16
+ "README.md"
17
+ ]
18
+ s.files = [
19
+ ".document",
20
+ ".gitignore",
21
+ "HISTORY",
22
+ "MIT-LICENSE",
23
+ "README.md",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "examples/basics.rb",
27
+ "examples/clear_account.rb",
28
+ "examples/task_assignments.rb",
29
+ "examples/user_assignments.rb",
30
+ "features/account.feature",
31
+ "features/client_contacts.feature",
32
+ "features/clients.feature",
33
+ "features/errors.feature",
34
+ "features/expense_categories.feature",
35
+ "features/expenses.feature",
36
+ "features/hardy_client.feature",
37
+ "features/projects.feature",
38
+ "features/reporting.feature",
39
+ "features/step_definitions/account_steps.rb",
40
+ "features/step_definitions/assignment_steps.rb",
41
+ "features/step_definitions/contact_steps.rb",
42
+ "features/step_definitions/debug_steps.rb",
43
+ "features/step_definitions/error_steps.rb",
44
+ "features/step_definitions/expenses_steps.rb",
45
+ "features/step_definitions/harvest_steps.rb",
46
+ "features/step_definitions/model_steps.rb",
47
+ "features/step_definitions/people_steps.rb",
48
+ "features/step_definitions/report_steps.rb",
49
+ "features/step_definitions/time_entry_steps.rb",
50
+ "features/support/env.rb",
51
+ "features/support/error_helpers.rb",
52
+ "features/support/fixtures/empty_clients.xml",
53
+ "features/support/fixtures/over_limit.xml",
54
+ "features/support/fixtures/receipt.png",
55
+ "features/support/fixtures/under_limit.xml",
56
+ "features/support/harvest_credentials.example.yml",
57
+ "features/support/harvest_helpers.rb",
58
+ "features/support/inflections.rb",
59
+ "features/task_assignment.feature",
60
+ "features/tasks.feature",
61
+ "features/time_tracking.feature",
62
+ "features/user_assignments.feature",
63
+ "features/users.feature",
64
+ "harvested.gemspec",
65
+ "lib/harvest/api/account.rb",
66
+ "lib/harvest/api/base.rb",
67
+ "lib/harvest/api/clients.rb",
68
+ "lib/harvest/api/contacts.rb",
69
+ "lib/harvest/api/expense_categories.rb",
70
+ "lib/harvest/api/expenses.rb",
71
+ "lib/harvest/api/projects.rb",
72
+ "lib/harvest/api/reports.rb",
73
+ "lib/harvest/api/task_assignments.rb",
74
+ "lib/harvest/api/tasks.rb",
75
+ "lib/harvest/api/time.rb",
76
+ "lib/harvest/api/user_assignments.rb",
77
+ "lib/harvest/api/users.rb",
78
+ "lib/harvest/base.rb",
79
+ "lib/harvest/base_model.rb",
80
+ "lib/harvest/behavior/activatable.rb",
81
+ "lib/harvest/behavior/crud.rb",
82
+ "lib/harvest/client.rb",
83
+ "lib/harvest/contact.rb",
84
+ "lib/harvest/credentials.rb",
85
+ "lib/harvest/errors.rb",
86
+ "lib/harvest/expense.rb",
87
+ "lib/harvest/expense_category.rb",
88
+ "lib/harvest/hardy_client.rb",
89
+ "lib/harvest/project.rb",
90
+ "lib/harvest/rate_limit_status.rb",
91
+ "lib/harvest/task.rb",
92
+ "lib/harvest/task_assignment.rb",
93
+ "lib/harvest/time_entry.rb",
94
+ "lib/harvest/timezones.rb",
95
+ "lib/harvest/user.rb",
96
+ "lib/harvest/user_assignment.rb",
97
+ "lib/harvested.rb",
98
+ "spec/harvest/base_spec.rb",
99
+ "spec/harvest/credentials_spec.rb",
100
+ "spec/harvest/expense_spec.rb",
101
+ "spec/harvest/task_assignment_spec.rb",
102
+ "spec/harvest/time_entry_spec.rb",
103
+ "spec/harvest/user_assignment_spec.rb",
104
+ "spec/harvest/user_spec.rb",
105
+ "spec/spec.default.opts",
106
+ "spec/spec_helper.rb"
107
+ ]
108
+ s.homepage = %q{http://github.com/quilted/harvested}
109
+ s.rdoc_options = ["--charset=UTF-8"]
110
+ s.require_paths = ["lib"]
111
+ s.rubygems_version = %q{1.3.7}
112
+ s.summary = %q{A Ruby Wrapper for the Harvest API http://www.getharvest.com/}
113
+ s.test_files = [
114
+ "spec/harvest/base_spec.rb",
115
+ "spec/harvest/credentials_spec.rb",
116
+ "spec/harvest/expense_spec.rb",
117
+ "spec/harvest/task_assignment_spec.rb",
118
+ "spec/harvest/time_entry_spec.rb",
119
+ "spec/harvest/user_assignment_spec.rb",
120
+ "spec/harvest/user_spec.rb",
121
+ "spec/spec_helper.rb",
122
+ "examples/basics.rb",
123
+ "examples/clear_account.rb",
124
+ "examples/task_assignments.rb",
125
+ "examples/user_assignments.rb"
126
+ ]
127
+
128
+ if s.respond_to? :specification_version then
129
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
130
+ s.specification_version = 3
131
+
132
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
133
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
134
+ s.add_development_dependency(%q<cucumber>, [">= 0"])
135
+ s.add_development_dependency(%q<ruby-debug>, [">= 0"])
136
+ s.add_development_dependency(%q<fakeweb>, [">= 0"])
137
+ s.add_runtime_dependency(%q<httparty>, [">= 0"])
138
+ s.add_runtime_dependency(%q<happymapper>, [">= 0"])
139
+ s.add_runtime_dependency(%q<builder>, [">= 0"])
140
+ else
141
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
142
+ s.add_dependency(%q<cucumber>, [">= 0"])
143
+ s.add_dependency(%q<ruby-debug>, [">= 0"])
144
+ s.add_dependency(%q<fakeweb>, [">= 0"])
145
+ s.add_dependency(%q<httparty>, [">= 0"])
146
+ s.add_dependency(%q<happymapper>, [">= 0"])
147
+ s.add_dependency(%q<builder>, [">= 0"])
148
+ end
149
+ else
150
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
151
+ s.add_dependency(%q<cucumber>, [">= 0"])
152
+ s.add_dependency(%q<ruby-debug>, [">= 0"])
153
+ s.add_dependency(%q<fakeweb>, [">= 0"])
154
+ s.add_dependency(%q<httparty>, [">= 0"])
155
+ s.add_dependency(%q<happymapper>, [">= 0"])
156
+ s.add_dependency(%q<builder>, [">= 0"])
157
+ end
158
+ end
159
+
@@ -0,0 +1,15 @@
1
+ module Harvest
2
+ module API
3
+
4
+ # API Methods to contain all account actions
5
+ class Account < Base
6
+
7
+ # Returns the current rate limit information
8
+ # @return [Harvest::RateLimitStatus]
9
+ def rate_limit_status
10
+ response = request(:get, credentials, '/account/rate_limit_status')
11
+ Harvest::RateLimitStatus.parse(response.body)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,42 @@
1
+ module Harvest
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
+ response = HTTParty.send(method, "#{credentials.host}#{path}", :query => options[:query], :body => options[:body], :headers => {"Accept" => "application/xml", "Content-Type" => "application/xml; charset=utf-8", "Authorization" => "Basic #{credentials.basic_auth}", "User-Agent" => "Harvestable/#{Harvest::VERSION}"}.update(options[:headers] || {}), :format => :plain)
23
+ case response.code
24
+ when 200..201
25
+ response
26
+ when 400
27
+ raise Harvest::BadRequest.new(response)
28
+ when 404
29
+ raise Harvest::NotFound.new(response)
30
+ when 500
31
+ raise Harvest::ServerError.new(response)
32
+ when 502
33
+ raise Harvest::Unavailable.new(response)
34
+ when 503
35
+ raise Harvest::RateLimited.new(response)
36
+ else
37
+ raise Harvest::InformHarvest.new(response)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,10 @@
1
+ module Harvest
2
+ module API
3
+ class Clients < Base
4
+ api_model Harvest::Client
5
+
6
+ include Harvest::Behavior::Crud
7
+ include Harvest::Behavior::Activatable
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ module Harvest
2
+ module API
3
+ class Contacts < Base
4
+ api_model Harvest::Contact
5
+
6
+ include Harvest::Behavior::Crud
7
+
8
+ def all(client_id = nil)
9
+ response = if client_id
10
+ request(:get, credentials, "/clients/#{client_id}/contacts")
11
+ else
12
+ request(:get, credentials, "/contacts")
13
+ end
14
+
15
+ api_model.parse(response.body)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ module Harvest
2
+ module API
3
+ class ExpenseCategories < Base
4
+ api_model Harvest::ExpenseCategory
5
+
6
+ include Harvest::Behavior::Crud
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,28 @@
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)
9
+ date = ::Time.parse(date) if String === date
10
+ response = request(:get, credentials, "#{api_model.api_path}/#{date.yday}/#{date.year}")
11
+ api_model.parse(response.body)
12
+ end
13
+
14
+
15
+ # This is currently broken, but will come back to it
16
+ def attach(expense, filename, receipt)
17
+ body = ""
18
+ body << "------------------------------b7edea381b46\r\n"
19
+ body << %Q{Content-Disposition: form-data; name="expense[receipt]"; filename="#{filename}"\r\n}
20
+ body << "Content-Type: image/png\r\n"
21
+ body << "\r\n#{receipt.read}\r\n"
22
+ body << "------------------------------b7edea381b46\r\n"
23
+
24
+ request(:post, credentials, "#{api_model.api_path}/#{expense.to_i}/receipt", :headers => {'Content-Type' => 'multipart/form-data; boundary=------------------------------b7edea381b46'}, :body => body)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,54 @@
1
+ module Harvest
2
+ module API
3
+ class Projects < Base
4
+ api_model Harvest::Project
5
+
6
+ include Harvest::Behavior::Crud
7
+
8
+ # Creates and Assigns a task to the project
9
+ #
10
+ # == Examples
11
+ # project = harvest.projects.find(401)
12
+ # harvest.projects.create_task(project, 'Bottling Glue') # creates and assigns a task to the project
13
+ #
14
+ # @return [Harvest::Project]
15
+ def create_task(project, task_name)
16
+ response = request(:post, credentials, "/projects/#{project.to_i}/task_assignments/add_with_create_new_task", :body => task_xml(task_name))
17
+ id = response.headers["location"].first.match(/\/.*\/(\d+)\/.*\/(\d+)/)[1]
18
+ find(id)
19
+ end
20
+
21
+ # Deactivates the project. Does nothing if the project is already deactivated
22
+ #
23
+ # @param [Harvest::Project] project the project you want to deactivate
24
+ # @return [Harvest::Project] the deactivated project
25
+ def deactivate(project)
26
+ if project.active?
27
+ request(:put, credentials, "#{api_model.api_path}/#{project.to_i}/toggle", :headers => {'Content-Length' => '0'})
28
+ project.active = false
29
+ end
30
+ project
31
+ end
32
+
33
+ # Activates the project. Does nothing if the project is already activated
34
+ #
35
+ # @param [Harvest::Project] project the project you want to activate
36
+ # @return [Harvest::Project] the activated project
37
+ def activate(project)
38
+ if !project.active?
39
+ request(:put, credentials, "#{api_model.api_path}/#{project.to_i}/toggle", :headers => {'Content-Length' => '0'})
40
+ project.active = true
41
+ end
42
+ project
43
+ end
44
+
45
+ private
46
+ def task_xml(name)
47
+ builder = Builder::XmlMarkup.new
48
+ builder.task do |t|
49
+ t.name(name)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,39 @@
1
+ module Harvest
2
+ module API
3
+ class Reports < Base
4
+
5
+ def time_by_project(project, start_date, end_date, user = nil)
6
+ query = {:from => start_date.strftime("%Y%m%d"), :to => end_date.strftime("%Y%m%d")}
7
+ query[:user_id] = user.to_i if user
8
+
9
+ response = request(:get, credentials, "/projects/#{project.to_i}/entries", :query => query)
10
+ Harvest::TimeEntry.parse(massage_xml(response.body))
11
+ end
12
+
13
+ def time_by_user(user, start_date, end_date, project = nil)
14
+ query = {:from => start_date.strftime("%Y%m%d"), :to => end_date.strftime("%Y%m%d")}
15
+ query[:project_id] = project.to_i if project
16
+
17
+ response = request(:get, credentials, "/people/#{user.to_i}/entries", :query => query)
18
+ Harvest::TimeEntry.parse(massage_xml(response.body))
19
+ end
20
+
21
+ def expenses_by_user(user, start_date, end_date)
22
+ query = {:from => start_date.strftime("%Y%m%d"), :to => end_date.strftime("%Y%m%d")}
23
+
24
+ response = request(:get, credentials, "/people/#{user.to_i}/expenses", :query => query)
25
+ Harvest::Expense.parse(response.body)
26
+ end
27
+
28
+ private
29
+ def massage_xml(original_xml)
30
+ # this needs to be done because of the differences in dashes and underscores in the harvest api
31
+ xml = original_xml
32
+ %w(day-entry adjustment-record created-at project-id spent-at task-id timer-started-at updated-at user-id).each do |dash_field|
33
+ xml = xml.gsub(dash_field, dash_field.gsub("-", "_"))
34
+ end
35
+ xml
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ module Harvest
2
+ module API
3
+ class TaskAssignments < Base
4
+
5
+ def all(project)
6
+ response = request(:get, credentials, "/projects/#{project.to_i}/task_assignments")
7
+ Harvest::TaskAssignment.parse(response.body)
8
+ end
9
+
10
+ def find(project, id)
11
+ response = request(:get, credentials, "/projects/#{project.to_i}/task_assignments/#{id}")
12
+ Harvest::TaskAssignment.parse(response.body, :single => true)
13
+ end
14
+
15
+ def create(task_assignment)
16
+ response = request(:post, credentials, "/projects/#{task_assignment.project_id}/task_assignments", :body => task_assignment.task_xml)
17
+ id = response.headers["location"].first.match(/\/.*\/(\d+)\/.*\/(\d+)/)[2]
18
+ find(task_assignment.project_id, id)
19
+ end
20
+
21
+ def update(task_assignment)
22
+ request(:put, credentials, "/projects/#{task_assignment.project_id}/task_assignments/#{task_assignment.to_i}", :body => task_assignment.to_xml)
23
+ find(task_assignment.project_id, task_assignment.id)
24
+ end
25
+
26
+ def delete(task_assignment)
27
+ request(:delete, credentials, "/projects/#{task_assignment.project_id}/task_assignments/#{task_assignment.to_i}")
28
+ task_assignment.id
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,9 @@
1
+ module Harvest
2
+ module API
3
+ class Tasks < Base
4
+ api_model Harvest::Task
5
+
6
+ include Harvest::Behavior::Crud
7
+ end
8
+ end
9
+ end