xeroizer 2.15.5 → 2.15.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +14 -3
- data/lib/xeroizer.rb +5 -0
- data/lib/xeroizer/generic_application.rb +15 -9
- data/lib/xeroizer/http.rb +44 -34
- data/lib/xeroizer/models/account.rb +15 -11
- data/lib/xeroizer/models/allocation.rb +13 -0
- data/lib/xeroizer/models/attachment.rb +93 -0
- data/lib/xeroizer/models/bank_transaction.rb +3 -2
- data/lib/xeroizer/models/contact.rb +20 -12
- data/lib/xeroizer/models/contact_person.rb +20 -0
- data/lib/xeroizer/models/credit_note.rb +2 -0
- data/lib/xeroizer/models/expense_claim.rb +29 -0
- data/lib/xeroizer/models/invoice.rb +42 -29
- data/lib/xeroizer/models/line_item.rb +1 -0
- data/lib/xeroizer/models/organisation.rb +6 -1
- data/lib/xeroizer/models/payment.rb +16 -11
- data/lib/xeroizer/models/receipt.rb +39 -0
- data/lib/xeroizer/models/tax_component.rb +13 -0
- data/lib/xeroizer/models/tax_rate.rb +14 -3
- data/lib/xeroizer/models/user.rb +26 -0
- data/lib/xeroizer/oauth.rb +3 -3
- data/lib/xeroizer/record/base.rb +40 -31
- data/lib/xeroizer/record/base_model.rb +31 -25
- data/lib/xeroizer/record/base_model_http_proxy.rb +4 -1
- data/lib/xeroizer/record/record_association_helper.rb +35 -35
- data/lib/xeroizer/record/xml_helper.rb +1 -1
- data/lib/xeroizer/report/factory.rb +2 -1
- data/lib/xeroizer/version.rb +3 -0
- data/test/stub_responses/organisation.xml +30 -0
- data/test/stub_responses/tax_rates.xml +81 -1
- data/test/stub_responses/users.xml +17 -0
- data/test/unit/generic_application_test.rb +21 -0
- data/test/unit/http_test.rb +18 -0
- data/test/unit/models/bank_transaction_test.rb +1 -4
- data/test/unit/models/line_item_sum_test.rb +3 -2
- data/test/unit/models/tax_rate_test.rb +81 -0
- data/test/unit/oauth_test.rb +4 -2
- data/test/unit/record/base_test.rb +1 -1
- data/test/unit/record/model_definition_test.rb +9 -3
- data/test/unit/record/record_association_test.rb +1 -1
- data/test/unit/record_definition_test.rb +1 -1
- metadata +540 -205
- data/.bundle/config +0 -2
- data/.gitattributes +0 -22
- data/Gemfile +0 -20
- data/Gemfile.lock +0 -59
- data/Rakefile +0 -55
- data/VERSION +0 -1
- data/xeroizer.gemspec +0 -409
@@ -5,9 +5,19 @@ module Xeroizer
|
|
5
5
|
|
6
6
|
set_permissions :read
|
7
7
|
|
8
|
+
# TaxRates can be created using either POST or PUT.
|
9
|
+
# POST will also silently update the tax, which can
|
10
|
+
# be unexpected. PUT is only for create.
|
11
|
+
def create_method
|
12
|
+
:http_put
|
13
|
+
end
|
8
14
|
end
|
9
15
|
|
10
16
|
class TaxRate < Base
|
17
|
+
set_primary_key :tax_type
|
18
|
+
set_possible_primary_keys :tax_type, :name
|
19
|
+
|
20
|
+
set_primary_key :name
|
11
21
|
|
12
22
|
string :name
|
13
23
|
string :tax_type
|
@@ -19,8 +29,9 @@ module Xeroizer
|
|
19
29
|
boolean :can_apply_to_revenue
|
20
30
|
decimal :display_tax_rate
|
21
31
|
decimal :effective_rate
|
22
|
-
|
32
|
+
|
33
|
+
has_many :tax_components
|
23
34
|
end
|
24
|
-
|
35
|
+
|
25
36
|
end
|
26
|
-
end
|
37
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Xeroizer
|
2
|
+
module Record
|
3
|
+
|
4
|
+
class UserModel < BaseModel
|
5
|
+
|
6
|
+
set_api_controller_name 'User'
|
7
|
+
set_permissions :read
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
class User < Base
|
12
|
+
|
13
|
+
set_primary_key :user_id
|
14
|
+
|
15
|
+
guid :user_id
|
16
|
+
string :email_address
|
17
|
+
string :first_name
|
18
|
+
string :last_name
|
19
|
+
datetime_utc :updated_date_utc, :api_name => 'UpdatedDateUTC'
|
20
|
+
boolean :is_subscriber
|
21
|
+
string :organisation_role
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/xeroizer/oauth.rb
CHANGED
@@ -84,13 +84,13 @@ module Xeroizer
|
|
84
84
|
#
|
85
85
|
# @option params [String] :oauth_callback URL to redirect user to when they have authenticated your application with Xero. If not specified, the user will be shown an authorisation code on the screen that they need to get into your application.
|
86
86
|
def request_token(params = {})
|
87
|
-
consumer.get_request_token(params)
|
87
|
+
consumer.get_request_token(params, {}, @consumer_options[:default_headers])
|
88
88
|
end
|
89
89
|
|
90
90
|
# Create an AccessToken from a PUBLIC/PARTNER authorisation.
|
91
91
|
def authorize_from_request(rtoken, rsecret, params = {})
|
92
92
|
request_token = ::OAuth::RequestToken.new(consumer, rtoken, rsecret)
|
93
|
-
access_token = request_token.get_access_token(params)
|
93
|
+
access_token = request_token.get_access_token(params, {}, @consumer_options[:default_headers])
|
94
94
|
update_attributes_from_token(access_token)
|
95
95
|
end
|
96
96
|
|
@@ -116,7 +116,7 @@ module Xeroizer
|
|
116
116
|
access_token = old_token.get_access_token({
|
117
117
|
:oauth_session_handle => (session_handle || @session_handle),
|
118
118
|
:token => old_token
|
119
|
-
})
|
119
|
+
}, {}, @consumer_options[:default_headers])
|
120
120
|
update_attributes_from_token(access_token)
|
121
121
|
end
|
122
122
|
|
data/lib/xeroizer/record/base.rb
CHANGED
@@ -6,60 +6,68 @@ require 'xeroizer/logging'
|
|
6
6
|
|
7
7
|
module Xeroizer
|
8
8
|
module Record
|
9
|
-
|
9
|
+
|
10
10
|
class Base
|
11
|
-
|
11
|
+
|
12
12
|
include ClassLevelInheritableAttributes
|
13
13
|
class_inheritable_attributes :fields, :possible_primary_keys, :primary_key_name, :summary_only, :validators
|
14
|
-
|
14
|
+
|
15
15
|
attr_reader :attributes
|
16
16
|
attr_reader :parent
|
17
|
+
attr_reader :model
|
17
18
|
attr_accessor :errors
|
18
19
|
attr_accessor :complete_record_downloaded
|
19
|
-
|
20
|
+
|
20
21
|
include ModelDefinitionHelper
|
21
22
|
include RecordAssociationHelper
|
22
23
|
include ValidationHelper
|
23
24
|
include XmlHelper
|
24
|
-
|
25
|
+
|
25
26
|
class << self
|
26
27
|
|
27
28
|
# Build a record with attributes set to the value of attributes.
|
28
29
|
def build(attributes, parent)
|
29
30
|
record = new(parent)
|
30
31
|
attributes.each do | key, value |
|
31
|
-
record.
|
32
|
+
attr = record.respond_to?("#{key}=") ? key : record.class.fields[key][:internal_name]
|
33
|
+
record.send("#{attr}=", value)
|
32
34
|
end
|
33
35
|
record
|
34
36
|
end
|
35
|
-
|
37
|
+
|
36
38
|
end
|
37
|
-
|
39
|
+
|
38
40
|
public
|
39
|
-
|
41
|
+
|
40
42
|
def initialize(parent)
|
41
43
|
@parent = parent
|
44
|
+
@model = new_model_class(self.class.name.demodulize)
|
42
45
|
@attributes = {}
|
43
46
|
end
|
44
|
-
|
47
|
+
|
45
48
|
def new_model_class(model_name)
|
46
|
-
Xeroizer::Record.const_get("#{model_name}Model".to_sym).new(parent.application, model_name.to_s)
|
49
|
+
Xeroizer::Record.const_get("#{model_name}Model".to_sym).new(parent.try(:application), model_name.to_s)
|
47
50
|
end
|
48
|
-
|
51
|
+
|
49
52
|
def [](attribute)
|
50
53
|
self.send(attribute)
|
51
54
|
end
|
52
|
-
|
55
|
+
|
53
56
|
def []=(attribute, value)
|
54
57
|
parent.mark_dirty(self) if parent
|
55
58
|
self.send("#{attribute}=".to_sym, value)
|
56
59
|
end
|
57
60
|
|
61
|
+
def non_calculated_attributes
|
62
|
+
attributes.reject {|name| self.class.fields[name][:calculated] }
|
63
|
+
end
|
64
|
+
|
58
65
|
def attributes=(new_attributes)
|
59
66
|
return unless new_attributes.is_a?(Hash)
|
60
67
|
parent.mark_dirty(self) if parent
|
61
68
|
new_attributes.each do | key, value |
|
62
|
-
|
69
|
+
attr = respond_to?("#{key}=") ? key : self.class.fields[key][:internal_name]
|
70
|
+
self.send("#{attr}=", value)
|
63
71
|
end
|
64
72
|
end
|
65
73
|
|
@@ -67,11 +75,11 @@ module Xeroizer
|
|
67
75
|
self.attributes = attributes
|
68
76
|
save
|
69
77
|
end
|
70
|
-
|
78
|
+
|
71
79
|
def new_record?
|
72
80
|
id.nil?
|
73
81
|
end
|
74
|
-
|
82
|
+
|
75
83
|
# Check to see if the complete record is downloaded.
|
76
84
|
def complete_record_downloaded?
|
77
85
|
if !!self.class.list_contains_summary_only?
|
@@ -80,7 +88,7 @@ module Xeroizer
|
|
80
88
|
true
|
81
89
|
end
|
82
90
|
end
|
83
|
-
|
91
|
+
|
84
92
|
# Downloads the complete record if we only have a summary of the record.
|
85
93
|
def download_complete_record!
|
86
94
|
record = self.parent.find(self.id)
|
@@ -89,7 +97,7 @@ module Xeroizer
|
|
89
97
|
parent.mark_clean(self)
|
90
98
|
self
|
91
99
|
end
|
92
|
-
|
100
|
+
|
93
101
|
def save
|
94
102
|
return false unless valid?
|
95
103
|
if new_record?
|
@@ -105,7 +113,7 @@ module Xeroizer
|
|
105
113
|
parent.mark_clean(self)
|
106
114
|
true
|
107
115
|
end
|
108
|
-
|
116
|
+
|
109
117
|
def to_json(*args)
|
110
118
|
to_h.to_json(*args)
|
111
119
|
end
|
@@ -128,37 +136,38 @@ module Xeroizer
|
|
128
136
|
end.join(", ")
|
129
137
|
"#<#{self.class} #{attribute_string}>"
|
130
138
|
end
|
131
|
-
|
139
|
+
|
132
140
|
protected
|
133
|
-
|
141
|
+
|
134
142
|
# Attempt to create a new record.
|
135
143
|
def create
|
136
144
|
request = to_xml
|
137
145
|
log "[CREATE SENT] (#{__FILE__}:#{__LINE__}) #{request}"
|
138
|
-
|
139
|
-
response = parent.
|
146
|
+
|
147
|
+
response = parent.send(parent.create_method, request)
|
148
|
+
|
140
149
|
log "[CREATE RECEIVED] (#{__FILE__}:#{__LINE__}) #{response}"
|
141
|
-
|
150
|
+
|
142
151
|
parse_save_response(response)
|
143
152
|
end
|
144
|
-
|
153
|
+
|
145
154
|
# Attempt to update an existing record.
|
146
155
|
def update
|
147
156
|
if self.class.possible_primary_keys && self.class.possible_primary_keys.all? { | possible_key | self[possible_key].nil? }
|
148
157
|
raise RecordKeyMustBeDefined.new(self.class.possible_primary_keys)
|
149
158
|
end
|
150
|
-
|
159
|
+
|
151
160
|
request = to_xml
|
152
|
-
|
161
|
+
|
153
162
|
log "[UPDATE SENT] (#{__FILE__}:#{__LINE__}) \r\n#{request}"
|
154
|
-
|
163
|
+
|
155
164
|
response = parent.http_post(request)
|
156
165
|
|
157
166
|
log "[UPDATE RECEIVED] (#{__FILE__}:#{__LINE__}) \r\n#{response}"
|
158
167
|
|
159
168
|
parse_save_response(response)
|
160
169
|
end
|
161
|
-
|
170
|
+
|
162
171
|
# Parse the response from a create/update request.
|
163
172
|
def parse_save_response(response_xml)
|
164
173
|
response = parent.parse_response(response_xml)
|
@@ -172,8 +181,8 @@ module Xeroizer
|
|
172
181
|
def log(what)
|
173
182
|
Xeroizer::Logging::Log.info what
|
174
183
|
end
|
175
|
-
|
184
|
+
|
176
185
|
end
|
177
|
-
|
186
|
+
|
178
187
|
end
|
179
188
|
end
|
@@ -2,19 +2,19 @@ require 'xeroizer/record/base_model_http_proxy'
|
|
2
2
|
|
3
3
|
module Xeroizer
|
4
4
|
module Record
|
5
|
-
|
5
|
+
|
6
6
|
class BaseModel
|
7
7
|
|
8
8
|
include ClassLevelInheritableAttributes
|
9
9
|
class_inheritable_attributes :api_controller_name
|
10
|
-
|
10
|
+
|
11
11
|
module InvaidPermissionError; end
|
12
12
|
class InvalidPermissionError < StandardError
|
13
13
|
include InvaidPermissionError
|
14
14
|
end
|
15
15
|
ALLOWED_PERMISSIONS = [:read, :write, :update]
|
16
16
|
class_inheritable_attributes :permissions
|
17
|
-
|
17
|
+
|
18
18
|
class_inheritable_attributes :xml_root_name
|
19
19
|
class_inheritable_attributes :optional_xml_root_name
|
20
20
|
class_inheritable_attributes :xml_node_name
|
@@ -27,18 +27,18 @@ module Xeroizer
|
|
27
27
|
attr_reader :model_name
|
28
28
|
attr :model_class
|
29
29
|
attr_reader :response
|
30
|
-
|
30
|
+
|
31
31
|
class << self
|
32
|
-
|
33
|
-
# Method to allow override of the default controller name used
|
34
|
-
# in the API URLs.
|
32
|
+
|
33
|
+
# Method to allow override of the default controller name used
|
34
|
+
# in the API URLs.
|
35
35
|
#
|
36
36
|
# Default: pluaralized model name (e.g. if the controller name is
|
37
37
|
# Invoice then the default is Invoices.
|
38
38
|
def set_api_controller_name(controller_name)
|
39
39
|
self.api_controller_name = controller_name
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
# Set the permissions allowed for this class type.
|
43
43
|
# There are no permissions set by default.
|
44
44
|
# Valid permissions are :read, :write, :update.
|
@@ -49,34 +49,34 @@ module Xeroizer
|
|
49
49
|
self.permissions[permission] = true
|
50
50
|
end
|
51
51
|
end
|
52
|
-
|
52
|
+
|
53
53
|
# Method to allow override of the default XML node name.
|
54
54
|
#
|
55
55
|
# Default: singularized model name in camel-case.
|
56
56
|
def set_xml_node_name(node_name)
|
57
57
|
self.xml_node_name = node_name
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
60
|
# Method to allow override of the default XML root name to use
|
61
61
|
# in has_many associations.
|
62
62
|
def set_xml_root_name(root_name)
|
63
63
|
self.xml_root_name = root_name
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
# Method to add an extra top-level node to use in has_many associations.
|
67
67
|
def set_optional_xml_root_name(optional_root_name)
|
68
|
-
self.
|
68
|
+
self.optional_xml_root_name = optional_root_name
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
public
|
74
|
-
|
74
|
+
|
75
75
|
def initialize(application, model_name)
|
76
76
|
@application = application
|
77
77
|
@model_name = model_name
|
78
78
|
end
|
79
|
-
|
79
|
+
|
80
80
|
# Retrieve the controller name.
|
81
81
|
#
|
82
82
|
# Default: pluaralized model name (e.g. if the controller name is
|
@@ -84,11 +84,11 @@ module Xeroizer
|
|
84
84
|
def api_controller_name
|
85
85
|
self.class.api_controller_name || model_name.pluralize
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
def model_class
|
89
89
|
@model_class ||= Xeroizer::Record.const_get(model_name.to_sym)
|
90
90
|
end
|
91
|
-
|
91
|
+
|
92
92
|
# Build a record with attributes set to the value of attributes.
|
93
93
|
def build(attributes = {})
|
94
94
|
model_class.build(attributes, self).tap do |resource|
|
@@ -113,15 +113,15 @@ module Xeroizer
|
|
113
113
|
def create(attributes = {})
|
114
114
|
build(attributes).tap { |resource| resource.save }
|
115
115
|
end
|
116
|
-
|
117
|
-
# Retreive full record list for this model.
|
116
|
+
|
117
|
+
# Retreive full record list for this model.
|
118
118
|
def all(options = {})
|
119
119
|
raise MethodNotAllowed.new(self, :all) unless self.class.permissions[:read]
|
120
120
|
response_xml = http_get(parse_params(options))
|
121
121
|
response = parse_response(response_xml, options)
|
122
122
|
response.response_items || []
|
123
123
|
end
|
124
|
-
|
124
|
+
|
125
125
|
# Helper method to retrieve just the first element from
|
126
126
|
# the full record list.
|
127
127
|
def first(options = {})
|
@@ -129,7 +129,7 @@ module Xeroizer
|
|
129
129
|
result = all(options)
|
130
130
|
result.first if result.is_a?(Array)
|
131
131
|
end
|
132
|
-
|
132
|
+
|
133
133
|
# Retrieve record matching the passed in ID.
|
134
134
|
def find(id, options = {})
|
135
135
|
raise MethodNotAllowed.new(self, :all) unless self.class.permissions[:read]
|
@@ -141,6 +141,7 @@ module Xeroizer
|
|
141
141
|
end
|
142
142
|
|
143
143
|
def batch_save(chunk_size = DEFAULT_RECORDS_PER_BATCH_SAVE)
|
144
|
+
no_errors = true
|
144
145
|
@objects = {}
|
145
146
|
@allow_batch_operations = true
|
146
147
|
|
@@ -149,15 +150,16 @@ module Xeroizer
|
|
149
150
|
if @objects[model_class]
|
150
151
|
objects = @objects[model_class].values.compact
|
151
152
|
return false unless objects.all?(&:valid?)
|
152
|
-
actions = objects.group_by {|o| o.new_record? ?
|
153
|
+
actions = objects.group_by {|o| o.new_record? ? create_method : :http_post }
|
153
154
|
actions.each_pair do |http_method, records|
|
154
155
|
records.each_slice(chunk_size) do |some_records|
|
155
156
|
request = to_bulk_xml(some_records)
|
156
157
|
response = parse_response(self.send(http_method, request, {:summarizeErrors => false}))
|
157
158
|
response.response_items.each_with_index do |record, i|
|
158
159
|
if record and record.is_a?(model_class)
|
159
|
-
some_records[i].attributes = record.
|
160
|
+
some_records[i].attributes = record.non_calculated_attributes
|
160
161
|
some_records[i].errors = record.errors
|
162
|
+
no_errors = record.errors.nil? || record.errors.empty? if no_errors
|
161
163
|
some_records[i].saved!
|
162
164
|
end
|
163
165
|
end
|
@@ -167,7 +169,7 @@ module Xeroizer
|
|
167
169
|
|
168
170
|
@objects = {}
|
169
171
|
@allow_batch_operations = false
|
170
|
-
|
172
|
+
no_errors
|
171
173
|
end
|
172
174
|
|
173
175
|
def parse_response(response_xml, options = {})
|
@@ -179,6 +181,10 @@ module Xeroizer
|
|
179
181
|
end
|
180
182
|
end
|
181
183
|
|
184
|
+
def create_method
|
185
|
+
:http_put
|
186
|
+
end
|
187
|
+
|
182
188
|
protected
|
183
189
|
|
184
190
|
# Parse the records part of the XML response and builds model instances as necessary.
|