xero_gateway 2.1.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +3 -11
  3. data/README.md +212 -0
  4. data/Rakefile +0 -1
  5. data/lib/oauth/oauth_consumer.rb +25 -9
  6. data/lib/xero_gateway.rb +5 -1
  7. data/lib/xero_gateway/address.rb +29 -27
  8. data/lib/xero_gateway/bank_transaction.rb +11 -3
  9. data/lib/xero_gateway/base_record.rb +97 -0
  10. data/lib/xero_gateway/contact_group.rb +87 -0
  11. data/lib/xero_gateway/currency.rb +8 -54
  12. data/lib/xero_gateway/dates.rb +4 -0
  13. data/lib/xero_gateway/exceptions.rb +14 -13
  14. data/lib/xero_gateway/gateway.rb +90 -5
  15. data/lib/xero_gateway/http.rb +16 -8
  16. data/lib/xero_gateway/invoice.rb +9 -3
  17. data/lib/xero_gateway/item.rb +27 -0
  18. data/lib/xero_gateway/line_item_calculations.rb +3 -3
  19. data/lib/xero_gateway/manual_journal.rb +5 -2
  20. data/lib/xero_gateway/organisation.rb +22 -73
  21. data/lib/xero_gateway/payment.rb +22 -9
  22. data/lib/xero_gateway/phone.rb +2 -0
  23. data/lib/xero_gateway/report.rb +95 -0
  24. data/lib/xero_gateway/response.rb +12 -7
  25. data/lib/xero_gateway/tax_rate.rb +13 -61
  26. data/lib/xero_gateway/tracking_category.rb +39 -13
  27. data/lib/xero_gateway/version.rb +3 -0
  28. data/test/integration/get_items_test.rb +25 -0
  29. data/test/integration/get_payments_test.rb +54 -0
  30. data/test/test_helper.rb +51 -16
  31. data/test/unit/address_test.rb +34 -0
  32. data/test/unit/bank_transaction_test.rb +7 -0
  33. data/test/unit/contact_group_test.rb +47 -0
  34. data/test/unit/contact_test.rb +35 -16
  35. data/test/unit/credit_note_test.rb +2 -2
  36. data/test/unit/gateway_test.rb +170 -1
  37. data/test/unit/invoice_test.rb +27 -7
  38. data/test/unit/item_test.rb +51 -0
  39. data/test/unit/organisation_test.rb +1 -0
  40. data/test/unit/payment_test.rb +18 -11
  41. data/test/unit/report_test.rb +78 -0
  42. data/xero_gateway.gemspec +29 -13
  43. metadata +176 -89
  44. data/README.textile +0 -357
  45. data/init.rb +0 -1
