xeroizer 2.17.1 → 3.0.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.
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