xero_gateway 2.3.0 → 2.4.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.
@@ -1,45 +1,46 @@
1
1
  module XeroGateway
2
2
  class Contact
3
3
  include Dates
4
-
4
+
5
5
  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)
6
-
6
+
7
7
  CONTACT_STATUS = {
8
8
  'ACTIVE' => 'Active',
9
9
  'DELETED' => 'Deleted'
10
10
  } unless defined?(CONTACT_STATUS)
11
-
11
+
12
12
  # Xero::Gateway associated with this contact.
13
13
  attr_accessor :gateway
14
-
14
+
15
15
  # Any errors that occurred when the #valid? method called.
16
16
  attr_reader :errors
17
-
18
- attr_accessor :contact_id, :contact_number, :status, :name, :first_name, :last_name, :email, :addresses, :phones, :updated_at,
17
+
18
+ attr_accessor :contact_id, :contact_number, :account_number, :status, :name, :first_name, :last_name, :email, :addresses, :phones, :updated_at,
19
19
  :bank_account_details, :tax_number, :accounts_receivable_tax_type, :accounts_payable_tax_type, :is_customer, :is_supplier,
20
20
  :default_currency, :contact_groups
21
21
 
22
-
22
+
23
23
  def initialize(params = {})
24
24
  @errors ||= []
25
25
 
26
- params = {}.merge(params)
26
+ params = {}.merge(params)
27
27
  params.each do |k,v|
28
28
  self.send("#{k}=", v)
29
29
  end
30
30
 
31
31
  @phones ||= []
32
- @addresses ||= []
32
+ @addresses ||= nil
33
33
  end
34
-
34
+
35
35
  def address=(address)
36
36
  self.addresses = [address]
37
37
  end
38
-
38
+
39
39
  def address
40
+ self.addresses ||= []
40
41
  self.addresses[0] ||= Address.new
41
42
  end
42
-
43
+
43
44
  # Helper method to add a new address object to this contact.
44
45
  #
45
46
  # Usage:
@@ -54,11 +55,11 @@ module XeroGateway
54
55
  def add_address(address_params)
55
56
  self.addresses << Address.new(address_params)
56
57
  end
57
-
58
+
58
59
  def phone=(phone)
59
60
  self.phones = [phone]
60
61
  end
61
-
62
+
62
63
  def phone
63
64
  if @phones.size > 1
64
65
  @phones.detect {|p| p.phone_type == 'DEFAULT'} || phones[0]
@@ -66,7 +67,7 @@ module XeroGateway
66
67
  @phones[0] ||= Phone.new
67
68
  end
68
69
  end
69
-
70
+
70
71
  # Helper method to add a new phone object to this contact.
71
72
  #
72
73
  # Usage:
@@ -77,41 +78,41 @@ module XeroGateway
77
78
  def add_phone(phone_params = {})
78
79
  self.phones << Phone.new(phone_params)
79
80
  end
80
-
81
+
81
82
  # Validate the Contact record according to what will be valid by the gateway.
82
83
  #
83
- # Usage:
84
+ # Usage:
84
85
  # contact.valid? # Returns true/false
85
- #
86
+ #
86
87
  # Additionally sets contact.errors array to an array of field/error.
87
88
  def valid?
88
89
  @errors = []
89
-
90
+
90
91
  if !contact_id.nil? && contact_id !~ GUID_REGEX
91
92
  @errors << ['contact_id', 'must be blank or a valid Xero GUID']
92
93
  end
93
-
94
+
94
95
  if status && !CONTACT_STATUS[status]
95
96
  @errors << ['status', "must be one of #{CONTACT_STATUS.keys.join('/')}"]
96
97
  end
97
-
98
+
98
99
  unless name
99
100
  @errors << ['name', "can't be blank"]
100
101
  end
101
-
102
+
102
103
  # Make sure all addresses are correct.
103
104
  unless addresses.all? { | address | address.valid? }
104
105
  @errors << ['addresses', 'at least one address is invalid']
105
106
  end
106
-
107
+
107
108
  # Make sure all phone numbers are correct.
