xero_gateway 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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