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 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.3
1
+ 0.2.0
data/lib/xeroizer.rb CHANGED
@@ -47,6 +47,8 @@ require 'xeroizer/models/tax_rate'
47
47
  require 'xeroizer/models/tracking_category'
48
48
  require 'xeroizer/models/tracking_category_child'
49
49
 
50
+ require 'xeroizer/report/factory'
51
+
50
52
  require 'xeroizer/response'
51
53
 
52
54
  require 'xeroizer/generic_application'
@@ -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
- # Parse the response retreived during any request.
96
- def parse_response(raw_response, request = {}, options = {})
97
- @response = Xeroizer::Response.new
98
- @response.response_xml = raw_response
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
- @response.response_items << model_class.build_from_node(element, self)
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,7 @@
1
+ module Xeroizer
2
+ module Report
3
+ class HeaderRow < Row
4
+
5
+ end
6
+ end
7
+ 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