@@ -0,0 +1,97 @@
1
+ module XeroGateway
2
+ class BaseRecord
3
+ class_attribute :element_name
4
+ class_attribute :attribute_definitions
5
+
6
+ class << self
7
+ def attributes(hash)
8
+ hash.each do |k, v|
9
+ attribute k, v
10
+ end
11
+ end
12
+
13
+ def attribute(name, value)
14
+ self.attribute_definitions ||= {}
15
+ self.attribute_definitions[name] = value
16
+
17
+ case value
18
+ when Hash
19
+ value.each do |k, v|
20
+ attribute("#{name}#{k}", v)
21
+ end
22
+ else
23
+ attr_accessor name.underscore
24
+ end
25
+ end
26
+
27
+ def from_xml(base_element)
28
+ new.from_xml(base_element)
29
+ end
30
+
31
+ def xml_element
32
+ element_name || self.name.split('::').last
33
+ end
34
+ end
35
+
36
+ def initialize(params = {})
37
+ params.each do |k,v|
38
+ self.send("#{k}=", v) if respond_to?("#{k}=")
39
+ end
40
+ end
41
+
42
+ def ==(other)
43
+ to_xml == other.to_xml
44
+ end
45
+
46
+ def to_xml
47
+ builder = Builder::XmlMarkup.new
48
+ builder.__send__(self.class.xml_element) do
49
+ to_xml_attributes(builder)
50
+ end
51
+ end
52
+
53
+ def from_xml(base_element)
54
+ from_xml_attributes(base_element)
55
+ self
56
+ end
57
+
58
+ def from_xml_attributes(element, attribute = nil, attr_definition = self.class.attribute_definitions)
59
+ if Hash === attr_definition
60
+ element.children.each do |child|
61
+ next unless child.respond_to?(:name)
62
+
63
+ child_attribute = child.name
64
+ child_attr_definition = attr_definition[child_attribute]
65
+ next unless child_attr_definition
66
+
67
+ from_xml_attributes(child, "#{attribute}#{child_attribute}", child_attr_definition)
68
+ end
69
+
70
+ return
71
+ end
72
+
73
+ value = case attr_definition
74
+ when :boolean then element.text == "true"
75
+ when :float then element.text.to_f
76
+ when :integer then element.text.to_i
77
+ else element.text
78
+ end if element.text.present?
79
+
80
+ send("#{attribute.underscore}=", value)
81
+ end
82
+
83
+ def to_xml_attributes(builder = Builder::XmlMarkup.new, path = nil, attr_definitions = self.class.attribute_definitions)
84
+ attr_definitions.each do |attr, value|
85
+ case value
86
+ when Hash
87
+ builder.__send__(attr) do
88
+ to_xml_attributes(builder, "#{path}#{attr}", value)
89
+ end
90
+ else
91
+ builder.__send__(attr, send("#{path}#{attr}".underscore))
92
+ end
93
+ end
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,87 @@
1
+ module XeroGateway
2
+ class ContactGroup
3
+
4
+ # Xero::Gateway associated with this invoice.
5
+ attr_accessor :gateway
6
+
7
+ # All accessible fields
8
+ attr_accessor :contact_group_id, :name, :status, :contacts
9
+
10
+ # Boolean representing whether the accounts list has been loaded.
11
+ attr_accessor :contacts_downloaded
12
+
13
+ def initialize(params = {})
14
+ @contacts = []
15
+ params.each do |k,v|
16
+ self.send("#{k}=", v)
17
+ end
18
+ end
19
+
20
+ # Return the list of Contacts. Will load the contacts if the group
21
+ # hasn't loaded the contacts yet (i.e. returned by a index call)
22
+ #
23
+ # Loaded contacts will only have Name and ContactId set.
24
+ def contacts
25
+ if !@contacts_downloaded && contact_group_id
26
+ @contacts_downloaded = true
27
+
28
+ # Load the contact list.
29
+ @contacts = gateway.get_contact_group_by_id(contact_group_id).contact_group.contacts || []
30
+ end
31
+
32
+ @contacts
33
+ end
34
+
35
+ # Returns the array of ContactIDs.
36
+ # If the contact_ids array has been assigned, will return that array.
37
+ # Otherwise, returns any loaded ContactIDs
38
+ def contact_ids
39
+ if @contact_ids
40
+ @contact_ids
41
+ else
42
+ contacts.map(&:contact_id)
43
+ end
44
+ end
45
+
46
+ # Assign ContactIDs to the group for updating.
47
+ def contact_ids=(contact_ids)
48
+ @contact_ids = contact_ids
49
+ end
50
+
51
+ def to_xml(b = Builder::XmlMarkup.new)
52
+ b.ContactGroup {
53
+ b.ContactGroupID contact_group_id unless contact_group_id.nil?
54
+ b.Name self.name
55
+ b.Status self.status
56
+
57
+ if @contacts_downloaded || @contact_ids
58
+ b.Contacts {
59
+ self.contact_ids.each do |contact_id|
60
+ b.Contact {
61
+ b.ContactID contact_id
62
+ }
63
+ end
64
+ }
65
+ end
66
+ }
67
+ end
68
+
69
+ def self.from_xml(contact_group_element, gateway, options = {})
70
+ contact_group = ContactGroup.new(gateway: gateway, contacts_downloaded: options[:contacts_downloaded])
71
+ contact_group_element.children.each do |element|
72
+ case(element.name)
73
+ when "ContactGroupID" then contact_group.contact_group_id = element.text
74
+ when "Name" then contact_group.name = element.text
75
+ when "Status" then contact_group.status = element.text
76
+ when "Contacts" then
77
+ contact_group.contacts_downloaded = true
78
+ element.children.each do |contact_child|
79
+ contact_group.contacts << Contact.from_xml(contact_child, gateway)
80
+ end
81
+ end
82
+ end
83
+ contact_group
84
+ end
85
+
86
+ end
87
+ end
@@ -1,56 +1,10 @@
1
1
  module XeroGateway
