xeroizer 2.18.1 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
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