108
109
  unless phones.all? { | phone | phone.valid? }
109
110
  @errors << ['phones', 'at least one phone is invalid']
110
111
  end
111
-
112
+
112
113
  @errors.size == 0
113
114
  end
114
-
115
+
115
116
  # General purpose create/save method.
116
117
  # If contact_id and contact_number are nil then create, otherwise, attempt to save.
117
118
  def save
@@ -121,25 +122,26 @@ module XeroGateway
121
122
  update
122
123
  end
123
124
  end
124
-
125
+
125
126
  # Creates this contact record (using gateway.create_contact) with the associated gateway.
126
127
  # If no gateway set, raise a NoGatewayError exception.
127
128
  def create
128
129
  raise NoGatewayError unless gateway
129
130
  gateway.create_contact(self)
130
131
  end
131
-
132
+
132
133
  # Creates this contact record (using gateway.update_contact) with the associated gateway.
133
134
  # If no gateway set, raise a NoGatewayError exception.
134
135
  def update
135
136
  raise NoGatewayError unless gateway
136
137
  gateway.update_contact(self)
137
138
  end
138
-
139
+
139
140
  def to_xml(b = Builder::XmlMarkup.new)
140
141
  b.Contact {
141
142
  b.ContactID self.contact_id if self.contact_id
142
143
  b.ContactNumber self.contact_number if self.contact_number
144
+ b.AccountNumber self.account_number if self.account_number
143
145
  b.Name self.name if self.name
144
146
  b.EmailAddress self.email if self.email
145
147
  b.FirstName self.first_name if self.first_name
@@ -154,13 +156,13 @@ module XeroGateway
154
156
  b.DefaultCurrency if self.default_currency
155
157
  b.Addresses {
156
158
  addresses.each { |address| address.to_xml(b) }
157
- } if self.addresses.any?
159
+ } unless addresses.nil?
158
160
  b.Phones {
159
161
  phones.each { |phone| phone.to_xml(b) }
160
162
  } if self.phones.any?
161
163
  }
162
164
  end
163
-
165
+
164
166
  # Take a Contact element and convert it into an Contact object
165
167
  def self.from_xml(contact_element, gateway = nil)
166
168
  contact = Contact.new(:gateway => gateway)
@@ -168,15 +170,14 @@ module XeroGateway
168
170
  case(element.name)
169
171
  when "ContactID" then contact.contact_id = element.text
170
172
  when "ContactNumber" then contact.contact_number = element.text
173
+ when "AccountNumber" then contact.account_number = element.text
171
174
  when "ContactStatus" then contact.status = element.text
172
175
  when "Name" then contact.name = element.text
173
176
  when "FirstName" then contact.first_name = element.text
174
177
  when "LastName" then contact.last_name = element.text
175
178
  when "EmailAddress" then contact.email = element.text
176
- when "Addresses" then element.children.each {|address_element| contact.addresses << Address.from_xml(address_element)}
177
- when "Phones" then element.children.each {|phone_element| contact.phones << Phone.from_xml(phone_element)}
178
- when "FirstName" then contact.first_name = element.text
179
- when "LastName" then contact.last_name = element.text
179
+ when "Addresses" then element.children.each { |address_element| contact.addresses ||= []; contact.addresses << Address.from_xml(address_element) }
180
+ when "Phones" then element.children.each { |phone_element| contact.phones << Phone.from_xml(phone_element) }
180
181
  when "BankAccountDetails" then contact.bank_account_details = element.text
181
182
  when "TaxNumber" then contact.tax_number = element.text
182
183
  when "AccountsReceivableTaxType" then contact.accounts_receivable_tax_type = element.text
@@ -185,19 +186,20 @@ module XeroGateway
185
186
  when "IsCustomer" then contact.is_customer = (element.text == "true")
186
187
  when "IsSupplier" then contact.is_supplier = (element.text == "true")
187
188
  when "DefaultCurrency" then contact.default_currency = element.text
189
+ when "UpdatedDateUTC" then contact.updated_at = parse_date_time(element.text)
188
190
  end
189
191
  end
190
192
  contact
