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.
- checksums.yaml +15 -0
- data/History.txt +8 -0
- data/LICENSE +10 -0
- data/Manifest.txt +45 -0
- data/README +58 -0
- data/Rakefile +27 -0
- data/lib/freshbooks.rb +95 -0
- data/lib/freshbooks/base.rb +176 -0
- data/lib/freshbooks/category.rb +11 -0
- data/lib/freshbooks/client.rb +23 -0
- data/lib/freshbooks/connection.rb +162 -0
- data/lib/freshbooks/estimate.rb +15 -0
- data/lib/freshbooks/expense.rb +12 -0
- data/lib/freshbooks/invoice.rb +22 -0
- data/lib/freshbooks/item.rb +11 -0
- data/lib/freshbooks/line.rb +11 -0
- data/lib/freshbooks/links.rb +7 -0
- data/lib/freshbooks/list_proxy.rb +80 -0
- data/lib/freshbooks/payment.rb +13 -0
- data/lib/freshbooks/project.rb +12 -0
- data/lib/freshbooks/recurring.rb +16 -0
- data/lib/freshbooks/response.rb +25 -0
- data/lib/freshbooks/schema/definition.rb +20 -0
- data/lib/freshbooks/schema/mixin.rb +40 -0
- data/lib/freshbooks/staff.rb +13 -0
- data/lib/freshbooks/task.rb +12 -0
- data/lib/freshbooks/time_entry.rb +12 -0
- data/lib/freshbooks/xml_serializer.rb +17 -0
- data/lib/freshbooks/xml_serializer/serializers.rb +116 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/fixtures/freshbooks_credentials.sample.yml +3 -0
- data/test/fixtures/invoice_create_response.xml +4 -0
- data/test/fixtures/invoice_get_response.xml +54 -0
- data/test/fixtures/invoice_list_response.xml +109 -0
- data/test/fixtures/success_response.xml +2 -0
- data/test/mock_connection.rb +13 -0
- data/test/schema/test_definition.rb +36 -0
- data/test/schema/test_mixin.rb +39 -0
- data/test/test_base.rb +151 -0
- data/test/test_connection.rb +145 -0
- data/test/test_helper.rb +48 -0
- data/test/test_invoice.rb +125 -0
- data/test/test_list_proxy.rb +60 -0
- data/test/test_page.rb +50 -0
- 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 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
|
data/script/console
ADDED
@@ -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"
|
data/script/destroy
ADDED
@@ -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)
|
data/script/generate
ADDED
@@ -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,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>
|