xeroizer 2.17.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (136) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +246 -213
  3. data/lib/xeroizer/connection.rb +49 -0
  4. data/lib/xeroizer/exceptions.rb +4 -0
  5. data/lib/xeroizer/generic_application.rb +13 -5
  6. data/lib/xeroizer/http.rb +7 -80
  7. data/lib/xeroizer/http_response.rb +154 -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 +12 -6
  13. data/lib/xeroizer/models/contact_group.rb +45 -0
  14. data/lib/xeroizer/models/credit_note.rb +24 -22
  15. data/lib/xeroizer/models/currency.rb +14 -2
  16. data/lib/xeroizer/models/from_bank_account.rb +1 -0
  17. data/lib/xeroizer/models/history_record.rb +72 -0
  18. data/lib/xeroizer/models/invoice.rb +17 -3
  19. data/lib/xeroizer/models/item.rb +2 -1
  20. data/lib/xeroizer/models/item_purchase_details.rb +1 -1
  21. data/lib/xeroizer/models/line_item.rb +17 -5
  22. data/lib/xeroizer/models/manual_journal.rb +2 -1
  23. data/lib/xeroizer/models/online_invoice.rb +37 -0
  24. data/lib/xeroizer/models/option.rb +1 -1
  25. data/lib/xeroizer/models/organisation.rb +2 -0
  26. data/lib/xeroizer/models/payment_service.rb +22 -0
  27. data/lib/xeroizer/models/payroll/address.rb +53 -0
  28. data/lib/xeroizer/models/payroll/bank_account.rb +18 -6
  29. data/lib/xeroizer/models/payroll/benefit_line.rb +26 -0
  30. data/lib/xeroizer/models/payroll/benefit_type.rb +45 -0
  31. data/lib/xeroizer/models/payroll/deduction_line.rb +32 -0
  32. data/lib/xeroizer/models/payroll/deduction_type.rb +49 -0
  33. data/lib/xeroizer/models/payroll/earnings_line.rb +39 -0
  34. data/lib/xeroizer/models/payroll/earnings_type.rb +53 -0
  35. data/lib/xeroizer/models/payroll/employee.rb +30 -8
  36. data/lib/xeroizer/models/payroll/leave_application.rb +27 -0
  37. data/lib/xeroizer/models/payroll/leave_line.rb +30 -0
  38. data/lib/xeroizer/models/payroll/leave_period.rb +15 -0
  39. data/lib/xeroizer/models/payroll/pay_items.rb +22 -0
  40. data/lib/xeroizer/models/payroll/pay_run.rb +33 -0
  41. data/lib/xeroizer/models/payroll/pay_schedule.rb +40 -0
  42. data/lib/xeroizer/models/payroll/pay_template.rb +24 -0
  43. data/lib/xeroizer/models/payroll/payment_method.rb +24 -0
  44. data/lib/xeroizer/models/payroll/paystub.rb +44 -0
  45. data/lib/xeroizer/models/payroll/reimbursement_line.rb +21 -0
  46. data/lib/xeroizer/models/payroll/reimbursement_type.rb +22 -0
  47. data/lib/xeroizer/models/payroll/salary_and_wage.rb +29 -0
  48. data/lib/xeroizer/models/payroll/super_line.rb +40 -0
  49. data/lib/xeroizer/models/payroll/tax_declaration.rb +50 -0
  50. data/lib/xeroizer/models/payroll/time_off_line.rb +20 -0
  51. data/lib/xeroizer/models/payroll/time_off_type.rb +32 -0
  52. data/lib/xeroizer/models/payroll/work_location.rb +25 -0
  53. data/lib/xeroizer/models/prepayment.rb +1 -0
  54. data/lib/xeroizer/models/purchase_order.rb +6 -6
  55. data/lib/xeroizer/models/quote.rb +76 -0
  56. data/lib/xeroizer/models/schedule.rb +1 -0
  57. data/lib/xeroizer/models/tax_component.rb +1 -0
  58. data/lib/xeroizer/models/to_bank_account.rb +1 -0
  59. data/lib/xeroizer/oauth.rb +12 -1
  60. data/lib/xeroizer/oauth2.rb +82 -0
  61. data/lib/xeroizer/oauth2_application.rb +49 -0
  62. data/lib/xeroizer/payroll_application.rb +8 -3
  63. data/lib/xeroizer/record/base.rb +11 -2
  64. data/lib/xeroizer/record/base_model.rb +1 -1
  65. data/lib/xeroizer/record/base_model_http_proxy.rb +37 -17
  66. data/lib/xeroizer/record/model_definition_helper.rb +1 -1
  67. data/lib/xeroizer/record/payroll_base.rb +4 -0
  68. data/lib/xeroizer/record/record_association_helper.rb +4 -4
  69. data/lib/xeroizer/record/validators/associated_validator.rb +1 -0
  70. data/lib/xeroizer/record/xml_helper.rb +18 -18
  71. data/lib/xeroizer/report/aged_receivables_by_contact.rb +1 -1
  72. data/lib/xeroizer/report/cell_xml_helper.rb +13 -13
  73. data/lib/xeroizer/response.rb +22 -17
  74. data/lib/xeroizer/version.rb +1 -1
  75. data/lib/xeroizer.rb +34 -4
  76. data/test/acceptance/about_creating_bank_transactions_test.rb +89 -81
  77. data/test/acceptance/about_creating_prepayment_test.rb +25 -30
  78. data/test/acceptance/about_fetching_bank_transactions_test.rb +12 -12
  79. data/test/acceptance/about_online_invoice_test.rb +25 -0
  80. data/test/acceptance/acceptance_test.rb +28 -26
  81. data/test/acceptance/bank_transfer_test.rb +12 -17
  82. data/test/acceptance/bulk_operations_test.rb +18 -16
  83. data/test/acceptance/connections_test.rb +11 -0
  84. data/test/stub_responses/bad_request.json +6 -0
  85. data/test/stub_responses/connections.json +16 -0
  86. data/test/stub_responses/expired_oauth2_token.json +6 -0
  87. data/test/stub_responses/generic_response_error.json +6 -0
  88. data/test/stub_responses/invalid_oauth2_request_token.json +6 -0
  89. data/test/stub_responses/invalid_tenant_header.json +6 -0
  90. data/test/stub_responses/object_not_found.json +6 -0
  91. data/test/stub_responses/organisations.xml +10 -0
  92. data/test/stub_responses/payment_service.xml +15 -0
  93. data/test/test_helper.rb +17 -12
  94. data/test/unit/generic_application_test.rb +21 -10
  95. data/test/unit/http_test.rb +282 -10
  96. data/test/unit/models/address_test.rb +2 -2
  97. data/test/unit/models/bank_transaction_model_parsing_test.rb +2 -2
  98. data/test/unit/models/bank_transaction_test.rb +1 -1
  99. data/test/unit/models/bank_transaction_validation_test.rb +1 -1
  100. data/test/unit/models/contact_test.rb +20 -11
  101. data/test/unit/models/credit_note_test.rb +8 -8
  102. data/test/unit/models/employee_test.rb +4 -4
  103. data/test/unit/models/invoice_test.rb +12 -12
  104. data/test/unit/models/journal_line_test.rb +6 -6
  105. data/test/unit/models/journal_test.rb +4 -4
  106. data/test/unit/models/line_item_sum_test.rb +1 -1
  107. data/test/unit/models/line_item_test.rb +29 -37
  108. data/test/unit/models/manual_journal_test.rb +3 -3
  109. data/test/unit/models/organisation_test.rb +16 -2
  110. data/test/unit/models/payment_service_test.rb +29 -0
  111. data/test/unit/models/phone_test.rb +7 -7
  112. data/test/unit/models/prepayment_test.rb +4 -4
  113. data/test/unit/models/repeating_invoice_test.rb +3 -3
  114. data/test/unit/models/tax_rate_test.rb +2 -2
  115. data/test/unit/oauth2_test.rb +171 -0
  116. data/test/unit/oauth_config_test.rb +1 -1
  117. data/test/unit/record/base_model_test.rb +13 -13
  118. data/test/unit/record/base_test.rb +73 -4
  119. data/test/unit/record/block_validator_test.rb +1 -1
  120. data/test/unit/record/connection_test.rb +60 -0
  121. data/test/unit/record/model_definition_test.rb +36 -36
  122. data/test/unit/record/parse_params_test.rb +59 -0
  123. data/test/unit/record/parse_where_hash_test.rb +13 -13
  124. data/test/unit/record/record_association_test.rb +14 -14
  125. data/test/unit/record/validators_test.rb +43 -43
  126. data/test/unit/record_definition_test.rb +7 -7
  127. data/test/unit/report_definition_test.rb +7 -7
  128. data/test/unit/report_test.rb +20 -20
  129. data/test/unit_test_helper.rb +16 -0
  130. metadata +117 -27
  131. data/lib/xeroizer/models/payroll/home_address.rb +0 -24
  132. data/lib/xeroizer/partner_application.rb +0 -51
  133. data/lib/xeroizer/private_application.rb +0 -25
  134. data/lib/xeroizer/public_application.rb +0 -21
  135. data/test/unit/oauth_test.rb +0 -118
  136. data/test/unit/private_application_test.rb +0 -20
