harvested 0.6.2 → 0.6.3
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 +6 -3
- data/HISTORY +30 -26
- data/README.md +3 -3
- data/Rakefile +2 -0
- data/VERSION +1 -1
- data/examples/project_create_script.rb +93 -0
- data/harvested.gemspec +18 -3
- data/lib/harvest/api/invoice_categories.rb +5 -5
- data/lib/harvest/api/invoice_payments.rb +31 -0
- data/lib/harvest/api/invoices.rb +27 -8
- data/lib/harvest/api/tasks.rb +27 -0
- data/lib/harvest/base.rb +78 -56
- data/lib/harvest/behavior/crud.rb +2 -1
- data/lib/harvest/expense.rb +2 -2
- data/lib/harvest/invoice.rb +56 -20
- data/lib/harvest/invoice_payment.rb +8 -0
- data/lib/harvest/time_entry.rb +2 -2
- data/lib/harvested.rb +6 -6
- data/spec/factories.rb +57 -0
- data/spec/functional/clients_spec.rb +10 -16
- data/spec/functional/invoice_payments_spec.rb +44 -0
- data/spec/functional/invoice_spec.rb +88 -40
- data/spec/functional/project_spec.rb +2 -8
- data/spec/functional/tasks_spec.rb +2 -8
- data/spec/harvest/expense_spec.rb +2 -2
- data/spec/harvest/invoice_payment_spec.rb +5 -0
- data/spec/harvest/invoice_spec.rb +9 -10
- data/spec/harvest/time_entry_spec.rb +3 -3
- data/spec/spec_helper.rb +3 -0
- data/spec/support/harvested_helpers.rb +1 -1
- metadata +58 -4
data/lib/harvest/base.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Harvest
|
2
2
|
class Base
|
3
3
|
attr_reader :request, :credentials
|
4
|
-
|
4
|
+
|
5
5
|
# @see Harvest.client
|
6
6
|
# @see Harvest.hardy_client
|
7
7
|
def initialize(subdomain, username, password, options = {})
|
@@ -9,7 +9,7 @@ module Harvest
|
|
9
9
|
@credentials = Credentials.new(subdomain, username, password, options[:ssl])
|
10
10
|
raise InvalidCredentials unless credentials.valid?
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
# All API actions surrounding accounts
|
14
14
|
#
|
15
15
|
# == Examples
|
@@ -19,76 +19,76 @@ module Harvest
|
|
19
19
|
def account
|
20
20
|
@account ||= Harvest::API::Account.new(credentials)
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
# All API Actions surrounding Clients
|
24
|
-
#
|
24
|
+
#
|
25
25
|
# == Examples
|
26
26
|
# harvest.clients.all() # Returns all clients in the system
|
27
|
-
#
|
27
|
+
#
|
28
28
|
# harvest.clients.find(100) # Returns the client with id = 100
|
29
|
-
#
|
29
|
+
#
|
30
30
|
# client = Harvest::Client.new(:name => 'SuprCorp')
|
31
31
|
# saved_client = harvest.clients.create(client) # returns a saved version of Harvest::Client
|
32
|
-
#
|
32
|
+
#
|
33
33
|
# client = harvest.clients.find(205)
|
34
34
|
# client.name = 'SuprCorp LTD.'
|
35
35
|
# updated_client = harvest.clients.update(client) # returns an updated version of Harvest::Client
|
36
|
-
#
|
36
|
+
#
|
37
37
|
# client = harvest.clients.find(205)
|
38
38
|
# harvest.clients.delete(client) # returns 205
|
39
|
-
#
|
39
|
+
#
|
40
40
|
# client = harvest.clients.find(301)
|
41
41
|
# deactivated_client = harvest.clients.deactivate(client) # returns an updated deactivated client
|
42
42
|
# activated_client = harvest.clients.activate(client) # returns an updated activated client
|
43
|
-
#
|
43
|
+
#
|
44
44
|
# @see Harvest::Behavior::Crud
|
45
45
|
# @see Harvest::Behavior::Activatable
|
46
46
|
# @return [Harvest::API::Clients]
|
47
47
|
def clients
|
48
48
|
@clients ||= Harvest::API::Clients.new(credentials)
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
# All API Actions surrounding Client Contacts
|
52
|
-
#
|
52
|
+
#
|
53
53
|
# == Examples
|
54
54
|
# harvest.contacts.all() # Returns all contacts in the system
|
55
55
|
# harvest.contacts.all(10) # Returns all contacts for the client id=10 in the system
|
56
|
-
#
|
56
|
+
#
|
57
57
|
# harvest.contacts.find(100) # Returns the contact with id = 100
|
58
|
-
#
|
58
|
+
#
|
59
59
|
# contact = Harvest::Contact.new(:first_name => 'Jane', :last_name => 'Doe', :client_id => 10)
|
60
60
|
# saved_contact = harvest.contacts.create(contact) # returns a saved version of Harvest::Contact
|
61
|
-
#
|
61
|
+
#
|
62
62
|
# contact = harvest.contacts.find(205)
|
63
63
|
# contact.first_name = 'Jilly'
|
64
64
|
# updated_contact = harvest.contacts.update(contact) # returns an updated version of Harvest::Contact
|
65
|
-
#
|
65
|
+
#
|
66
66
|
# contact = harvest.contacts.find(205)
|
67
67
|
# harvest.contacts.delete(contact) # returns 205
|
68
|
-
#
|
68
|
+
#
|
69
69
|
# @see Harvest::Behavior::Crud
|
70
70
|
# @return [Harvest::API::Contacts]
|
71
71
|
def contacts
|
72
72
|
@contacts ||= Harvest::API::Contacts.new(credentials)
|
73
73
|
end
|
74
|
-
|
74
|
+
|
75
75
|
# All API Actions surrounding Projects
|
76
|
-
#
|
76
|
+
#
|
77
77
|
# == Examples
|
78
78
|
# harvest.projects.all() # Returns all projects in the system
|
79
|
-
#
|
79
|
+
#
|
80
80
|
# harvest.projects.find(100) # Returns the project with id = 100
|
81
|
-
#
|
81
|
+
#
|
82
82
|
# project = Harvest::Project.new(:name => 'SuprGlu' :client_id => 10)
|
83
83
|
# saved_project = harvest.projects.create(project) # returns a saved version of Harvest::Project
|
84
|
-
#
|
84
|
+
#
|
85
85
|
# project = harvest.projects.find(205)
|
86
86
|
# project.name = 'SuprSticky'
|
87
87
|
# updated_project = harvest.projects.update(project) # returns an updated version of Harvest::Project
|
88
|
-
#
|
88
|
+
#
|
89
89
|
# project = harvest.project.find(205)
|
90
90
|
# harvest.projects.delete(project) # returns 205
|
91
|
-
#
|
91
|
+
#
|
92
92
|
# project = harvest.projects.find(301)
|
93
93
|
# deactivated_project = harvest.projects.deactivate(project) # returns an updated deactivated project
|
94
94
|
# activated_project = harvest.projects.activate(project) # returns an updated activated project
|
@@ -102,67 +102,67 @@ module Harvest
|
|
102
102
|
def projects
|
103
103
|
@projects ||= Harvest::API::Projects.new(credentials)
|
104
104
|
end
|
105
|
-
|
105
|
+
|
106
106
|
# All API Actions surrounding Tasks
|
107
|
-
#
|
107
|
+
#
|
108
108
|
# == Examples
|
109
109
|
# harvest.tasks.all() # Returns all tasks in the system
|
110
|
-
#
|
110
|
+
#
|
111
111
|
# harvest.tasks.find(100) # Returns the task with id = 100
|
112
|
-
#
|
112
|
+
#
|
113
113
|
# task = Harvest::Task.new(:name => 'Server Administration' :default => true)
|
114
114
|
# saved_task = harvest.tasks.create(task) # returns a saved version of Harvest::Task
|
115
|
-
#
|
115
|
+
#
|
116
116
|
# task = harvest.tasks.find(205)
|
117
117
|
# task.name = 'Server Administration'
|
118
118
|
# updated_task = harvest.tasks.update(task) # returns an updated version of Harvest::Task
|
119
|
-
#
|
119
|
+
#
|
120
120
|
# task = harvest.task.find(205)
|
121
121
|
# harvest.tasks.delete(task) # returns 205
|
122
|
-
#
|
122
|
+
#
|
123
123
|
# @see Harvest::Behavior::Crud
|
124
124
|
# @return [Harvest::API::Tasks]
|
125
125
|
def tasks
|
126
126
|
@tasks ||= Harvest::API::Tasks.new(credentials)
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
# All API Actions surrounding Users
|
130
|
-
#
|
130
|
+
#
|
131
131
|
# == Examples
|
132
132
|
# harvest.users.all() # Returns all users in the system
|
133
|
-
#
|
133
|
+
#
|
134
134
|
# harvest.users.find(100) # Returns the user with id = 100
|
135
|
-
#
|
135
|
+
#
|
136
136
|
# user = Harvest::User.new(:first_name => 'Edgar', :last_name => 'Ruth', :email => 'edgar@ruth.com', :password => 'mypassword', :timezone => :cst, :admin => false, :telephone => '444-4444')
|
137
137
|
# saved_user = harvest.users.create(user) # returns a saved version of Harvest::User
|
138
|
-
#
|
138
|
+
#
|
139
139
|
# user = harvest.users.find(205)
|
140
140
|
# user.email = 'edgar@ruth.com'
|
141
141
|
# updated_user = harvest.users.update(user) # returns an updated version of Harvest::User
|
142
|
-
#
|
142
|
+
#
|
143
143
|
# user = harvest.users.find(205)
|
144
144
|
# harvest.users.delete(user) # returns 205
|
145
|
-
#
|
145
|
+
#
|
146
146
|
# user = harvest.users.find(301)
|
147
147
|
# deactivated_user = harvest.users.deactivate(user) # returns an updated deactivated user
|
148
148
|
# activated_user = harvest.users.activate(user) # returns an updated activated user
|
149
|
-
#
|
149
|
+
#
|
150
150
|
# user = harvest.users.find(401)
|
151
151
|
# harvest.users.reset_password(user) # will trigger the reset password feature of harvest and shoot the user an email
|
152
|
-
#
|
152
|
+
#
|
153
153
|
# @see Harvest::Behavior::Crud
|
154
154
|
# @see Harvest::Behavior::Activatable
|
155
155
|
# @return [Harvest::API::Users]
|
156
156
|
def users
|
157
157
|
@users ||= Harvest::API::Users.new(credentials)
|
158
158
|
end
|
159
|
-
|
159
|
+
|
160
160
|
# All API Actions surrounding assigning tasks to projects
|
161
|
-
#
|
161
|
+
#
|
162
162
|
# == Examples
|
163
163
|
# project = harvest.projects.find(101)
|
164
164
|
# harvest.task_assignments.all(project) # returns all tasks assigned to the project (as Harvest::TaskAssignment)
|
165
|
-
#
|
165
|
+
#
|
166
166
|
# project = harvest.projects.find(201)
|
167
167
|
# harvest.task_assignments.find(project, 5) # returns the task assignment with ID 5 that is assigned to the project
|
168
168
|
#
|
@@ -184,13 +184,13 @@ module Harvest
|
|
184
184
|
def task_assignments
|
185
185
|
@task_assignments ||= Harvest::API::TaskAssignments.new(credentials)
|
186
186
|
end
|
187
|
-
|
187
|
+
|
188
188
|
# All API Actions surrounding assigning users to projects
|
189
|
-
#
|
189
|
+
#
|
190
190
|
# == Examples
|
191
191
|
# project = harvest.projects.find(101)
|
192
192
|
# harvest.user_assignments.all(project) # returns all users assigned to the project (as Harvest::UserAssignment)
|
193
|
-
#
|
193
|
+
#
|
194
194
|
# project = harvest.projects.find(201)
|
195
195
|
# harvest.user_assignments.find(project, 5) # returns the user assignment with ID 5 that is assigned to the project
|
196
196
|
#
|
@@ -212,21 +212,21 @@ module Harvest
|
|
212
212
|
def user_assignments
|
213
213
|
@user_assignments ||= Harvest::API::UserAssignments.new(credentials)
|
214
214
|
end
|
215
|
-
|
215
|
+
|
216
216
|
# All API Actions surrounding managing expense categories
|
217
217
|
#
|
218
218
|
# == Examples
|
219
219
|
# harvest.expense_categories.all() # Returns all expense categories in the system
|
220
|
-
#
|
220
|
+
#
|
221
221
|
# harvest.expense_categories.find(100) # Returns the expense category with id = 100
|
222
|
-
#
|
222
|
+
#
|
223
223
|
# category = Harvest::ExpenseCategory.new(:name => 'Mileage', :unit_price => 0.485)
|
224
224
|
# saved_category = harvest.expense_categories.create(category) # returns a saved version of Harvest::ExpenseCategory
|
225
|
-
#
|
225
|
+
#
|
226
226
|
# category = harvest.clients.find(205)
|
227
227
|
# category.name = 'Travel'
|
228
228
|
# updated_category = harvest.expense_categories.update(category) # returns an updated version of Harvest::ExpenseCategory
|
229
|
-
#
|
229
|
+
#
|
230
230
|
# category = harvest.expense_categories.find(205)
|
231
231
|
# harvest.expense_categories.delete(category) # returns 205
|
232
232
|
#
|
@@ -235,22 +235,22 @@ module Harvest
|
|
235
235
|
def expense_categories
|
236
236
|
@expense_categories ||= Harvest::API::ExpenseCategories.new(credentials)
|
237
237
|
end
|
238
|
-
|
238
|
+
|
239
239
|
# All API Actions surrounding expenses
|
240
240
|
#
|
241
241
|
# == Examples
|
242
242
|
# harvest.expenses.all() # Returns all expenses for the current week
|
243
243
|
# harvest.expenses.all(Time.parse('11/12/2009')) # returns all expenses for the week of 11/12/2009
|
244
|
-
#
|
244
|
+
#
|
245
245
|
# harvest.expenses.find(100) # Returns the expense with id = 100
|
246
246
|
def expenses
|
247
247
|
@expenses ||= Harvest::API::Expenses.new(credentials)
|
248
248
|
end
|
249
|
-
|
249
|
+
|
250
250
|
def time
|
251
251
|
@time ||= Harvest::API::Time.new(credentials)
|
252
252
|
end
|
253
|
-
|
253
|
+
|
254
254
|
def reports
|
255
255
|
@reports ||= Harvest::API::Reports.new(credentials)
|
256
256
|
end
|
@@ -262,5 +262,27 @@ module Harvest
|
|
262
262
|
def invoices
|
263
263
|
@invoices ||= Harvest::API::Invoices.new(credentials)
|
264
264
|
end
|
265
|
+
|
266
|
+
# All API Actions surrounding invoice payments
|
267
|
+
#
|
268
|
+
# == Examples
|
269
|
+
# invoice = harvest.invoices.find(100)
|
270
|
+
# harvest.invoice_payments.all(invoice) # returns all payments for the invoice (as Harvest::InvoicePayment)
|
271
|
+
#
|
272
|
+
# invoice = harvest.invoices.find(100)
|
273
|
+
# harvest.invoice_payments.find(invoice, 5) # returns the payment with ID 5 that is assigned to the invoice
|
274
|
+
#
|
275
|
+
# invoice = harvest.invoices.find(100)
|
276
|
+
# payment = Harvest::InvoicePayment.new(:invoice_id => invoice.id)
|
277
|
+
# saved_payment = harvest.invoice_payments.create(payment) # returns a saved version of the payment
|
278
|
+
#
|
279
|
+
# invoice = harvest.invoices.find(100)
|
280
|
+
# payment = harvest.invoice_payments.find(invoice, 5)
|
281
|
+
# harvest.invoice_payments.delete(payment) # returns 5
|
282
|
+
#
|
283
|
+
# @return [Harvest::API::InvoicePayments]
|
284
|
+
def invoice_payments
|
285
|
+
@invoice_payments ||= Harvest::API::InvoicePayments.new(credentials)
|
286
|
+
end
|
265
287
|
end
|
266
|
-
end
|
288
|
+
end
|
@@ -19,6 +19,7 @@ module Harvest
|
|
19
19
|
#
|
20
20
|
# @return [Harvest::BaseModel] the model depends on where you're calling it from (e.g. Harvest::Client from Harvest::Base#clients)
|
21
21
|
def find(id, user = nil)
|
22
|
+
raise "id required" unless id
|
22
23
|
response = request(:get, credentials, "#{api_model.api_path}/#{id}", :query => of_user_query(user))
|
23
24
|
api_model.parse(response.parsed_response).first
|
24
25
|
end
|
@@ -57,4 +58,4 @@ module Harvest
|
|
57
58
|
end
|
58
59
|
end
|
59
60
|
end
|
60
|
-
end
|
61
|
+
end
|
data/lib/harvest/expense.rb
CHANGED
@@ -13,12 +13,12 @@ module Harvest
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def spent_at=(date)
|
16
|
-
self["spent_at"] =
|
16
|
+
self["spent_at"] = Date.parse(date.to_s)
|
17
17
|
end
|
18
18
|
|
19
19
|
def as_json(args = {})
|
20
20
|
super(args).to_hash.stringify_keys.tap do |hash|
|
21
|
-
hash[json_root].update("spent_at" => (spent_at.nil? ? nil : spent_at.
|
21
|
+
hash[json_root].update("spent_at" => (spent_at.nil? ? nil : spent_at.xmlschema))
|
22
22
|
hash[json_root].delete("has_receipt")
|
23
23
|
hash[json_root].delete("receipt_url")
|
24
24
|
end
|
data/lib/harvest/invoice.rb
CHANGED
@@ -1,4 +1,41 @@
|
|
1
1
|
module Harvest
|
2
|
+
|
3
|
+
# == Fields
|
4
|
+
# [+due_at+] when the invoice is due
|
5
|
+
# [+due_at_human_format+] when the invoice is due in human representation (e.g., due upon receipt) overrides +due_at+
|
6
|
+
# [+client_id+] (REQUIRED) the client id of the invoice
|
7
|
+
# [+currency+] the invoice currency
|
8
|
+
# [+issued_at+] when the invoice was issued
|
9
|
+
# [+subject+] subject line for the invoice
|
10
|
+
# [+notes+] notes on the invoice
|
11
|
+
# [+number+] invoice number
|
12
|
+
# [+kind+] (REQUIRED) the type of the invoice +free_form|project|task|people|detailed+
|
13
|
+
# [+projects_to_invoice+] comma separated project ids to gather data from
|
14
|
+
# [+import_hours+] import hours from +project|task|people|detailed+ one of +yes|no+
|
15
|
+
# [+import_expenses+] import expenses from +project|task|people|detailed+ one of +yes|no+
|
16
|
+
# [+period_start+] start of the invoice period
|
17
|
+
# [+period_end+] end of the invoice period
|
18
|
+
# [+expense_period_start+] start of the invoice expense period
|
19
|
+
# [+expense_period_end+] end of the invoice expense period
|
20
|
+
# [+csv_line_items+] csv formatted line items for the invoice +kind,description,quantity,unit_price,amount,taxed,taxed2,project_id+
|
21
|
+
# [+created_at+] (READONLY) when the invoice was created
|
22
|
+
# [+updated_at+] (READONLY) when the invoice was updated
|
23
|
+
# [+id+] (READONLY) the id of the invoice
|
24
|
+
# [+amount+] (READONLY) the amount of the invoice
|
25
|
+
# [+due_amount+] (READONLY) the amount due on the invoice
|
26
|
+
# [+created_by_id+] who created the invoice
|
27
|
+
# [+purchase_order+] purchase order number/text
|
28
|
+
# [+client_key+] unique client key
|
29
|
+
# [+state+] (READONLY) state of the invoice
|
30
|
+
# [+tax+] applied tax percentage
|
31
|
+
# [+tax2+] applied tax 2 percentage
|
32
|
+
# [+tax_amount+] amount to tax
|
33
|
+
# [+tax_amount2+] amount to tax 2
|
34
|
+
# [+discount_amount+] discount amount to apply to invoice
|
35
|
+
# [+discount_type+] discount type
|
36
|
+
# [+recurring_invoice_id+] the id of the original invoice
|
37
|
+
# [+estimate_id+] id of the related estimate
|
38
|
+
# [+retainer_id+] id of the related retainer
|
2
39
|
class Invoice < Hashie::Mash
|
3
40
|
include Harvest::Model
|
4
41
|
|
@@ -6,14 +43,24 @@ module Harvest
|
|
6
43
|
|
7
44
|
attr_reader :line_items
|
8
45
|
|
9
|
-
def self.
|
10
|
-
|
46
|
+
def self.parse(json)
|
47
|
+
parsed = String === json ? JSON.parse(json) : json
|
48
|
+
invoices = Array.wrap(parsed).map {|attrs| new(attrs["invoices"])}
|
49
|
+
invoice = Array.wrap(parsed).map {|attrs| new(attrs["invoice"])}
|
50
|
+
if invoices.first && invoices.first.length > 0
|
51
|
+
invoices
|
52
|
+
else
|
53
|
+
invoice
|
54
|
+
end
|
55
|
+
end
|
11
56
|
|
12
57
|
def initialize(args = {}, _ = nil)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
58
|
+
if args
|
59
|
+
args = args.to_hash.stringify_keys
|
60
|
+
self.line_items = args.delete("csv_line_items")
|
61
|
+
self.line_items = args.delete("line_items")
|
62
|
+
self.line_items = [] if self.line_items.nil?
|
63
|
+
end
|
17
64
|
super
|
18
65
|
end
|
19
66
|
|
@@ -48,21 +95,10 @@ module Harvest
|
|
48
95
|
else
|
49
96
|
header = %w(kind description quantity unit_price amount taxed taxed2 project_id)
|
50
97
|
|
51
|
-
|
52
|
-
|
53
|
-
csv_data = ""
|
54
|
-
CSV.generate_row(header, header.size, csv_data)
|
98
|
+
CSV.generate do |csv|
|
99
|
+
csv << header
|
55
100
|
line_items.each do |item|
|
56
|
-
|
57
|
-
CSV.generate_row(row_data, row_data.size, csv_data)
|
58
|
-
end
|
59
|
-
csv_data
|
60
|
-
else
|
61
|
-
CSV.generate do |csv|
|
62
|
-
csv << header
|
63
|
-
line_items.each do |item|
|
64
|
-
csv << header.inject([]) {|row, attr| row << item[attr] }
|
65
|
-
end
|
101
|
+
csv << header.inject([]) {|row, attr| row << item[attr] }
|
66
102
|
end
|
67
103
|
end
|
68
104
|
end
|
data/lib/harvest/time_entry.rb
CHANGED
@@ -13,12 +13,12 @@ module Harvest
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def spent_at=(date)
|
16
|
-
self["spent_at"] =
|
16
|
+
self["spent_at"] = Date.parse(date.to_s)
|
17
17
|
end
|
18
18
|
|
19
19
|
def as_json(args = {})
|
20
20
|
super(args).to_hash.stringify_keys.tap do |hash|
|
21
|
-
hash.update("spent_at" => (spent_at.nil? ? nil : spent_at.
|
21
|
+
hash.update("spent_at" => (spent_at.nil? ? nil : spent_at.xmlschema))
|
22
22
|
end
|
23
23
|
end
|
24
24
|
end
|