mirror42-freshbooks.rb 3.0.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +15 -0
  2. data/History.txt +8 -0
  3. data/LICENSE +10 -0
  4. data/Manifest.txt +45 -0
  5. data/README +58 -0
  6. data/Rakefile +27 -0
  7. data/lib/freshbooks.rb +95 -0
  8. data/lib/freshbooks/base.rb +176 -0
  9. data/lib/freshbooks/category.rb +11 -0
  10. data/lib/freshbooks/client.rb +23 -0
  11. data/lib/freshbooks/connection.rb +162 -0
  12. data/lib/freshbooks/estimate.rb +15 -0
  13. data/lib/freshbooks/expense.rb +12 -0
  14. data/lib/freshbooks/invoice.rb +22 -0
  15. data/lib/freshbooks/item.rb +11 -0
  16. data/lib/freshbooks/line.rb +11 -0
  17. data/lib/freshbooks/links.rb +7 -0
  18. data/lib/freshbooks/list_proxy.rb +80 -0
  19. data/lib/freshbooks/payment.rb +13 -0
  20. data/lib/freshbooks/project.rb +12 -0
  21. data/lib/freshbooks/recurring.rb +16 -0
  22. data/lib/freshbooks/response.rb +25 -0
  23. data/lib/freshbooks/schema/definition.rb +20 -0
  24. data/lib/freshbooks/schema/mixin.rb +40 -0
  25. data/lib/freshbooks/staff.rb +13 -0
  26. data/lib/freshbooks/task.rb +12 -0
  27. data/lib/freshbooks/time_entry.rb +12 -0
  28. data/lib/freshbooks/xml_serializer.rb +17 -0
  29. data/lib/freshbooks/xml_serializer/serializers.rb +116 -0
  30. data/script/console +10 -0
  31. data/script/destroy +14 -0
  32. data/script/generate +14 -0
  33. data/test/fixtures/freshbooks_credentials.sample.yml +3 -0
  34. data/test/fixtures/invoice_create_response.xml +4 -0
  35. data/test/fixtures/invoice_get_response.xml +54 -0
  36. data/test/fixtures/invoice_list_response.xml +109 -0
  37. data/test/fixtures/success_response.xml +2 -0
  38. data/test/mock_connection.rb +13 -0
  39. data/test/schema/test_definition.rb +36 -0
  40. data/test/schema/test_mixin.rb +39 -0
  41. data/test/test_base.rb +151 -0
  42. data/test/test_connection.rb +145 -0
  43. data/test/test_helper.rb +48 -0
  44. data/test/test_invoice.rb +125 -0
  45. data/test/test_list_proxy.rb +60 -0
  46. data/test/test_page.rb +50 -0
  47. metadata +157 -0
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/definition'
2
+
3
+ module FreshBooks
4
+ module Schema
5
+ module Mixin
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def define_schema
12
+ # Create the class method accessor for the schema definition
13
+ cattr_accessor :schema_definition
14
+ self.schema_definition ||= FreshBooks::Schema::Definition.new
15
+
16
+ # Yield to the block for the user to define the schema
17
+ yield self.schema_definition
18
+
19
+ # Process the schema additions
20
+ schema_definition.members.each do |member|
21
+ process_schema_member(member)
22
+ end
23
+ end
24
+
25
+ def process_schema_member(member)
26
+ member_name = member.first
27
+ member_options = member.last
28
+
29
+ # Create accessor
30
+ attr_accessor member_name
31
+
32
+ # Protect write if read only
33
+ if member_options[:read_only]
34
+ protected "#{member_name}="
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,13 @@
1
+ module FreshBooks
2
+ class Staff < FreshBooks::Base
3
+ define_schema do |s|
4
+ s.fixnum :staff_id, :number_of_logins
5
+ s.string :username, :first_name, :last_name, :email, :business_phone, :mobile_phone
6
+ s.string :street1, :street2, :city, :state, :country, :code
7
+ s.float :rate
8
+ s.date_time :last_login, :signup_date
9
+ end
10
+
11
+ actions :list, :get
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module FreshBooks
2
+ class Task < FreshBooks::Base
3
+ define_schema do |s|
4
+ s.fixnum :task_id
5
+ s.string :name, :description
6
+ s.float :rate
7
+ s.boolean :billable
8
+ end
9
+
10
+ actions :list, :get, :create, :update, :delete
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module FreshBooks
2
+ class TimeEntry < FreshBooks::Base
3
+ define_schema do |s|
4
+ s.fixnum :time_entry_id, :project_id, :task_id, :staff_id
5
+ s.float :hours
6
+ s.date :date
7
+ s.string :notes
8
+ end
9
+
10
+ actions :list, :get, :create, :update, :delete
11
+ end
12
+ end
@@ -0,0 +1,17 @@
1
+ require File.dirname(__FILE__) + '/xml_serializer/serializers'
2
+
3
+ module FreshBooks
4
+ module XmlSerializer
5
+ def self.to_value(node, type)
6
+ create_serializer(type).to_value(node)
7
+ end
8
+
9
+ def self.to_node(member_name, value, type)
10
+ create_serializer(type).to_node(member_name, value)
11
+ end
12
+
13
+ def self.create_serializer(type)
14
+ "FreshBooks::XmlSerializer::#{type.to_s.classify}Serializer".constantize
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,116 @@
1
+ module FreshBooks
2
+ module XmlSerializer
3
+ class FixnumSerializer
4
+ def self.to_node(member_name, value)
5
+ element = REXML::Element.new(member_name)
6
+ element.text = value.to_s
7
+ element
8
+ end
9
+
10
+ def self.to_value(xml_val)
11
+ xml_val.text.to_i
12
+ end
13
+ end
14
+
15
+ class FloatSerializer
16
+ def self.to_node(member_name, value)
17
+ element = REXML::Element.new(member_name)
18
+ element.text = value.to_s
19
+ element
20
+ end
21
+
22
+ def self.to_value(xml_val)
23
+ xml_val.text.to_f
24
+ end
25
+ end
26
+
27
+ class DateSerializer
28
+ def self.to_node(member_name, value)
29
+ element = REXML::Element.new(member_name)
30
+ element.text = value.to_s
31
+ element
32
+ end
33
+
34
+ def self.to_value(xml_val)
35
+ begin
36
+ Date.parse(xml_val.text.to_s)
37
+ rescue ArgumentError => e
38
+ # Sometimes freshbooks gives dates that look like this 0000-00-00 00:00:00
39
+ nil
40
+ end
41
+ end
42
+ end
43
+
44
+ class StringSerializer
45
+ def self.to_node(member_name, value)
46
+ element = REXML::Element.new(member_name)
47
+ element.text = value.to_s
48
+ element
49
+ end
50
+
51
+ def self.to_value(xml_val)
52
+ xml_val.text.to_s
53
+ end
54
+ end
55
+
56
+ class BooleanSerializer
57
+ def self.to_node(member_name, value)
58
+ element = REXML::Element.new(member_name)
59
+ element.text = value ? '1' : '0'
60
+ element
61
+ end
62
+
63
+ def self.to_value(xml_val)
64
+ xml_val.text.to_s == "1"
65
+ end
66
+ end
67
+
68
+ class ObjectSerializer
69
+ def self.to_node(member_name, value)
70
+ REXML::Document.new(value.to_xml(member_name))
71
+ end
72
+
73
+ def self.to_value(xml_val)
74
+ FreshBooks::const_get(xml_val.name.camelize)::new_from_xml(xml_val)
75
+ end
76
+ end
77
+
78
+ class ArraySerializer
79
+ def self.to_node(member_name, value)
80
+ element = REXML::Element.new(member_name)
81
+ value.each { |array_elem|
82
+ element.add_element(REXML::Document.new(array_elem.to_xml))
83
+ }
84
+ element
85
+ end
86
+
87
+ def self.to_value(xml_val)
88
+ xml_val.elements.map { |elem|
89
+ FreshBooks::const_get(elem.name.camelize)::new_from_xml(elem)
90
+ }
91
+ end
92
+ end
93
+
94
+ # FreshBooks datetimes are specified in gmt-4. This library assumes utc and
95
+ # will convert to the appropriate timezone.
96
+ class DateTimeSerializer
97
+ def self.to_node(member_name, value)
98
+ element = REXML::Element.new(member_name)
99
+ element.text = (value.utc + time_offset.to_i.hours).to_s(:db) # hack to convert to gmt-4, any better way?
100
+ element
101
+ end
102
+
103
+ def self.to_value(xml_val)
104
+ DateTime.parse(xml_val.text.to_s + " #{time_offset}").utc # hack to convert from gmt-4 to utc
105
+ rescue ArgumentError => e
106
+ # Sometimes freshbooks gives dates that look like this 0000-00-00 00:00:00
107
+ nil
108
+ end
109
+
110
+ private
111
+ def self.time_offset
112
+ ENV['FRESHBOOKS_TIMEZONE'] || "-04:00"
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/freshbooks.rb'}"
9
+ puts "Loading freshbooks.rb gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,3 @@
1
+ fresh_books_test_account:
2
+ account_url: <insert your account url>
3
+ api_key: <insert your api key>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <response status="ok">
3
+ <invoice_id>1</invoice_id>
4
+ </response>
@@ -0,0 +1,54 @@
1
+ <?xml version="1.0"?>
2
+ <response status="ok">
3
+ <invoice>
4
+ <invoice_id>1</invoice_id>
5
+ <client_id>1</client_id>
6
+ <number>number1</number>
7
+ <recurring_id>1</recurring_id>
8
+ <organization>Organization 1</organization>
9
+ <status>draft</status>
10
+ <amount>100.00</amount>
11
+ <amount_outstanding>50.00</amount_outstanding>
12
+ <date>2009-02-01</date>
13
+
14
+ <po_number>1</po_number>
15
+ <discount>1</discount>
16
+ <notes>notes1</notes>
17
+ <terms>terms1</terms>
18
+
19
+ <first_name>first_name1</first_name>
20
+ <last_name>last_name1</last_name>
21
+ <p_street1>p_street11</p_street1>
22
+ <p_street2>p_street21</p_street2>
23
+ <p_city>p_city1</p_city>
24
+ <p_state>p_state1</p_state>
25
+ <p_country>p_country1</p_country>
26
+ <p_code>p_code1</p_code>
27
+
28
+ <return_uri>return_uri1</return_uri>
29
+ <updated>2009-08-1 01:00:00</updated>
30
+
31
+ <links>
32
+ <client_view>client_view1</client_view>
33
+ <view>view1</view>
34
+ <edit>edit1</edit>
35
+ </links>
36
+
37
+ <lines>
38
+ <line>
39
+ <amount>1</amount>
40
+ <name>name1</name>
41
+ <description>description1</description>
42
+ <unit_cost>1</unit_cost>
43
+ <quantity>1</quantity>
44
+ <tax1_name>tax1_name1</tax1_name>
45
+ <tax2_name>tax2_name1</tax2_name>
46
+ <tax1_percent>1</tax1_percent>
47
+ <tax2_percent>1</tax2_percent>
48
+ </line>
49
+ </lines>
50
+
51
+ <url deprecated="true">https://sample.freshbooks.com/inv/12345-1-6d30b</url>
52
+ <auth_url deprecated="true">https://sample.freshbooks.com/inv/12345-1-6d30b-z</auth_url>
53
+ </invoice>
54
+ </response>
@@ -0,0 +1,109 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <response status="ok">
3
+ <invoices page="1" per_page="2" pages="2" total="3">
4
+ <invoice>
5
+ <invoice_id>1</invoice_id>
6
+ <number>number1</number>
7
+ <client_id>1</client_id>
8
+ <recurring_id>1</recurring_id>
9
+ <organization>Organization 1</organization>
10
+ <status>draft</status>
11
+ <amount>100.00</amount>
12
+ <amount_outstanding>50.00</amount_outstanding>
13
+ <date>2009-02-01</date>
14
+
15
+ <po_number>1</po_number>
16
+ <discount>1</discount>
17
+ <notes>notes1</notes>
18
+ <terms>terms1</terms>
19
+
20
+ <first_name>first_name1</first_name>
21
+ <last_name>last_name1</last_name>
22
+ <p_street1>p_street11</p_street1>
23
+ <p_street2>p_street21</p_street2>
24
+ <p_city>p_city1</p_city>
25
+ <p_state>p_state1</p_state>
26
+ <p_country>p_country1</p_country>
27
+ <p_code>p_code1</p_code>
28
+
29
+ <return_uri>return_uri1</return_uri>
30
+ <updated>2009-08-1 01:00:00</updated>
31
+
32
+ <links>
33
+ <client_view>client_view1</client_view>
34
+ <view>view1</view>
35
+ <edit>edit1</edit>
36
+ </links>
37
+
38
+ <lines>
39
+ <line>
40
+ <amount>1</amount>
41
+ <name>name1</name>
42
+ <description>description1</description>
43
+ <unit_cost>1</unit_cost>
44
+ <quantity>1</quantity>
45
+ <tax1_name>tax1_name1</tax1_name>
46
+ <tax2_name>tax2_name1</tax2_name>
47
+ <tax1_percent>1</tax1_percent>
48
+ <tax2_percent>1</tax2_percent>
49
+ </line>
50
+ </lines>
51
+
52
+ <url deprecated="true">https://sample.freshbooks.com/inv/12345-1-6d30b</url>
53
+ <auth_url deprecated="true">https://sample.freshbooks.com/inv/12345-1-6d30b-z</auth_url>
54
+
55
+ </invoice>
56
+ <invoice>
57
+ <invoice_id>2</invoice_id>
58
+ <number>number2</number>
59
+ <client_id>2</client_id>
60
+ <recurring_id>2</recurring_id>
61
+ <organization>Organization 2</organization>
62
+ <status>draft</status>
63
+ <amount>200.00</amount>
64
+ <amount_outstanding>100.00</amount_outstanding>
65
+ <date>2009-02-02</date>
66
+
67
+ <po_number>2</po_number>
68
+ <discount>2</discount>
69
+ <notes>notes2</notes>
70
+ <terms>terms2</terms>
71
+
72
+ <first_name>first_name2</first_name>
73
+ <last_name>last_name2</last_name>
74
+ <p_street1>p_street12</p_street1>
75
+ <p_street2>p_street22</p_street2>
76
+ <p_city>p_city2</p_city>
77
+ <p_state>p_state2</p_state>
78
+ <p_country>p_country2</p_country>
79
+ <p_code>p_code2</p_code>
80
+
81
+ <return_uri>return_uri2</return_uri>
82
+ <updated>2009-08-2 02:00:00</updated>
83
+
84
+ <links>
85
+ <client_view>client_view2</client_view>
86
+ <view>view2</view>
87
+ <edit>edit2</edit>
88
+ </links>
89
+
90
+ <lines>
91
+ <line>
92
+ <amount>1</amount>
93
+ <name>name1</name>
94
+ <description>description1</description>
95
+ <unit_cost>1</unit_cost>
96
+ <quantity>1</quantity>
97
+ <tax1_name>tax1_name1</tax1_name>
98
+ <tax2_name>tax2_name1</tax2_name>
99
+ <tax1_percent>1</tax1_percent>
100
+ <tax2_percent>1</tax2_percent>
101
+ </line>
102
+ </lines>
103
+
104
+ <url deprecated="true">https://sample.freshbooks.com/inv/12345-1-6d30b</url>
105
+ <auth_url deprecated="true">https://sample.freshbooks.com/inv/12345-1-6d30b-z</auth_url>
106
+
107
+ </invoice>
108
+ </invoices>
109
+ </response>
@@ -0,0 +1,2 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <response status="ok"/>
@@ -0,0 +1,13 @@
1
+ require 'freshbooks/connection'
2
+
3
+ class MockConnection < FreshBooks::Connection
4
+ def initialize(response_body)
5
+ @response_body = response_body
6
+ end
7
+
8
+ protected
9
+
10
+ def post(request_body)
11
+ @response_body
12
+ end
13
+ end