xeroizer 2.18.1 → 3.0.1

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.
Files changed (133) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +199 -223
  3. data/lib/xeroizer/connection.rb +49 -0
  4. data/lib/xeroizer/exceptions.rb +2 -0
  5. data/lib/xeroizer/generic_application.rb +12 -5
  6. data/lib/xeroizer/http.rb +5 -78
  7. data/lib/xeroizer/http_response.rb +157 -0
  8. data/lib/xeroizer/models/bank_account.rb +1 -0
  9. data/lib/xeroizer/models/bank_transaction.rb +1 -0
  10. data/lib/xeroizer/models/batch_payment.rb +27 -0
  11. data/lib/xeroizer/models/branding_theme.rb +49 -9
  12. data/lib/xeroizer/models/contact.rb +10 -4
  13. data/lib/xeroizer/models/contact_group.rb +45 -0
  14. data/lib/xeroizer/models/credit_note.rb +23 -22
  15. data/lib/xeroizer/models/from_bank_account.rb +1 -0
  16. data/lib/xeroizer/models/history_record.rb +72 -0
  17. data/lib/xeroizer/models/invoice.rb +14 -3
  18. data/lib/xeroizer/models/line_item.rb +17 -5
  19. data/lib/xeroizer/models/manual_journal.rb +2 -1
  20. data/lib/xeroizer/models/option.rb +1 -1
  21. data/lib/xeroizer/models/organisation.rb +2 -0
  22. data/lib/xeroizer/models/payment_service.rb +22 -0
  23. data/lib/xeroizer/models/payroll/address.rb +53 -0
  24. data/lib/xeroizer/models/payroll/bank_account.rb +18 -6
  25. data/lib/xeroizer/models/payroll/benefit_line.rb +26 -0
  26. data/lib/xeroizer/models/payroll/benefit_type.rb +45 -0
  27. data/lib/xeroizer/models/payroll/deduction_line.rb +32 -0
  28. data/lib/xeroizer/models/payroll/deduction_type.rb +49 -0
  29. data/lib/xeroizer/models/payroll/earnings_line.rb +39 -0
  30. data/lib/xeroizer/models/payroll/earnings_type.rb +53 -0
  31. data/lib/xeroizer/models/payroll/employee.rb +30 -8
  32. data/lib/xeroizer/models/payroll/leave_application.rb +27 -0
  33. data/lib/xeroizer/models/payroll/leave_line.rb +30 -0
  34. data/lib/xeroizer/models/payroll/leave_period.rb +15 -0
  35. data/lib/xeroizer/models/payroll/pay_items.rb +22 -0
  36. data/lib/xeroizer/models/payroll/pay_run.rb +33 -0
  37. data/lib/xeroizer/models/payroll/pay_schedule.rb +40 -0
  38. data/lib/xeroizer/models/payroll/pay_template.rb +24 -0
  39. data/lib/xeroizer/models/payroll/payment_method.rb +24 -0
  40. data/lib/xeroizer/models/payroll/paystub.rb +44 -0
  41. data/lib/xeroizer/models/payroll/reimbursement_line.rb +21 -0
  42. data/lib/xeroizer/models/payroll/reimbursement_type.rb +22 -0
  43. data/lib/xeroizer/models/payroll/salary_and_wage.rb +29 -0
  44. data/lib/xeroizer/models/payroll/super_line.rb +40 -0
  45. data/lib/xeroizer/models/payroll/tax_declaration.rb +50 -0
  46. data/lib/xeroizer/models/payroll/time_off_line.rb +20 -0
  47. data/lib/xeroizer/models/payroll/time_off_type.rb +32 -0
  48. data/lib/xeroizer/models/payroll/work_location.rb +25 -0
  49. data/lib/xeroizer/models/prepayment.rb +1 -0
  50. data/lib/xeroizer/models/purchase_order.rb +6 -6
  51. data/lib/xeroizer/models/quote.rb +76 -0
  52. data/lib/xeroizer/models/schedule.rb +1 -0
  53. data/lib/xeroizer/models/tax_component.rb +1 -0
  54. data/lib/xeroizer/models/to_bank_account.rb +1 -0
  55. data/lib/xeroizer/oauth.rb +12 -1
  56. data/lib/xeroizer/oauth2.rb +82 -0
  57. data/lib/xeroizer/oauth2_application.rb +49 -0
  58. data/lib/xeroizer/payroll_application.rb +8 -3
  59. data/lib/xeroizer/record/base.rb +1 -1
  60. data/lib/xeroizer/record/base_model.rb +1 -1
  61. data/lib/xeroizer/record/base_model_http_proxy.rb +4 -0
  62. data/lib/xeroizer/record/model_definition_helper.rb +1 -1
  63. data/lib/xeroizer/record/payroll_base.rb +4 -0
  64. data/lib/xeroizer/record/record_association_helper.rb +4 -4
  65. data/lib/xeroizer/record/validators/associated_validator.rb +1 -0
  66. data/lib/xeroizer/record/xml_helper.rb +18 -18
  67. data/lib/xeroizer/report/aged_receivables_by_contact.rb +1 -1
  68. data/lib/xeroizer/report/cell_xml_helper.rb +13 -13
  69. data/lib/xeroizer/response.rb +22 -17
  70. data/lib/xeroizer/version.rb +1 -1
  71. data/lib/xeroizer.rb +33 -4
  72. data/test/acceptance/about_creating_bank_transactions_test.rb +80 -82
  73. data/test/acceptance/about_creating_prepayment_test.rb +25 -30
  74. data/test/acceptance/about_fetching_bank_transactions_test.rb +12 -12
  75. data/test/acceptance/about_online_invoice_test.rb +6 -10
  76. data/test/acceptance/acceptance_test.rb +28 -26
  77. data/test/acceptance/bank_transfer_test.rb +12 -17
  78. data/test/acceptance/bulk_operations_test.rb +18 -16
  79. data/test/acceptance/connections_test.rb +11 -0
  80. data/test/stub_responses/bad_request.json +6 -0
  81. data/test/stub_responses/connections.json +16 -0
  82. data/test/stub_responses/expired_oauth2_token.json +6 -0
  83. data/test/stub_responses/generic_response_error.json +6 -0
  84. data/test/stub_responses/invalid_oauth2_request_token.json +6 -0
  85. data/test/stub_responses/invalid_tenant_header.json +6 -0
  86. data/test/stub_responses/object_not_found.json +6 -0
  87. data/test/stub_responses/organisations.xml +10 -0
  88. data/test/stub_responses/payment_service.xml +15 -0
  89. data/test/test_helper.rb +16 -11
  90. data/test/unit/generic_application_test.rb +21 -10
  91. data/test/unit/http_test.rb +284 -10
  92. data/test/unit/models/address_test.rb +2 -2
  93. data/test/unit/models/bank_transaction_model_parsing_test.rb +2 -2
  94. data/test/unit/models/bank_transaction_test.rb +1 -1
  95. data/test/unit/models/bank_transaction_validation_test.rb +1 -1
  96. data/test/unit/models/contact_test.rb +2 -2
  97. data/test/unit/models/credit_note_test.rb +8 -8
  98. data/test/unit/models/employee_test.rb +4 -4
  99. data/test/unit/models/invoice_test.rb +12 -12
  100. data/test/unit/models/journal_line_test.rb +6 -6
  101. data/test/unit/models/journal_test.rb +4 -4
  102. data/test/unit/models/line_item_sum_test.rb +1 -1
  103. data/test/unit/models/line_item_test.rb +26 -28
  104. data/test/unit/models/manual_journal_test.rb +3 -3
  105. data/test/unit/models/organisation_test.rb +16 -2
  106. data/test/unit/models/payment_service_test.rb +29 -0
  107. data/test/unit/models/phone_test.rb +7 -7
  108. data/test/unit/models/prepayment_test.rb +4 -4
  109. data/test/unit/models/repeating_invoice_test.rb +3 -3
  110. data/test/unit/models/tax_rate_test.rb +2 -2
  111. data/test/unit/oauth2_test.rb +171 -0
  112. data/test/unit/oauth_config_test.rb +1 -1
  113. data/test/unit/record/base_model_test.rb +13 -13
  114. data/test/unit/record/base_test.rb +15 -4
  115. data/test/unit/record/block_validator_test.rb +1 -1
  116. data/test/unit/record/connection_test.rb +60 -0
  117. data/test/unit/record/model_definition_test.rb +36 -36
  118. data/test/unit/record/parse_params_test.rb +2 -2
  119. data/test/unit/record/parse_where_hash_test.rb +13 -13
  120. data/test/unit/record/record_association_test.rb +14 -14
  121. data/test/unit/record/validators_test.rb +43 -43
  122. data/test/unit/record_definition_test.rb +7 -7
  123. data/test/unit/report_definition_test.rb +7 -7
  124. data/test/unit/report_test.rb +20 -20
  125. data/test/unit_test_helper.rb +16 -0
  126. metadata +100 -25
  127. data/lib/xeroizer/models/payroll/home_address.rb +0 -24
  128. data/lib/xeroizer/partner_application.rb +0 -51
  129. data/lib/xeroizer/private_application.rb +0 -25
  130. data/lib/xeroizer/public_application.rb +0 -21
  131. data/test/unit/http_tsl_12_upgrade_test.rb +0 -31
  132. data/test/unit/oauth_test.rb +0 -118
  133. data/test/unit/private_application_test.rb +0 -20
