xero_gateway 2.1.0 → 2.3.0

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 (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