xero_gateway-n8vision 2.0.20

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 (78) hide show
  1. data/Gemfile +12 -0
  2. data/LICENSE +14 -0
  3. data/README.textile +357 -0
  4. data/Rakefile +14 -0
  5. data/examples/oauth.rb +25 -0
  6. data/examples/partner_app.rb +36 -0
  7. data/init.rb +1 -0
  8. data/lib/oauth/oauth_consumer.rb +14 -0
  9. data/lib/xero_gateway.rb +41 -0
  10. data/lib/xero_gateway/account.rb +86 -0
  11. data/lib/xero_gateway/accounts_list.rb +73 -0
  12. data/lib/xero_gateway/address.rb +96 -0
  13. data/lib/xero_gateway/bank_transaction.rb +175 -0
  14. data/lib/xero_gateway/ca-certificates.crt +2560 -0
  15. data/lib/xero_gateway/contact.rb +203 -0
  16. data/lib/xero_gateway/credit_note.rb +220 -0
  17. data/lib/xero_gateway/currency.rb +56 -0
  18. data/lib/xero_gateway/dates.rb +25 -0
  19. data/lib/xero_gateway/error.rb +18 -0
  20. data/lib/xero_gateway/exceptions.rb +51 -0
  21. data/lib/xero_gateway/gateway.rb +698 -0
  22. data/lib/xero_gateway/http.rb +135 -0
  23. data/lib/xero_gateway/http_encoding_helper.rb +49 -0
  24. data/lib/xero_gateway/invoice.rb +238 -0
  25. data/lib/xero_gateway/journal_line.rb +102 -0
  26. data/lib/xero_gateway/line_item.rb +125 -0
  27. data/lib/xero_gateway/line_item_calculations.rb +51 -0
  28. data/lib/xero_gateway/manual_journal.rb +163 -0
  29. data/lib/xero_gateway/money.rb +16 -0
  30. data/lib/xero_gateway/oauth.rb +92 -0
  31. data/lib/xero_gateway/organisation.rb +75 -0
  32. data/lib/xero_gateway/partner_app.rb +30 -0
  33. data/lib/xero_gateway/payment.rb +43 -0
  34. data/lib/xero_gateway/phone.rb +77 -0
  35. data/lib/xero_gateway/private_app.rb +17 -0
  36. data/lib/xero_gateway/response.rb +43 -0
  37. data/lib/xero_gateway/tax_rate.rb +63 -0
  38. data/lib/xero_gateway/tracking_category.rb +87 -0
  39. data/test/integration/accounts_list_test.rb +109 -0
  40. data/test/integration/create_bank_transaction_test.rb +38 -0
  41. data/test/integration/create_contact_test.rb +66 -0
  42. data/test/integration/create_credit_note_test.rb +49 -0
  43. data/test/integration/create_invoice_test.rb +49 -0
  44. data/test/integration/create_manual_journal_test.rb +35 -0
  45. data/test/integration/get_accounts_test.rb +23 -0
  46. data/test/integration/get_bank_transaction_test.rb +51 -0
  47. data/test/integration/get_bank_transactions_test.rb +88 -0
  48. data/test/integration/get_contact_test.rb +28 -0
  49. data/test/integration/get_contacts_test.rb +40 -0
  50. data/test/integration/get_credit_note_test.rb +48 -0
  51. data/test/integration/get_credit_notes_test.rb +90 -0
  52. data/test/integration/get_currencies_test.rb +25 -0
  53. data/test/integration/get_invoice_test.rb +48 -0
  54. data/test/integration/get_invoices_test.rb +92 -0
  55. data/test/integration/get_manual_journal_test.rb +50 -0
  56. data/test/integration/get_manual_journals_test.rb +88 -0
  57. data/test/integration/get_organisation_test.rb +24 -0
  58. data/test/integration/get_tax_rates_test.rb +25 -0
  59. data/test/integration/get_tracking_categories_test.rb +27 -0
  60. data/test/integration/update_bank_transaction_test.rb +31 -0
  61. data/test/integration/update_contact_test.rb +31 -0
  62. data/test/integration/update_invoice_test.rb +31 -0
  63. data/test/integration/update_manual_journal_test.rb +31 -0
  64. data/test/test_helper.rb +217 -0
  65. data/test/unit/account_test.rb +47 -0
  66. data/test/unit/bank_transaction_test.rb +126 -0
  67. data/test/unit/contact_test.rb +97 -0
  68. data/test/unit/credit_note_test.rb +284 -0
  69. data/test/unit/currency_test.rb +31 -0
  70. data/test/unit/gateway_test.rb +119 -0
  71. data/test/unit/invoice_test.rb +326 -0
  72. data/test/unit/manual_journal_test.rb +93 -0
  73. data/test/unit/oauth_test.rb +116 -0
  74. data/test/unit/organisation_test.rb +38 -0
  75. data/test/unit/tax_rate_test.rb +38 -0
  76. data/test/unit/tracking_category_test.rb +52 -0
  77. data/xero_gateway-n8vision.gemspec +15 -0
  78. metadata +178 -0
