xeroizer 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +20 -1
- data/VERSION +1 -1
- data/lib/xeroizer.rb +3 -1
- data/lib/xeroizer/exceptions.rb +7 -4
- data/lib/xeroizer/generic_application.rb +4 -2
- data/lib/xeroizer/http.rb +47 -28
- data/lib/xeroizer/models/account.rb +3 -1
- data/lib/xeroizer/models/contact.rb +2 -1
- data/lib/xeroizer/models/contact_group.rb +16 -0
- data/lib/xeroizer/oauth.rb +3 -3
- data/lib/xeroizer/record/base.rb +9 -2
- data/lib/xeroizer/record/base_model.rb +2 -0
- data/lib/xeroizer/record/xml_helper.rb +1 -1
- data/lib/xeroizer/report/aged_receivables_by_contact.rb +44 -0
- data/lib/xeroizer/report/base.rb +1 -1
- data/lib/xeroizer/report/cell_xml_helper.rb +10 -7
- data/lib/xeroizer/report/factory.rb +14 -3
- data/lib/xeroizer/report/row/header.rb +7 -2
- data/lib/xeroizer/report/row/row.rb +8 -2
- data/test/test_helper.rb +2 -2
- data/test/unit/oauth_test.rb +27 -2
- data/test/unit/record/base_test.rb +2 -2
- data/test/unit/report_test.rb +2 -2
- data/xeroizer.gemspec +4 -2
- metadata +6 -4
data/README.md
CHANGED
@@ -465,4 +465,23 @@ Reports are accessed like the following example:
|
|
465
465
|
|
466
466
|
end
|
467
467
|
end
|
468
|
-
|
468
|
+
|
469
|
+
Xero API Rate Limits
|
470
|
+
--------------------
|
471
|
+
|
472
|
+
The Xero API imposes the following limits on calls per organisation:
|
473
|
+
|
474
|
+
* A limit of 60 API calls in any 60 second period
|
475
|
+
* A limit of 1000 API calls in any 24 hour period
|
476
|
+
|
477
|
+
By default, the library will raise a `Xeroizer::OAuth::RateLimitExceeded`
|
478
|
+
exception when one of these limits is exceeded.
|
479
|
+
|
480
|
+
If required, the library can handle these exceptions internally by sleeping
|
481
|
+
for a configurable number of seconds and then repeating the last request.
|
482
|
+
You can set this option when initializing an application:
|
483
|
+
|
484
|
+
# Sleep for 2 seconds every time the rate limit is exceeded.
|
485
|
+
client = Xeroizer::PublicApplication.new(YOUR_OAUTH_CONSUMER_KEY,
|
486
|
+
YOUR_OAUTH_CONSUMER_SECRET,
|
487
|
+
:rate_limit_sleep => 2)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.2.
|
1
|
+
0.2.1
|
data/lib/xeroizer.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'date'
|
3
3
|
require 'forwardable'
|
4
|
-
require
|
4
|
+
require 'active_support/inflector'
|
5
|
+
require 'active_support/memoizable'
|
5
6
|
# require "active_support/core_ext"
|
6
7
|
require 'oauth'
|
7
8
|
require 'oauth/signature/rsa/sha1'
|
@@ -29,6 +30,7 @@ require 'xeroizer/models/account'
|
|
29
30
|
require 'xeroizer/models/address'
|
30
31
|
require 'xeroizer/models/branding_theme'
|
31
32
|
require 'xeroizer/models/contact'
|
33
|
+
require 'xeroizer/models/contact_group'
|
32
34
|
require 'xeroizer/models/credit_note'
|
33
35
|
require 'xeroizer/models/currency'
|
34
36
|
require 'xeroizer/models/invoice'
|
data/lib/xeroizer/exceptions.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
module Xeroizer
|
2
2
|
class ApiException < StandardError
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
@
|
4
|
+
attr_reader :type, :message, :xml, :request_body
|
5
|
+
|
6
|
+
def initialize(type, message, xml, request_body)
|
7
|
+
@type = type
|
8
|
+
@message = message
|
9
|
+
@xml = xml
|
10
|
+
@request_body = request_body
|
8
11
|
end
|
9
12
|
|
10
13
|
def message
|
@@ -6,7 +6,7 @@ module Xeroizer
|
|
6
6
|
include Http
|
7
7
|
extend Record::ApplicationHelper
|
8
8
|
|
9
|
-
attr_reader :client, :xero_url, :logger
|
9
|
+
attr_reader :client, :xero_url, :logger, :rate_limit_sleep, :rate_limit_max_attempts
|
10
10
|
|
11
11
|
extend Forwardable
|
12
12
|
def_delegators :client, :access_token
|
@@ -43,8 +43,10 @@ module Xeroizer
|
|
43
43
|
# @see PartnerApplication
|
44
44
|
def initialize(consumer_key, consumer_secret, options = {})
|
45
45
|
@xero_url = options[:xero_url] || "https://api.xero.com/api.xro/2.0"
|
46
|
+
@rate_limit_sleep = options[:rate_limit_sleep] || false
|
47
|
+
@rate_limit_max_attempts = options[:rate_limit_max_attempts] || 5
|
46
48
|
@client = OAuth.new(consumer_key, consumer_secret, options)
|
47
49
|
end
|
48
50
|
|
49
51
|
end
|
50
|
-
end
|
52
|
+
end
|
data/lib/xeroizer/http.rb
CHANGED
@@ -64,34 +64,48 @@ module Xeroizer
|
|
64
64
|
end
|
65
65
|
|
66
66
|
uri = URI.parse(url)
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
unless response.code.to_i == 200
|
80
|
-
logger.info("== #{uri.request_uri} Response Body \n\n #{response.plain_body} \n == End Response Body")
|
67
|
+
|
68
|
+
attempts = 0
|
69
|
+
|
70
|
+
begin
|
71
|
+
attempts += 1
|
72
|
+
logger.info("\n== [#{Time.now.to_s}] XeroGateway Request: #{uri.request_uri} ") if self.logger
|
73
|
+
|
74
|
+
response = case method
|
75
|
+
when :get then client.get(uri.request_uri, headers)
|
76
|
+
when :post then client.post(uri.request_uri, { :xml => body }, headers)
|
77
|
+
when :put then client.put(uri.request_uri, { :xml => body }, headers)
|
81
78
|
end
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
response.
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
79
|
+
|
80
|
+
if self.logger
|
81
|
+
logger.info("== [#{Time.now.to_s}] XeroGateway Response (#{response.code})")
|
82
|
+
|
83
|
+
unless response.code.to_i == 200
|
84
|
+
logger.info("== #{uri.request_uri} Response Body \n\n #{response.plain_body} \n == End Response Body")
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
case response.code.to_i
|
89
|
+
when 200
|
90
|
+
response.plain_body
|
91
|
+
when 400
|
92
|
+
handle_error!(response, body)
|
93
|
+
when 401
|
94
|
+
handle_oauth_error!(response)
|
95
|
+
when 404
|
96
|
+
handle_object_not_found!(response, url)
|
97
|
+
else
|
98
|
+
raise "Unknown response code: #{response.code.to_i}"
|
99
|
+
end
|
100
|
+
rescue Xeroizer::OAuth::RateLimitExceeded
|
101
|
+
if self.rate_limit_sleep
|
102
|
+
raise if attempts > rate_limit_max_attempts
|
103
|
+
logger.info("== Rate limit exceeded, retrying") if self.logger
|
104
|
+
sleep_for(self.rate_limit_sleep)
|
105
|
+
retry
|
93
106
|
else
|
94
|
-
raise
|
107
|
+
raise
|
108
|
+
end
|
95
109
|
end
|
96
110
|
end
|
97
111
|
|
@@ -111,7 +125,7 @@ module Xeroizer
|
|
111
125
|
end
|
112
126
|
end
|
113
127
|
|
114
|
-
def handle_error!(response)
|
128
|
+
def handle_error!(response, request_body)
|
115
129
|
|
116
130
|
raw_response = response.plain_body
|
117
131
|
|
@@ -126,7 +140,8 @@ module Xeroizer
|
|
126
140
|
|
127
141
|
raise ApiException.new(doc.root.xpath("Type").text,
|
128
142
|
doc.root.xpath("Message").text,
|
129
|
-
raw_response
|
143
|
+
raw_response,
|
144
|
+
request_body)
|
130
145
|
|
131
146
|
else
|
132
147
|
|
@@ -143,6 +158,10 @@ module Xeroizer
|
|
143
158
|
else raise ObjectNotFound.new(request_url)
|
144
159
|
end
|
145
160
|
end
|
161
|
+
|
162
|
+
def sleep_for(seconds = 1)
|
163
|
+
sleep seconds
|
164
|
+
end
|
146
165
|
|
147
166
|
end
|
148
167
|
end
|
@@ -35,10 +35,12 @@ module Xeroizer
|
|
35
35
|
'RRINPUT' => 'Reduced rate VAT on expenses (UK Only)',
|
36
36
|
'EXEMPTOUTPUT' => 'VAT on sales exempt from VAT (UK only)',
|
37
37
|
'OUTPUT' => 'OUTPUT',
|
38
|
+
'OUTPUT2' => 'OUTPUT2',
|
38
39
|
'SROUTPUT' => 'SROUTPUT',
|
39
40
|
'ZERORATEDOUTPUT' => 'Sales made from overseas (UK only)',
|
40
41
|
'RROUTPUT' => 'Reduced rate VAT on sales (UK Only)',
|
41
|
-
'ZERORATED' => 'Zero-rated supplies/sales from overseas (NZ Only)'
|
42
|
+
'ZERORATED' => 'Zero-rated supplies/sales from overseas (NZ Only)',
|
43
|
+
'ECZROUTPUT' => 'Zero-rated EC Income (UK only)'
|
42
44
|
} unless defined?(TAX_TYPE)
|
43
45
|
|
44
46
|
set_primary_key :account_id
|
@@ -36,6 +36,7 @@ module Xeroizer
|
|
36
36
|
|
37
37
|
has_many :addresses
|
38
38
|
has_many :phones
|
39
|
+
has_many :contact_groups
|
39
40
|
|
40
41
|
validates_presence_of :name
|
41
42
|
validates_inclusion_of :contact_status, :in => CONTACT_STATUS.keys, :allow_blanks => true
|
@@ -43,4 +44,4 @@ module Xeroizer
|
|
43
44
|
end
|
44
45
|
|
45
46
|
end
|
46
|
-
end
|
47
|
+
end
|
data/lib/xeroizer/oauth.rb
CHANGED
@@ -61,7 +61,7 @@ module Xeroizer
|
|
61
61
|
#
|
62
62
|
# @return [OAuth::Consumer] consumer object for GET/POST/PUT methods.
|
63
63
|
def consumer
|
64
|
-
|
64
|
+
create_consumer
|
65
65
|
end
|
66
66
|
|
67
67
|
# RequestToken for PUBLIC/PARTNER authorisation
|
@@ -69,7 +69,7 @@ module Xeroizer
|
|
69
69
|
#
|
70
70
|
# @option params [String] :oauth_callback URL to redirect user to when they have authenticated your application with Xero. If not specified, the user will be shown an authorisation code on the screen that they need to get into your application.
|
71
71
|
def request_token(params = {})
|
72
|
-
|
72
|
+
consumer.get_request_token(params)
|
73
73
|
end
|
74
74
|
|
75
75
|
# Create an AccessToken from a PUBLIC/PARTNER authorisation.
|
@@ -81,7 +81,7 @@ module Xeroizer
|
|
81
81
|
|
82
82
|
# AccessToken created from authorize_from_access method.
|
83
83
|
def access_token
|
84
|
-
|
84
|
+
::OAuth::AccessToken.new(consumer, @atoken, @asecret)
|
85
85
|
end
|
86
86
|
|
87
87
|
# Used for PRIVATE applications where the AccessToken uses the
|
data/lib/xeroizer/record/base.rb
CHANGED
@@ -54,7 +54,7 @@ module Xeroizer
|
|
54
54
|
end
|
55
55
|
|
56
56
|
def new_record?
|
57
|
-
|
57
|
+
id.nil?
|
58
58
|
end
|
59
59
|
|
60
60
|
# Check to see if the complete record is downloaded.
|
@@ -83,6 +83,13 @@ module Xeroizer
|
|
83
83
|
end
|
84
84
|
true
|
85
85
|
end
|
86
|
+
|
87
|
+
def inspect
|
88
|
+
attribute_string = self.attributes.collect do |attr, value|
|
89
|
+
"#{attr.inspect}: #{value.inspect}"
|
90
|
+
end.join(", ")
|
91
|
+
"#<#{self.class} #{attribute_string}>"
|
92
|
+
end
|
86
93
|
|
87
94
|
protected
|
88
95
|
|
@@ -113,4 +120,4 @@ module Xeroizer
|
|
113
120
|
end
|
114
121
|
|
115
122
|
end
|
116
|
-
end
|
123
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Xeroizer
|
2
|
+
module Report
|
3
|
+
class AgedReceivablesByContact < Base
|
4
|
+
|
5
|
+
extend ActiveSupport::Memoizable
|
6
|
+
|
7
|
+
public
|
8
|
+
|
9
|
+
def total
|
10
|
+
summary.cell(:Total).value
|
11
|
+
end
|
12
|
+
|
13
|
+
def total_paid
|
14
|
+
summary.cell(:Paid).value
|
15
|
+
end
|
16
|
+
|
17
|
+
def total_credited
|
18
|
+
summary.cell(:Credited).value
|
19
|
+
end
|
20
|
+
|
21
|
+
def total_due
|
22
|
+
summary.cell(:Due).value
|
23
|
+
end
|
24
|
+
|
25
|
+
def total_overdue
|
26
|
+
now = Time.now
|
27
|
+
sum(:Due) do | row |
|
28
|
+
due_date = row.cell('Due Date').value
|
29
|
+
due_date && due_date < now
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def sum(column_name, &block)
|
34
|
+
sections.first.rows.inject(BigDecimal.new('0')) do | sum, row |
|
35
|
+
cell = row.cell(column_name)
|
36
|
+
sum += row.cell(column_name).value if row.class == Xeroizer::Report::Row && (block.nil? || block.call(row))
|
37
|
+
sum
|
38
|
+
end
|
39
|
+
end
|
40
|
+
memoize :total, :total_paid, :total_credited, :total_due, :total_overdue, :sum
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/xeroizer/report/base.rb
CHANGED
@@ -32,7 +32,7 @@ module Xeroizer
|
|
32
32
|
cell = new
|
33
33
|
node.elements.each do | element |
|
34
34
|
case element.name.to_s
|
35
|
-
when 'Value' then cell.value =
|
35
|
+
when 'Value' then cell.value = parse_value(element.text)
|
36
36
|
when 'Attributes'
|
37
37
|
element.elements.each do | attribute_node |
|
38
38
|
(id, value) = parse_attribute(attribute_node)
|
@@ -44,12 +44,15 @@ module Xeroizer
|
|
44
44
|
end
|
45
45
|
|
46
46
|
protected
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
|
48
|
+
def parse_value(value)
|
49
|
+
case value
|
50
|
+
when /^[-]?\d+(\.\d+)?$/ then BigDecimal.new(value)
|
51
|
+
when /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/ then Time.xmlschema(value)
|
52
|
+
else value
|
53
|
+
end
|
51
54
|
end
|
52
|
-
|
55
|
+
|
53
56
|
def parse_attribute(attribute_node)
|
54
57
|
id = nil
|
55
58
|
value = nil
|
@@ -68,4 +71,4 @@ module Xeroizer
|
|
68
71
|
|
69
72
|
end
|
70
73
|
end
|
71
|
-
end
|
74
|
+
end
|
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'xeroizer/application_http_proxy'
|
2
2
|
require 'xeroizer/report/base'
|
3
|
+
require 'xeroizer/report/aged_receivables_by_contact'
|
3
4
|
|
4
5
|
module Xeroizer
|
5
6
|
module Report
|
6
7
|
class Factory
|
7
8
|
|
9
|
+
extend ActiveSupport::Memoizable
|
8
10
|
include ApplicationHttpProxy
|
9
11
|
|
10
12
|
attr_reader :application
|
@@ -27,17 +29,26 @@ module Xeroizer
|
|
27
29
|
end
|
28
30
|
|
29
31
|
def api_controller_name
|
30
|
-
"
|
32
|
+
"Reports/#{report_type}"
|
31
33
|
end
|
32
34
|
|
35
|
+
def klass
|
36
|
+
begin
|
37
|
+
Xeroizer::Report.const_get(report_type)
|
38
|
+
rescue NameError => ex # use default class
|
39
|
+
Base
|
40
|
+
end
|
41
|
+
end
|
42
|
+
memoize :klass
|
43
|
+
|
33
44
|
protected
|
34
45
|
|
35
46
|
def parse_reports(response, elements)
|
36
47
|
elements.each do | element |
|
37
|
-
response.response_items <<
|
48
|
+
response.response_items << klass.build_from_node(element, self)
|
38
49
|
end
|
39
50
|
end
|
40
51
|
|
41
52
|
end
|
42
53
|
end
|
43
|
-
end
|
54
|
+
end
|
@@ -4,6 +4,7 @@ module Xeroizer
|
|
4
4
|
module Report
|
5
5
|
class Row
|
6
6
|
|
7
|
+
extend ActiveSupport::Memoizable
|
7
8
|
include RowXmlHelper
|
8
9
|
|
9
10
|
attr_reader :report
|
@@ -37,7 +38,12 @@ module Xeroizer
|
|
37
38
|
def parent?
|
38
39
|
rows.size > 0
|
39
40
|
end
|
40
|
-
|
41
|
+
|
42
|
+
def cell(column_name)
|
43
|
+
index = header.column_index(column_name)
|
44
|
+
cells[index] if index >= 0
|
45
|
+
end
|
46
|
+
|
41
47
|
end
|
42
48
|
end
|
43
|
-
end
|
49
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -51,7 +51,7 @@ module TestHelper
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def mock_report_api(report_type)
|
54
|
-
@client.stubs(:http_get).with { | client, url, params | url =~ /
|
54
|
+
@client.stubs(:http_get).with { | client, url, params | url =~ /Reports\/#{report_type}$/ }.returns(get_report_xml(report_type))
|
55
55
|
end
|
56
56
|
|
57
|
-
end
|
57
|
+
end
|
data/test/unit/oauth_test.rb
CHANGED
@@ -33,13 +33,38 @@ class OAuthTest < Test::Unit::TestCase
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
should "
|
36
|
+
should "raise rate limit exceeded" do
|
37
37
|
Xeroizer::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("rate_limit_exceeded"), :code => "401"))
|
38
38
|
|
39
39
|
assert_raises Xeroizer::OAuth::RateLimitExceeded do
|
40
40
|
@client.Organisation.first
|
41
41
|
end
|
42
42
|
end
|
43
|
+
|
44
|
+
should "automatically handle rate limit exceeded" do
|
45
|
+
auto_rate_limit_client = Xeroizer::PublicApplication.new(CONSUMER_KEY, CONSUMER_SECRET, :rate_limit_sleep => 1)
|
46
|
+
|
47
|
+
# Return rate limit exceeded on first call, OK on the second
|
48
|
+
Xeroizer::OAuth.any_instance.stubs(:get).returns(
|
49
|
+
stub(:plain_body => get_file_as_string("rate_limit_exceeded"), :code => "401"),
|
50
|
+
stub(:plain_body => get_record_xml(:organisation), :code => '200')
|
51
|
+
)
|
52
|
+
|
53
|
+
auto_rate_limit_client.expects(:sleep_for).with(1).returns(1)
|
54
|
+
|
55
|
+
auto_rate_limit_client.Organisation.first
|
56
|
+
end
|
57
|
+
|
58
|
+
should "only retry rate limited request a configurable number of times" do
|
59
|
+
auto_rate_limit_client = Xeroizer::PublicApplication.new(CONSUMER_KEY, CONSUMER_SECRET, :rate_limit_sleep => 1, :rate_limit_max_attempts => 4)
|
60
|
+
Xeroizer::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("rate_limit_exceeded"), :code => "401"))
|
61
|
+
|
62
|
+
auto_rate_limit_client.expects(:sleep_for).with(1).times(4).returns(1)
|
63
|
+
|
64
|
+
assert_raises Xeroizer::OAuth::RateLimitExceeded do
|
65
|
+
auto_rate_limit_client.Organisation.first
|
66
|
+
end
|
67
|
+
end
|
43
68
|
|
44
69
|
should "handle unknown errors" do
|
45
70
|
Xeroizer::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("bogus_oauth_error"), :code => "401"))
|
@@ -69,4 +94,4 @@ class OAuthTest < Test::Unit::TestCase
|
|
69
94
|
|
70
95
|
end
|
71
96
|
|
72
|
-
end
|
97
|
+
end
|
@@ -41,12 +41,12 @@ class RecordBaseTest < Test::Unit::TestCase
|
|
41
41
|
assert(@contact.contact_id =~ GUID_REGEX, "@contact.contact_id is not a GUID, it is '#{@contact.contact_id}'")
|
42
42
|
end
|
43
43
|
|
44
|
-
should "new_record? should be false if we have
|
44
|
+
should "new_record? should be false if we have specified a primary key" do
|
45
45
|
contact = @client.Contact.build(:contact_id => 'ABC')
|
46
46
|
assert_equal(false, contact.new_record?)
|
47
47
|
|
48
48
|
contact = @client.Contact.build(:contact_number => 'CDE')
|
49
|
-
assert_equal(
|
49
|
+
assert_equal(true, contact.new_record?)
|
50
50
|
|
51
51
|
contact = @client.Contact.build(:name => 'TEST NAME')
|
52
52
|
assert_equal(true, contact.new_record?)
|
data/test/unit/report_test.rb
CHANGED
@@ -17,7 +17,7 @@ class FactoryTest < Test::Unit::TestCase
|
|
17
17
|
:BudgetSummary, :ExecutiveSummary, :ProfitAndLoss, :TrialBalance
|
18
18
|
].each do | report_type |
|
19
19
|
report_factory = @client.send(report_type)
|
20
|
-
assert_equal("
|
20
|
+
assert_equal("Reports/#{report_type}", report_factory.api_controller_name)
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -142,4 +142,4 @@ class FactoryTest < Test::Unit::TestCase
|
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
145
|
-
end
|
145
|
+
end
|
data/xeroizer.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{xeroizer}
|
8
|
-
s.version = "0.2.
|
8
|
+
s.version = "0.2.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Wayne Robinson"]
|
12
|
-
s.date = %q{2011-
|
12
|
+
s.date = %q{2011-05-05}
|
13
13
|
s.description = %q{Ruby library for the Xero accounting system API.}
|
14
14
|
s.email = %q{wayne.robinson@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -39,6 +39,7 @@ Gem::Specification.new do |s|
|
|
39
39
|
"lib/xeroizer/models/address.rb",
|
40
40
|
"lib/xeroizer/models/branding_theme.rb",
|
41
41
|
"lib/xeroizer/models/contact.rb",
|
42
|
+
"lib/xeroizer/models/contact_group.rb",
|
42
43
|
"lib/xeroizer/models/credit_note.rb",
|
43
44
|
"lib/xeroizer/models/currency.rb",
|
44
45
|
"lib/xeroizer/models/invoice.rb",
|
@@ -72,6 +73,7 @@ Gem::Specification.new do |s|
|
|
72
73
|
"lib/xeroizer/record/validators/presence_of_validator.rb",
|
73
74
|
"lib/xeroizer/record/validators/validator.rb",
|
74
75
|
"lib/xeroizer/record/xml_helper.rb",
|
76
|
+
"lib/xeroizer/report/aged_receivables_by_contact.rb",
|
75
77
|
"lib/xeroizer/report/base.rb",
|
76
78
|
"lib/xeroizer/report/cell.rb",
|
77
79
|
"lib/xeroizer/report/cell_xml_helper.rb",
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xeroizer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 21
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 1
|
10
|
+
version: 0.2.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Wayne Robinson
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-05-05 00:00:00 +10:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -214,6 +214,7 @@ files:
|
|
214
214
|
- lib/xeroizer/models/address.rb
|
215
215
|
- lib/xeroizer/models/branding_theme.rb
|
216
216
|
- lib/xeroizer/models/contact.rb
|
217
|
+
- lib/xeroizer/models/contact_group.rb
|
217
218
|
- lib/xeroizer/models/credit_note.rb
|
218
219
|
- lib/xeroizer/models/currency.rb
|
219
220
|
- lib/xeroizer/models/invoice.rb
|
@@ -247,6 +248,7 @@ files:
|
|
247
248
|
- lib/xeroizer/record/validators/presence_of_validator.rb
|
248
249
|
- lib/xeroizer/record/validators/validator.rb
|
249
250
|
- lib/xeroizer/record/xml_helper.rb
|
251
|
+
- lib/xeroizer/report/aged_receivables_by_contact.rb
|
250
252
|
- lib/xeroizer/report/base.rb
|
251
253
|
- lib/xeroizer/report/cell.rb
|
252
254
|
- lib/xeroizer/report/cell_xml_helper.rb
|