2
- class Currency
3
-
4
- unless defined? ATTRS
5
- ATTRS = {
6
- "Code" => :string, # 3 letter alpha code for the currency – see list of currency codes
7
- "Description" => :string, # Name of Currency
8
- }
9
- end
10
-
11
- attr_accessor *ATTRS.keys.map(&:underscore)
12
-
13
- def initialize(params = {})
14
- params.each do |k,v|
15
- self.send("#{k}=", v)
16
- end
17
- end
18
-
19
- def ==(other)
20
- ATTRS.keys.map(&:underscore).each do |field|
21
- return false if send(field) != other.send(field)
22
- end
23
- return true
24
- end
25
-
26
- def to_xml
27
- b = Builder::XmlMarkup.new
28
-
29
- b.Currency do
30
- ATTRS.keys.each do |attr|
31
- eval("b.#{attr} '#{self.send(attr.underscore.to_sym)}'")
32
- end
33
- end
34
- end
35
-
36
- def self.from_xml(currency_element)
37
- Currency.new.tap do |currency|
38
- currency_element.children.each do |element|
39
-
40
- attribute = element.name
41
- underscored_attribute = element.name.underscore
42
-
43
- raise "Unknown attribute: #{attribute}" unless ATTRS.keys.include?(attribute)
44
-
45
- case (ATTRS[attribute])
46
- when :boolean then currency.send("#{underscored_attribute}=", (element.text == "true"))
47
- when :float then currency.send("#{underscored_attribute}=", element.text.to_f)
48
- else currency.send("#{underscored_attribute}=", element.text)
49
- end
50
-
51
- end
52
- end
53
- end
54
-
2
+ class Currency < BaseRecord
3
+
4
+ attributes({
5
+ "Code" => :string, # 3 letter alpha code for the currency – see list of currency codes
6
+ "Description" => :string, # Name of Currency
7
+ })
8
+
55
9
  end
56
- end
10
+ end
@@ -20,6 +20,10 @@ module XeroGateway
20
20
  def parse_date_time(time)
21
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
22
  end
23
+
24
+ def parse_date_time_utc(time)
25
+ Time.utc(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)
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -4,48 +4,49 @@ module XeroGateway
4
4
  class InvalidLineItemError < StandardError; end
5
5
 
6
6
  class ApiException < StandardError
7
-
7
+
8
8
  def initialize(type, message, request_xml, response_xml)
9
9
  @type = type
10
10
  @message = message
11
11
  @request_xml = request_xml
12
12
  @response_xml = response_xml
13
13
  end
14
-
14
+
15
15
  attr_reader :request_xml, :response_xml
16
-
16
+
17
17
  def message
18
18
  "#{@type}: #{@message} \n Generated by the following XML: \n #{@response_xml}"
19
19
  end
20
-
20
+
21
21
  end
22
-
22
+
23
23
  class UnparseableResponse < StandardError
24
-
24
+
25
25
  def initialize(root_element_name)
26
26
  @root_element_name = root_element_name
27
27
  end
28
-
28
+
29
29
  def message
30
30
  "A root element of #{@root_element_name} was returned, and we don't understand that!"
31
31
  end
32
-
32
+
33
33
  end
34
-
34
+
35
35
  class ObjectNotFound < StandardError
36
-
36
+
37
37
  def initialize(api_endpoint)
38
38
  @api_endpoint = api_endpoint
39
39
  end
40
-
40
+
41
41
  def message
42
42
  "Couldn't find object for API Endpoint #{@api_endpoint}"
43
43
  end
44
-
44
+
45
45
  end
46
-
46
+
47
47
  class InvoiceNotFoundError < StandardError; end
48
48
  class BankTransactionNotFoundError < StandardError; end