@@ -0,0 +1,76 @@
1
+ require "xeroizer/models/attachment"
2
+
3
+ module Xeroizer
4
+ module Record
5
+
6
+ class QuoteModel < BaseModel
7
+ set_permissions :read, :write, :update
8
+
9
+ include AttachmentModel::Extensions
10
+
11
+ public
12
+
13
+ # Retrieve the PDF version of the quote matching the `id`.
14
+ # @param [String] id quote's ID.
15
+ # @param [String] filename optional filename to store the PDF in instead of returning the data.
16
+ def pdf(id, filename = nil)
17
+ pdf_data = @application.http_get(@application.client, "#{url}/#{CGI.escape(id)}", :response => :pdf)
18
+ if filename
19
+ File.open(filename, "wb") { | fp | fp.write pdf_data }
20
+ nil
21
+ else
22
+ pdf_data
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ class Quote < Base
29
+
30
+ QUOTE_STATUS = {
31
+ 'DRAFT' => 'A draft quote (default)',
32
+ 'DELETED' => 'A deleted quote',
33
+ 'SENT' => 'A quote that is marked as sent',
34
+ 'DECLINED' => 'A quote that was declined by the customer',
35
+ 'ACCEPTED' => 'A quote that was accepted by the customer',
36
+ 'INVOICED' => 'A quote that has been invoiced'
37
+ } unless defined?(QUOTE_STATUS)
38
+ QUOTE_STATUSES = QUOTE_STATUS.keys.sort
39
+
40
+ include Attachment::Extensions
41
+
42
+ set_primary_key :quote_id
43
+ set_possible_primary_keys :quote_id, :quote_number
44
+ list_contains_summary_only false
45
+
46
+ guid :quote_id
47
+ string :quote_number
48
+ string :reference
49
+ guid :branding_theme_id
50
+ date :date
51
+ date :expiry_date
52
+ string :status
53
+ string :line_amount_types
54
+ decimal :sub_total
55
+ decimal :total_tax
56
+ decimal :total
57
+ decimal :total_discount
58
+ datetime_utc :updated_date_utc, :api_name => 'UpdatedDateUTC'
59
+ string :currency_code
60
+ decimal :currency_rate
61
+ string :title
62
+ string :summary
63
+ string :terms
64
+ boolean :has_attachments
65
+
66
+ belongs_to :contact
67
+ has_many :line_items
68
+
69
+ # Retrieve the PDF version of this quote.
70
+ # @param [String] filename optional filename to store the PDF in instead of returning the data.
71
+ def pdf(filename = nil)
72
+ parent.pdf(id, filename)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -10,6 +10,7 @@ module Xeroizer
10
10
  UNIT = {
11
11
  'WEEKLY' => 'Weekly',
12
12
  'MONTHLY' => 'Monthly',
13
+ 'YEARLY' => 'Yearly',
13
14
  } unless defined?(UNIT)
