turingstudio-freshbooks-rb 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,30 @@
1
+ module FreshBooks
2
+ class Estimate < Base
3
+ attribute :estimate_id, :integer
4
+ attribute :client_id, :integer
5
+ attribute :status, :string
6
+ attribute :date, :string
7
+ attribute :po_number, :integer
8
+ attribute :discount, :float
9
+ attribute :notes, :string
10
+ attribute :terms, :string
11
+ attribute :first_name, :string
12
+ attribute :last_name, :string
13
+ attribute :organization, :string
14
+ attribute :p_street1, :string
15
+ attribute :p_street2, :string
16
+ attribute :p_city, :string
17
+ attribute :p_state, :string
18
+ attribute :p_country, :string
19
+ attribute :p_code, :string
20
+ attribute :lines, :array
21
+
22
+ belongs_to :client
23
+ method :list, :get, :create, :update, :delete, :send_by_email
24
+
25
+ def initialize(data = {})
26
+ super
27
+ self.lines ||= []
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ module FreshBooks
2
+ class Expense < Base
3
+ attribute :expense_id, :integer
4
+ attribute :staff_id, :integer
5
+ attribute :category_id, :integer
6
+ attribute :project_id, :integer
7
+ attribute :client_id, :integer
8
+ attribute :amount, :float
9
+ attribute :date, :string
10
+ attribute :notes, :string
11
+ attribute :status, :string
12
+
13
+ belongs_to :staff, :category, :project, :client
14
+ method :list, :get, :create, :update, :delete, :send_by_email
15
+ end
16
+ end
@@ -0,0 +1,34 @@
1
+ module FreshBooks
2
+ class Invoice < Base
3
+ attribute :invoice_id, :integer
4
+ attribute :client_id, :integer
5
+ attribute :number, :string
6
+ attribute :date, :string
7
+ attribute :po_number, :integer
8
+ attribute :terms, :string
9
+ attribute :first_name, :string
10
+ attribute :last_name, :string
11
+ attribute :organization, :string
12
+ attribute :p_street1, :string
13
+ attribute :p_street2, :string
14
+ attribute :p_city, :string
15
+ attribute :p_state, :string
16
+ attribute :p_country, :string
17
+ attribute :p_code, :string
18
+ attribute :amount, :float
19
+ attribute :lines, :array
20
+ attribute :discount, :float
21
+ attribute :status, :string
22
+ attribute :notes, :string
23
+ attribute :url, :string, :read_only => true
24
+
25
+ belongs_to :client
26
+ has_many :payments
27
+ method :list, :get, :create, :update, :delete, :send_by_email, :send_by_snail_mail
28
+
29
+ def initialize(data = {})
30
+ super
31
+ self.lines ||= []
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,12 @@
1
+ module FreshBooks
2
+ class Item < Base
3
+ attribute :item_id, :integer
4
+ attribute :name, :string
5
+ attribute :description, :string
6
+ attribute :unit_cost, :float
7
+ attribute :quantity, :integer
8
+ attribute :inventory, :integer
9
+
10
+ method :list, :get, :create, :update, :delete
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ module FreshBooks
2
+ class Line < Base
3
+ attribute :name, :string
4
+ attribute :description, :string
5
+ attribute :unit_cost, :float
6
+ attribute :quantity, :integer
7
+ attribute :tax1_name, :string
8
+ attribute :tax2_name, :string
9
+ attribute :tax1_percent, :float
10
+ attribute :tax2_percent, :float
11
+ attribute :amount, :float
12
+ end
13
+ end
@@ -0,0 +1,22 @@
1
+ module FreshBooks
2
+ # * If you specify an invoice_id only, the payment will be recorded as an invoice payment.
3
+ # * If you specify a client_id only, the payment will be recorded as a client credit.
4
+ # * If you specify both an invoice_id and client_id, the payment will be recorded
5
+ # as an invoice payment, and the amount will be subtracted from the client’s credit.
6
+ #
7
+ # Payment type must be one of: ‘Check’, ‘Credit’, ‘Bank Transfer’, ‘PayPal’, ‘2Checkout’,
8
+ # ‘VISA’, ‘MASTERCARD’, ‘DISCOVER’, ‘NOVA’, ‘AMEX’, ‘DINERS’, ‘EUROCARD’, ‘JCB’ or ‘ACH’.
9
+
10
+ class Payment < Base
11
+ attribute :payment_id, :integer
12
+ attribute :client_id, :integer
13
+ attribute :invoice_id, :integer
14
+ attribute :date, :string
15
+ attribute :amount, :float
16
+ attribute :type, :string
17
+ attribute :notes, :string
18
+
19
+ belongs_to :client, :invoice
20
+ method :list, :get, :create, :update
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ module FreshBooks
2
+ # If you specify project-rate or flat-rate for
3
+ # billing_method when creating a new project, you must supply a rate.
4
+ #
5
+ # Billing Method Types
6
+ # * task-rate
7
+ # * flat-rate
8
+ # * project-rate
9
+ # * staff-rate
10
+
11
+ class Project < Base
12
+ attribute :project_id, :integer
13
+ attribute :client_id, :integer
14
+ attribute :name, :string
15
+ attribute :bill_method, :string
16
+ attribute :rate, :float
17
+ attribute :description, :string
18
+ attribute :tasks, :array
19
+
20
+ belongs_to :client
21
+ has_many :expenses
22
+ method :list, :get, :create, :update, :delete
23
+
24
+ def initialize(data = {})
25
+ super
26
+ self.tasks ||= []
27
+ end
28
+
29
+ protected
30
+
31
+ def tasks_to_xml(root)
32
+ return if tasks.empty?
33
+ node = root.add_element('tasks')
34
+ tasks.each do |task|
35
+ node.add_element('task_id').text = task.task_id
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,51 @@
1
+ module FreshBooks
2
+ # Create a new recurring profile.
3
+ #
4
+ # The method arguments are nearly identical to invoice.create, but include four new fields:
5
+ #
6
+ # * occurrences
7
+ # Number of invoices to generate, with zero (0) being infinite
8
+ # * frequency
9
+ # Rate at which to generate invoices - can be one of ‘weekly’, ‘2 weeks’, ‘4 weeks’,
10
+ # ‘monthly’, ‘2 months’, ‘3 months’, ‘6 months’, ‘yearly’, ‘2 years’
11
+ # * stopped
12
+ # This profile is no longer generating invoices (1 - stopped, 0 - active)
13
+ # * send_email
14
+ # Notify client by email each time a new invoice is generated (1 or 0)
15
+ # * send_snail_mail
16
+ # Send a copy of your invoice by snail mail, each time it’s generated (1 or 0)
17
+
18
+ class Recurring < Base
19
+ attribute :recurring_id, :integer
20
+ attribute :client_id, :integer
21
+ attribute :date, :string
22
+ attribute :po_number, :integer
23
+ attribute :terms, :string
24
+ attribute :first_name, :string
25
+ attribute :last_name, :string
26
+ attribute :organization, :string
27
+ attribute :p_street1, :string
28
+ attribute :p_street2, :string
29
+ attribute :p_city, :string
30
+ attribute :p_state, :string
31
+ attribute :p_country, :string
32
+ attribute :p_code, :string
33
+ attribute :amount, :float
34
+ attribute :lines, :array
35
+ attribute :discount, :float
36
+ attribute :status, :string
37
+ attribute :notes, :string
38
+ attribute :occurrences, :integer
39
+ attribute :frequency, :string
40
+ attribute :send_email, :integer
41
+ attribute :send_snail_mail, :integer
42
+
43
+ belongs_to :client
44
+ method :list, :get, :create, :update, :delete
45
+
46
+ def initialize(data = {})
47
+ super
48
+ self.lines ||= []
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,25 @@
1
+ module FreshBooks
2
+ class Response
3
+ attr_accessor :doc
4
+
5
+ def initialize(xml_raw)
6
+ @doc = REXML::Document.new(xml_raw)
7
+ end
8
+
9
+ def elements
10
+ @doc.root.elements
11
+ end
12
+
13
+ def success?
14
+ @doc.root.attributes['status'] == 'ok'
15
+ end
16
+
17
+ def fail?
18
+ !success?
19
+ end
20
+
21
+ def error_msg
22
+ return @doc.root.elements['error'].text
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,24 @@
1
+ module FreshBooks
2
+ class Staff < Base
3
+ attribute :staff_id, :integer
4
+ attribute :username, :string
5
+ attribute :first_name, :string
6
+ attribute :last_name, :string
7
+ attribute :email, :string
8
+ attribute :business_phone, :string
9
+ attribute :mobile_phone, :string
10
+ attribute :rate, :string
11
+ attribute :last_login, :string
12
+ attribute :number_of_logins,:string
13
+ attribute :signup_date, :string
14
+ attribute :street1, :string
15
+ attribute :street2, :string
16
+ attribute :city, :string
17
+ attribute :state, :string
18
+ attribute :country, :string
19
+ attribute :code, :string
20
+
21
+ has_many :expenses
22
+ method :list, :get
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ module FreshBooks
2
+ class Task < Base
3
+ attribute :task_id, :integer
4
+ attribute :name, :string
5
+ attribute :billable, :string
6
+ attribute :rate, :float
7
+ attribute :description, :string
8
+
9
+ method :list, :get, :create, :update, :delete
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module FreshBooks
2
+ class TimeEntry < Base
3
+ attribute :time_entry_id, :integer
4
+ attribute :project_id, :integer
5
+ attribute :task_id, :integer
6
+ attribute :hours, :float
7
+ attribute :notes, :string
8
+ attribute :date, :string
9
+
10
+ belongs_to :project, :task
11
+ method :list, :get, :create, :update, :delete
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ module FreshBooks
2
+ VERSION = '3.0.1' # Gem version
3
+ API_VERSION = '2.1' # FreshBooks API version
4
+
5
+ SERVICE_URL = "/api/#{API_VERSION}/xml-in"
6
+ end
@@ -0,0 +1,385 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe FreshBooks::Base do
4
+ class First < FreshBooks::Base
5
+ attribute :first_id, :integer
6
+ attribute :name, :string
7
+ attribute :amount, :float
8
+ attribute :url, :string, :read_only => true
9
+ attribute :seconds, :array
10
+ end
11
+
12
+ class Second < FreshBooks::Base
13
+ attribute :second_id, :integer
14
+ attribute :first_id, :integer
15
+ belongs_to :first
16
+ has_many :tests
17
+ end
18
+
19
+ class Test < FreshBooks::Base
20
+ attribute :test_id, :integer
21
+ method :list, :get, :create, :update, :delete, :send_by_email, :send_by_snail_mail
22
+ end
23
+
24
+ class TestWithoutMethods < FreshBooks::Base
25
+ attribute :test_id, :integer
26
+ end
27
+
28
+ before(:each) do
29
+ @object = Test.new
30
+ end
31
+
32
+ it "should return corrent node name" do
33
+ FreshBooks::Base.node_name.should == 'base'
34
+ First.node_name.should == 'first'
35
+ Second.node_name.should == 'second'
36
+ end
37
+
38
+ it "should has correct attributes" do
39
+ First.attributes[:first_id].should == { :type => :integer }
40
+ First.attributes[:name].should == { :type => :string }
41
+ First.attributes[:amount].should == { :type => :float }
42
+ First.attributes[:url].should == { :type => :string, :read_only => true }
43
+ First.attributes[:seconds].should == { :type => :array }
44
+ end
45
+
46
+ it "should has attribure accessors for decalred attributes" do
47
+ first = First.new
48
+ lambda { first.first_id }.should_not raise_error
49
+ lambda { first.first_id = 1 }.should_not raise_error
50
+ first.first_id.should == 1
51
+ end
52
+
53
+ it "should provide changed detector for decalred attributes" do
54
+ first = First.new
55
+ lambda { first.first_id_changed? }.should_not raise_error
56
+ first.first_id_changed?.should be_false
57
+ first.first_id = 1
58
+ first.first_id_changed?.should be_true
59
+ end
60
+
61
+ describe "belongs_to association" do
62
+ before(:each) do
63
+ First.stub!(:get)
64
+ @second = Second.new(:first_id => 1)
65
+ end
66
+
67
+ it "should has method" do
68
+ lambda { @second.first }.should_not raise_error
69
+ end
70
+
71
+ it "should find associated object" do
72
+ First.should_receive(:get).with(1)
73
+ @second.first
74
+ end
75
+ end
76
+
77
+ describe "belongs_to association" do
78
+ before(:each) do
79
+ Test.stub!(:list)
80
+ @second = Second.new(:second_id => 1)
81
+ end
82
+
83
+ it "should has method" do
84
+ lambda { @second.tests }.should_not raise_error
85
+ end
86
+
87
+ it "should find associated object" do
88
+ Test.should_receive(:list).with({'second_id' => 1, 'name' => 'value'})
89
+ @second.tests('name' => 'value')
90
+ end
91
+ end
92
+
93
+ describe "xml conversion" do
94
+ before(:each) do
95
+ @second1 = Second.new(:second_id => 1)
96
+ @second2 = Second.new(:second_id => 2)
97
+ @first = First.new(
98
+ :first_id => 1,
99
+ :name => 'name',
100
+ :url => 'url',
101
+ :seconds => [@second1, @second2]
102
+ )
103
+ @xml = @first.to_xml
104
+ end
105
+
106
+ it "should return correct xml" do
107
+ @xml.to_s.should =~ Regexp.new(Regexp.escape('<first_id>1</first_id>'))
108
+ @xml.to_s.should =~ Regexp.new(Regexp.escape('<name>name</name>'))
109
+ @xml.to_s.should =~ Regexp.new(Regexp.escape('<seconds><second><second_id>1</second_id></second><second><second_id>2</second_id></second></seconds>'))
110
+ end
111
+
112
+ it "should not include read_only attributes in xml" do
113
+ @xml.to_s.should_not =~ Regexp.new(Regexp.escape('<url>url</url>'))
114
+ end
115
+
116
+ it "should not include unchanged attributes in xml" do
117
+ @xml.to_s.should_not =~ Regexp.new(Regexp.escape('<amount>'))
118
+ end
119
+
120
+ it "should return correct xml and initialize correctly from xml" do
121
+ first = First.new_from_xml(@xml)
122
+ first.first_id.should == 1
123
+ first.seconds.size.should == 2
124
+ first.seconds[0].second_id.should == 1
125
+ first.seconds[1].second_id.should == 2
126
+ end
127
+ end
128
+
129
+ it "should raise an exception when unknown method is being called" do
130
+ lambda { TestWithoutMethods.list }.should raise_error
131
+ lambda { TestWithoutMethods.new.create }.should raise_error
132
+ end
133
+
134
+ describe "create method" do
135
+ before(:each) do
136
+ @elem = mock('XmlObj', :text => '123')
137
+ @resp = mock('XmlResp', :elements => [nil, @elem])
138
+ @resp.stub!(:success?).and_return(true)
139
+ FreshBooks::Base.stub!(:call_api).and_return(@resp)
140
+ end
141
+
142
+ it "should call api method" do
143
+ FreshBooks::Base.should_receive(:call_api).with('test.create', 'test' => @object).and_return(@resp)
144
+ @object.create
145
+ end
146
+
147
+ it "should assign id returned by api if call succeeded" do
148
+ @object.create
149
+ @object.test_id.should == 123
150
+ end
151
+
152
+ it "should not assign id returned by api if call failed" do
153
+ @resp.stub!(:success?).and_return(false)
154
+ @object.create
155
+ @object.test_id.should be_nil
156
+ end
157
+
158
+ it "should return id if call succeeded" do
159
+ @object.create.should == 123
160
+ end
161
+
162
+ it "should return nil if call failed" do
163
+ @resp.stub!(:success?).and_return(false)
164
+ @object.create.should be_nil
165
+ end
166
+ end
167
+
168
+ describe "update method" do
169
+ before(:each) do
170
+ @resp = mock('XmlResp')
171
+ @resp.stub!(:success?).and_return(true)
172
+ FreshBooks::Base.stub!(:call_api).and_return(@resp)
173
+ end
174
+
175
+ it "should call api method" do
176
+ FreshBooks::Base.should_receive(:call_api).with('test.update', 'test' => @object).and_return(@resp)
177
+ @object.update
178
+ end
179
+
180
+ it "should return true if request succeeded" do
181
+ @resp.stub!(:success?).and_return(true)
182
+ @object.update.should be_true
183
+ end
184
+
185
+ it "should return false if request failed" do
186
+ @resp.stub!(:success?).and_return(false)
187
+ @object.update.should be_false
188
+ end
189
+ end
190
+
191
+ describe "delete method" do
192
+ before(:each) do
193
+ @object.test_id = 1
194
+ @resp = mock('XmlResp')
195
+ @resp.stub!(:success?).and_return(true)
196
+ FreshBooks::Base.stub!(:call_api).and_return(@resp)
197
+ end
198
+
199
+ it "should call api method" do
200
+ FreshBooks::Base.should_receive(:call_api).with('test.delete', 'test_id' => 1).and_return(@resp)
201
+ @object.delete
202
+ end
203
+
204
+ it "should return true if request succeeded" do
205
+ @resp.stub!(:success?).and_return(true)
206
+ @object.delete.should be_true
207
+ end
208
+
209
+ it "should return false if request failed" do
210
+ @resp.stub!(:success?).and_return(false)
211
+ @object.delete.should be_false
212
+ end
213
+ end
214
+
215
+ describe "delete class method" do
216
+ before(:each) do
217
+ @resp = mock('XmlResp')
218
+ @resp.stub!(:success?).and_return(true)
219
+ FreshBooks::Base.stub!(:call_api).and_return(@resp)
220
+ end
221
+
222
+ it "should call api method" do
223
+ FreshBooks::Base.should_receive(:call_api).with('test.delete', 'test_id' => 1).and_return(@resp)
224
+ Test.delete(1)
225
+ end
226
+
227
+ it "should return true if request succeeded" do
228
+ @resp.stub!(:success?).and_return(true)
229
+ Test.delete(1).should be_true
230
+ end
231
+
232
+ it "should return false if request failed" do
233
+ @resp.stub!(:success?).and_return(false)
234
+ Test.delete(1).should be_false
235
+ end
236
+ end
237
+
238
+ describe "get class method" do
239
+ before(:each) do
240
+ @resp = mock('XmlResp', :elements => [])
241
+ @resp.stub!(:success?).and_return(true)
242
+ FreshBooks::Base.stub!(:call_api).and_return(@resp)
243
+ Test.stub!(:new_from_xml).and_return(true)
244
+ end
245
+
246
+ it "should call api method" do
247
+ FreshBooks::Base.should_receive(:call_api).with('test.get', 'test_id' => 1).and_return(@resp)
248
+ Test.get(1)
249
+ end
250
+
251
+ it "should build and return instance from xml if request succeeded" do
252
+ @resp.stub!(:success?).and_return(true)
253
+ Test.should_receive(:new_from_xml).and_return(true)
254
+ Test.get(1).should be_true
255
+ end
256
+
257
+ it "should return false if request failed" do
258
+ @resp.stub!(:success?).and_return(false)
259
+ Test.get(1).should be_nil
260
+ end
261
+ end
262
+
263
+ describe "list class method" do
264
+ before(:each) do
265
+ @elem1 = mock('XmlElement')
266
+ @elem2 = mock('XmlElement')
267
+ @elems = mock('XmlElements', :elements => [@elem1, @elem2])
268
+ @resp = mock('XmlResp', :elements => [nil, @elems])
269
+ @resp.stub!(:success?).and_return(true)
270
+ FreshBooks::Base.stub!(:call_api).and_return(@resp)
271
+ Test.stub!(:new_from_xml).and_return(true)
272
+ end
273
+
274
+ it "should call api method" do
275
+ FreshBooks::Base.should_receive(:call_api).with('test.list', :name => 'value').and_return(@resp)
276
+ Test.list(:name => 'value')
277
+ end
278
+
279
+ it "should return nil if request failed" do
280
+ @resp.stub!(:success?).and_return(false)
281
+ Test.list.should be_nil
282
+ end
283
+
284
+ it "should build instances from response records if request succeeded" do
285
+ @resp.stub!(:success?).and_return(true)
286
+ Test.should_receive(:new_from_xml).with(@elem1).and_return(1)
287
+ Test.should_receive(:new_from_xml).with(@elem2).and_return(2)
288
+ Test.list.should == [1, 2]
289
+ end
290
+ end
291
+
292
+ describe "send_by_email method" do
293
+ before(:each) do
294
+ @object.test_id = 1
295
+ @resp = mock('XmlResp')
296
+ @resp.stub!(:success?).and_return(true)
297
+ FreshBooks::Base.stub!(:call_api).and_return(@resp)
298
+ end
299
+
300
+ it "should call api method" do
301
+ FreshBooks::Base.should_receive(:call_api).with('test.sendByEmail', 'test_id' => 1).and_return(@resp)
302
+ @object.send_by_email
303
+ end
304
+
305
+ it "should return true if request succeeded" do
306
+ @resp.stub!(:success?).and_return(true)
307
+ @object.send_by_email.should be_true
308
+ end
309
+
310
+ it "should return false if request failed" do
311
+ @resp.stub!(:success?).and_return(false)
312
+ @object.send_by_email.should be_false
313
+ end
314
+ end
315
+
316
+ describe "send_by_email class method" do
317
+ before(:each) do
318
+ @resp = mock('XmlResp')
319
+ @resp.stub!(:success?).and_return(true)
320
+ FreshBooks::Base.stub!(:call_api).and_return(@resp)
321
+ end
322
+
323
+ it "should call api method" do
324
+ FreshBooks::Base.should_receive(:call_api).with('test.sendByEmail', 'test_id' => 1).and_return(@resp)
325
+ Test.send_by_email(1)
326
+ end
327
+
328
+ it "should return true if request succeeded" do
329
+ @resp.stub!(:success?).and_return(true)
330
+ Test.send_by_email(1).should be_true
331
+ end
332
+
333
+ it "should return false if request failed" do
334
+ @resp.stub!(:success?).and_return(false)
335
+ Test.send_by_email(1).should be_false
336
+ end
337
+ end
338
+
339
+ describe "send_by_snail_mail method" do
340
+ before(:each) do
341
+ @object.test_id = 1
342
+ @resp = mock('XmlResp')
343
+ @resp.stub!(:success?).and_return(true)
344
+ FreshBooks::Base.stub!(:call_api).and_return(@resp)
345
+ end
346
+
347
+ it "should call api method" do
348
+ FreshBooks::Base.should_receive(:call_api).with('test.sendBySnailMail', 'test_id' => 1).and_return(@resp)
349
+ @object.send_by_snail_mail
350
+ end
351
+
352
+ it "should return true if request succeeded" do
353
+ @resp.stub!(:success?).and_return(true)
354
+ @object.send_by_snail_mail.should be_true
355
+ end
356
+
357
+ it "should return false if request failed" do
358
+ @resp.stub!(:success?).and_return(false)
359
+ @object.send_by_snail_mail.should be_false
360
+ end
361
+ end
362
+
363
+ describe "send_by_snail_mail class method" do
364
+ before(:each) do
365
+ @resp = mock('XmlResp')
366
+ @resp.stub!(:success?).and_return(true)
367
+ FreshBooks::Base.stub!(:call_api).and_return(@resp)
368
+ end
369
+
370
+ it "should call api method" do
371
+ FreshBooks::Base.should_receive(:call_api).with('test.sendBySnailMail', 'test_id' => 1).and_return(@resp)
372
+ Test.send_by_snail_mail(1)
373
+ end
374
+
375
+ it "should return true if request succeeded" do
376
+ @resp.stub!(:success?).and_return(true)
377
+ Test.send_by_snail_mail(1).should be_true
378
+ end
379
+
380
+ it "should return false if request failed" do
381
+ @resp.stub!(:success?).and_return(false)
382
+ Test.send_by_snail_mail(1).should be_false
383
+ end
384
+ end
385
+ end