191
193
  end
192
-
194
+
193
195
  def ==(other)
194
- [ :contact_id, :contact_number, :status, :name, :first_name, :last_name, :email, :addresses, :phones, :updated_at,
196
+ [ :contact_id, :contact_number, :account_number, :status, :name, :first_name, :last_name, :email, :addresses, :phones, :updated_at,
195
197
  :bank_account_details, :tax_number, :accounts_receivable_tax_type, :accounts_payable_tax_type, :is_customer, :is_supplier,
196
198
  :default_currency, :contact_groups ].each do |field|
197
199
  return false if send(field) != other.send(field)
198
200
  end
199
201
  return true
200
- end
201
-
202
+ end
203
+
202
204
  end
203
205
  end
@@ -1,20 +1,22 @@
1
1
  module XeroGateway
2
2
  class ContactGroup
3
-
3
+
4
4
  # Xero::Gateway associated with this invoice.
5
5
  attr_accessor :gateway
6
-
6
+
7
7
  # All accessible fields
8
- attr_accessor :contact_group_id, :name, :status, :contacts
9
-
8
+ attr_accessor :contact_group_id, :name, :status
9
+ attr_writer :contacts
10
+
10
11
  # Boolean representing whether the accounts list has been loaded.
11
12
  attr_accessor :contacts_downloaded
12
-
13
+
13
14
  def initialize(params = {})
14
15
  @contacts = []
15
16
  params.each do |k,v|
16
17
  self.send("#{k}=", v)
17
18
  end
19
+ @contacts_downloaded = (params.delete(:contacts_downloaded) == true)
18
20
  end
19
21
 
20
22
  # Return the list of Contacts. Will load the contacts if the group
@@ -32,11 +34,11 @@ module XeroGateway
32
34
  @contacts
33
35
  end
34
36
 
35
- # Returns the array of ContactIDs.
37
+ # Returns the array of ContactIDs.
36
38
  # If the contact_ids array has been assigned, will return that array.
37
39
  # Otherwise, returns any loaded ContactIDs
38
40
  def contact_ids
39
- if @contact_ids
41
+ if defined?(@contact_ids)
40
42
  @contact_ids
41
43
  else
42
44
  contacts.map(&:contact_id)
@@ -80,8 +82,8 @@ module XeroGateway
80
82
  end
81
83
  end
82
84
  end
83
- contact_group
84
- end
85
+ contact_group
86
+ end
85
87
 
86
88
  end
87
- end
89
+ end
@@ -3,18 +3,18 @@ module XeroGateway
3
3
  include Dates
4
4
  include Money
5
5
  include LineItemCalculations
6
-
6
+
7
7
  CREDIT_NOTE_TYPE = {
8
8
  'ACCRECCREDIT' => 'Accounts Receivable',
9
9
  'ACCPAYCREDIT' => 'Accounts Payable'
10
10
  } unless defined?(CREDIT_NOTE_TYPE)
11
-
11
+
12
12
  LINE_AMOUNT_TYPES = {
13
13
  "Inclusive" => 'CreditNote lines are inclusive tax',
14
14
  "Exclusive" => 'CreditNote lines are exclusive of tax (default)',
15
15
  "NoTax" => 'CreditNotes lines have no tax'
16
16
  } unless defined?(LINE_AMOUNT_TYPES)
17
-
17
+
18
18
  CREDIT_NOTE_STATUS = {
19
19
  'AUTHORISED' => 'Approved credit_notes awaiting payment',
20
20
  'DELETED' => 'Draft credit_notes that are deleted',
@@ -23,53 +23,53 @@ module XeroGateway
23
23
  'SUBMITTED' => 'CreditNotes entered by an employee awaiting approval',
24
24
  'VOID' => 'Approved credit_notes that are voided'
25
25
  } unless defined?(CREDIT_NOTE_STATUS)
26
-
26
+
27
27
  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)
28
-
28
+
29
29
  # Xero::Gateway associated with this credit_note.
30
30
  attr_accessor :gateway
31
-
31
+
32
32
  # Any errors that occurred when the #valid? method called.
