quilted-harvested 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +3 -0
- data/.gitignore +25 -0
- data/HISTORY +19 -0
- data/MIT-LICENSE +20 -0
- data/README.md +68 -0
- data/Rakefile +25 -0
- data/VERSION +1 -0
- data/examples/basics.rb +35 -0
- data/examples/clear_account.rb +28 -0
- data/examples/task_assignments.rb +27 -0
- data/examples/user_assignments.rb +24 -0
- data/features/account.feature +7 -0
- data/features/client_contacts.feature +23 -0
- data/features/clients.feature +29 -0
- data/features/errors.feature +25 -0
- data/features/expense_categories.feature +21 -0
- data/features/expenses.feature +55 -0
- data/features/hardy_client.feature +40 -0
- data/features/projects.feature +39 -0
- data/features/reporting.feature +72 -0
- data/features/step_definitions/account_steps.rb +7 -0
- data/features/step_definitions/assignment_steps.rb +100 -0
- data/features/step_definitions/contact_steps.rb +11 -0
- data/features/step_definitions/debug_steps.rb +3 -0
- data/features/step_definitions/error_steps.rb +113 -0
- data/features/step_definitions/expenses_steps.rb +46 -0
- data/features/step_definitions/harvest_steps.rb +8 -0
- data/features/step_definitions/model_steps.rb +90 -0
- data/features/step_definitions/people_steps.rb +4 -0
- data/features/step_definitions/report_steps.rb +91 -0
- data/features/step_definitions/time_entry_steps.rb +40 -0
- data/features/support/env.rb +37 -0
- data/features/support/error_helpers.rb +18 -0
- data/features/support/fixtures/empty_clients.xml +2 -0
- data/features/support/fixtures/over_limit.xml +8 -0
- data/features/support/fixtures/receipt.png +0 -0
- data/features/support/fixtures/under_limit.xml +8 -0
- data/features/support/harvest_credentials.example.yml +4 -0
- data/features/support/harvest_helpers.rb +11 -0
- data/features/support/inflections.rb +9 -0
- data/features/task_assignment.feature +69 -0
- data/features/tasks.feature +25 -0
- data/features/time_tracking.feature +29 -0
- data/features/user_assignments.feature +33 -0
- data/features/users.feature +55 -0
- data/harvested.gemspec +159 -0
- data/lib/harvest/api/account.rb +15 -0
- data/lib/harvest/api/base.rb +42 -0
- data/lib/harvest/api/clients.rb +10 -0
- data/lib/harvest/api/contacts.rb +19 -0
- data/lib/harvest/api/expense_categories.rb +9 -0
- data/lib/harvest/api/expenses.rb +28 -0
- data/lib/harvest/api/projects.rb +54 -0
- data/lib/harvest/api/reports.rb +39 -0
- data/lib/harvest/api/task_assignments.rb +32 -0
- data/lib/harvest/api/tasks.rb +9 -0
- data/lib/harvest/api/time.rb +32 -0
- data/lib/harvest/api/user_assignments.rb +32 -0
- data/lib/harvest/api/users.rb +21 -0
- data/lib/harvest/base.rb +258 -0
- data/lib/harvest/base_model.rb +73 -0
- data/lib/harvest/behavior/activatable.rb +31 -0
- data/lib/harvest/behavior/crud.rb +57 -0
- data/lib/harvest/client.rb +30 -0
- data/lib/harvest/contact.rb +29 -0
- data/lib/harvest/credentials.rb +21 -0
- data/lib/harvest/errors.rb +23 -0
- data/lib/harvest/expense.rb +19 -0
- data/lib/harvest/expense_category.rb +18 -0
- data/lib/harvest/hardy_client.rb +80 -0
- data/lib/harvest/project.rb +56 -0
- data/lib/harvest/rate_limit_status.rb +28 -0
- data/lib/harvest/task.rb +30 -0
- data/lib/harvest/task_assignment.rb +34 -0
- data/lib/harvest/time_entry.rb +42 -0
- data/lib/harvest/timezones.rb +149 -0
- data/lib/harvest/user.rb +66 -0
- data/lib/harvest/user_assignment.rb +34 -0
- data/lib/harvested.rb +62 -0
- data/spec/harvest/base_spec.rb +9 -0
- data/spec/harvest/credentials_spec.rb +22 -0
- data/spec/harvest/expense_spec.rb +15 -0
- data/spec/harvest/task_assignment_spec.rb +10 -0
- data/spec/harvest/time_entry_spec.rb +22 -0
- data/spec/harvest/user_assignment_spec.rb +10 -0
- data/spec/harvest/user_spec.rb +32 -0
- data/spec/spec.default.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- metadata +264 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
module Harvest
|
2
|
+
module API
|
3
|
+
class Time < Base
|
4
|
+
|
5
|
+
def find(id)
|
6
|
+
response = request(:get, credentials, "/daily/show/#{id}")
|
7
|
+
Harvest::TimeEntry.parse(response.body, :single => true)
|
8
|
+
end
|
9
|
+
|
10
|
+
def all(date = ::Time.now)
|
11
|
+
date = ::Time.parse(date) if String === date
|
12
|
+
response = request(:get, credentials, "/daily/#{date.yday}/#{date.year}")
|
13
|
+
Harvest::TimeEntry.parse(response.body)
|
14
|
+
end
|
15
|
+
|
16
|
+
def create(entry)
|
17
|
+
response = request(:post, credentials, '/daily/add', :body => entry.to_xml)
|
18
|
+
Harvest::TimeEntry.parse(response.body).first
|
19
|
+
end
|
20
|
+
|
21
|
+
def update(entry)
|
22
|
+
request(:put, credentials, "/daily/update/#{entry.to_i}", :body => entry.to_xml)
|
23
|
+
find(entry.id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(entry)
|
27
|
+
request(:delete, credentials, "/daily/delete/#{entry.to_i}")
|
28
|
+
entry.id
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
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.body)
|
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.body, :single => true)
|
13
|
+
end
|
14
|
+
|
15
|
+
def create(user_assignment)
|
16
|
+
response = request(:post, credentials, "/projects/#{user_assignment.project_id}/user_assignments", :body => user_assignment.user_xml)
|
17
|
+
id = response.headers["location"].first.match(/\/.*\/(\d+)\/.*\/(\d+)/)[2]
|
18
|
+
find(user_assignment.project_id, id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def update(user_assignment)
|
22
|
+
request(:put, credentials, "/projects/#{user_assignment.project_id}/user_assignments/#{user_assignment.id}", :body => user_assignment.to_xml)
|
23
|
+
find(user_assignment.project_id, user_assignment.id)
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(user_assignment)
|
27
|
+
request(:delete, credentials, "/projects/#{user_assignment.project_id}/user_assignments/#{user_assignment.to_i}")
|
28
|
+
user_assignment.id
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
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
|
data/lib/harvest/base.rb
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
module Harvest
|
2
|
+
class Base
|
3
|
+
attr_reader :request, :credentials
|
4
|
+
|
5
|
+
# @see Harvest.client
|
6
|
+
# @see Harvest.hardy_client
|
7
|
+
def initialize(subdomain, username, password, options = {})
|
8
|
+
options[:ssl] = true if options[:ssl].nil?
|
9
|
+
@credentials = Credentials.new(subdomain, username, password, options[:ssl])
|
10
|
+
raise InvalidCredentials unless credentials.valid?
|
11
|
+
end
|
12
|
+
|
13
|
+
# All API actions surrounding accounts
|
14
|
+
#
|
15
|
+
# == Examples
|
16
|
+
# harvest.account.rate_limit_status # Returns a Harvest::RateLimitStatus
|
17
|
+
#
|
18
|
+
# @return [Harvest::API::Account]
|
19
|
+
def account
|
20
|
+
@account ||= Harvest::API::Account.new(credentials)
|
21
|
+
end
|
22
|
+
|
23
|
+
# All API Actions surrounding Clients
|
24
|
+
#
|
25
|
+
# == Examples
|
26
|
+
# harvest.clients.all() # Returns all clients in the system
|
27
|
+
#
|
28
|
+
# harvest.clients.find(100) # Returns the client with id = 100
|
29
|
+
#
|
30
|
+
# client = Harvest::Client.new(:name => 'SuprCorp')
|
31
|
+
# saved_client = harvest.clients.create(client) # returns a saved version of Harvest::Client
|
32
|
+
#
|
33
|
+
# client = harvest.clients.find(205)
|
34
|
+
# client.name = 'SuprCorp LTD.'
|
35
|
+
# updated_client = harvest.clients.update(client) # returns an updated version of Harvest::Client
|
36
|
+
#
|
37
|
+
# client = harvest.clients.find(205)
|
38
|
+
# harvest.clients.delete(client) # returns 205
|
39
|
+
#
|
40
|
+
# client = harvest.clients.find(301)
|
41
|
+
# deactivated_client = harvest.clients.deactivate(client) # returns an updated deactivated client
|
42
|
+
# activated_client = harvest.clients.activate(client) # returns an updated activated client
|
43
|
+
#
|
44
|
+
# @see Harvest::Behavior::Crud
|
45
|
+
# @see Harvest::Behavior::Activatable
|
46
|
+
# @return [Harvest::API::Clients]
|
47
|
+
def clients
|
48
|
+
@clients ||= Harvest::API::Clients.new(credentials)
|
49
|
+
end
|
50
|
+
|
51
|
+
# All API Actions surrounding Client Contacts
|
52
|
+
#
|
53
|
+
# == Examples
|
54
|
+
# harvest.contacts.all() # Returns all contacts in the system
|
55
|
+
# harvest.contacts.all(10) # Returns all contacts for the client id=10 in the system
|
56
|
+
#
|
57
|
+
# harvest.contacts.find(100) # Returns the contact with id = 100
|
58
|
+
#
|
59
|
+
# contact = Harvest::Contact.new(:first_name => 'Jane', :last_name => 'Doe', :client_id => 10)
|
60
|
+
# saved_contact = harvest.contacts.create(contact) # returns a saved version of Harvest::Contact
|
61
|
+
#
|
62
|
+
# contact = harvest.contacts.find(205)
|
63
|
+
# contact.first_name = 'Jilly'
|
64
|
+
# updated_contact = harvest.contacts.update(contact) # returns an updated version of Harvest::Contact
|
65
|
+
#
|
66
|
+
# contact = harvest.contacts.find(205)
|
67
|
+
# harvest.contacts.delete(contact) # returns 205
|
68
|
+
#
|
69
|
+
# @see Harvest::Behavior::Crud
|
70
|
+
# @return [Harvest::API::Contacts]
|
71
|
+
def contacts
|
72
|
+
@contacts ||= Harvest::API::Contacts.new(credentials)
|
73
|
+
end
|
74
|
+
|
75
|
+
# All API Actions surrounding Projects
|
76
|
+
#
|
77
|
+
# == Examples
|
78
|
+
# harvest.projects.all() # Returns all projects in the system
|
79
|
+
#
|
80
|
+
# harvest.projects.find(100) # Returns the project with id = 100
|
81
|
+
#
|
82
|
+
# project = Harvest::Project.new(:name => 'SuprGlu' :client_id => 10)
|
83
|
+
# saved_project = harvest.projects.create(project) # returns a saved version of Harvest::Project
|
84
|
+
#
|
85
|
+
# project = harvest.projects.find(205)
|
86
|
+
# project.name = 'SuprSticky'
|
87
|
+
# updated_project = harvest.projects.update(project) # returns an updated version of Harvest::Project
|
88
|
+
#
|
89
|
+
# project = harvest.project.find(205)
|
90
|
+
# harvest.projects.delete(project) # returns 205
|
91
|
+
#
|
92
|
+
# project = harvest.projects.find(301)
|
93
|
+
# deactivated_project = harvest.projects.deactivate(project) # returns an updated deactivated project
|
94
|
+
# activated_project = harvest.projects.activate(project) # returns an updated activated project
|
95
|
+
#
|
96
|
+
# project = harvest.projects.find(401)
|
97
|
+
# harvest.projects.create_task(project, 'Bottling Glue') # creates and assigns a task to the project
|
98
|
+
#
|
99
|
+
# @see Harvest::Behavior::Crud
|
100
|
+
# @see Harvest::Behavior::Activatable
|
101
|
+
# @return [Harvest::API::Projects]
|
102
|
+
def projects
|
103
|
+
@projects ||= Harvest::API::Projects.new(credentials)
|
104
|
+
end
|
105
|
+
|
106
|
+
# All API Actions surrounding Tasks
|
107
|
+
#
|
108
|
+
# == Examples
|
109
|
+
# harvest.tasks.all() # Returns all tasks in the system
|
110
|
+
#
|
111
|
+
# harvest.tasks.find(100) # Returns the task with id = 100
|
112
|
+
#
|
113
|
+
# task = Harvest::Task.new(:name => 'Server Administration' :default => true)
|
114
|
+
# saved_task = harvest.tasks.create(task) # returns a saved version of Harvest::Task
|
115
|
+
#
|
116
|
+
# task = harvest.tasks.find(205)
|
117
|
+
# task.name = 'Server Administration'
|
118
|
+
# updated_task = harvest.tasks.update(task) # returns an updated version of Harvest::Task
|
119
|
+
#
|
120
|
+
# task = harvest.task.find(205)
|
121
|
+
# harvest.tasks.delete(task) # returns 205
|
122
|
+
#
|
123
|
+
# @see Harvest::Behavior::Crud
|
124
|
+
# @return [Harvest::API::Tasks]
|
125
|
+
def tasks
|
126
|
+
@tasks ||= Harvest::API::Tasks.new(credentials)
|
127
|
+
end
|
128
|
+
|
129
|
+
# All API Actions surrounding Users
|
130
|
+
#
|
131
|
+
# == Examples
|
132
|
+
# harvest.users.all() # Returns all users in the system
|
133
|
+
#
|
134
|
+
# harvest.users.find(100) # Returns the user with id = 100
|
135
|
+
#
|
136
|
+
# user = Harvest::User.new(:first_name => 'Edgar', :last_name => 'Ruth', :email => 'edgar@ruth.com', :password => 'mypassword', :password_confirmation => 'mypassword', :timezone => :cst, :admin => false, :telephone => '444-4444')
|
137
|
+
# saved_user = harvest.users.create(user) # returns a saved version of Harvest::User
|
138
|
+
#
|
139
|
+
# user = harvest.users.find(205)
|
140
|
+
# user.email = 'edgar@ruth.com'
|
141
|
+
# updated_user = harvest.users.update(user) # returns an updated version of Harvest::User
|
142
|
+
#
|
143
|
+
# user = harvest.users.find(205)
|
144
|
+
# harvest.users.delete(user) # returns 205
|
145
|
+
#
|
146
|
+
# user = harvest.users.find(301)
|
147
|
+
# deactivated_user = harvest.users.deactivate(user) # returns an updated deactivated user
|
148
|
+
# activated_user = harvest.users.activate(user) # returns an updated activated user
|
149
|
+
#
|
150
|
+
# user = harvest.users.find(401)
|
151
|
+
# harvest.users.reset_password(user) # will trigger the reset password feature of harvest and shoot the user an email
|
152
|
+
#
|
153
|
+
# @see Harvest::Behavior::Crud
|
154
|
+
# @see Harvest::Behavior::Activatable
|
155
|
+
# @return [Harvest::API::Users]
|
156
|
+
def users
|
157
|
+
@users ||= Harvest::API::Users.new(credentials)
|
158
|
+
end
|
159
|
+
|
160
|
+
# All API Actions surrounding assigning tasks to projects
|
161
|
+
#
|
162
|
+
# == Examples
|
163
|
+
# project = harvest.projects.find(101)
|
164
|
+
# harvest.task_assignments.all(project) # returns all tasks assigned to the project (as Harvest::TaskAssignment)
|
165
|
+
#
|
166
|
+
# project = harvest.projects.find(201)
|
167
|
+
# harvest.task_assignments.find(project, 5) # returns the task assignment with ID 5 that is assigned to the project
|
168
|
+
#
|
169
|
+
# project = harvest.projects.find(301)
|
170
|
+
# task = harvest.tasks.find(100)
|
171
|
+
# assignment = Harvest::TaskAssignment.new(:task_id => task.id, :project_id => project.id)
|
172
|
+
# saved_assignment = harvest.task_assignments.create(assignment) # returns a saved version of the task assignment
|
173
|
+
#
|
174
|
+
# project = harvest.projects.find(401)
|
175
|
+
# assignment = harvest.task_assignments.find(project, 15)
|
176
|
+
# assignment.hourly_rate = 150
|
177
|
+
# updated_assignment = harvest.task_assignments.update(assignment) # returns an updated assignment
|
178
|
+
#
|
179
|
+
# project = harvest.projects.find(501)
|
180
|
+
# assignment = harvest.task_assignments.find(project, 25)
|
181
|
+
# harvest.task_assignments.delete(assignment) # returns 25
|
182
|
+
#
|
183
|
+
# @return [Harvest::API::TaskAssignments]
|
184
|
+
def task_assignments
|
185
|
+
@task_assignments ||= Harvest::API::TaskAssignments.new(credentials)
|
186
|
+
end
|
187
|
+
|
188
|
+
# All API Actions surrounding assigning users to projects
|
189
|
+
#
|
190
|
+
# == Examples
|
191
|
+
# project = harvest.projects.find(101)
|
192
|
+
# harvest.user_assignments.all(project) # returns all users assigned to the project (as Harvest::UserAssignment)
|
193
|
+
#
|
194
|
+
# project = harvest.projects.find(201)
|
195
|
+
# harvest.user_assignments.find(project, 5) # returns the user assignment with ID 5 that is assigned to the project
|
196
|
+
#
|
197
|
+
# project = harvest.projects.find(301)
|
198
|
+
# user = harvest.users.find(100)
|
199
|
+
# assignment = Harvest::UserAssignment.new(:user_id => user.id, :project_id => project.id)
|
200
|
+
# saved_assignment = harvest.user_assignments.create(assignment) # returns a saved version of the user assignment
|
201
|
+
#
|
202
|
+
# project = harvest.projects.find(401)
|
203
|
+
# assignment = harvest.user_assignments.find(project, 15)
|
204
|
+
# assignment.project_manager = true
|
205
|
+
# updated_assignment = harvest.user_assignments.update(assignment) # returns an updated assignment
|
206
|
+
#
|
207
|
+
# project = harvest.projects.find(501)
|
208
|
+
# assignment = harvest.user_assignments.find(project, 25)
|
209
|
+
# harvest.user_assignments.delete(assignment) # returns 25
|
210
|
+
#
|
211
|
+
# @return [Harvest::API::UserAssignments]
|
212
|
+
def user_assignments
|
213
|
+
@user_assignments ||= Harvest::API::UserAssignments.new(credentials)
|
214
|
+
end
|
215
|
+
|
216
|
+
# All API Actions surrounding managing expense categories
|
217
|
+
#
|
218
|
+
# == Examples
|
219
|
+
# harvest.expense_categories.all() # Returns all expense categories in the system
|
220
|
+
#
|
221
|
+
# harvest.expense_categories.find(100) # Returns the expense category with id = 100
|
222
|
+
#
|
223
|
+
# category = Harvest::ExpenseCategory.new(:name => 'Mileage', :unit_price => 0.485)
|
224
|
+
# saved_category = harvest.expense_categories.create(category) # returns a saved version of Harvest::ExpenseCategory
|
225
|
+
#
|
226
|
+
# category = harvest.clients.find(205)
|
227
|
+
# category.name = 'Travel'
|
228
|
+
# updated_category = harvest.expense_categories.update(category) # returns an updated version of Harvest::ExpenseCategory
|
229
|
+
#
|
230
|
+
# category = harvest.expense_categories.find(205)
|
231
|
+
# harvest.expense_categories.delete(category) # returns 205
|
232
|
+
#
|
233
|
+
# @see Harvest::Behavior::Crud
|
234
|
+
# @return [Harvest::API::ExpenseCategories]
|
235
|
+
def expense_categories
|
236
|
+
@expense_categories ||= Harvest::API::ExpenseCategories.new(credentials)
|
237
|
+
end
|
238
|
+
|
239
|
+
# All API Actions surrounding expenses
|
240
|
+
#
|
241
|
+
# == Examples
|
242
|
+
# harvest.expenses.all() # Returns all expenses for the current week
|
243
|
+
# harvest.expenses.all(Time.parse('11/12/2009')) # returns all expenses for the week of 11/12/2009
|
244
|
+
#
|
245
|
+
# harvest.expenses.find(100) # Returns the expense with id = 100
|
246
|
+
def expenses
|
247
|
+
@expenses ||= Harvest::API::Expenses.new(credentials)
|
248
|
+
end
|
249
|
+
|
250
|
+
def time
|
251
|
+
@time ||= Harvest::API::Time.new(credentials)
|
252
|
+
end
|
253
|
+
|
254
|
+
def reports
|
255
|
+
@reports ||= Harvest::API::Reports.new(credentials)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Harvest
|
2
|
+
# The parent class of all Harvest models. Contains useful inheritable methods
|
3
|
+
class BaseModel
|
4
|
+
|
5
|
+
# Initializes the model. You can pass the attributes as a hash in a ActiveRecord-like style
|
6
|
+
#
|
7
|
+
# == Examples
|
8
|
+
# client = Harvest::Client.new(:name => 'John Doe')
|
9
|
+
# client.name # returns 'John Doe'
|
10
|
+
def initialize(attributes = {})
|
11
|
+
self.attributes = attributes
|
12
|
+
end
|
13
|
+
|
14
|
+
# Given a hash, sets the corresponding attributes
|
15
|
+
#
|
16
|
+
# == Examples
|
17
|
+
# client = Harvest::Client.new(:name => 'John Doe')
|
18
|
+
# client.name # returns 'John Doe'
|
19
|
+
# client.attributes = {:name => 'Terry Vaughn'}
|
20
|
+
# client.name # returns 'Terry Vaughn'
|
21
|
+
#
|
22
|
+
# @return [void]
|
23
|
+
def attributes=(attributes)
|
24
|
+
attributes.each {|k,v| send("#{k}=", v)}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Checks the equality of another model based on the id
|
28
|
+
#
|
29
|
+
# == Examples
|
30
|
+
# client1 = Harvest::Client.new(:id => 1)
|
31
|
+
# client2 = Harvest::Client.new(:id => 1)
|
32
|
+
# client3 = Harvest::Client.new(:id => 2)
|
33
|
+
#
|
34
|
+
# client1 == client2 # returns true
|
35
|
+
# client1 == client3 # returns false
|
36
|
+
#
|
37
|
+
# @return [Boolean]
|
38
|
+
def ==(other)
|
39
|
+
id == other.id
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the id of the model
|
43
|
+
#
|
44
|
+
# == Examples
|
45
|
+
# client = Harvest::Client.new(:id => 1)
|
46
|
+
# client.to_i # returns 1
|
47
|
+
#
|
48
|
+
# @return [Fixnum]
|
49
|
+
def to_i
|
50
|
+
id
|
51
|
+
end
|
52
|
+
|
53
|
+
# Builds the XML of the model. Primarily used to interact with the Harvest API.
|
54
|
+
#
|
55
|
+
# @return [String]
|
56
|
+
def to_xml
|
57
|
+
builder = Builder::XmlMarkup.new
|
58
|
+
builder.tag!(self.class.tag_name) do |c|
|
59
|
+
self.class.elements.each do |f|
|
60
|
+
c.tag!(f.tag, send(f.name)) if send(f.name)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class << self
|
66
|
+
# This sets the API path so the API collections can use them in an agnostic way
|
67
|
+
# @return [void]
|
68
|
+
def api_path(path = nil)
|
69
|
+
@path ||= path
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Harvest
|
2
|
+
module Behavior
|
3
|
+
|
4
|
+
# Activate/Deactivate behaviors that can be brought into API collections
|
5
|
+
module Activatable
|
6
|
+
# Deactivates the item. Does nothing if the item is already deactivated
|
7
|
+
#
|
8
|
+
# @param [Harvest::BaseModel] model the model you want to deactivate
|
9
|
+
# @return [Harvest::BaseModel] the deactivated model
|
10
|
+
def deactivate(model)
|
11
|
+
if model.active?
|
12
|
+
request(:post, credentials, "#{api_model.api_path}/#{model.to_i}/toggle")
|
13
|
+
model.active = false
|
14
|
+
end
|
15
|
+
model
|
16
|
+
end
|
17
|
+
|
18
|
+
# Activates the item. Does nothing if the item is already activated
|
19
|
+
#
|
20
|
+
# @param [Harvest::BaseModel] model the model you want to activate
|
21
|
+
# @return [Harvest::BaseModel] the activated model
|
22
|
+
def activate(model)
|
23
|
+
if !model.active?
|
24
|
+
request(:post, credentials, "#{api_model.api_path}/#{model.to_i}/toggle")
|
25
|
+
model.active = true
|
26
|
+
end
|
27
|
+
model
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Harvest
|
2
|
+
module Behavior
|
3
|
+
module Crud
|
4
|
+
# Retrieves all items
|
5
|
+
# @return [Array<Harvest::BaseModel>] an array of models depending on where you're calling it from (e.g. [Harvest::Client] from Harvest::Base#clients)
|
6
|
+
def all
|
7
|
+
response = request(:get, credentials, api_model.api_path)
|
8
|
+
api_model.parse(response.body)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Retrieves an item by id
|
12
|
+
# @overload find(id)
|
13
|
+
# @param [Integer] the id of the item you want to retreive
|
14
|
+
# @overload find(id)
|
15
|
+
# @param [String] id the String version of the id
|
16
|
+
# @overload find(model)
|
17
|
+
# @param [Harvest::BaseModel] id you can pass a model and it will return a refreshed version
|
18
|
+
#
|
19
|
+
# @return [Harvest::BaseModel] the model depends on where you're calling it from (e.g. Harvest::Client from Harvest::Base#clients)
|
20
|
+
def find(id)
|
21
|
+
response = request(:get, credentials, "#{api_model.api_path}/#{id}")
|
22
|
+
api_model.parse(response.body, :single => true)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Creates an item
|
26
|
+
# @param [Harvest::BaseModel] model the item you want to create
|
27
|
+
# @return [Harvest::BaseModel] the created model depending on where you're calling it from (e.g. Harvest::Client from Harvest::Base#clients)
|
28
|
+
def create(model)
|
29
|
+
response = request(:post, credentials, "#{api_model.api_path}", :body => model.to_xml)
|
30
|
+
id = response.headers["location"].first.match(/\/.*\/(\d+)/)[1]
|
31
|
+
find(id)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Updates an item
|
35
|
+
# @param [Harvest::BaseModel] model the model you want to update
|
36
|
+
# @return [Harvest::BaseModel] the created model depending on where you're calling it from (e.g. Harvest::Client from Harvest::Base#clients)
|
37
|
+
def update(model)
|
38
|
+
request(:put, credentials, "#{api_model.api_path}/#{model.to_i}", :body => model.to_xml)
|
39
|
+
find(model.id)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Deletes an item
|
43
|
+
# @overload delete(model)
|
44
|
+
# @param [Harvest::BaseModel] model the item you want to delete
|
45
|
+
# @overload delete(id)
|
46
|
+
# @param [Integer] id the id of the item you want to delete
|
47
|
+
# @overload delete(id)
|
48
|
+
# @param [String] id the String version of the id of the item you want to delete
|
49
|
+
#
|
50
|
+
# @return [Integer] the id of the item deleted
|
51
|
+
def delete(model)
|
52
|
+
request(:delete, credentials, "#{api_model.api_path}/#{model.to_i}")
|
53
|
+
model.to_i
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Harvest
|
2
|
+
# The model that contains information about a client
|
3
|
+
#
|
4
|
+
# == Fields
|
5
|
+
# [+id+] (READONLY) the id of the client
|
6
|
+
# [+name+] (REQUIRED) the name of the client
|
7
|
+
# [+details+] the details of the client
|
8
|
+
# [+currency+] what type of currency is associated with the client
|
9
|
+
# [+currency_symbol+] what currency symbol is associated with the client
|
10
|
+
# [+active?+] true|false on whether the client is active
|
11
|
+
# [+highrise_id+] (READONLY) the highrise id associated with this client
|
12
|
+
# [+update_at+] (READONLY) the last modification timestamp
|
13
|
+
class Client < BaseModel
|
14
|
+
include HappyMapper
|
15
|
+
|
16
|
+
api_path '/clients'
|
17
|
+
|
18
|
+
element :id, Integer
|
19
|
+
element :active, Boolean
|
20
|
+
element :name, String
|
21
|
+
element :details, String
|
22
|
+
element :currency, String
|
23
|
+
element :currency_symbol, String, :tag => "currency-symbol"
|
24
|
+
element :cache_version, Integer, :tag => "cache-version"
|
25
|
+
element :updated_at, Time, :tag => "updated-at"
|
26
|
+
element :highrise_id, Integer, :tag => 'highrise-id'
|
27
|
+
|
28
|
+
alias_method :active?, :active
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Harvest
|
2
|
+
# The model that contains information about a client contact
|
3
|
+
#
|
4
|
+
# == Fields
|
5
|
+
# [+id+] (READONLY) the id of the contact
|
6
|
+
# [+client_id+] (REQUIRED) the id of the client this contact is associated with
|
7
|
+
# [+first_name+] (REQUIRED) the first name of the contact
|
8
|
+
# [+last_name+] (REQUIRED) the last name of the contact
|
9
|
+
# [+email+] the email of the contact
|
10
|
+
# [+title+] the title of the contact
|
11
|
+
# [+phone_office+] the office phone number of the contact
|
12
|
+
# [+phone_moble+] the moble phone number of the contact
|
13
|
+
# [+fax+] the fax number of the contact
|
14
|
+
class Contact < BaseModel
|
15
|
+
include HappyMapper
|
16
|
+
|
17
|
+
api_path '/contacts'
|
18
|
+
|
19
|
+
element :id, Integer
|
20
|
+
element :client_id, Integer, :tag => "client-id"
|
21
|
+
element :email, String
|
22
|
+
element :first_name, String, :tag => "first-name"
|
23
|
+
element :last_name, String, :tag => "last-name"
|
24
|
+
element :phone_office, String, :tag => "phone-office"
|
25
|
+
element :phone_mobile, String, :tag => "phone-mobile"
|
26
|
+
element :fax, String
|
27
|
+
element :title, String
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Harvest
|
2
|
+
class Credentials
|
3
|
+
attr_accessor :subdomain, :username, :password, :ssl
|
4
|
+
|
5
|
+
def initialize(subdomain, username, password, ssl = true)
|
6
|
+
@subdomain, @username, @password, @ssl = subdomain, username, password, ssl
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid?
|
10
|
+
!subdomain.nil? && !username.nil? && !password.nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
def basic_auth
|
14
|
+
Base64.encode64("#{username}:#{password}").delete("\r\n")
|
15
|
+
end
|
16
|
+
|
17
|
+
def host
|
18
|
+
"#{ssl ? "https" : "http"}://#{subdomain}.harvestapp.com"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Harvest
|
2
|
+
class InvalidCredentials < StandardError; end
|
3
|
+
|
4
|
+
class HTTPError < StandardError
|
5
|
+
attr_reader :response
|
6
|
+
def initialize(response)
|
7
|
+
@response = response
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
hint = response.headers["hint"].nil? ? nil : response.headers["hint"].first
|
13
|
+
"#{self.class.to_s} : #{response.code}#{" - #{hint}" if hint}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class RateLimited < HTTPError; end
|
18
|
+
class NotFound < HTTPError; end
|
19
|
+
class Unavailable < HTTPError; end
|
20
|
+
class InformHarvest < HTTPError; end
|
21
|
+
class BadRequest < HTTPError; end
|
22
|
+
class ServerError < HTTPError; end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Harvest
|
2
|
+
class Expense < BaseModel
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
api_path '/expenses'
|
6
|
+
|
7
|
+
element :id, Integer
|
8
|
+
element :notes, String
|
9
|
+
element :units, Integer
|
10
|
+
element :total_cost, Float, :tag => 'total-cost'
|
11
|
+
element :project_id, Integer, :tag => 'project-id'
|
12
|
+
element :expense_category_id, Integer, :tag => 'expense-category-id'
|
13
|
+
element :spent_at, Time, :tag => 'spent-at'
|
14
|
+
|
15
|
+
def spent_at=(date)
|
16
|
+
@spent_at = (String === date ? Time.parse(date) : date)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Harvest
|
2
|
+
class ExpenseCategory < BaseModel
|
3
|
+
include HappyMapper
|
4
|
+
|
5
|
+
tag 'expense-category'
|
6
|
+
api_path '/expense_categories'
|
7
|
+
|
8
|
+
element :id, Integer
|
9
|
+
element :name, String
|
10
|
+
element :unit_name, String, :tag => 'unit-name'
|
11
|
+
element :unit_price, Float, :tag => 'unit-price'
|
12
|
+
element :deactivated, Boolean
|
13
|
+
|
14
|
+
def active?
|
15
|
+
!deactivated
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|