@@ -0,0 +1,32 @@
1
+ module Xeroizer
2
+ module Record
3
+ module Payroll
4
+
5
+ class TimeOffTypeModel < PayrollBaseModel
6
+
7
+ set_permissions :read
8
+
9
+ end
10
+
11
+ class TimeOffType < PayrollBase
12
+
13
+ TIME_OFF_CATEGORIES = {
14
+ 'PAID' => '',
15
+ 'UNPAID' => ''
16
+ } unless defined?(TIME_OFF_CATEGORIES)
17
+
18
+ set_primary_key :time_off_type_id
19
+
20
+ guid :time_off_type_id
21
+ string :time_off_type
22
+ string :time_off_category
23
+ string :expense_account_code
24
+ string :liability_account_code
25
+ boolean :show_balance_to_employee
26
+
27
+ validates_inclusion_of :time_off_category, :in => TIME_OFF_CATEGORIES
28
+
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ module Xeroizer
2
+ module Record
3
+ module Payroll
4
+
5
+ class WorkLocationModel < PayrollBaseModel
6
+
7
+ end
8
+
9
+ class WorkLocation < PayrollBase
10
+
11
+ guid :work_location_id
12
+ boolean :is_primary
13
+ string :street_address
14
+ string :suite_or_apt_or_unit
15
+ string :city
16
+ string :state
17
+ string :zip
18
+ decimal :latitude
19
+ decimal :longitude
20
+
21
+ validates_presence_of :work_location_id, :unless => :new_record?
22
+ end
23
+ end
24
+ end
25
+ end
@@ -25,6 +25,7 @@ module Xeroizer
25
25
  string :reference