49
+ class PaymentNotFoundError < StandardError; end
49
50
  class CreditNoteNotFoundError < StandardError; end
50
51
  class ManualJournalNotFoundError < StandardError; end
51
52
  end
@@ -34,9 +34,10 @@ module XeroGateway
34
34
 
35
35
  request_params[:ContactID] = options[:contact_id] if options[:contact_id]
36
36
  request_params[:ContactNumber] = options[:contact_number] if options[:contact_number]
37
- request_params[:OrderBy] = options[:order] if options[:order]
37
+ request_params[:order] = options[:order] if options[:order]
38
38
  request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
39
39
  request_params[:where] = options[:where] if options[:where]
40
+ request_params[:page] = options[:page] if options[:page]
40
41
 
41
42
  response_xml = http_get(@client, "#{@xero_url}/Contacts", request_params)
42
43
 
@@ -126,6 +127,31 @@ module XeroGateway
126
127
  response
127
128
  end
128
129
 
130
+ # Retreives all contact groups from Xero
131
+ #
132
+ # Usage:
133
+ # groups = gateway.get_contact_groups()
134
+ #
135
+ def get_contact_groups(options = {})
136
+ request_params = {}
137
+
138
+ request_params[:ContactGroupID] = options[:contact_group_id] if options[:contact_group_id]
139
+ request_params[:order] = options[:order] if options[:order]
140
+ request_params[:where] = options[:where] if options[:where]
141
+
142
+ response_xml = http_get(@client, "#{@xero_url}/ContactGroups", request_params)
143
+
144
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contactgroups'})
145
+ end
146
+
147
+ # Retreives a contact group by its id.
148
+ def get_contact_group_by_id(contact_group_id)
149
+ request_params = { :ContactGroupID => contact_group_id }
150
+ response_xml = http_get(@client, "#{@xero_url}/ContactGroups/#{URI.escape(contact_group_id)}", request_params)
151
+
152
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contactgroup'})
153
+ end
154
+
129
155
  # Retrieves all invoices from Xero
130
156
  #
131
157
  # Usage : get_invoices
@@ -138,7 +164,7 @@ module XeroGateway
138
164
 
139
165
  request_params[:InvoiceID] = options[:invoice_id] if options[:invoice_id]
140
166
  request_params[:InvoiceNumber] = options[:invoice_number] if options[:invoice_number]
141
- request_params[:OrderBy] = options[:order] if options[:order]
167
+ request_params[:order] = options[:order] if options[:order]
142
168
  request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
143
169
 
144
170
  request_params[:where] = options[:where] if options[:where]
@@ -263,10 +289,10 @@ module XeroGateway
263
289
 
264
290
  request_params[:CreditNoteID] = options[:credit_note_id] if options[:credit_note_id]
265
291
  request_params[:CreditNoteNumber] = options[:credit_note_number] if options[:credit_note_number]
266
- request_params[:OrderBy] = options[:order] if options[:order]
267
- request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
292
+ request_params[:order] = options[:order] if options[:order]
293
+ request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
268
294
 
269
- request_params[:where] = options[:where] if options[:where]
295
+ request_params[:where] = options[:where] if options[:where]
270
296
 
271
297
  response_xml = http_get(@client, "#{@xero_url}/CreditNotes", request_params)
272
298
 
@@ -413,6 +439,8 @@ module XeroGateway
413
439
  request_params = {}
414
440
  request_params[:BankTransactionID] = options[:bank_transaction_id] if options[:bank_transaction_id]
415
441
  request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
442
+ request_params[:order] = options[:order] if options[:order]
443
+ request_params[:where] = options[:where] if options[:where]
416
444
 
417
445
  response_xml = http_get(@client, "#{@xero_url}/BankTransactions", request_params)
418
446
 
@@ -529,6 +557,14 @@ module XeroGateway
529
557
  parse_response(response_xml, {}, {:request_signature => 'GET/tax_rates'})
530
558
  end
531
559
 
560
+ #
561
+ # Gets all Items for a specific organisation in Xero
562
+ #
563
+ def get_items
564
+ response_xml = http_get(@client, "#{xero_url}/Items")
565
+ parse_response(response_xml, {}, {:request_signature => 'GET/items'})
566
+ end
567
+
532
568
  #