@@ -0,0 +1,25 @@
1
+ module XeroGateway
2
+ module Dates
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def format_date(time)
9
+ return time.strftime("%Y-%m-%d")
10
+ end
11
+
12
+ def format_date_time(time)
13
+ return time.strftime("%Y%m%d%H%M%S")
14
+ end
15
+
16
+ def parse_date(time)
17
+ Date.civil(time[0..3].to_i, time[5..6].to_i, time[8..9].to_i)
18
+ end
19
+
20
+ def parse_date_time(time)
21
+ Time.local(time[0..3].to_i, time[5..6].to_i, time[8..9].to_i, time[11..12].to_i, time[14..15].to_i, time[17..18].to_i)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
1
+ module XeroGateway
2
+ class Error
3
+ attr_accessor :description, :date_time, :type, :message
4
+
5
+ def initialize(params = {})
6
+ params.each do |k,v|
7
+ self.send("#{k}=", v)
8
+ end
9
+ end
10
+
11
+ def ==(other)
12
+ [:description, :date_time, :type, :message].each do |field|
13
+ return false if send(field) != other.send(field)
14
+ end
15
+ return true
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,51 @@
1
+ module XeroGateway
2
+ class NoGatewayError < StandardError; end
3
+ class AccountsListNotLoadedError < StandardError; end
4
+ class InvalidLineItemError < StandardError; end
5
+
6
+ class ApiException < StandardError
7
+
8
+ def initialize(type, message, request_xml, response_xml)
9
+ @type = type
10
+ @message = message
11
+ @request_xml = request_xml
12
+ @response_xml = response_xml
13
+ end
14
+
15
+ attr_reader :request_xml, :response_xml
16
+
17
+ def message
18
+ "#{@type}: #{@message} \n Generated by the following XML: \n #{@response_xml}"
19
+ end
20
+
21
+ end
22
+
23
+ class UnparseableResponse < StandardError
24
+
25
+ def initialize(root_element_name)
26
+ @root_element_name = root_element_name
27
+ end
28
+
29
+ def message
30
+ "A root element of #{@root_element_name} was returned, and we don't understand that!"
31
+ end
32
+
33
+ end
34
+
35
+ class ObjectNotFound < StandardError
36
+
37
+ def initialize(api_endpoint)
38
+ @api_endpoint = api_endpoint
39
+ end
40
+
41
+ def message
42
+ "Couldn't find object for API Endpoint #{@api_endpoint}"
43
+ end
44
+
45
+ end
46
+
47
+ class InvoiceNotFoundError < StandardError; end
48
+ class BankTransactionNotFoundError < StandardError; end
49
+ class CreditNoteNotFoundError < StandardError; end
50
+ class ManualJournalNotFoundError < StandardError; end
51
+ end
@@ -0,0 +1,698 @@
1
+ module XeroGateway
2
+
3
+ class Gateway
4
+ include Http
5
+ include Dates
6
+
7
+ attr_accessor :client, :xero_url, :logger
8
+
9
+ extend Forwardable
10
+ def_delegators :client, :request_token, :access_token, :authorize_from_request, :authorize_from_access, :expires_at, :authorization_expires_at
11
+
12
+ #
13
+ # The consumer key and secret here correspond to those provided
14
+ # to you by Xero inside the API Previewer.
15
+ def initialize(consumer_key, consumer_secret, options = {})
16
+ @xero_url = options[:xero_url] || "https://api.xero.com/api.xro/2.0"
17
+ @client = OAuth.new(consumer_key, consumer_secret, options)
18
+ end
19
+
20
+ #
21
+ # Retrieve all contacts from Xero
22
+ #
23
+ # Usage : get_contacts(:order => :name)
24
+ # get_contacts(:modified_since => Time)
25
+ #
26
+ # Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
27
+ def get_contacts(options = {})
28
+ request_params = {}
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
+
35
+ request_params[:ContactID] = options[:contact_id] if options[:contact_id]
36
+ request_params[:ContactNumber] = options[:contact_number] if options[:contact_number]
37
+ request_params[:OrderBy] = options[:order] if options[:order]
38
+ request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
39
+ request_params[:where] = options[:where] if options[:where]
40
+
41
+ response_xml = http_get(@client, "#{@xero_url}/Contacts", request_params)
42
+
43
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contacts'})
44
+ end
45
+
46
+ # Retrieve a contact from Xero
47
+ # Usage get_contact_by_id(contact_id)
48
+ def get_contact_by_id(contact_id)
49
+ get_contact(contact_id)
50
+ end
51
+
52
+ # Retrieve a contact from Xero
53
+ # Usage get_contact_by_id(contact_id)
54
+ def get_contact_by_number(contact_number)
55
+ get_contact(nil, contact_number)
56
+ end
57
+
58
+ # Factory method for building new Contact objects associated with this gateway.
59
+ def build_contact(contact = {})
60
+ case contact
61
+ when Contact then contact.gateway = self
62
+ when Hash then contact = Contact.new(contact.merge({:gateway => self}))
63
+ end
64
+ contact
65
+ end
66
+
67
+ #
68
+ # Creates a contact in Xero
69
+ #
70
+ # Usage :
71
+ #
72
+ # contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT #{Time.now.to_i}")
73
+ # contact.email = "whoever@something.com"
74
+ # contact.phone.number = "12345"
75
+ # contact.address.line_1 = "LINE 1 OF THE ADDRESS"
76
+ # contact.address.line_2 = "LINE 2 OF THE ADDRESS"
77
+ # contact.address.line_3 = "LINE 3 OF THE ADDRESS"
78
+ # contact.address.line_4 = "LINE 4 OF THE ADDRESS"
79
+ # contact.address.city = "WELLINGTON"
80
+ # contact.address.region = "WELLINGTON"
81
+ # contact.address.country = "NEW ZEALAND"
82
+ # contact.address.post_code = "6021"
83
+ #
84
+ # create_contact(contact)
85
+ def create_contact(contact)
86
+ save_contact(contact)
87
+ end
88
+
89
+ #
90
+ # Updates an existing Xero contact
91
+ #
92
+ # Usage :
93
+ #
94
+ # contact = xero_gateway.get_contact(some_contact_id)
95
+ # contact.email = "a_new_email_ddress"
96
+ #
97
+ # xero_gateway.update_contact(contact)
98
+ def update_contact(contact)
99
+ raise "contact_id or contact_number is required for updating contacts" if contact.contact_id.nil? and contact.contact_number.nil?
100
+ save_contact(contact)
101
+ end
102
+
103
+ #
104
+ # Updates an array of contacts in a single API operation.
105
+ #
106
+ # Usage :
107
+ # contacts = [XeroGateway::Contact.new(:name => 'Joe Bloggs'), XeroGateway::Contact.new(:name => 'Jane Doe')]
108
+ # result = gateway.update_contacts(contacts)
109
+ #
110
+ # Will update contacts with matching contact_id, contact_number or name or create if they don't exist.
111
+ #
112
+ def update_contacts(contacts)
113
+ b = Builder::XmlMarkup.new
114
+ request_xml = b.Contacts {
115
+ contacts.each do | contact |
116
+ contact.to_xml(b)
117
+ end
118
+ }
119
+
120
+ response_xml = http_post(@client, "#{@xero_url}/Contacts", request_xml, {})
121
+
122
+ response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'POST/contacts'})
123
+ response.contacts.each_with_index do | response_contact, index |
124
+ contacts[index].contact_id = response_contact.contact_id if response_contact && response_contact.contact_id
125
+ end
126
+ response
127
+ end
128
+
129
+ # Retrieves all invoices from Xero
130
+ #
131
+ # Usage : get_invoices
132
+ # get_invoices(:invoice_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
133
+ #
134
+ # Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
135
+ def get_invoices(options = {})
136
+
137
+ request_params = {}
138
+
139
+ request_params[:InvoiceID] = options[:invoice_id] if options[:invoice_id]
140
+ request_params[:InvoiceNumber] = options[:invoice_number] if options[:invoice_number]
141
+ request_params[:OrderBy] = options[:order] if options[:order]
142
+ request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
143
+
144
+ request_params[:where] = options[:where] if options[:where]
145
+
146
+ response_xml = http_get(@client, "#{@xero_url}/Invoices", request_params)
147
+
148
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/Invoices'})
149
+ end
150
+
151
+ # Retrieves a single invoice
152
+ #
153
+ # Usage : get_invoice("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
154
+ # get_invoice("OIT-12345") # By number
155
+ def get_invoice(invoice_id_or_number)
156
+ request_params = {}
157
+
158
+ url = "#{@xero_url}/Invoices/#{URI.escape(invoice_id_or_number)}"
159
+
160
+ response_xml = http_get(@client, url, request_params)
161
+
162
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/Invoice'})
163
+ end
164
+
165
+ # Factory method for building new Invoice objects associated with this gateway.
166
+ def build_invoice(invoice = {})
167
+ case invoice
168
+ when Invoice then invoice.gateway = self
169
+ when Hash then invoice = Invoice.new(invoice.merge(:gateway => self))
170
+ end
171
+ invoice
172
+ end
173
+
174
+ # Creates an invoice in Xero based on an invoice object.
175
+ #
176
+ # Invoice and line item totals are calculated automatically.
177
+ #
178
+ # Usage :
179
+ #
180
+ # invoice = XeroGateway::Invoice.new({
181
+ # :invoice_type => "ACCREC",
182
+ # :due_date => 1.month.from_now,
183
+ # :invoice_number => "YOUR INVOICE NUMBER",
184
+ # :reference => "YOUR REFERENCE (NOT NECESSARILY UNIQUE!)",
185
+ # :line_amount_types => "Inclusive"
186
+ # })
187
+ # invoice.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT")
188
+ # invoice.contact.phone.number = "12345"
189
+ # invoice.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
190
+ # invoice.line_items << XeroGateway::LineItem.new(
191
+ # :description => "THE DESCRIPTION OF THE LINE ITEM",
192
+ # :unit_amount => 100,
193
+ # :tax_amount => 12.5,
194
+ # :tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM",
195
+ # :tracking_option => "THE TRACKING OPTION FOR THE LINE ITEM"
196
+ # )
197
+ #
198
+ # create_invoice(invoice)
199
+ def create_invoice(invoice)
200
+ save_invoice(invoice)
201
+ end
202
+
203
+ #
204
+ # Updates an existing Xero invoice
205
+ #
206
+ # Usage :
207
+ #
208
+ # invoice = xero_gateway.get_invoice(some_invoice_id)
209
+ # invoice.due_date = Date.today
210
+ #
211
+ # xero_gateway.update_invoice(invoice)
212
+
213
+ def update_invoice(invoice)
214
+ raise "invoice_id is required for updating invoices" if invoice.invoice_id.nil?
215
+ save_invoice(invoice)
216
+ end
217
+
218
+ #
219
+ # Creates an array of invoices with a single API request.
220
+ #
221
+ # Usage :
222
+ # invoices = [XeroGateway::Invoice.new(...), XeroGateway::Invoice.new(...)]
223
+ # result = gateway.create_invoices(invoices)
224
+ #
225
+ def create_invoices(invoices)
226
+ b = Builder::XmlMarkup.new
227
+ request_xml = b.Invoices {
228
+ invoices.each do | invoice |
229
+ invoice.to_xml(b)
230
+ end
231
+ }
232
+
233
+ response_xml = http_put(@client, "#{@xero_url}/Invoices", request_xml, {})
234
+
235
+ response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/invoices'})
236
+ response.invoices.each_with_index do | response_invoice, index |
237
+ invoices[index].invoice_id = response_invoice.invoice_id if response_invoice && response_invoice.invoice_id
238
+ end
239
+ response
240
+ end
241
+
242
+ # Retrieves all credit_notes from Xero
243
+ #
244
+ # Usage : get_credit_notes
245
+ # get_credit_notes(:credit_note_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
246
+ #
247
+ # Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
248
+ def get_credit_notes(options = {})
249
+
250
+ request_params = {}
251
+
252
+ request_params[:CreditNoteID] = options[:credit_note_id] if options[:credit_note_id]
253
+ request_params[:CreditNoteNumber] = options[:credit_note_number] if options[:credit_note_number]
254
+ request_params[:OrderBy] = options[:order] if options[:order]
255
+ request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
256
+
257
+ request_params[:where] = options[:where] if options[:where]
258
+
259
+ response_xml = http_get(@client, "#{@xero_url}/CreditNotes", request_params)
260
+
261
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/CreditNotes'})
262
+ end
263
+
264
+ # Retrieves a single credit_note
265
+ #
266
+ # Usage : get_credit_note("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
267
+ # get_credit_note("OIT-12345") # By number
268
+ def get_credit_note(credit_note_id_or_number)
269
+ request_params = {}
270
+
271
+ url = "#{@xero_url}/CreditNotes/#{URI.escape(credit_note_id_or_number)}"
272
+
273
+ response_xml = http_get(@client, url, request_params)
274
+
275
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/CreditNote'})
276
+ end
277
+
278
+ # Factory method for building new CreditNote objects associated with this gateway.
279
+ def build_credit_note(credit_note = {})
280
+ case credit_note
281
+ when CreditNote then credit_note.gateway = self
282
+ when Hash then credit_note = CreditNote.new(credit_note.merge(:gateway => self))
283
+ end
284
+ credit_note
285
+ end
286
+
287
+ # Creates an credit_note in Xero based on an credit_note object.
288
+ #
289
+ # CreditNote and line item totals are calculated automatically.
290
+ #
291
+ # Usage :
292
+ #
293
+ # credit_note = XeroGateway::CreditNote.new({
294
+ # :credit_note_type => "ACCREC",
295
+ # :due_date => 1.month.from_now,
296
+ # :credit_note_number => "YOUR CREDIT_NOTE NUMBER",
297
+ # :reference => "YOUR REFERENCE (NOT NECESSARILY UNIQUE!)",
298
+ # :line_amount_types => "Inclusive"
299
+ # })
300
+ # credit_note.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT")
301
+ # credit_note.contact.phone.number = "12345"
302
+ # credit_note.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
303
+ # credit_note.line_items << XeroGateway::LineItem.new(
304
+ # :description => "THE DESCRIPTION OF THE LINE ITEM",
305
+ # :unit_amount => 100,
306
+ # :tax_amount => 12.5,
307
+ # :tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM",
308
+ # :tracking_option => "THE TRACKING OPTION FOR THE LINE ITEM"
309
+ # )
310
+ #
311
+ # create_credit_note(credit_note)
312
+ def create_credit_note(credit_note)
313
+ request_xml = credit_note.to_xml
314
+ response_xml = http_put(@client, "#{@xero_url}/CreditNotes", request_xml)
315
+ response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/credit_note'})
316
+
317
+ # Xero returns credit_notes inside an <CreditNotes> tag, even though there's only ever
318
+ # one for this request
319
+ response.response_item = response.credit_notes.first
320
+
321
+ if response.success? && response.credit_note && response.credit_note.credit_note_id
322
+ credit_note.credit_note_id = response.credit_note.credit_note_id
323
+ end
324
+
325
+ response
326
+ end
327
+
328
+ #
329
+ # Creates an array of credit_notes with a single API request.
330
+ #
331
+ # Usage :
332
+ # credit_notes = [XeroGateway::CreditNote.new(...), XeroGateway::CreditNote.new(...)]
333
+ # result = gateway.create_credit_notes(credit_notes)
334
+ #
335
+ def create_credit_notes(credit_notes)
336
+ b = Builder::XmlMarkup.new
337
+ request_xml = b.CreditNotes {
338
+ credit_notes.each do | credit_note |
339
+ credit_note.to_xml(b)
340
+ end
341
+ }
342
+
343
+ response_xml = http_put(@client, "#{@xero_url}/CreditNotes", request_xml, {})
344
+
345
+ response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/credit_notes'})
346
+ response.credit_notes.each_with_index do | response_credit_note, index |
347
+ credit_notes[index].credit_note_id = response_credit_note.credit_note_id if response_credit_note && response_credit_note.credit_note_id
348
+ end
349
+ response
350
+ end
351
+
352
+ # Creates a bank transaction in Xero based on a bank transaction object.
353
+ #
354
+ # Bank transaction and line item totals are calculated automatically.
355
+ #
356
+ # Usage :
357
+ #
358
+ # bank_transaction = XeroGateway::BankTransaction.new({
359
+ # :type => "RECEIVE",
360
+ # :date => 1.month.from_now,
361
+ # :reference => "YOUR INVOICE NUMBER",
362
+ # })
363
+ # bank_transaction.contact = XeroGateway::Contact.new(:name => "THE NAME OF THE CONTACT")
364
+ # bank_transaction.contact.phone.number = "12345"
365
+ # bank_transaction.contact.address.line_1 = "LINE 1 OF THE ADDRESS"
366
+ # bank_transaction.line_items << XeroGateway::LineItem.new(
367
+ # :description => "THE DESCRIPTION OF THE LINE ITEM",
368
+ # :unit_amount => 100,
369
+ # :tax_amount => 12.5,
370
+ # :tracking_category => "THE TRACKING CATEGORY FOR THE LINE ITEM",
371
+ # :tracking_option => "THE TRACKING OPTION FOR THE LINE ITEM"
372
+ # )
373
+ # bank_transaction.bank_account = XeroGateway::Account.new(:code => 'BANK-ABC)
374
+ #
375
+ # create_bank_transaction(bank_transaction)
376
+ def create_bank_transaction(bank_transaction)
377
+ save_bank_transaction(bank_transaction)
378
+ end
379
+
380
+ #
381
+ # Updates an existing Xero bank transaction
382
+ #
383
+ # Usage :
384
+ #
385
+ # bank_transaction = xero_gateway.get_bank_transaction(some_bank_transaction_id)
386
+ # bank_transaction.due_date = Date.today
387
+ #
388
+ # xero_gateway.update_bank_transaction(bank_transaction)
389
+ def update_bank_transaction(bank_transaction)
390
+ raise "bank_transaction_id is required for updating bank transactions" if bank_transaction.bank_transaction_id.nil?
391
+ save_bank_transaction(bank_transaction)
392
+ end
393
+
394
+ # Retrieves all bank transactions from Xero
395
+ #
396
+ # Usage : get_bank_transactions
397
+ # get_bank_transactions(:bank_transaction_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
398
+ #
399
+ # Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
400
+ def get_bank_transactions(options = {})
401
+ request_params = {}
402
+ request_params[:BankTransactionID] = options[:bank_transaction_id] if options[:bank_transaction_id]
403
+ request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
404
+
405
+ response_xml = http_get(@client, "#{@xero_url}/BankTransactions", request_params)
406
+
407
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/BankTransactions'})
408
+ end
409
+
410
+ # Retrieves a single bank transaction
411
+ #
412
+ # Usage : get_bank_transaction("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
413
+ # get_bank_transaction("OIT-12345") # By number
414
+ def get_bank_transaction(bank_transaction_id)
415
+ request_params = {}
416
+ url = "#{@xero_url}/BankTransactions/#{URI.escape(bank_transaction_id)}"
417
+ response_xml = http_get(@client, url, request_params)
418
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/BankTransaction'})
419
+ end
420
+
421
+ # Creates a manual journal in Xero based on a manual journal object.
422
+ #
423
+ # Manual journal and line item totals are calculated automatically.
424
+ #
425
+ # Usage : # TODO
426
+
427
+ def create_manual_journal(manual_journal)
428
+ save_manual_journal(manual_journal)
429
+ end
430
+
431
+ #
432
+ # Updates an existing Xero manual journal
433
+ #
434
+ # Usage :
435
+ #
436
+ # manual_journal = xero_gateway.get_manual_journal(some_manual_journal_id)
437
+ #
438
+ # xero_gateway.update_manual_journal(manual_journal)
439
+ def update_manual_journal(manual_journal)
440
+ raise "manual_journal_id is required for updating manual journals" if manual_journal.manual_journal_id.nil?
441
+ save_manual_journal(manual_journal)
442
+ end
443
+
444
+ # Retrieves all manual journals from Xero
445
+ #
446
+ # Usage : get_manual_journal
447
+ # getmanual_journal(:manual_journal_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
448
+ #
449
+ # Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
450
+ def get_manual_journals(options = {})
451
+ request_params = {}
452
+ request_params[:ManualJournalID] = options[:manual_journal_id] if options[:manual_journal_id]
453
+ request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
454
+
455
+ response_xml = http_get(@client, "#{@xero_url}/ManualJournals", request_params)
456
+
457
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/ManualJournals'})
458
+ end
459
+
460
+ # Retrieves a single manual journal
461
+ #
462
+ # Usage : get_manual_journal("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
463
+ # get_manual_journal("OIT-12345") # By number
464
+ def get_manual_journal(manual_journal_id)
465
+ request_params = {}
466
+ url = "#{@xero_url}/ManualJournals/#{URI.escape(manual_journal_id)}"
467
+ response_xml = http_get(@client, url, request_params)
468
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/ManualJournal'})
469
+ end
470
+
471
+ #
472
+ # Gets all accounts for a specific organization in Xero.
473
+ #
474
+ def get_accounts
475
+ response_xml = http_get(@client, "#{xero_url}/Accounts")
476
+ parse_response(response_xml, {}, {:request_signature => 'GET/accounts'})
477
+ end
478
+
479
+ #
480
+ # Returns a XeroGateway::AccountsList object that makes working with
481
+ # the Xero list of accounts easier and allows caching the results.
482
+ #
483
+ def get_accounts_list(load_on_init = true)
484
+ AccountsList.new(self, load_on_init)
485
+ end
486
+
487
+ #
488
+ # Gets all tracking categories for a specific organization in Xero.
489
+ #
490
+ def get_tracking_categories
491
+ response_xml = http_get(@client, "#{xero_url}/TrackingCategories")
492
+
493
+ parse_response(response_xml, {}, {:request_signature => 'GET/TrackingCategories'})
494
+ end
495
+
496
+ #
497
+ # Gets Organisation details
498
+ #
499
+ def get_organisation
500
+ response_xml = http_get(@client, "#{xero_url}/Organisation")
501
+ parse_response(response_xml, {}, {:request_signature => 'GET/organisation'})
502
+ end
503
+
504
+ #
505
+ # Gets all currencies for a specific organisation in Xero
506
+ #
507
+ def get_currencies
508
+ response_xml = http_get(@client, "#{xero_url}/Currencies")
509
+ parse_response(response_xml, {}, {:request_signature => 'GET/currencies'})
510
+ end
511
+
512
+ #
513
+ # Gets all Tax Rates for a specific organisation in Xero
514
+ #
515
+ def get_tax_rates
516
+ response_xml = http_get(@client, "#{xero_url}/TaxRates")
517
+ parse_response(response_xml, {}, {:request_signature => 'GET/tax_rates'})
518
+ end
519
+
520
+ private
521
+
522
+ def get_contact(contact_id = nil, contact_number = nil)
523
+ request_params = contact_id ? { :contactID => contact_id } : { :contactNumber => contact_number }
524
+ response_xml = http_get(@client, "#{@xero_url}/Contacts/#{URI.escape(contact_id||contact_number)}", request_params)
525
+
526
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contact'})
527
+ end
528
+
529
+ # Create or update a contact record based on if it has a contact_id or contact_number.
530
+ def save_contact(contact)
531
+ request_xml = contact.to_xml
532
+
533
+ response_xml = nil
534
+ create_or_save = nil
535
+ if contact.contact_id.nil? && contact.contact_number.nil?
536
+ # Create new contact record.
537
+ response_xml = http_put(@client, "#{@xero_url}/Contacts", request_xml, {})
538
+ create_or_save = :create
539
+ else
540
+ # Update existing contact record.
541
+ response_xml = http_post(@client, "#{@xero_url}/Contacts", request_xml, {})
542
+ create_or_save = :save
543
+ end
544
+
545
+ response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/contact"})
546
+ contact.contact_id = response.contact.contact_id if response.contact && response.contact.contact_id
547
+ response
548
+ end
549
+
550
+ # Create or update an invoice record based on if it has an invoice_id.
551
+ def save_invoice(invoice)
552
+ request_xml = invoice.to_xml
553
+
554
+ response_xml = nil
555
+ create_or_save = nil
556
+ if invoice.invoice_id.nil?
557
+ # Create new invoice record.
558
+ response_xml = http_put(@client, "#{@xero_url}/Invoices", request_xml, {})
559
+ create_or_save = :create
560
+ else
561
+ # Update existing invoice record.
562
+ response_xml = http_post(@client, "#{@xero_url}/Invoices", request_xml, {})
563
+ create_or_save = :save
564
+ end
565
+
566
+ response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/invoice"})
567
+
568
+ # Xero returns invoices inside an <Invoices> tag, even though there's only ever
569
+ # one for this request
570
+ response.response_item = response.invoices.first
571
+
572
+ if response.success? && response.invoice && response.invoice.invoice_id
573
+ invoice.invoice_id = response.invoice.invoice_id
574
+ end
575
+
576
+ response
577
+ end
578
+
579
+ # Create or update a bank transaction record based on if it has an bank_transaction_id.
580
+ def save_bank_transaction(bank_transaction)
581
+ request_xml = bank_transaction.to_xml
582
+ response_xml = nil
583
+ create_or_save = nil
584
+
585
+ if bank_transaction.bank_transaction_id.nil?
586
+ # Create new bank transaction record.
587
+ response_xml = http_put(@client, "#{@xero_url}/BankTransactions", request_xml, {})
588
+ create_or_save = :create
589
+ else
590
+ # Update existing bank transaction record.
591
+ response_xml = http_post(@client, "#{@xero_url}/BankTransactions", request_xml, {})
592
+ create_or_save = :save
593
+ end
594
+
595
+ response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/BankTransactions"})
596
+
597
+ # Xero returns bank transactions inside an <BankTransactions> tag, even though there's only ever
598
+ # one for this request
599
+ response.response_item = response.bank_transactions.first
600
+
601
+ if response.success? && response.bank_transaction && response.bank_transaction.bank_transaction_id
602
+ bank_transaction.bank_transaction_id = response.bank_transaction.bank_transaction_id
603
+ end
604
+
605
+ response
606
+ end
607
+
608
+ # Create or update a manual journal record based on if it has an manual_journal_id.
609
+ def save_manual_journal(manual_journal)
610
+ request_xml = manual_journal.to_xml
611
+ response_xml = nil
612
+ create_or_save = nil
613
+
614
+ if manual_journal.manual_journal_id.nil?
615
+ # Create new manual journal record.
616
+ response_xml = http_put(@client, "#{@xero_url}/ManualJournals", request_xml, {})
617
+ create_or_save = :create
618
+ else
619
+ # Update existing manual journal record.
620
+ response_xml = http_post(@client, "#{@xero_url}/ManualJournals", request_xml, {})
621
+ create_or_save = :save
622
+ end
623
+
624
+ response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/ManualJournals"})
625
+
626
+ # Xero returns manual journals inside an <ManualJournals> tag, even though there's only ever
627
+ # one for this request
628
+ response.response_item = response.manual_journals.first
629
+
630
+ manual_journal.manual_journal_id = response.manual_journal.manual_journal_id if response.success? && response.manual_journal && response.manual_journal.manual_journal_id
631
+
632
+ response
633
+ end
634
+
635
+ def parse_response(raw_response, request = {}, options = {})
636
+
637
+ response = XeroGateway::Response.new
638
+
639
+ doc = REXML::Document.new(raw_response, :ignore_whitespace_nodes => :all)
640
+
641
+ # check for responses we don't understand
642
+ raise UnparseableResponse.new(doc.root.name) unless doc.root.name == "Response"
643
+
644
+ response_element = REXML::XPath.first(doc, "/Response")
645
+
646
+ response_element.children.reject { |e| e.is_a? REXML::Text }.each do |element|
647
+ case(element.name)
648
+ when "ID" then response.response_id = element.text
649
+ when "Status" then response.status = element.text
650
+ when "ProviderName" then response.provider = element.text
651
+ when "DateTimeUTC" then response.date_time = element.text
652
+ when "Contact" then response.response_item = Contact.from_xml(element, self)
653
+ when "Invoice" then response.response_item = Invoice.from_xml(element, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"})
654
+ when "BankTransaction"
655
+ response.response_item = BankTransaction.from_xml(element, self, {:line_items_downloaded => options[:request_signature] != "GET/BankTransactions"})
656
+ when "ManualJournal"
657
+ response.response_item = ManualJournal.from_xml(element, self, {:journal_lines_downloaded => options[:request_signature] != "GET/ManualJournals"})
658
+ when "Contacts" then element.children.each {|child| response.response_item << Contact.from_xml(child, self) }
659
+ when "Invoices" then element.children.each {|child| response.response_item << Invoice.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"}) }
660
+ when "BankTransactions"
661
+ element.children.each do |child|
662
+ response.response_item << BankTransaction.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/BankTransactions"})
663
+ end
664
+ when "ManualJournals"
665
+ element.children.each do |child|
666
+ response.response_item << ManualJournal.from_xml(child, self, {:journal_lines_downloaded => options[:request_signature] != "GET/ManualJournals"})
667
+ end
668
+ when "CreditNotes" then element.children.each {|child| response.response_item << CreditNote.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/CreditNotes"}) }
669
+ when "Accounts" then element.children.each {|child| response.response_item << Account.from_xml(child) }
670
+ when "TaxRates" then element.children.each {|child| response.response_item << TaxRate.from_xml(child) }
671
+ when "Currencies" then element.children.each {|child| response.response_item << Currency.from_xml(child) }
672
+ when "Organisations" then response.response_item = Organisation.from_xml(element.children.first) # Xero only returns the Authorized Organisation
673
+ when "TrackingCategories" then element.children.each {|child| response.response_item << TrackingCategory.from_xml(child) }
674
+ when "Errors" then element.children.each { |error| parse_error(error, response) }
675
+ end
676
+ end if response_element
677
+
678
+ # If a single result is returned don't put it in an array
679
+ if response.response_item.is_a?(Array) && response.response_item.size == 1
680
+ response.response_item = response.response_item.first
681
+ end
682
+
683
+ response.request_params = request[:request_params]
684
+ response.request_xml = request[:request_xml]
685
+ response.response_xml = raw_response
686
+ response
687
+ end
688
+
689
+ def parse_error(error_element, response)
690
+ response.errors << Error.new(
691
+ :description => REXML::XPath.first(error_element, "Description").text,
692
+ :date_time => REXML::XPath.first(error_element, "//DateTime").text,
693
+ :type => REXML::XPath.first(error_element, "//ExceptionType").text,
694
+ :message => REXML::XPath.first(error_element, "//Message").text
695
+ )
696
+ end
697
+ end
698
+ end