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.
- checksums.yaml +5 -5
- data/README.md +199 -223
- data/lib/xeroizer/connection.rb +49 -0
- data/lib/xeroizer/exceptions.rb +2 -0
- data/lib/xeroizer/generic_application.rb +12 -5
- data/lib/xeroizer/http.rb +5 -78
- data/lib/xeroizer/http_response.rb +157 -0
- data/lib/xeroizer/models/bank_account.rb +1 -0
- data/lib/xeroizer/models/bank_transaction.rb +1 -0
- data/lib/xeroizer/models/batch_payment.rb +27 -0
- data/lib/xeroizer/models/branding_theme.rb +49 -9
- data/lib/xeroizer/models/contact.rb +10 -4
- data/lib/xeroizer/models/contact_group.rb +45 -0
- data/lib/xeroizer/models/credit_note.rb +23 -22
- data/lib/xeroizer/models/from_bank_account.rb +1 -0
- data/lib/xeroizer/models/history_record.rb +72 -0
- data/lib/xeroizer/models/invoice.rb +14 -3
- data/lib/xeroizer/models/line_item.rb +17 -5
- data/lib/xeroizer/models/manual_journal.rb +2 -1
- data/lib/xeroizer/models/option.rb +1 -1
- data/lib/xeroizer/models/organisation.rb +2 -0
- data/lib/xeroizer/models/payment_service.rb +22 -0
- data/lib/xeroizer/models/payroll/address.rb +53 -0
- data/lib/xeroizer/models/payroll/bank_account.rb +18 -6
- data/lib/xeroizer/models/payroll/benefit_line.rb +26 -0
- data/lib/xeroizer/models/payroll/benefit_type.rb +45 -0
- data/lib/xeroizer/models/payroll/deduction_line.rb +32 -0
- data/lib/xeroizer/models/payroll/deduction_type.rb +49 -0
- data/lib/xeroizer/models/payroll/earnings_line.rb +39 -0
- data/lib/xeroizer/models/payroll/earnings_type.rb +53 -0
- data/lib/xeroizer/models/payroll/employee.rb +30 -8
- data/lib/xeroizer/models/payroll/leave_application.rb +27 -0
- data/lib/xeroizer/models/payroll/leave_line.rb +30 -0
- data/lib/xeroizer/models/payroll/leave_period.rb +15 -0
- data/lib/xeroizer/models/payroll/pay_items.rb +22 -0
- data/lib/xeroizer/models/payroll/pay_run.rb +33 -0
- data/lib/xeroizer/models/payroll/pay_schedule.rb +40 -0
- data/lib/xeroizer/models/payroll/pay_template.rb +24 -0
- data/lib/xeroizer/models/payroll/payment_method.rb +24 -0
- data/lib/xeroizer/models/payroll/paystub.rb +44 -0
- data/lib/xeroizer/models/payroll/reimbursement_line.rb +21 -0
- data/lib/xeroizer/models/payroll/reimbursement_type.rb +22 -0
- data/lib/xeroizer/models/payroll/salary_and_wage.rb +29 -0
- data/lib/xeroizer/models/payroll/super_line.rb +40 -0
- data/lib/xeroizer/models/payroll/tax_declaration.rb +50 -0
- data/lib/xeroizer/models/payroll/time_off_line.rb +20 -0
- data/lib/xeroizer/models/payroll/time_off_type.rb +32 -0
- data/lib/xeroizer/models/payroll/work_location.rb +25 -0
- data/lib/xeroizer/models/prepayment.rb +1 -0
- data/lib/xeroizer/models/purchase_order.rb +6 -6
- data/lib/xeroizer/models/quote.rb +76 -0
- data/lib/xeroizer/models/schedule.rb +1 -0
- data/lib/xeroizer/models/tax_component.rb +1 -0
- data/lib/xeroizer/models/to_bank_account.rb +1 -0
- data/lib/xeroizer/oauth.rb +12 -1
- data/lib/xeroizer/oauth2.rb +82 -0
- data/lib/xeroizer/oauth2_application.rb +49 -0
- data/lib/xeroizer/payroll_application.rb +8 -3
- data/lib/xeroizer/record/base.rb +1 -1
- data/lib/xeroizer/record/base_model.rb +1 -1
- data/lib/xeroizer/record/base_model_http_proxy.rb +4 -0
- data/lib/xeroizer/record/model_definition_helper.rb +1 -1
- data/lib/xeroizer/record/payroll_base.rb +4 -0
- data/lib/xeroizer/record/record_association_helper.rb +4 -4
- data/lib/xeroizer/record/validators/associated_validator.rb +1 -0
- data/lib/xeroizer/record/xml_helper.rb +18 -18
- data/lib/xeroizer/report/aged_receivables_by_contact.rb +1 -1
- data/lib/xeroizer/report/cell_xml_helper.rb +13 -13
- data/lib/xeroizer/response.rb +22 -17
- data/lib/xeroizer/version.rb +1 -1
- data/lib/xeroizer.rb +33 -4
- data/test/acceptance/about_creating_bank_transactions_test.rb +80 -82
- data/test/acceptance/about_creating_prepayment_test.rb +25 -30
- data/test/acceptance/about_fetching_bank_transactions_test.rb +12 -12
- data/test/acceptance/about_online_invoice_test.rb +6 -10
- data/test/acceptance/acceptance_test.rb +28 -26
- data/test/acceptance/bank_transfer_test.rb +12 -17
- data/test/acceptance/bulk_operations_test.rb +18 -16
- data/test/acceptance/connections_test.rb +11 -0
- data/test/stub_responses/bad_request.json +6 -0
- data/test/stub_responses/connections.json +16 -0
- data/test/stub_responses/expired_oauth2_token.json +6 -0
- data/test/stub_responses/generic_response_error.json +6 -0
- data/test/stub_responses/invalid_oauth2_request_token.json +6 -0
- data/test/stub_responses/invalid_tenant_header.json +6 -0
- data/test/stub_responses/object_not_found.json +6 -0
- data/test/stub_responses/organisations.xml +10 -0
- data/test/stub_responses/payment_service.xml +15 -0
- data/test/test_helper.rb +16 -11
- data/test/unit/generic_application_test.rb +21 -10
- data/test/unit/http_test.rb +284 -10
- data/test/unit/models/address_test.rb +2 -2
- data/test/unit/models/bank_transaction_model_parsing_test.rb +2 -2
- data/test/unit/models/bank_transaction_test.rb +1 -1
- data/test/unit/models/bank_transaction_validation_test.rb +1 -1
- data/test/unit/models/contact_test.rb +2 -2
- data/test/unit/models/credit_note_test.rb +8 -8
- data/test/unit/models/employee_test.rb +4 -4
- data/test/unit/models/invoice_test.rb +12 -12
- data/test/unit/models/journal_line_test.rb +6 -6
- data/test/unit/models/journal_test.rb +4 -4
- data/test/unit/models/line_item_sum_test.rb +1 -1
- data/test/unit/models/line_item_test.rb +26 -28
- data/test/unit/models/manual_journal_test.rb +3 -3
- data/test/unit/models/organisation_test.rb +16 -2
- data/test/unit/models/payment_service_test.rb +29 -0
- data/test/unit/models/phone_test.rb +7 -7
- data/test/unit/models/prepayment_test.rb +4 -4
- data/test/unit/models/repeating_invoice_test.rb +3 -3
- data/test/unit/models/tax_rate_test.rb +2 -2
- data/test/unit/oauth2_test.rb +171 -0
- data/test/unit/oauth_config_test.rb +1 -1
- data/test/unit/record/base_model_test.rb +13 -13
- data/test/unit/record/base_test.rb +15 -4
- data/test/unit/record/block_validator_test.rb +1 -1
- data/test/unit/record/connection_test.rb +60 -0
- data/test/unit/record/model_definition_test.rb +36 -36
- data/test/unit/record/parse_params_test.rb +2 -2
- data/test/unit/record/parse_where_hash_test.rb +13 -13
- data/test/unit/record/record_association_test.rb +14 -14
- data/test/unit/record/validators_test.rb +43 -43
- data/test/unit/record_definition_test.rb +7 -7
- data/test/unit/report_definition_test.rb +7 -7
- data/test/unit/report_test.rb +20 -20
- data/test/unit_test_helper.rb +16 -0
- metadata +100 -25
- data/lib/xeroizer/models/payroll/home_address.rb +0 -24
- data/lib/xeroizer/partner_application.rb +0 -51
- data/lib/xeroizer/private_application.rb +0 -25
- data/lib/xeroizer/public_application.rb +0 -21
- data/test/unit/http_tsl_12_upgrade_test.rb +0 -31
- data/test/unit/oauth_test.rb +0 -118
- 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
|
data/lib/xeroizer/oauth.rb
CHANGED
@@ -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
|
data/lib/xeroizer/record/base.rb
CHANGED
@@ -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
|
-
#
|
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
|
|
@@ -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
|
@@ -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
|
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
|
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
|
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
|
51
|
-
when
|
52
|
-
else
|
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
|
data/lib/xeroizer/response.rb
CHANGED
@@ -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
|
data/lib/xeroizer/version.rb
CHANGED