mirror42-freshbooks.rb 3.0.25

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.
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