33
33
  attr_reader :errors
34
34
 
35
35
  # Represents whether the line_items have been downloaded when getting from GET /API.XRO/2.0/CreditNotes
36
36
  attr_accessor :line_items_downloaded
37
-
37
+
38
38
  # All accessible fields
39
- 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
39
+ attr_accessor :credit_note_id, :credit_note_number, :type, :status, :date, :reference, :line_amount_types, :currency_code, :payments, :fully_paid_on, :amount_credited
40
+ attr_writer :line_items, :contact
40
41
 
41
-
42
42
  def initialize(params = {})
43
43
  @errors ||= []
44
44
  @payments ||= []
45
-
45
+
46
46
  # Check if the line items have been downloaded.
47
47
  @line_items_downloaded = (params.delete(:line_items_downloaded) == true)
48
-
48
+
49
49
  params = {
50
50
  :line_amount_types => "Inclusive"
51
51
  }.merge(params)
52
-
52
+
53
53
  params.each do |k,v|
54
54
  self.send("#{k}=", v)
55
55
  end
56
-
56
+
57
57
  @line_items ||= []
58
58
  end
59
-
59
+
60
60
  # Validate the Address record according to what will be valid by the gateway.
61
61
  #
62
- # Usage:
62
+ # Usage:
63
63
  # address.valid? # Returns true/false
64
- #
64
+ #
65
65
  # Additionally sets address.errors array to an array of field/error.
66
66
  def valid?
67
67
  @errors = []
68
-
68
+
69
69
  if !credit_note_id.nil? && credit_note_id !~ GUID_REGEX
70
70
  @errors << ['credit_note_id', 'must be blank or a valid Xero GUID']
71
71
  end
72
-
72
+
73
73
  if status && !CREDIT_NOTE_STATUS[status]
74
74
  @errors << ['status', "must be one of #{CREDIT_NOTE_STATUS.keys.join('/')}"]
75
75
  end
@@ -77,99 +77,93 @@ module XeroGateway
77
77
  if line_amount_types && !LINE_AMOUNT_TYPES[line_amount_types]
78
78
  @errors << ['line_amount_types', "must be one of #{LINE_AMOUNT_TYPES.keys.join('/')}"]
79
79
  end
80
-
80
+
81
81
  unless date
82
82
  @errors << ['credit_note_date', "can't be blank"]
83
83
  end
84
-
84
+
85
85
  # Make sure contact is valid.
86
86
  unless @contact && @contact.valid?
87
87
  @errors << ['contact', 'is invalid']
88
88
  end
89
-
89
+
90
90
  # Make sure all line_items are valid.
91
91
  unless line_items.all? { | line_item | line_item.valid? }
92
92
  @errors << ['line_items', "at least one line item invalid"]
93
93
  end
94
-
94
+
95
95
  @errors.size == 0
96
96
  end
97
-
97
+
98
98
  # Helper method to create the associated contact object.
99
99
  def build_contact(params = {})
100
100
  self.contact = gateway ? gateway.build_contact(params) : Contact.new(params)
101
101
  end
102
-
102
+
103
103
  def contact
104
104
  @contact ||= build_contact
105
105
  end
106
-
106
+
107
107
  # Helper method to check if the credit_note is accounts payable.
108
108
  def accounts_payable?
109
109
  type == 'ACCPAYCREDIT'
110
110
  end
111
-
111
+
112
112
  # Helper method to check if the credit_note is accounts receivable.
113
113
  def accounts_receivable?
114
114
  type == 'ACCRECCREDIT'
115
115
  end
116
-
116
+
117
117
  # Whether or not the line_items have been downloaded (GET/credit_notes does not download line items).
118
118
  def line_items_downloaded?
119
119
  @line_items_downloaded
120
120
  end
121
-
121
+
122
122
  # If line items are not downloaded, then attempt a download now (if this record was found to begin with).
123
123
  def line_items
124
124
  if line_items_downloaded?
125
125
  @line_items
126
-
126
+
127
127
  # There is an credit_note_is so we can assume this record was loaded from Xero.
128
128
  # attempt to download the line_item records.
