xeroizer 0.1.3 → 0.2.0
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.
- 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 
|
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
|