26
26
  decimal :currency_rate
27
27
  decimal :remaining_credit
28
+ decimal :applied_amount
28
29
  boolean :has_attachments
29
30
 
30
31
  belongs_to :contact
@@ -30,15 +30,15 @@ module Xeroizer
30
30
  boolean :is_discounted
31
31
  string :reference
32
32
  string :type
33
- string :currency_rate
33
+ decimal :currency_rate
34
34
  string :currency_code
35
- guid :branding_theme_id
35
+ guid :branding_theme_id
36
36
  string :status
37
37
  string :line_amount_types
38
- string :sub_total
39
- string :total_tax
40
- string :total
41
- date :updated_date_UTC
38
+ decimal :sub_total
39
+ decimal :total_tax
40
+ decimal :total
41
+ datetime_utc :updated_date_utc, :api_name => 'UpdatedDateUTC'
42
42
  boolean :has_attachments
43
43
 
44
44
  has_many :line_items
@@ -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
@@ -105,12 +105,21 @@ module Xeroizer
105
105
  end
106
106
 
107
107
  def save
108
- return false unless valid?
108
+ save!
109
+ true
110
+ rescue XeroizerError => e
111
+ log "[ERROR SAVING] (#{__FILE__}:#{__LINE__}) - #{e.message}"
112
+ false
113
+ end
114
+
115
+ def save!
116
+ raise RecordInvalid unless valid?
109
117
  if new_record?
110
118
  create
111
119
  else
112
120
  update
113
121
  end
122
+
114
123
  saved!
115
124
  end
116
125
 
@@ -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))
@@ -1,36 +1,45 @@
1
- require 'xeroizer/application_http_proxy'
1
+ require 'xeroizer/application_http_proxy'
2
2
 
3
3
  module Xeroizer
4
4
  module Record
5
5
  module BaseModelHttpProxy
6
-
6
+
7
7
  def self.included(base)
8
8
  base.send :include, Xeroizer::ApplicationHttpProxy
9
9
  base.send :include, InstanceMethods
10
10
  end