129
129
  elsif credit_note_id =~ GUID_REGEX
130
130
  raise NoGatewayError unless @gateway
131
-
131
+
132
132
  response = @gateway.get_credit_note(credit_note_id)
133
133
  raise CreditNoteNotFoundError, "CreditNote with ID #{credit_note_id} not found in Xero." unless response.success? && response.credit_note.is_a?(XeroGateway::CreditNote)
134
-
134
+
135
135
  @line_items = response.credit_note.line_items
136
136
  @line_items_downloaded = true
137
-
137
+
138
138
  @line_items
139
-
139
+
140
140
  # Otherwise, this is a new credit_note, so return the line_items reference.
141
141
  else
142
142
  @line_items
143
143
  end
144
144
  end
145
-
145
+
146
146
  def ==(other)
147
147
  ["credit_note_number", "type", "status", "reference", "currency_code", "line_amount_types", "contact", "line_items"].each do |field|
148
148
  return false if send(field) != other.send(field)
149
149
  end
150
-
150
+
151
151
  ["date"].each do |field|
152
152
  return false if send(field).to_s != other.send(field).to_s
153
153
  end
154
154
  return true
155
155
  end
156
-
157
- # General purpose createsave method.
158
- # If contact_id and contact_number are nil then create, otherwise, attempt to save.
159
- def save
160
- create
161
- end
162
-
156
+
163
157
  # Creates this credit_note record (using gateway.create_credit_note) with the associated gateway.
164
158
  # If no gateway set, raise a NoGatewayError exception.
165
159
  def create
166
160
  raise NoGatewayError unless gateway
167
161
  gateway.create_credit_note(self)
168
162
  end
169
-
163
+
170
164
  # Alias create as save as this is currently the only write action.
171
165
  alias_method :save, :create
172
-
166
+
173
167
  def to_xml(b = Builder::XmlMarkup.new)
174
168
  b.CreditNote {
175
169
  b.Type self.type
@@ -187,14 +181,14 @@ module XeroGateway
187
181
  }
188
182
  }
189
183
  end
190
-
184
+
191
185
  #TODO UpdatedDateUTC
192
186
  def self.from_xml(credit_note_element, gateway = nil, options = {})
193
187
  credit_note = CreditNote.new(options.merge({:gateway => gateway}))
194
188
  credit_note_element.children.each do |element|
195
189
  case(element.name)
196
190
  when "CreditNoteID" then credit_note.credit_note_id = element.text
197
- when "CreditNoteNumber" then credit_note.credit_note_number = element.text
191
+ when "CreditNoteNumber" then credit_note.credit_note_number = element.text
198
192
  when "Type" then credit_note.type = element.text
199
193
  when "CurrencyCode" then credit_note.currency_code = element.text
200
194
  when "Contact" then credit_note.contact = Contact.from_xml(element)
@@ -206,15 +200,13 @@ module XeroGateway
206
200
  when "SubTotal" then credit_note.sub_total = BigDecimal.new(element.text)
207
201
  when "TotalTax" then credit_note.total_tax = BigDecimal.new(element.text)
208
202
  when "Total" then credit_note.total = BigDecimal.new(element.text)
209
- when "CreditNoteID" then credit_note.credit_note_id = element.text
210
- when "CreditNoteNumber" then credit_note.credit_note_number = element.text
211
203
  when "Payments" then element.children.each { | payment | credit_note.payments << Payment.from_xml(payment) }
212
204
  when "AmountDue" then credit_note.amount_due = BigDecimal.new(element.text)
213
205
  when "AmountPaid" then credit_note.amount_paid = BigDecimal.new(element.text)
214
206
  when "AmountCredited" then credit_note.amount_credited = BigDecimal.new(element.text)
215
207
  end
216
- end
208
+ end
217
209
  credit_note
218
- end
210
+ end
219
211
  end
220
212
  end
@@ -147,7 +147,7 @@ module XeroGateway
147
147
  # Retreives a contact group by its id.
148
148
  def get_contact_group_by_id(contact_group_id)
149
149
  request_params = { :ContactGroupID => contact_group_id }