14
15
 
15
16
  PAYMENT_TERM = {
@@ -8,6 +8,7 @@ module Xeroizer
8
8
  string :name
9
9
  decimal :rate
10
10
  boolean :is_compound
11
+ boolean :is_non_recoverable
11
12
  end
12
13
  end
13
14
  end
@@ -7,6 +7,7 @@ module Xeroizer
7
7
  class ToBankAccount < Base
8
8
  guid :account_id
9
9
  string :code
10
+ string :name
10
11
  end
11
12
  end
12
13
  end
@@ -26,12 +26,23 @@ module Xeroizer
26
26
  class OAuthError < XeroizerError; end
27
27
  class TokenExpired < OAuthError; end
28
28
  class TokenInvalid < OAuthError; end
29
- class RateLimitExceeded < OAuthError; end
30
29
  class ConsumerKeyUnknown < OAuthError; end
31
30
  class NonceUsed < OAuthError; end
32
31
  class OrganisationOffline < OAuthError; end
32
+ class Forbidden < OAuthError; end
33
33
  class UnknownError < OAuthError; end
34
34
 
35
+ class RateLimitExceeded < OAuthError
36
+ def initialize(description, retry_after: nil, daily_limit_remaining: nil)
37
+ super(description)
38
+
39
+ @retry_after = retry_after
40
+ @daily_limit_remaining = daily_limit_remaining
41
+ end
42
+
43
+ attr_reader :retry_after, :daily_limit_remaining
44
+ end
45
+
35
46
  unless defined? XERO_CONSUMER_OPTIONS
36
47
  XERO_CONSUMER_OPTIONS = {
37
48
  :site => "https://api.xero.com",
@@ -0,0 +1,82 @@
1
+ module Xeroizer
2
+ class OAuth2
3
+
4
+ attr_reader :client, :access_token
5
+
6
+ attr_accessor :tenant_id
7
+
8
+ def initialize(client_key, client_secret, options = {})
9
+ @client = ::OAuth2::Client.new(client_key, client_secret, options)
10
+ end
11
+
12
+ def authorize_url(options)
13
+ @client.auth_code.authorize_url(options)
14
+ end
15
+
16
+ def authorize_from_access(access_token, options = {})
17
+ @access_token = ::OAuth2::AccessToken.new(client, access_token, options)
18
+ end
19
+
20
+ def authorize_from_code(code, options = {})
21
+ @access_token = @client.auth_code.get_token(code, options)
22
+ end
23
+
24
+ def authorize_from_client_credentials(params = {}, options = {})
25
+ @access_token = @client.client_credentials.get_token(params, options)
26
+ end
27
+
28
+ def renew_access_token
29
+ @access_token = @access_token.refresh!
30
+ end
31
+
32
+ def get(path, headers = {})
33
+ wrap_response(access_token.get(path, headers: wrap_headers(headers)))
34
+ end
35
+
36
+ def post(path, body = "", headers = {})
37
+ wrap_response(access_token.post(path, {body: body, headers: wrap_headers(headers)}))
38
+ end
39
+
40
+ def put(path, body = "", headers = {})
41
+ wrap_response(access_token.put(path, body: body, headers: wrap_headers(headers)))
42
+ end
43
+
44
+ def delete(path, headers = {})
45
+ wrap_response(access_token.delete(path, headers: wrap_headers(headers)))
46
+ end
47
+
48
+ private
49
+
50
+ def wrap_headers(headers)
51
+ if tenant_id
52
+ headers.merge("Xero-tenant-id" => tenant_id)
53
+ else
54
+ headers
55
+ end
56
+ end
57
+
58
+ def wrap_response(response)
59
+ Response.new(response)
60
+ end
61
+
62
+ class Response
63
+ attr_reader :response
64
+
65
+ def initialize(response)
66
+ @response = response
67
+ end
68
+
69
+ def code
70
+ response.status
71
+ end
72
+
73
+ def success?
74
+ (200..299).to_a.include?(code)
75
+ end
76
+
77
+ def plain_body
78
+ response.body
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,49 @@
1
+ module Xeroizer
2
+ class OAuth2Application < GenericApplication
3
+
4
+ extend Forwardable
5
+ def_delegators :client,
6
+ :authorize_from_access,
7
+ :authorize_from_client_credentials,
8
+ :authorize_from_code,
9
+ :authorize_url,
10
+ :renew_access_token,
11
+ :tenant_id,
12
+ :tenant_id=
13
+
14
+ public
15
+
16
+ # OAuth2 applications allow for connecting to Xero over OAuth2, as opposed to the
17
+ # Partner and Private applications which talk over OAuth1.
18
+ #
19
+ # @param [String] client_id client id/token from application developer (found at http://api.xero.com for your application).
20
+ # @param [String] client_secret client secret from application developer (found at http://api.xero.com for your application).
21
+ # @param [Hash] options other options to pass to the GenericApplication constructor
22
+ # @return [OAuth2Application] instance of OAuth2Application
23
+ def initialize(client_id, client_secret, options = {})
24
+ default_options = {
25
+ :xero_url => 'https://api.xero.com/api.xro/2.0',
26
+ :site => 'https://api.xero.com',
27
+ :authorize_url => 'https://login.xero.com/identity/connect/authorize',
28
+ :token_url => 'https://identity.xero.com/connect/token',
29
+ :tenets_url => 'https://api.xero.com/connections',
30
+ :raise_errors => false
31
+ }
32
+ options = default_options.merge(options)
33
+ client = OAuth2.new(client_id, client_secret, options)
34
+ super(client, options)
35
+
36
+ if options[:access_token]
37
+ authorize_from_access(options[:access_token], options)
38
+ end
39
+
40
+ if options[:tenant_id]
41
+ client.tenant_id = options[:tenant_id]
42
+ end
43
+ end
44
+
45
+ def current_connections
46
+ Connection.current_connections(client)
47
+ end
48
+ end
49
+ end
@@ -1,6 +1,6 @@
1
1
  module Xeroizer
2
2
  class PayrollApplication
3
-
3
+
4
4
  attr_reader :application
5
5
 
6
6
  # Factory for new Payroll BaseModel instances with the class name `record_type`.
@@ -15,11 +15,16 @@ module Xeroizer
15
15
  instance_variable_set(var_name, Xeroizer::Record::Payroll.const_get("#{record_type}Model".to_sym).new(self.application, record_type.to_s))
16
16
  end
17
17
  instance_variable_get(var_name)
18
- end
18
+ end
19
19
  end
20
20
 
21
21
  record :Employee
22
-
22
+ record :PayRun
23
+ record :Paystub
24
+ record :PayItems
25
+ record :PaySchedule
26
+ record :LeaveApplication
27
+
23
28
  def initialize(application)
24
29
  @application = application
25
30
  end
@@ -30,7 +30,7 @@ module Xeroizer
30
30
  def build(attributes, parent)
31
31
  record = new(parent)
32
32
  attributes.each do | key, value |
33
- attr = record.respond_to?("#{key}=") ? key : record.class.fields[key][:internal_name]
33
+ attr = record.respond_to?("#{key}=") || record.class.fields[key].nil? ? key : record.class.fields[key][:internal_name]
34
34
  record.send("#{attr}=", value)
35
35
  end
36
36
  record
@@ -116,7 +116,7 @@ module Xeroizer
116
116
  build(attributes).tap { |resource| resource.save }
117
117
  end
118
118
 
119
- # Retreive full record list for this model.
119
+ # Retrieve full record list for this model.
120
120
  def all(options = {})
121
121
  raise MethodNotAllowed.new(self, :all) unless self.class.permissions[:read]
122
122
  response_xml = http_get(parse_params(options))
@@ -33,6 +33,9 @@ module Xeroizer
33
33
  end
34
34
  end
35
35
  params[:offset] = options[:offset] if options[:offset]
36
+ params[:Status] = options[:status] if options[:status]
37
+ params[:DateFrom] = options[:date_from] if options[:date_from]
38
+ params[:DateTo] = options[:date_to] if options[:date_to]
36
39
  params[:page] = options[:page] if options[:page]
37
40
  params
38
41
  end
@@ -117,6 +120,7 @@ module Xeroizer
117
120
  when :datetime_utc then [field[:api_name], expression, "DateTime.Parse(\"#{value.utc.strftime("%Y-%m-%dT%H:%M:%S")}\")"]
118
121
  when :belongs_to then
119
122
  when :has_many then
123
+ when :has_one then
120
124
  end
121
125
  end
122
126
 
@@ -68,7 +68,7 @@ module Xeroizer
68
68
  :type => field_type
69
69
  })
