xeroizer 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +47 -1
- data/VERSION +1 -1
- data/lib/xeroizer.rb +2 -0
- data/lib/xeroizer/application_http_proxy.rb +30 -0
- data/lib/xeroizer/generic_application.rb +10 -0
- data/lib/xeroizer/record/application_helper.rb +10 -0
- data/lib/xeroizer/record/base_model.rb +7 -30
- data/lib/xeroizer/record/base_model_http_proxy.rb +3 -19
- data/lib/xeroizer/report/base.rb +43 -0
- data/lib/xeroizer/report/cell.rb +32 -0
- data/lib/xeroizer/report/cell_xml_helper.rb +71 -0
- data/lib/xeroizer/report/factory.rb +43 -0
- data/lib/xeroizer/report/row/header.rb +7 -0
- data/lib/xeroizer/report/row/row.rb +43 -0
- data/lib/xeroizer/report/row/section.rb +10 -0
- data/lib/xeroizer/report/row/summary.rb +9 -0
- data/lib/xeroizer/report/row/xml_helper.rb +77 -0
- data/lib/xeroizer/report/xml_helper.rb +58 -0
- data/lib/xeroizer/response.rb +38 -0
- data/test/stub_responses/reports/trial_balance.xml +1435 -0
- data/test/test_helper.rb +8 -0
- data/test/unit/record_definition_test.rb +27 -0
- data/test/unit/report_definition_test.rb +26 -0
- data/test/unit/report_test.rb +145 -0
- data/xeroizer.gemspec +21 -3
- metadata +23 -5
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Xeroizer API Library
|
1
|
+
Xeroizer API Library ![Project status](http://stillmaintained.com/waynerobinson/xeroizer.png)
|
2
2
|
====================
|
3
3
|
|
4
4
|
**Homepage**: [http://waynerobinson.github.com/xeroizer](http://waynerobinson.github.com/xeroizer)
|
@@ -420,3 +420,49 @@ that attribute. For example:
|
|
420
420
|
|
421
421
|
If something goes really wrong and the particular validation isn't handled by the internal
|
422
422
|
validators then the library may raise a `Xeroizer::ApiException`.
|
423
|
+
|
424
|
+
Reports
|
425
|
+
-------
|
426
|
+
|
427
|
+
All Xero reports except GST report can be accessed through Xeroizer.
|
428
|
+
|
429
|
+
Currently, only generic report access functionality exists. This will be extended
|
430
|
+
to provide a more report-specific version of the data in the future (public submissions
|
431
|
+
are welcome).
|
432
|
+
|
433
|
+
Reports are accessed like the following example:
|
434
|
+
|
435
|
+
trial_balance = xero.TrialBalance.get(:date => '2011-03-21')
|
436
|
+
|
437
|
+
# Array containing report headings.
|
438
|
+
trial_balance.header.cells.map { | cell | cell.value }
|
439
|
+
|
440
|
+
# Report rows by section
|
441
|
+
trial_balance.sections.each do | section |
|
442
|
+
puts "Section Title: #{section.title}"
|
443
|
+
section.rows.each do | row |
|
444
|
+
puts "\t#{row.cells.map { | cell | cell.value }.join("\t")}"
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
# Summary row (if only one on the report)
|
449
|
+
trial_balance.summary.cells.map { | cell | cell.value }
|
450
|
+
|
451
|
+
# All report rows (including HeaderRow, SectionRow, Row and SummaryRow)
|
452
|
+
trial_balance.rows.each do | row |
|
453
|
+
case row
|
454
|
+
when Xeroizer::Report::HeaderRow
|
455
|
+
# do something with header
|
456
|
+
|
457
|
+
when Xeroizer::Report::SectionRow
|
458
|
+
# do something with section, will need to step into the rows for this section
|
459
|
+
|
460
|
+
when Xeroizer::Report::Row
|
461
|
+
# do something for standard report rows
|
462
|
+
|
463
|
+
when Xeroizer::Report::SummaryRow
|
464
|
+
# do something for summary rows
|
465
|
+
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/lib/xeroizer.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
module Xeroizer
|
2
|
+
module ApplicationHttpProxy
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :include, InstanceMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module InstanceMethods
|
9
|
+
|
10
|
+
# URL end-point for this model.
|
11
|
+
def url
|
12
|
+
@application.xero_url + '/' + api_controller_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def http_get(extra_params = {})
|
16
|
+
application.http_get(application.client, url, extra_params)
|
17
|
+
end
|
18
|
+
|
19
|
+
def http_put(xml, extra_params = {})
|
20
|
+
application.http_put(application.client, url, xml, extra_params)
|
21
|
+
end
|
22
|
+
|
23
|
+
def http_post(xml, extra_params = {})
|
24
|
+
application.http_post(application.client, url, xml, extra_params)
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -25,6 +25,16 @@ module Xeroizer
|
|
25
25
|
record :TaxRate
|
26
26
|
record :TrackingCategory
|
27
27
|
|
28
|
+
report :AgedPayablesByContact
|
29
|
+
report :AgedReceivablesByContact
|
30
|
+
report :BalanceSheet
|
31
|
+
report :BankStatement
|
32
|
+
report :BankSummary
|
33
|
+
report :BudgetSummary
|
34
|
+
report :ExecutiveSummary
|
35
|
+
report :ProfitAndLoss
|
36
|
+
report :TrialBalance
|
37
|
+
|
28
38
|
public
|
29
39
|
|
30
40
|
# Never used directly. Use sub-classes instead.
|
@@ -17,6 +17,16 @@ module Xeroizer
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
+
def report(report_type)
|
21
|
+
define_method report_type do
|
22
|
+
var_name = "@#{report_type}_cache".to_sym
|
23
|
+
unless instance_variable_defined?(var_name)
|
24
|
+
instance_variable_set(var_name, Xeroizer::Report::Factory.new(self, report_type.to_s))
|
25
|
+
end
|
26
|
+
instance_variable_get(var_name)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
20
30
|
end
|
21
31
|
end
|
22
32
|
end
|
@@ -92,43 +92,20 @@ module Xeroizer
|
|
92
92
|
result
|
93
93
|
end
|
94
94
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
doc = Nokogiri::XML(raw_response) { | cfg | cfg.noblanks }
|
101
|
-
|
102
|
-
# check for responses we don't understand
|
103
|
-
raise Xeroizer::UnparseableResponse.new(doc.root.name) unless doc.root.name == 'Response'
|
104
|
-
|
105
|
-
doc.root.elements.each do | element |
|
106
|
-
|
107
|
-
# Text element
|
108
|
-
if element.children && element.children.size == 1 && element.children.first.text?
|
109
|
-
case element.name
|
110
|
-
when 'Id' then @response.id = element.text
|
111
|
-
when 'Status' then @response.status = element.text
|
112
|
-
when 'ProviderName' then @response.provider = element.text
|
113
|
-
when 'DateTimeUTC' then @response.date_time = Time.parse(element.text)
|
114
|
-
end
|
115
|
-
|
116
|
-
# Records in response
|
117
|
-
elsif element.children && element.children.size > 0 && element.children.first.name == model_name
|
118
|
-
parse_records(element.children)
|
95
|
+
def parse_response(response_xml, options = {})
|
96
|
+
Response.parse(response_xml, options) do | response, elements, response_model_name |
|
97
|
+
if model_name == response_model_name
|
98
|
+
parse_records(response, elements)
|
119
99
|
end
|
120
100
|
end
|
121
|
-
|
122
|
-
@response.response_items
|
123
101
|
end
|
124
|
-
|
102
|
+
|
125
103
|
protected
|
126
104
|
|
127
105
|
# Parse the records part of the XML response and builds model instances as necessary.
|
128
|
-
def parse_records(elements)
|
129
|
-
@response.response_items = []
|
106
|
+
def parse_records(response, elements)
|
130
107
|
elements.each do | element |
|
131
|
-
|
108
|
+
response.response_items << model_class.build_from_node(element, self)
|
132
109
|
end
|
133
110
|
end
|
134
111
|
|
@@ -1,32 +1,16 @@
|
|
1
|
+
require 'xeroizer/application_http_proxy'
|
2
|
+
|
1
3
|
module Xeroizer
|
2
4
|
module Record
|
3
5
|
module BaseModelHttpProxy
|
4
6
|
|
5
7
|
def self.included(base)
|
8
|
+
base.send :include, Xeroizer::ApplicationHttpProxy
|
6
9
|
base.send :include, InstanceMethods
|
7
10
|
end
|
8
11
|
|
9
12
|
module InstanceMethods
|
10
13
|
|
11
|
-
public
|
12
|
-
|
13
|
-
# URL end-point for this model.
|
14
|
-
def url
|
15
|
-
@application.xero_url + '/' + api_controller_name
|
16
|
-
end
|
17
|
-
|
18
|
-
def http_get(extra_params = {})
|
19
|
-
application.http_get(application.client, url, extra_params)
|
20
|
-
end
|
21
|
-
|
22
|
-
def http_put(xml, extra_params = {})
|
23
|
-
application.http_put(application.client, url, xml, extra_params)
|
24
|
-
end
|
25
|
-
|
26
|
-
def http_post(xml, extra_params = {})
|
27
|
-
application.http_post(application.client, url, xml, extra_params)
|
28
|
-
end
|
29
|
-
|
30
14
|
protected
|
31
15
|
|
32
16
|
# Parse parameters for GET requests.
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'xeroizer/report/base'
|
2
|
+
require 'xeroizer/report/cell'
|
3
|
+
require 'xeroizer/report/row/row'
|
4
|
+
require 'xeroizer/report/row/header'
|
5
|
+
require 'xeroizer/report/row/section'
|
6
|
+
require 'xeroizer/report/row/summary'
|
7
|
+
require 'xeroizer/report/xml_helper'
|
8
|
+
|
9
|
+
module Xeroizer
|
10
|
+
module Report
|
11
|
+
class Base
|
12
|
+
|
13
|
+
include XmlHelper
|
14
|
+
|
15
|
+
attr_reader :factory
|
16
|
+
|
17
|
+
attr_accessor :id
|
18
|
+
attr_accessor :name
|
19
|
+
attr_accessor :type
|
20
|
+
attr_accessor :titles
|
21
|
+
attr_accessor :date
|
22
|
+
attr_accessor :updated_at
|
23
|
+
|
24
|
+
attr_accessor :rows
|
25
|
+
|
26
|
+
attr_accessor :header
|
27
|
+
attr_accessor :summary
|
28
|
+
attr_accessor :sections
|
29
|
+
|
30
|
+
public
|
31
|
+
|
32
|
+
def initialize(factory)
|
33
|
+
@titles = []
|
34
|
+
@rows = []
|
35
|
+
@sections = []
|
36
|
+
@factory = factory
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'xeroizer/report/cell_xml_helper'
|
2
|
+
|
3
|
+
module Xeroizer
|
4
|
+
module Report
|
5
|
+
class Cell
|
6
|
+
|
7
|
+
include CellXmlHelper
|
8
|
+
|
9
|
+
attr_accessor :value
|
10
|
+
attr_accessor :attributes
|
11
|
+
|
12
|
+
public
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@attributes = {}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return first attribute's ID in the hash. Assumes there is only one as hashes get out of order.
|
19
|
+
# In all cases I've seen so far there is only one attribute returned.
|
20
|
+
def attribute_id
|
21
|
+
@attributes.each { | id, value | return id }
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return first attribute's value in the hash. Assumes there is only one as hashes get out of order.
|
25
|
+
# In all cases I've seen so far there is only one attribute returned.
|
26
|
+
def attribute_value
|
27
|
+
@attributes.each { | id, value | return value }
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Xeroizer
|
2
|
+
module Report
|
3
|
+
module CellXmlHelper
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
base.send :include, InstanceMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
|
12
|
+
public
|
13
|
+
|
14
|
+
# Create an instance of Cell from the node.
|
15
|
+
#
|
16
|
+
# Additionally, parse the attributes and return them as a hash to the
|
17
|
+
# cell. If a cell's attributes look like:
|
18
|
+
#
|
19
|
+
# <Attributes>
|
20
|
+
# <Attribute>
|
21
|
+
# <Value>1335b8b2-4d63-4af8-937f-04087ae2e36e</Value>
|
22
|
+
# <Id>account</Id>
|
23
|
+
# </Attribute>
|
24
|
+
# </Attributes>
|
25
|
+
#
|
26
|
+
# Return a hash like:
|
27
|
+
#
|
28
|
+
# {
|
29
|
+
# 'account' => '1335b8b2-4d63-4af8-937f-04087ae2e36e'
|
30
|
+
# }
|
31
|
+
def build_from_node(node)
|
32
|
+
cell = new
|
33
|
+
node.elements.each do | element |
|
34
|
+
case element.name.to_s
|
35
|
+
when 'Value' then cell.value = potentially_convert_to_number(element.text)
|
36
|
+
when 'Attributes'
|
37
|
+
element.elements.each do | attribute_node |
|
38
|
+
(id, value) = parse_attribute(attribute_node)
|
39
|
+
cell.attributes[id] = value
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
cell
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
# If a cell's value is a valid number then return it is as BigDecimal.
|
49
|
+
def potentially_convert_to_number(value)
|
50
|
+
value =~ /^[-]?\d+(\.\d+)?$/ ? BigDecimal.new(value) : value
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_attribute(attribute_node)
|
54
|
+
id = nil
|
55
|
+
value = nil
|
56
|
+
attribute_node.elements.each do | element |
|
57
|
+
case element.name.to_s
|
58
|
+
when 'Id' then id = element.text
|
59
|
+
when 'Value' then value = element.text
|
60
|
+
end
|
61
|
+
end
|
62
|
+
[id, value]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module InstanceMethods
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'xeroizer/application_http_proxy'
|
2
|
+
require 'xeroizer/report/base'
|
3
|
+
|
4
|
+
module Xeroizer
|
5
|
+
module Report
|
6
|
+
class Factory
|
7
|
+
|
8
|
+
include ApplicationHttpProxy
|
9
|
+
|
10
|
+
attr_reader :application
|
11
|
+
attr_reader :report_type
|
12
|
+
|
13
|
+
public
|
14
|
+
|
15
|
+
def initialize(application, report_type)
|
16
|
+
@application = application
|
17
|
+
@report_type = report_type
|
18
|
+
end
|
19
|
+
|
20
|
+
# Retreive a report with the `options` as a hash containing
|
21
|
+
# valid query-string parameters to pass to the API.
|
22
|
+
def get(options = {})
|
23
|
+
response_xml = options[:cache_file] ? File.read(options[:cache_file]) : http_get(options)
|
24
|
+
Response.parse(response_xml, options) do | response, elements |
|
25
|
+
parse_reports(response, elements)
|
26
|
+
end.first # there is is only one
|
27
|
+
end
|
28
|
+
|
29
|
+
def api_controller_name
|
30
|
+
"Report/#{report_type}"
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def parse_reports(response, elements)
|
36
|
+
elements.each do | element |
|
37
|
+
response.response_items << Base.build_from_node(element, self)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'xeroizer/report/row/xml_helper'
|
2
|
+
|
3
|
+
module Xeroizer
|
4
|
+
module Report
|
5
|
+
class Row
|
6
|
+
|
7
|
+
include RowXmlHelper
|
8
|
+
|
9
|
+
attr_reader :report
|
10
|
+
|
11
|
+
attr_accessor :type
|
12
|
+
attr_accessor :title
|
13
|
+
attr_accessor :rows
|
14
|
+
attr_accessor :cells
|
15
|
+
|
16
|
+
attr_accessor :parent
|
17
|
+
|
18
|
+
attr_accessor :header
|
19
|
+
|
20
|
+
public
|
21
|
+
|
22
|
+
def initialize(report)
|
23
|
+
@rows = []
|
24
|
+
@cells = []
|
25
|
+
@report = report
|
26
|
+
end
|
27
|
+
|
28
|
+
def header?; @type == 'Header'; end
|
29
|
+
def summary?; @type == 'SummaryRow'; end
|
30
|
+
def section?; @type == 'Section'; end
|
31
|
+
def row?; @type == 'Row'; end
|
32
|
+
|
33
|
+
def child?
|
34
|
+
!parent.nil?
|
35
|
+
end
|
36
|
+
|
37
|
+
def parent?
|
38
|
+
rows.size > 0
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|