150
- response_xml = http_get(@client, "#{@xero_url}/ContactGroups/#{URI.escape(contact_group_id)}", request_params)
150
+ response_xml = http_get(@client, "#{@xero_url}/ContactGroups/#{CGI.escape(contact_group_id)}", request_params)
151
151
 
152
152
  parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contactgroup'})
153
153
  end
@@ -155,7 +155,9 @@ module XeroGateway
155
155
  # Retrieves all invoices from Xero
156
156
  #
157
157
  # Usage : get_invoices
158
- # get_invoices(:invoice_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
158
+ # get_invoices(:invoice_id => "297c2dc5-cc47-4afd-8ec8-74990b8761e9")
159
+ # get_invoices(:invoice_number => "175")
160
+ # get_invoices(:contact_ids => ["297c2dc5-cc47-4afd-8ec8-74990b8761e9"] )
159
161
  #
160
162
  # Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
161
163
  def get_invoices(options = {})
@@ -166,6 +168,7 @@ module XeroGateway
166
168
  request_params[:InvoiceNumber] = options[:invoice_number] if options[:invoice_number]
167
169
  request_params[:order] = options[:order] if options[:order]
168
170
  request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
171
+ request_params[:ContactIDs] = Array(options[:contact_ids]).join(",") if options[:contact_ids]
169
172
 
170
173
  request_params[:where] = options[:where] if options[:where]
171
174
 
@@ -186,7 +189,7 @@ module XeroGateway
186
189
 
187
190
  headers.merge!("Accept" => "application/pdf") if format == :pdf
188
191
 
189
- url = "#{@xero_url}/Invoices/#{URI.escape(invoice_id_or_number)}"
192
+ url = "#{@xero_url}/Invoices/#{CGI.escape(invoice_id_or_number)}"
190
193
 
191
194
  response = http_get(@client, url, request_params, headers)
192
195
 
@@ -306,7 +309,7 @@ module XeroGateway
306
309
  def get_credit_note(credit_note_id_or_number)
307
310
  request_params = {}
308
311
 
309
- url = "#{@xero_url}/CreditNotes/#{URI.escape(credit_note_id_or_number)}"
312
+ url = "#{@xero_url}/CreditNotes/#{CGI.escape(credit_note_id_or_number)}"
310
313
 
311
314
  response_xml = http_get(@client, url, request_params)
312
315
 
@@ -453,7 +456,7 @@ module XeroGateway
453
456
  # get_bank_transaction("OIT-12345") # By number
454
457
  def get_bank_transaction(bank_transaction_id)
455
458
  request_params = {}
456
- url = "#{@xero_url}/BankTransactions/#{URI.escape(bank_transaction_id)}"
459
+ url = "#{@xero_url}/BankTransactions/#{CGI.escape(bank_transaction_id)}"
457
460
  response_xml = http_get(@client, url, request_params)
458
461
  parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/BankTransaction'})
459
462
  end
@@ -503,7 +506,7 @@ module XeroGateway
503
506
  # get_manual_journal("OIT-12345") # By number
504
507
  def get_manual_journal(manual_journal_id)
505
508
  request_params = {}
506
- url = "#{@xero_url}/ManualJournals/#{URI.escape(manual_journal_id)}"
509
+ url = "#{@xero_url}/ManualJournals/#{CGI.escape(manual_journal_id)}"
507
510
  response_xml = http_get(@client, url, request_params)
508
511
  parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/ManualJournal'})
509
512
  end
@@ -621,7 +624,7 @@ module XeroGateway
621
624
 
622
625
  def get_contact(contact_id = nil, contact_number = nil)
623
626
  request_params = contact_id ? { :contactID => contact_id } : { :contactNumber => contact_number }
624
- response_xml = http_get(@client, "#{@xero_url}/Contacts/#{URI.escape(contact_id||contact_number)}", request_params)
627
+ response_xml = http_get(@client, "#{@xero_url}/Contacts/#{CGI.escape(contact_id||contact_number)}", request_params)
625
628
 
626
629
  parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/contact'})
627
630
  end