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 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