70
70
  define_method internal_field_name do
71
- @attributes[field_name] || value_if_nil
71
+ @attributes[field_name].nil? ? value_if_nil : @attributes[field_name]
72
72
  end
73
73
 
74
74
  unless options[:skip_writer]
@@ -12,6 +12,10 @@ module Xeroizer
12
12
  def self.has_many(field_name, options = {})
13
13
  super(field_name, {:base_module => Xeroizer::Record::Payroll}.merge(options))
14
14
  end
15
+
16
+ def self.has_one(field_name, options = {})
17
+ super(field_name, {:base_module => Xeroizer::Record::Payroll}.merge(options))
18
+ end
15
19
 
16
20
  public
17
21
 
@@ -35,7 +35,7 @@ module Xeroizer
35
35
  def has_many(field_name, options = {})
36
36
  internal_field_name = options[:internal_name] || field_name
37
37
  internal_singular_field_name = options[:internal_name_singular] || internal_field_name.to_s.singularize
38
-
38
+
39
39
  define_association_attribute(field_name, internal_field_name, :has_many, options)
40
40
 
41
41
  # Create an #add_record_name method to build the record and add to the attributes.
@@ -80,7 +80,7 @@ module Xeroizer
80
80
  end
81
81
 
82
82
  end
83
-
83
+
84
84
  def define_association_attribute(field_name, internal_field_name, association_type, options)