533
569
  # Create Payment record in Xero
534
570
  #
@@ -543,6 +579,44 @@ module XeroGateway
543
579
  parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => 'PUT/payments'})
544
580
  end
545
581
 
582
+ #
583
+ # Gets all Payments for a specific organisation in Xero
584
+ #
585
+ def get_payments(options = {})
586
+ request_params = {}
587
+ request_params[:PaymentID] = options[:payment_id] if options[:payment_id]
588
+ request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
589
+ request_params[:order] = options[:order] if options[:order]
590
+ request_params[:where] = options[:where] if options[:where]
591
+
592
+ response_xml = http_get(@client, "#{@xero_url}/Payments", request_params)
593
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/payments'})
594
+ end
595
+
596
+
597
+ #
598
+ # Gets a single Payment for a specific organsation in Xero
599
+ #
600
+ def get_payment(payment_id, options = {})
601
+ request_params = {}
602
+ response_xml = http_get(client, "#{xero_url}/Payments/#{payment_id}", request_params)
603
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/payments'})
604
+ end
605
+
606
+ # Retrieves reports from Xero
607
+ #
608
+ # Usage : get_report("BankStatement", bank_account_id: "AC993F75-035B-433C-82E0-7B7A2D40802C")
609
+ # get_report("297c2dc5-cc47-4afd-8ec8-74990b8761e9", bank_account_id: "AC993F75-035B-433C-82E0-7B7A2D40802C")
610
+ def get_report(id_or_name, options={})
611
+ request_params = options.inject({}) do |params, (key, val)|
612
+ xero_key = key.to_s.camelize.gsub(/id/i, "ID").to_sym
613
+ params[xero_key] = val
614
+ params
615
+ end
616
+ response_xml = http_get(@client, "#{@xero_url}/reports/#{id_or_name}", request_params)
617
+ parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/reports'})
618
+ end
619
+
546
620
  private
547
621
 
548
622
  def get_contact(contact_id = nil, contact_number = nil)
@@ -682,6 +756,7 @@ module XeroGateway
682
756
  when "ManualJournal"
683
757
  response.response_item = ManualJournal.from_xml(element, self, {:journal_lines_downloaded => options[:request_signature] != "GET/ManualJournals"})
684
758
  when "Contacts" then element.children.each {|child| response.response_item << Contact.from_xml(child, self) }
759
+ when "ContactGroups" then element.children.each {|child| response.response_item << ContactGroup.from_xml(child, self, {:contacts_downloaded => options[:request_signature] != "GET/contactgroups"}) }
685
760
  when "Invoices" then element.children.each {|child| response.response_item << Invoice.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"}) }
686
761
  when "BankTransactions"
687
762
  element.children.each do |child|
@@ -694,9 +769,19 @@ module XeroGateway
694
769
  when "CreditNotes" then element.children.each {|child| response.response_item << CreditNote.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/CreditNotes"}) }
695
770
  when "Accounts" then element.children.each {|child| response.response_item << Account.from_xml(child) }
696
771
  when "TaxRates" then element.children.each {|child| response.response_item << TaxRate.from_xml(child) }
772
+ when "Items" then element.children.each {|child| response.response_item << Item.from_xml(child) }
697
773
  when "Currencies" then element.children.each {|child| response.response_item << Currency.from_xml(child) }
698
774
  when "Organisations" then response.response_item = Organisation.from_xml(element.children.first) # Xero only returns the Authorized Organisation
699
775
  when "TrackingCategories" then element.children.each {|child| response.response_item << TrackingCategory.from_xml(child) }
776
+ when "Payment" then response.response_item = Payment.from_xml(element)
777
+ when "Payments"
778
+ element.children.each do |child|
779
+ response.response_item << Payment.from_xml(child)
780
+ end
781
+ when "Reports"
782
+ element.children.each do |child|
783
+ response.response_item << Report.from_xml(child)
784
+ end
700
785
  when "Errors" then response.errors = element.children.map { |error| Error.parse(error) }
701
786
  end
702
787
  end if response_element