turingstudio-freshbooks-rb 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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