85
85
  define_simple_attribute(field_name, association_type, options.merge!(:skip_writer => true), ((association_type == :has_many) ? [] : nil))
86
86
 
@@ -96,8 +96,8 @@ module Xeroizer
96
96
  when :has_many
97
97
  self.attributes[field_name] = []
98
98
  self.send("add_#{internal_singular_field_name}".to_sym, value)
99
-
100
- when :belongs_to
99
+
100
+ when :belongs_to
101
101
  self.attributes[field_name] = (options[:base_module] || Xeroizer::Record).const_get(model_name).build(value, new_model_class(model_name))
102
102
 
103
103
  end
@@ -22,6 +22,7 @@ module Xeroizer
22
22
  else
23
23
  record.errors << [attribute, "must have one or more records"]
24
24
  end
25
+
25
26
  end
26
27
  end
27
28
 
@@ -3,14 +3,14 @@ require 'active_support/time'
3
3
  module Xeroizer
4
4
  module Record
5
5
  module XmlHelper
6
-
6
+
7
7
  def self.included(base)
8
8
  base.extend(ClassMethods)
9
9
  base.send :include, InstanceMethods
10
10
  end
11
-
11
+
12
12
  module ClassMethods
13
-
13
+
14
14
  # Build a record instance from the XML node.
15
15
  def build_from_node(node, parent, base_module)