11
-
11
+
12
12
  module InstanceMethods
13
-
13
+
14
14
  protected
15
-
15
+
16
16
  # Parse parameters for GET requests.
17
17
  def parse_params(options)
18
18
  params = {}
19
19
  params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
20
20
  params[:includeArchived] = options[:include_archived] if options[:include_archived]
21
21
  params[:order] = options[:order] if options[:order]
22
+ params[:createdByMyApp] = options[:createdByMyApp] if options[:createdByMyApp]
23
+
24
+ params[:IDs] = filterize(options[:IDs]) if options[:IDs]
25
+ params[:InvoiceNumbers] = filterize(options[:InvoiceNumbers]) if options[:InvoiceNumbers]
26
+ params[:ContactIDs] = filterize(options[:ContactIDs]) if options[:ContactIDs]
27
+ params[:Statuses] = filterize(options[:Statuses]) if options[:Statuses]
22
28
 
23
29
  if options[:where]
24
30
  params[:where] = case options[:where]
25
- when String then options[:where]
31
+ when String then options[:where]
26
32
  when Hash then parse_where_hash(options[:where])
27
33
  end
28
34
  end
29
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]
30
39
  params[:page] = options[:page] if options[:page]
31
40
  params
32
41
  end
33
-
42
+
34
43
  # Parse the :where part of the options for GET parameters and construct a valid
35
44
  # .Net version of the criteria to pass to Xero.
36
45
  #
@@ -46,14 +55,14 @@ module Xeroizer
46
55
  (attribute_name, expression) = extract_expression_from_attribute_name(key)
47
56
  (_, field) = model_class.fields.find { | k, v | v[:internal_name] == attribute_name }
48
57
  if field
49
- conditions << where_condition_part(field, expression, value)
58
+ conditions << where_condition_part(field, expression, value)
50
59
  else
51
60
  raise InvalidAttributeInWhere.new(model_name, attribute_name)
52
61
  end
53
62
  end
54
63
  conditions.map { | (attr, expression, value) | "#{attr}#{expression}#{value}"}.join('&&')
55
64
  end
56
-
65
+
57
66
  # Extract the attribute name and expression from the attribute.
58
67
  #
59
68
  # @return [Array] containing [actual_attribute_name, expression]
@@ -64,37 +73,37 @@ module Xeroizer
64
73
  key.to_s.gsub(/(_is_not|\<\>)$/, '').to_sym,
65
74
  '<>'
66
75
  ]
67
-
76
+
68
77
  when /(_is_greater_than|\>)$/
69
78
  [
70
79
  key.to_s.gsub(/(_is_greater_than|\>)$/, '').to_sym,
71
80
  '>'
72
81
  ]
73
-
82
+
74
83
  when /(_is_greater_than_or_equal_to|\>\=)$/
75
84
  [
76
85
  key.to_s.gsub(/(_is_greater_than_or_equal_to|\>\=)$/, '').to_sym,
77
86
  '>='
78
87
  ]
79
-
88
+
80
89
  when /(_is_less_than|\<)$/
81
90
  [
82
91
  key.to_s.gsub(/(_is_less_than|\<)$/, '').to_sym,
83
92
  '<'
84
93
  ]
85
-
94
+
86
95
  when /(_is_less_than_or_equal_to|\<\=)$/
87
96
  [
88
97
  key.to_s.gsub(/(_is_less_than_or_equal_to|\<\=)$/, '').to_sym,
89
98
  '<='
90
99
  ]
91
-
100
+
92
101
  else
93
102
  [key, '==']
94
-
103
+
95
104
  end
96
105
  end
97
-
106
+
98
107
  # Creates a condition part array containing the:
99
108
  # * Field's API name
100
109
  # * Expression
@@ -111,11 +120,22 @@ module Xeroizer
111
120
  when :datetime_utc then [field[:api_name], expression, "DateTime.Parse(\"#{value.utc.strftime("%Y-%m-%dT%H:%M:%S")}\")"]
112
121
  when :belongs_to then
113
122
  when :has_many then
123
+ when :has_one then
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ # Filtering params expect a comma separated list of strings
130
+ def filterize(values)
131
+ case values
132
+ when String then values
133
+ when Array then values.join(',')
114
134
  end
115
135
  end
116
136
 
117
137
  end
118
-
138
+
119
139
  end
120
140
  end
121
141
  end
@@ -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