xero_gateway 2.0.4 → 2.0.5
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.
- data/README.textile +57 -5
- data/lib/xero_gateway/account.rb +8 -2
- data/lib/xero_gateway/credit_note.rb +275 -0
- data/lib/xero_gateway/exceptions.rb +3 -1
- data/lib/xero_gateway/gateway.rb +119 -3
- data/lib/xero_gateway/http.rb +10 -6
- data/lib/xero_gateway/oauth.rb +3 -1
- data/lib/xero_gateway/response.rb +2 -0
- data/lib/xero_gateway/tracking_category.rb +5 -2
- data/lib/xero_gateway.rb +1 -0
- data/test/integration/create_credit_note_test.rb +49 -0
- data/test/integration/get_credit_note_test.rb +48 -0
- data/test/integration/get_credit_notes_test.rb +90 -0
- data/test/integration/get_tracking_categories_test.rb +3 -2
- data/test/test_helper.rb +24 -1
- data/test/unit/account_test.rb +3 -2
- data/test/unit/credit_note_test.rb +284 -0
- data/test/unit/gateway_test.rb +17 -1
- data/xero_gateway.gemspec +3 -76
- metadata +13 -29
- data/CHANGELOG.textile +0 -57
- data/test/stub_responses/accounts.xml +0 -1
- data/test/stub_responses/api_exception.xml +0 -153
- data/test/stub_responses/contact.xml +0 -1
- data/test/stub_responses/contacts.xml +0 -2189
- data/test/stub_responses/create_invoice.xml +0 -64
- data/test/stub_responses/currencies.xml +0 -16
- data/test/stub_responses/invalid_api_key_error.xml +0 -1
- data/test/stub_responses/invalid_consumer_key +0 -1
- data/test/stub_responses/invalid_request_token +0 -1
- data/test/stub_responses/invoice.xml +0 -1
- data/test/stub_responses/invoice_not_found_error.xml +0 -1
- data/test/stub_responses/invoices.xml +0 -1
- data/test/stub_responses/organisation.xml +0 -14
- data/test/stub_responses/tax_rates.xml +0 -52
- data/test/stub_responses/token_expired +0 -1
- data/test/stub_responses/tracking_categories.xml +0 -1
- data/test/stub_responses/unknown_error.xml +0 -1
- data/test/xsd/README +0 -2
- data/test/xsd/create_contact.xsd +0 -61
- data/test/xsd/create_invoice.xsd +0 -107
data/README.textile
CHANGED
@@ -147,7 +147,7 @@ h3. GET /api.xro/2.0/contacts (get_contacts)
|
|
147
147
|
|
148
148
|
Gets all contact records for a particular Xero customer.
|
149
149
|
<pre><code> gateway.get_contacts(:type => :all, :sort => :name, :direction => :desc)
|
150
|
-
gateway.get_contacts(:type => :all, :
|
150
|
+
gateway.get_contacts(:type => :all, :modified_since => 1.month.ago) # modified since 1 month ago</code></pre>
|
151
151
|
|
152
152
|
|
153
153
|
|
@@ -200,7 +200,7 @@ h3. GET /api.xro/2.0/invoices (get_invoices)
|
|
200
200
|
|
201
201
|
Gets all invoice records for a particular Xero customer.
|
202
202
|
<pre><code> gateway.get_invoices
|
203
|
-
gateway.get_invoices(1.month.ago) # modified since 1 month ago</code></pre>
|
203
|
+
gateway.get_invoices(:modified_since => 1.month.ago) # modified since 1 month ago</code></pre>
|
204
204
|
|
205
205
|
|
206
206
|
|
@@ -214,8 +214,7 @@ Invoice and line item totals are calculated automatically.
|
|
214
214
|
:due_date => 1.month.from_now,
|
215
215
|
:invoice_number => "YOUR INVOICE NUMBER",
|
216
216
|
:reference => "YOUR REFERENCE (NOT NECESSARILY UNIQUE!)",
|
217
|
-
:
|
218
|
-
:includes_tax => false
|
217
|
+
:line_amount_types => "Inclusive" # "Inclusive", "Exclusive" or "NoTax"
|
219
218
|
})
|
220
219
|
invoice.contact.name = "THE NAME OF THE CONTACT"
|
221
220
|
invoice.contact.phone.number = "12345"
|
@@ -238,6 +237,59 @@ This method uses only a single API request to create/update multiple contacts.
|
|
238
237
|
<pre><code> invoices = [XeroGateway::Invoice.new(...), XeroGateway::Invoice.new(...)]
|
239
238
|
result = gateway.create_invoices(invoices)</code></pre>
|
240
239
|
|
240
|
+
h3. GET /api.xro/2.0/credit_note (get_credit_note_by_id)
|
241
|
+
|
242
|
+
Gets an credit_note record for a specific Xero organisation
|
243
|
+
<pre><code> gateway.get_credit_note_by_id(credit_note_id)</code></pre>
|
244
|
+
|
245
|
+
|
246
|
+
h3. GET /api.xro/2.0/credit_note (get_credit_note_by_number)
|
247
|
+
|
248
|
+
Gets a credit note record for a specific Xero organisation
|
249
|
+
<pre><code> gateway.get_credit_note_by_number(credit_note_number)</code></pre>
|
250
|
+
|
251
|
+
|
252
|
+
|
253
|
+
h3. GET /api.xro/2.0/credit_notes (get_credit_notes)
|
254
|
+
|
255
|
+
Gets all credit note records for a particular Xero customer.
|
256
|
+
<pre><code> gateway.get_credit_notes
|
257
|
+
gateway.get_credit_notes(:modified_since => 1.month.ago) # modified since 1 month ago</code></pre>
|
258
|
+
|
259
|
+
|
260
|
+
|
261
|
+
h3. PUT /api.xro/2.0/credit_note
|
262
|
+
|
263
|
+
Inserts a credit note for a specific organization in Xero (Currently only adding new credit notes is allowed).
|
264
|
+
|
265
|
+
CreditNote and line item totals are calculated automatically.
|
266
|
+
<pre><code> credit_note = gateway.build_credit_note({
|
267
|
+
:credit_note_type => "ACCRECCREDIT",
|
268
|
+
:credit_note_number => "YOUR CREDIT NOTE NUMBER",
|
269
|
+
:reference => "YOUR REFERENCE (NOT NECESSARILY UNIQUE!)",
|
270
|
+
:line_amount_types => "Inclusive" # "Inclusive", "Exclusive" or "NoTax"
|
271
|
+
})
|
272
|
+
credit_note.contact.name = "THE NAME OF THE CONTACT"
|
273
|
+
credit_note.contact.phone.number = "12345"
|
274
|
+
credit_note.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
|
275
|
+
credit_note.add_line_item({
|
276
|
+
:description => "THE DESCRIPTION OF THE LINE ITEM",
|
277
|
+
:unit_amount => 1000,
|
278
|
+
:tax_amount => 125,
|
279
|
+
:tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM",
|
280
|
+
:tracking_option => "THE TRACKING OPTION FOR THE LINE ITEM"
|
281
|
+
})
|
282
|
+
|
283
|
+
credit_note.create</code></pre>
|
284
|
+
|
285
|
+
|
286
|
+
h3. PUT /api.xro/2.0/credit_notes
|
287
|
+
|
288
|
+
Inserts multiple credit notes for a specific organization in Xero (currently only adding new credit notes is allowed).
|
289
|
+
This method uses only a single API request to create/update multiple contacts.
|
290
|
+
<pre><code> credit_notes = [XeroGateway::CreditNote.new(...), XeroGateway::CreditNote.new(...)]
|
291
|
+
result = gateway.create_credit_notes(credit_notes)</code></pre>
|
292
|
+
|
241
293
|
h3. GET /api.xro/2.0/accounts
|
242
294
|
|
243
295
|
Gets all accounts for a specific organization in Xero.
|
@@ -286,4 +338,4 @@ You can specify a logger to use (so you can track down those tricky exceptions)
|
|
286
338
|
gateway.logger = ActiveSupport::BufferedLogger.new("log_file_name.log")
|
287
339
|
</pre>
|
288
340
|
|
289
|
-
It doesn't have to be a buffered logger - anything that responds to "info" will do just fine.
|
341
|
+
It doesn't have to be a buffered logger - anything that responds to "info" will do just fine.
|
data/lib/xero_gateway/account.rb
CHANGED
@@ -33,7 +33,7 @@ module XeroGateway
|
|
33
33
|
'ZERORATED' => 'Zero-rated supplies/sales from overseas (NZ Only)'
|
34
34
|
} unless defined?(TAX_TYPE)
|
35
35
|
|
36
|
-
attr_accessor :code, :name, :type, :tax_type, :description
|
36
|
+
attr_accessor :account_id, :code, :name, :type, :tax_type, :description, :system_account, :enable_payments_to_account
|
37
37
|
|
38
38
|
def initialize(params = {})
|
39
39
|
params.each do |k,v|
|
@@ -42,7 +42,7 @@ module XeroGateway
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def ==(other)
|
45
|
-
[:code, :name, :type, :tax_type, :description].each do |field|
|
45
|
+
[:account_id, :code, :name, :type, :tax_type, :description, :system_account, :enable_payments_to_account].each do |field|
|
46
46
|
return false if send(field) != other.send(field)
|
47
47
|
end
|
48
48
|
return true
|
@@ -52,11 +52,14 @@ module XeroGateway
|
|
52
52
|
b = Builder::XmlMarkup.new
|
53
53
|
|
54
54
|
b.Account {
|
55
|
+
b.AccountID self.account_id
|
55
56
|
b.Code self.code
|
56
57
|
b.Name self.name
|
57
58
|
b.Type self.type
|
58
59
|
b.TaxType self.tax_type
|
59
60
|
b.Description self.description
|
61
|
+
b.SystemAccount self.system_account unless self.system_account.nil?
|
62
|
+
b.EnablePaymentsToAccount self.enable_payments_to_account
|
60
63
|
}
|
61
64
|
end
|
62
65
|
|
@@ -64,11 +67,14 @@ module XeroGateway
|
|
64
67
|
account = Account.new
|
65
68
|
account_element.children.each do |element|
|
66
69
|
case(element.name)
|
70
|
+
when "AccountID" then account.account_id = element.text
|
67
71
|
when "Code" then account.code = element.text
|
68
72
|
when "Name" then account.name = element.text
|
69
73
|
when "Type" then account.type = element.text
|
70
74
|
when "TaxType" then account.tax_type = element.text
|
71
75
|
when "Description" then account.description = element.text
|
76
|
+
when "SystemAccount" then account.system_account = element.text
|
77
|
+
when "EnablePaymentsToAccount" then account.enable_payments_to_account = (element.text == 'true')
|
72
78
|
end
|
73
79
|
end
|
74
80
|
account
|
@@ -0,0 +1,275 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
class CreditNote
|
3
|
+
include Dates
|
4
|
+
include Money
|
5
|
+
|
6
|
+
class Error < RuntimeError; end
|
7
|
+
class NoGatewayError < Error; end
|
8
|
+
class InvalidLineItemError < Error; end
|
9
|
+
|
10
|
+
CREDIT_NOTE_TYPE = {
|
11
|
+
'ACCRECCREDIT' => 'Accounts Receivable',
|
12
|
+
'ACCPAYCREDIT' => 'Accounts Payable'
|
13
|
+
} unless defined?(CREDIT_NOTE_TYPE)
|
14
|
+
|
15
|
+
LINE_AMOUNT_TYPES = {
|
16
|
+
"Inclusive" => 'CreditNote lines are inclusive tax',
|
17
|
+
"Exclusive" => 'CreditNote lines are exclusive of tax (default)',
|
18
|
+
"NoTax" => 'CreditNotes lines have no tax'
|
19
|
+
} unless defined?(LINE_AMOUNT_TYPES)
|
20
|
+
|
21
|
+
CREDIT_NOTE_STATUS = {
|
22
|
+
'AUTHORISED' => 'Approved credit_notes awaiting payment',
|
23
|
+
'DELETED' => 'Draft credit_notes that are deleted',
|
24
|
+
'DRAFT' => 'CreditNotes saved as draft or entered via API',
|
25
|
+
'PAID' => 'CreditNotes approved and fully paid',
|
26
|
+
'SUBMITTED' => 'CreditNotes entered by an employee awaiting approval',
|
27
|
+
'VOID' => 'Approved credit_notes that are voided'
|
28
|
+
} unless defined?(CREDIT_NOTE_STATUS)
|
29
|
+
|
30
|
+
GUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/ unless defined?(GUID_REGEX)
|
31
|
+
|
32
|
+
# Xero::Gateway associated with this credit_note.
|
33
|
+
attr_accessor :gateway
|
34
|
+
|
35
|
+
# Any errors that occurred when the #valid? method called.
|
36
|
+
attr_reader :errors
|
37
|
+
|
38
|
+
# Represents whether the line_items have been downloaded when getting from GET /API.XRO/2.0/CreditNotes
|
39
|
+
attr_accessor :line_items_downloaded
|
40
|
+
|
41
|
+
# All accessible fields
|
42
|
+
attr_accessor :credit_note_id, :credit_note_number, :type, :status, :date, :reference, :line_amount_types, :currency_code, :line_items, :contact, :payments, :fully_paid_on, :amount_credited
|
43
|
+
|
44
|
+
|
45
|
+
def initialize(params = {})
|
46
|
+
@errors ||= []
|
47
|
+
@payments ||= []
|
48
|
+
|
49
|
+
# Check if the line items have been downloaded.
|
50
|
+
@line_items_downloaded = (params.delete(:line_items_downloaded) == true)
|
51
|
+
|
52
|
+
params = {
|
53
|
+
:line_amount_types => "Inclusive"
|
54
|
+
}.merge(params)
|
55
|
+
|
56
|
+
params.each do |k,v|
|
57
|
+
self.send("#{k}=", v)
|
58
|
+
end
|
59
|
+
|
60
|
+
@line_items ||= []
|
61
|
+
end
|
62
|
+
|
63
|
+
# Validate the Address record according to what will be valid by the gateway.
|
64
|
+
#
|
65
|
+
# Usage:
|
66
|
+
# address.valid? # Returns true/false
|
67
|
+
#
|
68
|
+
# Additionally sets address.errors array to an array of field/error.
|
69
|
+
def valid?
|
70
|
+
@errors = []
|
71
|
+
|
72
|
+
if !credit_note_id.nil? && credit_note_id !~ GUID_REGEX
|
73
|
+
@errors << ['credit_note_id', 'must be blank or a valid Xero GUID']
|
74
|
+
end
|
75
|
+
|
76
|
+
if status && !CREDIT_NOTE_STATUS[status]
|
77
|
+
@errors << ['status', "must be one of #{CREDIT_NOTE_STATUS.keys.join('/')}"]
|
78
|
+
end
|
79
|
+
|
80
|
+
if line_amount_types && !LINE_AMOUNT_TYPES[line_amount_types]
|
81
|
+
@errors << ['line_amount_types', "must be one of #{LINE_AMOUNT_TYPES.keys.join('/')}"]
|
82
|
+
end
|
83
|
+
|
84
|
+
unless date
|
85
|
+
@errors << ['credit_note_date', "can't be blank"]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Make sure contact is valid.
|
89
|
+
unless @contact && @contact.valid?
|
90
|
+
@errors << ['contact', 'is invalid']
|
91
|
+
end
|
92
|
+
|
93
|
+
# Make sure all line_items are valid.
|
94
|
+
unless line_items.all? { | line_item | line_item.valid? }
|
95
|
+
@errors << ['line_items', "at least one line item invalid"]
|
96
|
+
end
|
97
|
+
|
98
|
+
@errors.size == 0
|
99
|
+
end
|
100
|
+
|
101
|
+
# Helper method to create the associated contact object.
|
102
|
+
def build_contact(params = {})
|
103
|
+
self.contact = gateway ? gateway.build_contact(params) : Contact.new(params)
|
104
|
+
end
|
105
|
+
|
106
|
+
def contact
|
107
|
+
@contact ||= build_contact
|
108
|
+
end
|
109
|
+
|
110
|
+
# Helper method to create a new associated line_item.
|
111
|
+
# Usage:
|
112
|
+
# credit_note.add_line_item({:description => "Bob's Widgets", :quantity => 1, :unit_amount => 120})
|
113
|
+
def add_line_item(params = {})
|
114
|
+
line_item = nil
|
115
|
+
case params
|
116
|
+
when Hash then line_item = LineItem.new(params)
|
117
|
+
when LineItem then line_item = params
|
118
|
+
else raise InvalidLineItemError
|
119
|
+
end
|
120
|
+
|
121
|
+
@line_items << line_item
|
122
|
+
|
123
|
+
line_item
|
124
|
+
end
|
125
|
+
|
126
|
+
# Deprecated (but API for setter remains).
|
127
|
+
#
|
128
|
+
# As sub_total must equal SUM(line_item.line_amount) for the API call to pass, this is now
|
129
|
+
# automatically calculated in the sub_total method.
|
130
|
+
def sub_total=(value)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Calculate the sub_total as the SUM(line_item.line_amount).
|
134
|
+
def sub_total
|
135
|
+
line_items.inject(BigDecimal.new('0')) { | sum, line_item | sum + BigDecimal.new(line_item.line_amount.to_s) }
|
136
|
+
end
|
137
|
+
|
138
|
+
# Deprecated (but API for setter remains).
|
139
|
+
#
|
140
|
+
# As total_tax must equal SUM(line_item.tax_amount) for the API call to pass, this is now
|
141
|
+
# automatically calculated in the total_tax method.
|
142
|
+
def total_tax=(value)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Calculate the total_tax as the SUM(line_item.tax_amount).
|
146
|
+
def total_tax
|
147
|
+
line_items.inject(BigDecimal.new('0')) { | sum, line_item | sum + BigDecimal.new(line_item.tax_amount.to_s) }
|
148
|
+
end
|
149
|
+
|
150
|
+
# Deprecated (but API for setter remains).
|
151
|
+
#
|
152
|
+
# As total must equal sub_total + total_tax for the API call to pass, this is now
|
153
|
+
# automatically calculated in the total method.
|
154
|
+
def total=(value)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Calculate the toal as sub_total + total_tax.
|
158
|
+
def total
|
159
|
+
sub_total + total_tax
|
160
|
+
end
|
161
|
+
|
162
|
+
# Helper method to check if the credit_note is accounts payable.
|
163
|
+
def accounts_payable?
|
164
|
+
type == 'ACCPAYCREDIT'
|
165
|
+
end
|
166
|
+
|
167
|
+
# Helper method to check if the credit_note is accounts receivable.
|
168
|
+
def accounts_receivable?
|
169
|
+
type == 'ACCRECCREDIT'
|
170
|
+
end
|
171
|
+
|
172
|
+
# Whether or not the line_items have been downloaded (GET/credit_notes does not download line items).
|
173
|
+
def line_items_downloaded?
|
174
|
+
@line_items_downloaded
|
175
|
+
end
|
176
|
+
|
177
|
+
# If line items are not downloaded, then attempt a download now (if this record was found to begin with).
|
178
|
+
def line_items
|
179
|
+
if line_items_downloaded?
|
180
|
+
@line_items
|
181
|
+
|
182
|
+
# There is an credit_note_is so we can assume this record was loaded from Xero.
|
183
|
+
# attempt to download the line_item records.
|
184
|
+
elsif credit_note_id =~ GUID_REGEX
|
185
|
+
raise NoGatewayError unless @gateway
|
186
|
+
|
187
|
+
response = @gateway.get_credit_note(credit_note_id)
|
188
|
+
raise CreditNoteNotFoundError, "CreditNote with ID #{credit_note_id} not found in Xero." unless response.success? && response.credit_note.is_a?(XeroGateway::CreditNote)
|
189
|
+
|
190
|
+
@line_items = response.credit_note.line_items
|
191
|
+
@line_items_downloaded = true
|
192
|
+
|
193
|
+
@line_items
|
194
|
+
|
195
|
+
# Otherwise, this is a new credit_note, so return the line_items reference.
|
196
|
+
else
|
197
|
+
@line_items
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def ==(other)
|
202
|
+
["credit_note_number", "type", "status", "reference", "currency_code", "line_amount_types", "contact", "line_items"].each do |field|
|
203
|
+
return false if send(field) != other.send(field)
|
204
|
+
end
|
205
|
+
|
206
|
+
["date"].each do |field|
|
207
|
+
return false if send(field).to_s != other.send(field).to_s
|
208
|
+
end
|
209
|
+
return true
|
210
|
+
end
|
211
|
+
|
212
|
+
# General purpose createsave method.
|
213
|
+
# If contact_id and contact_number are nil then create, otherwise, attempt to save.
|
214
|
+
def save
|
215
|
+
create
|
216
|
+
end
|
217
|
+
|
218
|
+
# Creates this credit_note record (using gateway.create_credit_note) with the associated gateway.
|
219
|
+
# If no gateway set, raise a Xero::CreditNote::NoGatewayError exception.
|
220
|
+
def create
|
221
|
+
raise NoGatewayError unless gateway
|
222
|
+
gateway.create_credit_note(self)
|
223
|
+
end
|
224
|
+
|
225
|
+
# Alias create as save as this is currently the only write action.
|
226
|
+
alias_method :save, :create
|
227
|
+
|
228
|
+
def to_xml(b = Builder::XmlMarkup.new)
|
229
|
+
b.CreditNote {
|
230
|
+
b.Type self.type
|
231
|
+
contact.to_xml(b)
|
232
|
+
b.Date CreditNote.format_date(self.date || Date.today)
|
233
|
+
b.Status self.status if self.status
|
234
|
+
b.CreditNoteNumber self.credit_note_number if credit_note_number
|
235
|
+
b.Reference self.reference if self.reference
|
236
|
+
b.CurrencyCode self.currency_code if self.currency_code
|
237
|
+
b.LineAmountTypes self.line_amount_types
|
238
|
+
b.LineItems {
|
239
|
+
self.line_items.each do |line_item|
|
240
|
+
line_item.to_xml(b)
|
241
|
+
end
|
242
|
+
}
|
243
|
+
}
|
244
|
+
end
|
245
|
+
|
246
|
+
#TODO UpdatedDateUTC
|
247
|
+
def self.from_xml(credit_note_element, gateway = nil, options = {})
|
248
|
+
credit_note = CreditNote.new(options.merge({:gateway => gateway}))
|
249
|
+
credit_note_element.children.each do |element|
|
250
|
+
case(element.name)
|
251
|
+
when "CreditNoteID" then credit_note.credit_note_id = element.text
|
252
|
+
when "CreditNoteNumber" then credit_note.credit_note_number = element.text
|
253
|
+
when "Type" then credit_note.type = element.text
|
254
|
+
when "CurrencyCode" then credit_note.currency_code = element.text
|
255
|
+
when "Contact" then credit_note.contact = Contact.from_xml(element)
|
256
|
+
when "Date" then credit_note.date = parse_date(element.text)
|
257
|
+
when "Status" then credit_note.status = element.text
|
258
|
+
when "Reference" then credit_note.reference = element.text
|
259
|
+
when "LineAmountTypes" then credit_note.line_amount_types = element.text
|
260
|
+
when "LineItems" then element.children.each {|line_item| credit_note.line_items_downloaded = true; credit_note.line_items << LineItem.from_xml(line_item) }
|
261
|
+
when "SubTotal" then credit_note.sub_total = BigDecimal.new(element.text)
|
262
|
+
when "TotalTax" then credit_note.total_tax = BigDecimal.new(element.text)
|
263
|
+
when "Total" then credit_note.total = BigDecimal.new(element.text)
|
264
|
+
when "CreditNoteID" then credit_note.credit_note_id = element.text
|
265
|
+
when "CreditNoteNumber" then credit_note.credit_note_number = element.text
|
266
|
+
when "Payments" then element.children.each { | payment | credit_note.payments << Payment.from_xml(payment) }
|
267
|
+
when "AmountDue" then credit_note.amount_due = BigDecimal.new(element.text)
|
268
|
+
when "AmountPaid" then credit_note.amount_paid = BigDecimal.new(element.text)
|
269
|
+
when "AmountCredited" then credit_note.amount_credited = BigDecimal.new(element.text)
|
270
|
+
end
|
271
|
+
end
|
272
|
+
credit_note
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
data/lib/xero_gateway/gateway.rb
CHANGED
@@ -21,16 +21,21 @@ module XeroGateway
|
|
21
21
|
# Retrieve all contacts from Xero
|
22
22
|
#
|
23
23
|
# Usage : get_contacts(:order => :name)
|
24
|
-
# get_contacts(:
|
24
|
+
# get_contacts(:modified_since => Time)
|
25
25
|
#
|
26
26
|
# Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
|
27
27
|
def get_contacts(options = {})
|
28
28
|
request_params = {}
|
29
29
|
|
30
|
+
if !options[:updated_after].nil?
|
31
|
+
warn '[warning] :updated_after is depracated in XeroGateway#get_contacts. Use :modified_since'
|
32
|
+
options[:modified_since] = options.delete(:updated_after)
|
33
|
+
end
|
34
|
+
|
30
35
|
request_params[:ContactID] = options[:contact_id] if options[:contact_id]
|
31
36
|
request_params[:ContactNumber] = options[:contact_number] if options[:contact_number]
|
32
37
|
request_params[:OrderBy] = options[:order] if options[:order]
|
33
|
-
request_params[:ModifiedAfter] =
|
38
|
+
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
34
39
|
request_params[:where] = options[:where] if options[:where]
|
35
40
|
|
36
41
|
response_xml = http_get(@client, "#{@xero_url}/Contacts", request_params)
|
@@ -134,7 +139,7 @@ module XeroGateway
|
|
134
139
|
request_params[:InvoiceID] = options[:invoice_id] if options[:invoice_id]
|
135
140
|
request_params[:InvoiceNumber] = options[:invoice_number] if options[:invoice_number]
|
136
141
|
request_params[:OrderBy] = options[:order] if options[:order]
|
137
|
-
request_params[:ModifiedAfter] = options[:modified_since]
|
142
|
+
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
138
143
|
|
139
144
|
request_params[:where] = options[:where] if options[:where]
|
140
145
|
|
@@ -231,6 +236,116 @@ module XeroGateway
|
|
231
236
|
response
|
232
237
|
end
|
233
238
|
|
239
|
+
# Retrieves all credit_notes from Xero
|
240
|
+
#
|
241
|
+
# Usage : get_credit_notes
|
242
|
+
# get_credit_notes(:credit_note_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
|
243
|
+
#
|
244
|
+
# Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
|
245
|
+
def get_credit_notes(options = {})
|
246
|
+
|
247
|
+
request_params = {}
|
248
|
+
|
249
|
+
request_params[:CreditNoteID] = options[:credit_note_id] if options[:credit_note_id]
|
250
|
+
request_params[:CreditNoteNumber] = options[:credit_note_number] if options[:credit_note_number]
|
251
|
+
request_params[:OrderBy] = options[:order] if options[:order]
|
252
|
+
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
253
|
+
|
254
|
+
request_params[:where] = options[:where] if options[:where]
|
255
|
+
|
256
|
+
response_xml = http_get(@client, "#{@xero_url}/CreditNotes", request_params)
|
257
|
+
|
258
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/CreditNotes'})
|
259
|
+
end
|
260
|
+
|
261
|
+
# Retrieves a single credit_note
|
262
|
+
#
|
263
|
+
# Usage : get_credit_note("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
|
264
|
+
# get_credit_note("OIT-12345") # By number
|
265
|
+
def get_credit_note(credit_note_id_or_number)
|
266
|
+
request_params = {}
|
267
|
+
|
268
|
+
url = "#{@xero_url}/CreditNotes/#{URI.escape(credit_note_id_or_number)}"
|
269
|
+
|
270
|
+
response_xml = http_get(@client, url, request_params)
|
271
|
+
|
272
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/CreditNote'})
|
273
|
+
end
|
274
|
+
|
275
|
+
# Factory method for building new CreditNote objects associated with this gateway.
|
276
|
+
def build_credit_note(credit_note = {})
|
277
|
+
case credit_note
|
278
|
+
when CreditNote then credit_note.gateway = self
|
279
|
+
when Hash then credit_note = CreditNote.new(credit_note.merge(:gateway => self))
|
280
|
+
end
|
281
|
+
credit_note
|
282
|
+
end
|
283
|
+
|
284
|
+
# Creates an credit_note in Xero based on an credit_note object.
|
285
|
+
#
|
286
|
+
# CreditNote and line item totals are calculated automatically.
|
287
|
+
#
|
288
|
+
# Usage :
|
289
|
+
#
|
290
|
+
# credit_note = XeroGateway::CreditNote.new({
|
291
|
+
# :credit_note_type => "ACCREC",
|
292
|
+
# :due_date => 1.month.from_now,
|
293
|
+
# :credit_note_number => "YOUR CREDIT_NOTE NUMBER",
|
294
|
+
# :reference => "YOUR REFERENCE (NOT NECESSARILY UNIQUE!)",
|
295
|
+
# :line_amount_types => "Inclusive"
|
296
|
+
# })
|
297
|
+
# credit_note.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT")
|
298
|
+
# credit_note.contact.phone.number = "12345"
|
299
|
+
# credit_note.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
|
300
|
+
# credit_note.line_items << XeroGateway::LineItem.new(
|
301
|
+
# :description => "THE DESCRIPTION OF THE LINE ITEM",
|
302
|
+
# :unit_amount => 100,
|
303
|
+
# :tax_amount => 12.5,
|
304
|
+
# :tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM",
|
305
|
+
# :tracking_option => "THE TRACKING OPTION FOR THE LINE ITEM"
|
306
|
+
# )
|
307
|
+
#
|
308
|
+
# create_credit_note(credit_note)
|
309
|
+
def create_credit_note(credit_note)
|
310
|
+
request_xml = credit_note.to_xml
|
311
|
+
response_xml = http_put(@client, "#{@xero_url}/CreditNotes", request_xml)
|
312
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/credit_note'})
|
313
|
+
|
314
|
+
# Xero returns credit_notes inside an <CreditNotes> tag, even though there's only ever
|
315
|
+
# one for this request
|
316
|
+
response.response_item = response.credit_notes.first
|
317
|
+
|
318
|
+
if response.success? && response.credit_note && response.credit_note.credit_note_id
|
319
|
+
credit_note.credit_note_id = response.credit_note.credit_note_id
|
320
|
+
end
|
321
|
+
|
322
|
+
response
|
323
|
+
end
|
324
|
+
|
325
|
+
#
|
326
|
+
# Creates an array of credit_notes with a single API request.
|
327
|
+
#
|
328
|
+
# Usage :
|
329
|
+
# credit_notes = [XeroGateway::CreditNote.new(...), XeroGateway::CreditNote.new(...)]
|
330
|
+
# result = gateway.create_credit_notes(credit_notes)
|
331
|
+
#
|
332
|
+
def create_credit_notes(credit_notes)
|
333
|
+
b = Builder::XmlMarkup.new
|
334
|
+
request_xml = b.CreditNotes {
|
335
|
+
credit_notes.each do | credit_note |
|
336
|
+
credit_note.to_xml(b)
|
337
|
+
end
|
338
|
+
}
|
339
|
+
|
340
|
+
response_xml = http_put(@client, "#{@xero_url}/CreditNotes", request_xml, {})
|
341
|
+
|
342
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/credit_notes'})
|
343
|
+
response.credit_notes.each_with_index do | response_credit_note, index |
|
344
|
+
credit_notes[index].credit_note_id = response_credit_note.credit_note_id if response_credit_note && response_credit_note.credit_note_id
|
345
|
+
end
|
346
|
+
response
|
347
|
+
end
|
348
|
+
|
234
349
|
#
|
235
350
|
# Gets all accounts for a specific organization in Xero.
|
236
351
|
#
|
@@ -331,6 +446,7 @@ module XeroGateway
|
|
331
446
|
when "Invoice" then response.response_item = Invoice.from_xml(element, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"})
|
332
447
|
when "Contacts" then element.children.each {|child| response.response_item << Contact.from_xml(child, self) }
|
333
448
|
when "Invoices" then element.children.each {|child| response.response_item << Invoice.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"}) }
|
449
|
+
when "CreditNotes" then element.children.each {|child| response.response_item << CreditNote.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/CreditNotes"}) }
|
334
450
|
when "Accounts" then element.children.each {|child| response.response_item << Account.from_xml(child) }
|
335
451
|
when "TaxRates" then element.children.each {|child| response.response_item << TaxRate.from_xml(child) }
|
336
452
|
when "Currencies" then element.children.each {|child| response.response_item << Currency.from_xml(child) }
|
data/lib/xero_gateway/http.rb
CHANGED
@@ -85,10 +85,14 @@ module XeroGateway
|
|
85
85
|
description = error_details["oauth_problem_advice"].first
|
86
86
|
|
87
87
|
# see http://oauth.pbworks.com/ProblemReporting
|
88
|
-
#
|
88
|
+
# In addition to token_expired and token_rejected, Xero also returns
|
89
|
+
# 'rate limit exceeded' when more than 60 requests have been made in
|
90
|
+
# a second.
|
89
91
|
case (error_details["oauth_problem"].first)
|
90
92
|
when "token_expired" then raise OAuth::TokenExpired.new(description)
|
91
93
|
when "token_rejected" then raise OAuth::TokenInvalid.new(description)
|
94
|
+
when "rate limit exceeded" then raise OAuth::RateLimitExceeded.new(description)
|
95
|
+
else raise OAuth::UnknownError.new(error_details["oauth_problem"].first + ':' + description)
|
92
96
|
end
|
93
97
|
end
|
94
98
|
|
@@ -117,12 +121,12 @@ module XeroGateway
|
|
117
121
|
end
|
118
122
|
|
119
123
|
def handle_object_not_found!(response, request_url)
|
120
|
-
|
121
|
-
raise InvoiceNotFoundError.new("Invoice not found in Xero.")
|
122
|
-
|
123
|
-
raise ObjectNotFound.new(request_url)
|
124
|
+
case(request_url)
|
125
|
+
when /Invoices/ then raise InvoiceNotFoundError.new("Invoice not found in Xero.")
|
126
|
+
when /CreditNotes/ then raise CreditNoteNotFoundError.new("Credit Note not found in Xero.")
|
127
|
+
else raise ObjectNotFound.new(request_url)
|
124
128
|
end
|
125
129
|
end
|
126
130
|
|
127
131
|
end
|
128
|
-
end
|
132
|
+
end
|
data/lib/xero_gateway/oauth.rb
CHANGED
@@ -10,6 +10,8 @@ module XeroGateway
|
|
10
10
|
|
11
11
|
class TokenExpired < StandardError; end
|
12
12
|
class TokenInvalid < StandardError; end
|
13
|
+
class RateLimitExceeded < StandardError; end
|
14
|
+
class UnknownError < StandardError; end
|
13
15
|
|
14
16
|
unless defined? XERO_CONSUMER_OPTIONS
|
15
17
|
XERO_CONSUMER_OPTIONS = {
|
@@ -53,4 +55,4 @@ module XeroGateway
|
|
53
55
|
end
|
54
56
|
|
55
57
|
end
|
56
|
-
end
|
58
|
+
end
|
@@ -7,9 +7,11 @@ module XeroGateway
|
|
7
7
|
end
|
8
8
|
|
9
9
|
alias_method :invoice, :response_item
|
10
|
+
alias_method :credit_note, :response_item
|
10
11
|
alias_method :contact, :response_item
|
11
12
|
alias_method :organisation, :response_item
|
12
13
|
alias_method :invoices, :array_wrapped_response_item
|
14
|
+
alias_method :credit_notes, :array_wrapped_response_item
|
13
15
|
alias_method :contacts, :array_wrapped_response_item
|
14
16
|
alias_method :accounts, :array_wrapped_response_item
|
15
17
|
alias_method :tracking_categories, :array_wrapped_response_item
|