16
16
  record = new(parent)
@@ -22,14 +22,14 @@ module Xeroizer
22
22
  when :string then element.text
23
23
  when :boolean then (element.text == 'true')
24
24
  when :integer then element.text.to_i
25
- when :decimal then BigDecimal.new(element.text)
25
+ when :decimal then BigDecimal(element.text)
26
26
  when :date then Date.parse(element.text)
27
27
  when :datetime then Time.parse(element.text)
28
28
  when :datetime_utc then ActiveSupport::TimeZone['UTC'].parse(element.text).utc
29
- when :belongs_to
29
+ when :belongs_to
30
30
  model_name = field[:model_name] ? field[:model_name].to_sym : element.name.to_sym
31
31
  base_module.const_get(model_name).build_from_node(element, parent, base_module)
32
-
32
+
33
33
  when :has_many
34
34
  if element.element_children.size > 0
35
35
  sub_field_name = field[:model_name] ? field[:model_name].to_sym : element.children.first.name.to_sym
@@ -51,13 +51,13 @@ module Xeroizer
51
51
  parent.mark_clean(record)
52
52
  record
53
53
  end
54
-
54
+
55
55
  end
56
-
56
+
57
57
  module InstanceMethods
58
-
58
+
59
59
  public
60
-
60
+
61
61
  # Turn a record into its XML representation.
