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