62
62
  def to_xml(b = Builder::XmlMarkup.new(:indent => 2))
63
63
  optional_root_tag(parent.class.optional_xml_root_name, b) do |c|
@@ -70,9 +70,9 @@ module Xeroizer
70
70
  }
71
71
  end
72
72
  end
73
-
73
+
74
74
  protected
75
-
75
+
76
76
  # Add top-level root name if required.
77
77
  # E.g. Payments need specifying in the form:
78
78
  # <Payments>
@@ -87,7 +87,7 @@ module Xeroizer
87
87
  yield(b)
88
88
  end
89
89
  end
90
-
90
+
91
91
  # Format an attribute for use in the XML passed to Xero.
92
92
  def xml_value_from_field(b, field, value)
93
93
  case field[:type]
@@ -95,10 +95,10 @@ module Xeroizer
95
95
  when :string then b.tag!(field[:api_name], value)
96
96
  when :boolean then b.tag!(field[:api_name], value ? 'true' : 'false')
97
97
  when :integer then b.tag!(field[:api_name], value.to_i)
98
- when :decimal
98
+ when :decimal
99
99
  real_value = case value
100
100
  when BigDecimal then value.to_s
101
- when String then BigDecimal.new(value).to_s
101
+ when String then BigDecimal(value).to_s
102
102
  else value
103
103
  end
104
104
  b.tag!(field[:api_name], real_value)
@@ -111,13 +111,13 @@ module Xeroizer
111
111
  else raise ArgumentError.new("Expected Date or Time object for the #{field[:api_name]} field")
112
112
  end
113
113
  b.tag!(field[:api_name], real_value)
114
-
114
+
115
115
  when :datetime then b.tag!(field[:api_name], value.utc.strftime("%Y-%m-%dT%H:%M:%S"))
116
- when :belongs_to
116
+ when :belongs_to
117
117
  value.to_xml(b)
118
118
  nil
119
119
 
120
- when :has_many
120
+ when :has_many
121
121
  if value.size > 0
122
122
  sub_parent = value.first.parent
123
123
  b.tag!(sub_parent.class.xml_root_name || sub_parent.model_name.pluralize) {
@@ -31,7 +31,7 @@ module Xeroizer
31
31
  end
32
32
 
33
33
  def sum(column_name, &block)
34
- sections.first.rows.inject(BigDecimal.new('0')) do | sum, row |
34
+ sections.first.rows.inject(BigDecimal('0')) do | sum, row |
35
35
  sum += row.cell(column_name).value if row.class == Xeroizer::Report::Row && (block.nil? || block.call(row))
36
36
  sum
37
37
  end
@@ -1,19 +1,19 @@
1
1
  module Xeroizer
2
2
  module Report
3
3
  module CellXmlHelper
4
-
4
+
5
5
  def self.included(base)
6
6
  base.extend(ClassMethods)
7
7
  base.send :include, InstanceMethods
8
8
  end
9
-
9
+
10
10
  module ClassMethods
11
-
11
+
12
12
  public
13
-
13
+
14
14
  # Create an instance of Cell from the node.
15
15
  #
16
- # Additionally, parse the attributes and return them as a hash to the
16
+ # Additionally, parse the attributes and return them as a hash to the
17
17
  # cell. If a cell's attributes look like:
18
18
  #
19
19
  # <Attributes>
@@ -22,7 +22,7 @@ module Xeroizer
22
22
  # <Id>account</Id>
23
23
  # </Attribute>
24
24
  # </Attributes>
25
- #
25
+ #
26
26
  # Return a hash like:
27
27
  #
28
28
  # {
@@ -42,17 +42,17 @@ module Xeroizer
42
42
  end
43
43
  cell
44
44
  end
45
-
45
+
46
46
  protected
47
47
 
48
48
  def parse_value(value)
49
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
50
+ when /\A[-]?\d+(\.\d+)?\z/ then BigDecimal(value)
51
+ when /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\z/ then Time.xmlschema(value)
52
+ else value
53
53
  end
54
54
  end
55
-
55
+
56
56
  def parse_attribute(attribute_node)
57
57
  id = nil
58
58
  value = nil
@@ -65,10 +65,10 @@ module Xeroizer
65
65
  [id, value]
66
66
  end
67
67
  end
68
-
68
+
69
69
  module InstanceMethods
70
70
  end
71
-
71
+
72
72
  end
73
73
  end
74
74
  end
@@ -1,9 +1,9 @@
1
1
  # Copyright (c) 2008 Tim Connor <tlconnor@gmail.com>
2
- #
2
+ #
3
3
  # Permission to use, copy, modify, and/or distribute this software for any
4
4
  # purpose with or without fee is hereby granted, provided that the above
5
5
  # copyright notice and this permission notice appear in all copies.
6
- #
6
+ #
7
7
  # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8
8
  # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9
9
  # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
@@ -14,23 +14,23 @@
14
14
 
15
15
  module Xeroizer
16
16
  class Response
17
-
17
+
18
18
  attr_accessor :id, :status, :errors, :provider, :date_time, :response_items, :request_params, :request_xml, :response_xml
19
-
19
+
20
20
  class << self
21
-
21
+
22
22
  # Parse the response retreived during any request.
23
23
  def parse(raw_response, request = {}, options = {}, &block)
24
24
  response = Xeroizer::Response.new
25
25
  response.response_xml = raw_response
26
-
26
+
27
27
  doc = Nokogiri::XML(raw_response) { | cfg | cfg.noblanks }
28
-
28
+
29
29
  # check for responses we don't understand
30
30
  raise Xeroizer::UnparseableResponse.new(doc.root.name) unless doc.root.name == 'Response'
31
-
31
+
32
32
  doc.root.elements.each do | element |
33
-
33
+
34
34
  # Text element
35
35
  if element.children && element.children.size == 1 && element.children.first.text?
36
36
  case element.name
@@ -39,31 +39,36 @@ module Xeroizer
39
39
  when 'ProviderName' then response.provider = element.text
40
40
  when 'DateTimeUTC' then response.date_time = Time.parse(element.text)
41
41
  end
42
-
42
+
43
+ # Special case for Paystubs and PayItems because they are not wrapped in plural element or
44
+ # don't have singular children
45
+ elsif element.children && element.children.size > 0 && (element.name == 'Paystub' || element.name == 'PayItems')
46
+ yield(response, [element], element.name)
47
+
43
48
  # Records in response
44
49
  elsif element.children && element.children.size > 0
45
50
  yield(response, element.children, element.children.first.name)
46
51
  end
47
52
  end
48
-
53
+
49
54
  response
50
55
  end
51
-
56
+
52
57
  end
53
-
58
+
54
59
  public
55
-
60
+
56
61
  def initialize
57
62
  @response_items = []
58
63
  end
59
-
64
+
60
65
  def success?
61
66
  status == 'OK'
62
67
  end
63
-
68
+
64
69
  def error
65
70
  errors.blank? ? nil : errors[0]
66
71
  end
67
-
72
+
68
73
  end
69
74
  end
@@ -1,3 +1,3 @@
1
1
  module Xeroizer
2
- VERSION = "2.18.1".freeze
2
+ VERSION = "3.0.1".freeze
3
3
  end