xero_gateway 2.5.0 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +24 -9
- data/lib/xero_gateway.rb +3 -0
- data/lib/xero_gateway/base_record.rb +7 -3
- data/lib/xero_gateway/contact.rb +10 -1
- data/lib/xero_gateway/contact_person.rb +33 -0
- data/lib/xero_gateway/gateway.rb +24 -2
- data/lib/xero_gateway/invoice.rb +2 -0
- data/lib/xero_gateway/line_item.rb +25 -21
- data/lib/xero_gateway/pay_run.rb +19 -0
- data/lib/xero_gateway/payroll_calendar.rb +12 -0
- data/lib/xero_gateway/private_app.rb +3 -2
- data/lib/xero_gateway/report.rb +86 -65
- data/lib/xero_gateway/report/cell.rb +28 -0
- data/lib/xero_gateway/report/row.rb +57 -0
- data/lib/xero_gateway/version.rb +1 -1
- data/test/unit/contact_test.rb +9 -0
- data/test/unit/gateway_test.rb +12 -0
- data/test/unit/invoice_test.rb +42 -1
- data/test/unit/report/row_test.rb +45 -0
- data/test/unit/report_test.rb +74 -48
- data/xero_gateway.gemspec +1 -1
- metadata +23 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 15bce269e603d39203dbafb5858843c8e8fe8b3e
|
4
|
+
data.tar.gz: a996fa84e4ca2471b3efc06eb68c080fbe4ed8cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 57f8ec61dbe74dcd7fb6fbdb4a2f65087079e2f4808bc8a08696e445d6f91b2b9ec4661ca557029041b1ffe35a11e3b2f49f42faf458c9b42c4bacad77b02cca
|
7
|
+
data.tar.gz: 4a203e5a9a76a0146c758249126bc595946422960d4cb37a056fb9607e0e5ad1493d336f5b24ad02a87076875a41b5dfd2ed09c3cc51f5d14b27caccb9158045
|
data/README.md
CHANGED
@@ -26,19 +26,19 @@ The Xero Gateway uses [OAuth 1.0a](https://oauth.net/core/1.0a/) for authenticat
|
|
26
26
|
implements OAuth in a very similar manner to the [Twitter gem by John Nunemaker](http://github.com/jnunemaker/twitter)
|
27
27
|
, so if you've used that before this will all seem familiar.
|
28
28
|
|
29
|
-
### Authenticating: Public
|
29
|
+
### Authenticating: Public Applications
|
30
30
|
|
31
|
-
Public
|
31
|
+
Public are traditional three-legged OAuth apps that can be used to access many different Xero accounts.
|
32
32
|
|
33
33
|
1. **Get a Consumer Key & Secret**
|
34
34
|
|
35
35
|
First off, you'll need to get a Consumer Key/Secret pair for your application from Xero.
|
36
36
|
|
37
|
-
Head to <https://
|
37
|
+
Head to <https://developer.xero.com/myapps>, log in and then click New Application.
|
38
38
|
|
39
|
-
Part of the process for this will ask you for an "OAuth
|
39
|
+
Part of the process for this will ask you for an "OAuth Callback Domain". This is the domain where customers will be redirected once they complete logging in with Xero.
|
40
40
|
|
41
|
-
|
41
|
+
Further down in your application's page there's a box titled "App Credentials". Use the Key and Secret from this box in order to set up a new Gateway instance.
|
42
42
|
|
43
43
|
2. **Create a Xero Gateway in your App**
|
44
44
|
|
@@ -106,7 +106,7 @@ Public (or Partner, if you've been through the process to be approved) are tradi
|
|
106
106
|
gateway.authorize_from_access(your_stored_token.access_token, your_stored_token.access_secret)
|
107
107
|
```
|
108
108
|
|
109
|
-
|
109
|
+
### Authenticating: Private Applications
|
110
110
|
|
111
111
|
Private applications are used to access a single Xero account.
|
112
112
|
|
@@ -117,8 +117,8 @@ Private applications are used to access a single Xero account.
|
|
117
117
|
You'll need to generate an RSA keypair and an X509 certificate. This can be done with OpenSSL as below:
|
118
118
|
|
119
119
|
```bash
|
120
|
-
openssl genrsa -out privatekey.pem
|
121
|
-
openssl req -
|
120
|
+
openssl genrsa -out privatekey.pem 1024
|
121
|
+
openssl req -new -x509 -key privatekey.pem -out publickey.cer -days 1825
|
122
122
|
```
|
123
123
|
|
124
124
|
You can then copy `publickey.cer` and paste it into the certificate box (`cat publickey.cer | pbcopy` on a Mac :apple:)
|
@@ -138,13 +138,28 @@ Private applications are used to access a single Xero account.
|
|
138
138
|
|
139
139
|
Note that for private apps, your consumer key and secret do double duty as your access token and secret pair :)
|
140
140
|
|
141
|
+
### Authenticating: Partner Applications
|
142
|
+
|
143
|
+
Partner applications are public applications that have been upgraded to support long-term access tokens.
|
144
|
+
|
145
|
+
Use the same three-legged authentication process as for public applications, but with an RSA keypair and an X509 certificate as for private applications:
|
146
|
+
|
147
|
+
```ruby
|
148
|
+
require 'xero_gateway'
|
149
|
+
gateway = XeroGateway::PartnerApp.new(YOUR_OAUTH_CONSUMER_KEY, YOUR_OAUTH_CONSUMER_SECRET, PATH_TO_YOUR_PRIVATE_KEY)
|
150
|
+
|
151
|
+
pp gateway.get_contacts
|
152
|
+
```
|
153
|
+
|
154
|
+
For more information on partner applications see the Xero documentation: <https://developer.xero.com/documentation/auth-and-limits/partner-applications>
|
155
|
+
|
141
156
|
## Examples
|
142
157
|
|
143
158
|
Open `examples/oauth.rb` and change `CONSUMER_KEY` and `CONSUMER_SECRET` to
|
144
159
|
the values for a Test OAuth Public Application in order to see an example of
|
145
160
|
OAuth at work.
|
146
161
|
|
147
|
-
|
162
|
+
See also `examples/private_app.rb` for an example private app or `examples/partner_app.rb` for an example partner app.
|
148
163
|
|
149
164
|
If you're working with Rails, a controller similar to this might come in
|
150
165
|
handy:
|
data/lib/xero_gateway.rb
CHANGED
@@ -24,8 +24,11 @@ require File.join(File.dirname(__FILE__), 'xero_gateway', 'accounts_list')
|
|
24
24
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'tracking_category')
|
25
25
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'contact')
|
26
26
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'contact_group')
|
27
|
+
require File.join(File.dirname(__FILE__), 'xero_gateway', 'contact_person')
|
27
28
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'line_item')
|
28
29
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'payment')
|
30
|
+
require File.join(File.dirname(__FILE__), 'xero_gateway', 'payroll_calendar')
|
31
|
+
require File.join(File.dirname(__FILE__), 'xero_gateway', 'pay_run')
|
29
32
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'invoice')
|
30
33
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'bank_transaction')
|
31
34
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'credit_note')
|
@@ -76,9 +76,13 @@ module XeroGateway
|
|
76
76
|
end
|
77
77
|
|
78
78
|
value = case attr_definition
|
79
|
-
when :boolean
|
80
|
-
when :float
|
81
|
-
when :integer
|
79
|
+
when :boolean then element.text == "true"
|
80
|
+
when :float then element.text.to_f
|
81
|
+
when :integer then element.text.to_i
|
82
|
+
when :currency then BigDecimal.new(element.text)
|
83
|
+
when :date then Date.strptime(element.text, "%Y-%m-%d")
|
84
|
+
when :datetime then Date.strptime(element.text, "%Y-%m-%dT%H:%M:%S")
|
85
|
+
when :datetime_utc then Date.strptime(element.text + "Z", "%Y-%m-%dT%H:%M:%S%Z")
|
82
86
|
when Array then array_from_xml(element, attr_definition)
|
83
87
|
else element.text
|
84
88
|
end if element.text.present? || element.children.present?
|
data/lib/xero_gateway/contact.rb
CHANGED
@@ -17,7 +17,7 @@ module XeroGateway
|
|
17
17
|
|
18
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
|
-
:default_currency, :contact_groups
|
20
|
+
:default_currency, :contact_groups, :contact_persons
|
21
21
|
|
22
22
|
|
23
23
|
def initialize(params = {})
|
@@ -79,6 +79,11 @@ module XeroGateway
|
|
79
79
|
self.phones << Phone.new(phone_params)
|
80
80
|
end
|
81
81
|
|
82
|
+
def add_contact_person(contact_person_params = {})
|
83
|
+
self.contact_persons ||= []
|
84
|
+
self.contact_persons << ContactPerson.new(contact_person_params)
|
85
|
+
end
|
86
|
+
|
82
87
|
# Validate the Contact record according to what will be valid by the gateway.
|
83
88
|
#
|
84
89
|
# Usage:
|
@@ -160,6 +165,9 @@ module XeroGateway
|
|
160
165
|
b.Phones {
|
161
166
|
phones.each { |phone| phone.to_xml(b) }
|
162
167
|
} if self.phones.any?
|
168
|
+
b.ContactPersons {
|
169
|
+
contact_persons.each { |contact_person| contact_person.to_xml(b) }
|
170
|
+
} unless contact_persons.nil?
|
163
171
|
}
|
164
172
|
end
|
165
173
|
|
@@ -187,6 +195,7 @@ module XeroGateway
|
|
187
195
|
when "IsSupplier" then contact.is_supplier = (element.text == "true")
|
188
196
|
when "DefaultCurrency" then contact.default_currency = element.text
|
189
197
|
when "UpdatedDateUTC" then contact.updated_at = parse_date_time(element.text)
|
198
|
+
when "ContactPersons" then element.children.each { |contact_person_element| contact.contact_persons ||= []; contact.contact_persons << ContactPerson.from_xml(contact_person_element) }
|
190
199
|
end
|
191
200
|
end
|
192
201
|
contact
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
class ContactPerson
|
3
|
+
attr_accessor :first_name, :last_name, :email_address, :include_in_emails
|
4
|
+
|
5
|
+
def initialize(params = {})
|
6
|
+
params.each do |k,v|
|
7
|
+
self.send("#{k}=", v)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_xml(b = Builder::XmlMarkup.new)
|
12
|
+
b.ContactPerson {
|
13
|
+
b.FirstName first_name if first_name
|
14
|
+
b.LastName last_name if last_name
|
15
|
+
b.EmailAddress email_address if email_address
|
16
|
+
b.IncludeInEmails include_in_emails if include_in_emails
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.from_xml(contact_person_element)
|
21
|
+
contact_person = ContactPerson.new
|
22
|
+
contact_person_element.children.each do |element|
|
23
|
+
case(element.name)
|
24
|
+
when "FirstName" then contact_person.first_name = element.text
|
25
|
+
when "LastName" then contact_person.last_name = element.text
|
26
|
+
when "EmailAddress" then contact_person.email_address = element.text
|
27
|
+
when "IncludeInEmails" then contact_person.include_in_emails = (element.text == "true")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
contact_person
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/xero_gateway/gateway.rb
CHANGED
@@ -4,7 +4,7 @@ module XeroGateway
|
|
4
4
|
include Http
|
5
5
|
include Dates
|
6
6
|
|
7
|
-
attr_accessor :client, :xero_url, :logger
|
7
|
+
attr_accessor :client, :xero_url, :payroll_url, :logger
|
8
8
|
|
9
9
|
extend Forwardable
|
10
10
|
def_delegators :client, :request_token, :access_token, :authorize_from_request, :authorize_from_access, :expires_at, :authorization_expires_at
|
@@ -14,6 +14,7 @@ module XeroGateway
|
|
14
14
|
# to you by Xero inside the API Previewer.
|
15
15
|
def initialize(consumer_key, consumer_secret, options = {})
|
16
16
|
@xero_url = options[:xero_url] || "https://api.xero.com/api.xro/2.0"
|
17
|
+
@payroll_url = options[:payroll_url] || "https://api.xero.com/payroll.xro/1.0"
|
17
18
|
@client = OAuth.new(consumer_key, consumer_secret, options)
|
18
19
|
end
|
19
20
|
|
@@ -445,6 +446,7 @@ module XeroGateway
|
|
445
446
|
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
446
447
|
request_params[:order] = options[:order] if options[:order]
|
447
448
|
request_params[:where] = options[:where] if options[:where]
|
449
|
+
request_params[:page] = options[:page] if options[:page]
|
448
450
|
|
449
451
|
response_xml = http_get(@client, "#{@xero_url}/BankTransactions", request_params)
|
450
452
|
|
@@ -603,10 +605,28 @@ module XeroGateway
|
|
603
605
|
#
|
604
606
|
def get_payment(payment_id, options = {})
|
605
607
|
request_params = {}
|
606
|
-
response_xml = http_get(client, "#{xero_url}/Payments/#{payment_id}", request_params)
|
608
|
+
response_xml = http_get(client, "#{@xero_url}/Payments/#{payment_id}", request_params)
|
607
609
|
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/payments'})
|
608
610
|
end
|
609
611
|
|
612
|
+
#
|
613
|
+
# Get the Payroll calendars for a specific organization in Xero
|
614
|
+
#
|
615
|
+
def get_payroll_calendars(options = {})
|
616
|
+
request_params = {}
|
617
|
+
response_xml = http_get(client, "#{@payroll_url}/PayrollCalendars", request_params)
|
618
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/payroll_calendars'})
|
619
|
+
end
|
620
|
+
|
621
|
+
#
|
622
|
+
# Get the Pay Runs for a specific organization in Xero
|
623
|
+
#
|
624
|
+
def get_pay_runs(options = {})
|
625
|
+
request_params = {}
|
626
|
+
response_xml = http_get(client, "#{@payroll_url}/PayRuns", request_params)
|
627
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/pay_runs'})
|
628
|
+
end
|
629
|
+
|
610
630
|
# Retrieves reports from Xero
|
611
631
|
#
|
612
632
|
# Usage : get_report("BankStatement", bank_account_id: "AC993F75-035B-433C-82E0-7B7A2D40802C")
|
@@ -782,6 +802,8 @@ module XeroGateway
|
|
782
802
|
element.children.each do |child|
|
783
803
|
response.response_item << Payment.from_xml(child)
|
784
804
|
end
|
805
|
+
when "PayrollCalendars" then element.children.each {|child| response.response_item << PayrollCalendar.from_xml(child) }
|
806
|
+
when "PayRuns" then element.children.each {|child| response.response_item << PayRun.from_xml(child) }
|
785
807
|
when "Reports"
|
786
808
|
element.children.each do |child|
|
787
809
|
response.response_item << Report.from_xml(child)
|
data/lib/xero_gateway/invoice.rb
CHANGED
@@ -185,6 +185,7 @@ module XeroGateway
|
|
185
185
|
contact.to_xml(b)
|
186
186
|
b.Date Invoice.format_date(self.date || Date.today)
|
187
187
|
b.DueDate Invoice.format_date(self.due_date) if self.due_date
|
188
|
+
b.FullyPaidOnDate Invoice.format_date(self.fully_paid_on) if self.fully_paid_on
|
188
189
|
b.Status self.invoice_status if self.invoice_status
|
189
190
|
b.Reference self.reference if self.reference
|
190
191
|
b.BrandingThemeID self.branding_theme_id if self.branding_theme_id
|
@@ -212,6 +213,7 @@ module XeroGateway
|
|
212
213
|
when "Contact" then invoice.contact = Contact.from_xml(element)
|
213
214
|
when "Date" then invoice.date = parse_date(element.text)
|
214
215
|
when "DueDate" then invoice.due_date = parse_date(element.text)
|
216
|
+
when "FullyPaidOnDate" then invoice.fully_paid_on = parse_date(element.text)
|
215
217
|
when "UpdatedDateUTC" then invoice.updated_date_utc = parse_date(element.text)
|
216
218
|
when "Status" then invoice.invoice_status = element.text
|
217
219
|
when "Reference" then invoice.reference = element.text
|
@@ -3,73 +3,75 @@ require File.join(File.dirname(__FILE__), 'account')
|
|
3
3
|
module XeroGateway
|
4
4
|
class LineItem
|
5
5
|
include Money
|
6
|
-
|
6
|
+
|
7
7
|
TAX_TYPE = Account::TAX_TYPE unless defined?(TAX_TYPE)
|
8
8
|
|
9
9
|
# Any errors that occurred when the #valid? method called.
|
10
10
|
attr_reader :errors
|
11
11
|
|
12
12
|
# All accessible fields
|
13
|
-
attr_accessor :line_item_id, :description, :quantity, :unit_amount, :item_code, :tax_type, :tax_amount, :account_code, :tracking
|
14
|
-
|
13
|
+
attr_accessor :line_item_id, :description, :quantity, :unit_amount, :discount_rate, :item_code, :tax_type, :tax_amount, :account_code, :tracking
|
14
|
+
|
15
15
|
def initialize(params = {})
|
16
16
|
@errors ||= []
|
17
17
|
@tracking ||= []
|
18
18
|
@quantity = 1
|
19
19
|
@unit_amount = BigDecimal.new('0')
|
20
|
-
|
20
|
+
|
21
21
|
params.each do |k,v|
|
22
22
|
self.send("#{k}=", v)
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
# Validate the LineItem record according to what will be valid by the gateway.
|
27
27
|
#
|
28
|
-
# Usage:
|
28
|
+
# Usage:
|
29
29
|
# line_item.valid? # Returns true/false
|
30
|
-
#
|
30
|
+
#
|
31
31
|
# Additionally sets line_item.errors array to an array of field/error.
|
32
32
|
def valid?
|
33
33
|
@errors = []
|
34
|
-
|
34
|
+
|
35
35
|
if !line_item_id.nil? && line_item_id !~ GUID_REGEX
|
36
36
|
@errors << ['line_item_id', 'must be blank or a valid Xero GUID']
|
37
37
|
end
|
38
|
-
|
38
|
+
|
39
39
|
unless description
|
40
40
|
@errors << ['description', "can't be blank"]
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
if tax_type && !TAX_TYPE[tax_type]
|
44
44
|
@errors << ['tax_type', "must be one of #{TAX_TYPE.keys.join('/')}"]
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
@errors.size == 0
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def has_tracking?
|
51
51
|
return false if tracking.nil?
|
52
|
-
|
52
|
+
|
53
53
|
if tracking.is_a?(Array)
|
54
54
|
return tracking.any?
|
55
55
|
else
|
56
56
|
return tracking.is_a?(TrackingCategory)
|
57
57
|
end
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
60
|
# Deprecated (but API for setter remains).
|
61
61
|
#
|
62
62
|
# As line_amount must equal quantity * unit_amount for the API call to pass, this is now
|
63
63
|
# automatically calculated in the line_amount method.
|
64
64
|
def line_amount=(value)
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
# Calculate the line_amount as quantity * unit_amount as this value must be correct
|
68
68
|
# for the API call to succeed.
|
69
69
|
def line_amount
|
70
|
-
quantity * unit_amount
|
70
|
+
total = quantity * unit_amount
|
71
|
+
total = total * (1 - (discount_rate / BigDecimal.new(100))) if discount_rate
|
72
|
+
total
|
71
73
|
end
|
72
|
-
|
74
|
+
|
73
75
|
def to_xml(b = Builder::XmlMarkup.new)
|
74
76
|
b.LineItem {
|
75
77
|
b.Description description
|
@@ -79,6 +81,7 @@ module XeroGateway
|
|
79
81
|
b.TaxType tax_type if tax_type
|
80
82
|
b.TaxAmount tax_amount if tax_amount
|
81
83
|
b.LineAmount line_amount if line_amount
|
84
|
+
b.DiscountRate discount_rate if discount_rate
|
82
85
|
b.AccountCode account_code if account_code
|
83
86
|
if has_tracking?
|
84
87
|
b.Tracking {
|
@@ -92,7 +95,7 @@ module XeroGateway
|
|
92
95
|
end
|
93
96
|
}
|
94
97
|
end
|
95
|
-
|
98
|
+
|
96
99
|
def self.from_xml(line_item_element)
|
97
100
|
line_item = LineItem.new
|
98
101
|
line_item_element.children.each do |element|
|
@@ -105,6 +108,7 @@ module XeroGateway
|
|
105
108
|
when "TaxType" then line_item.tax_type = element.text
|
106
109
|
when "TaxAmount" then line_item.tax_amount = BigDecimal.new(element.text)
|
107
110
|
when "LineAmount" then line_item.line_amount = BigDecimal.new(element.text)
|
111
|
+
when "DiscountRate" then line_item.discount_rate = BigDecimal.new(element.text)
|
108
112
|
when "AccountCode" then line_item.account_code = element.text
|
109
113
|
when "Tracking" then
|
110
114
|
element.children.each do | tracking_element |
|
@@ -113,13 +117,13 @@ module XeroGateway
|
|
113
117
|
end
|
114
118
|
end
|
115
119
|
line_item
|
116
|
-
end
|
120
|
+
end
|
117
121
|
|
118
122
|
def ==(other)
|
119
|
-
[:description, :quantity, :unit_amount, :tax_type, :tax_amount, :line_amount, :account_code, :item_code].each do |field|
|
123
|
+
[:description, :quantity, :unit_amount, :tax_type, :tax_amount, :line_amount, :discount_rate, :account_code, :item_code].each do |field|
|
120
124
|
return false if send(field) != other.send(field)
|
121
125
|
end
|
122
126
|
return true
|
123
127
|
end
|
124
|
-
end
|
128
|
+
end
|
125
129
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
class PayRun < BaseRecord
|
3
|
+
attributes({
|
4
|
+
"PayRunID" => :string,
|
5
|
+
"PayrollCalendarID" => :string,
|
6
|
+
"PayRunPeriodStartDate" => :date,
|
7
|
+
"PayRunPeriodEndDate" => :date,
|
8
|
+
"PaymentDate" => :date,
|
9
|
+
"Wages" => :currency,
|
10
|
+
"Deductions" => :currency,
|
11
|
+
"Tax" => :currency,
|
12
|
+
"Super" => :currency,
|
13
|
+
"Reimbursement" => :currency,
|
14
|
+
"NetPay" => :currency,
|
15
|
+
"PayRunStatus" => :string,
|
16
|
+
"UpdatedDateUTC" => :datetime_utc
|
17
|
+
})
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
class PayrollCalendar < BaseRecord
|
3
|
+
attributes({
|
4
|
+
"PayrollCalendarID" => :string,
|
5
|
+
"Name" => :string,
|
6
|
+
"CalendarType" => :string,
|
7
|
+
"StartDate" => :date,
|
8
|
+
"PaymentDate" => :date,
|
9
|
+
"UpdatedDateUTC" => :datetime_utc
|
10
|
+
})
|
11
|
+
end
|
12
|
+
end
|
@@ -2,14 +2,15 @@ module XeroGateway
|
|
2
2
|
class PrivateApp < Gateway
|
3
3
|
#
|
4
4
|
# The consumer key and secret here correspond to those provided
|
5
|
-
# to you by Xero inside the API Previewer.
|
5
|
+
# to you by Xero inside the API Previewer.
|
6
6
|
def initialize(consumer_key, consumer_secret, path_to_private_key, options = {})
|
7
7
|
options.merge!(
|
8
8
|
:signature_method => 'RSA-SHA1',
|
9
9
|
:private_key_file => path_to_private_key
|
10
10
|
)
|
11
|
-
|
11
|
+
|
12
12
|
@xero_url = options[:xero_url] || "https://api.xero.com/api.xro/2.0"
|
13
|
+
@payroll_url = options[:payroll_url] || "https://api.xero.com/payroll.xro/1.0"
|
13
14
|
@client = OAuth.new(consumer_key, consumer_secret, options)
|
14
15
|
@client.authorize_from_access(consumer_key, consumer_secret)
|
15
16
|
end
|
data/lib/xero_gateway/report.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
|
+
require_relative './report/cell'
|
2
|
+
require_relative './report/row'
|
3
|
+
|
1
4
|
module XeroGateway
|
2
5
|
class Report
|
6
|
+
|
3
7
|
include Money
|
4
8
|
include Dates
|
5
9
|
|
@@ -7,6 +11,8 @@ module XeroGateway
|
|
7
11
|
attr_accessor :report_id, :report_name, :report_type, :report_titles, :report_date, :updated_at,
|
8
12
|
:body, :column_names
|
9
13
|
|
14
|
+
alias :rows :body
|
15
|
+
|
10
16
|
def initialize(params={})
|
11
17
|
@errors ||= []
|
12
18
|
@report_titles ||= []
|
@@ -17,78 +23,93 @@ module XeroGateway
|
|
17
23
|
end
|
18
24
|
end
|
19
25
|
|
20
|
-
|
21
|
-
|
22
|
-
report_element
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
report.
|
35
|
-
|
36
|
-
report.
|
37
|
-
|
26
|
+
class << self
|
27
|
+
|
28
|
+
def from_xml(report_element)
|
29
|
+
report = Report.new
|
30
|
+
report_element.children.each do | element |
|
31
|
+
case element.name
|
32
|
+
when 'ReportID' then report.report_id = element.text
|
33
|
+
when 'ReportName' then report.report_name = element.text
|
34
|
+
when 'ReportType' then report.report_type = element.text
|
35
|
+
when 'ReportTitles'
|
36
|
+
each_title(element) do |title|
|
37
|
+
report.report_titles << title
|
38
|
+
end
|
39
|
+
when 'ReportDate' then report.report_date = Date.parse(element.text)
|
40
|
+
when 'UpdatedDateUTC' then report.updated_at = parse_date_time_utc(element.text)
|
41
|
+
when 'Rows'
|
42
|
+
report.column_names ||= find_body_column_names(element)
|
43
|
+
each_row_content(element) do |row|
|
44
|
+
report.body << row
|
45
|
+
end
|
46
|
+
end
|
38
47
|
end
|
48
|
+
report
|
39
49
|
end
|
40
|
-
report
|
41
|
-
end
|
42
50
|
|
43
|
-
|
44
|
-
|
45
|
-
def self.each_row_content(xml_element, &block)
|
46
|
-
column_names = find_body_column_names(xml_element).keys
|
47
|
-
xpath_body = REXML::XPath.first(xml_element, "//RowType[text()='Section']").parent
|
48
|
-
rows_contents = []
|
49
|
-
xpath_body.elements.each("Rows/Row") do |xpath_cells|
|
50
|
-
values = find_body_cell_values(xpath_cells)
|
51
|
-
content_hash = Hash[column_names.zip values]
|
52
|
-
rows_contents << content_hash
|
53
|
-
yield content_hash if block_given?
|
54
|
-
end
|
55
|
-
rows_contents
|
56
|
-
end
|
51
|
+
private
|
57
52
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
title = xpath_title.text.strip
|
62
|
-
yield title if block_given?
|
63
|
-
end
|
64
|
-
end
|
53
|
+
def each_row_content(xml_element, &block)
|
54
|
+
column_names = find_body_column_names(xml_element).values
|
55
|
+
report_sections = REXML::XPath.each(xml_element, "//RowType[text()='Section']/parent::Row")
|
65
56
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
57
|
+
report_sections.each do |section_row|
|
58
|
+
section_name = section_row.get_elements("Title").first.try(:text)
|
59
|
+
section_row.elements.each("Rows/Row") do |xpath_cells|
|
60
|
+
values = find_body_cell_values(xpath_cells)
|
61
|
+
yield Row.new(column_names, values, section_name)
|
62
|
+
end
|
63
|
+
end
|
72
64
|
end
|
73
|
-
values << nil
|
74
|
-
end
|
75
|
-
values
|
76
|
-
end
|
77
65
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
66
|
+
def each_title(xml_element, &block)
|
67
|
+
xpath_titles = REXML::XPath.first(xml_element, "//ReportTitles")
|
68
|
+
xpath_titles.elements.each("//ReportTitle") do |xpath_title|
|
69
|
+
title = xpath_title.text.strip
|
70
|
+
yield title if block_given?
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def find_body_cell_values(xml_cells)
|
75
|
+
values = []
|
76
|
+
xml_cells.elements.each("Cells/Cell") do |xml_cell|
|
77
|
+
if value = xml_cell.children.first # finds <Value>...</Value>
|
78
|
+
values << Cell.new(value.text.try(:strip), collect_attributes(xml_cell))
|
79
|
+
next
|
80
|
+
end
|
81
|
+
values << nil
|
82
|
+
end
|
83
|
+
values
|
84
|
+
end
|
85
|
+
|
86
|
+
# Collects "<Attribute>" elements into a hash
|
87
|
+
def collect_attributes(xml_cell)
|
88
|
+
Array.wrap(xml_cell.elements["Attributes/Attribute"]).inject({}) do |hash, xml_attribute|
|
89
|
+
if (key = xml_attribute.elements["Id"].try(:text)) &&
|
90
|
+
(value = xml_attribute.elements["Value"].try(:text))
|
91
|
+
|
92
|
+
hash[key] = value
|
93
|
+
end
|
94
|
+
hash
|
95
|
+
end.symbolize_keys
|
96
|
+
end
|
97
|
+
|
98
|
+
# returns something like { column_1: "Amount", column_2: "Description", ... }
|
99
|
+
def find_body_column_names(body)
|
100
|
+
header = REXML::XPath.first(body, "//RowType[text()='Header']")
|
101
|
+
names_map = {}
|
102
|
+
column_count = 0
|
103
|
+
header.parent.elements.each("Cells/Cell") do |header_cell|
|
104
|
+
column_count += 1
|
105
|
+
column_key = "column_#{column_count}".to_sym
|
106
|
+
column_name = nil
|
107
|
+
name_value = header_cell.children.first
|
108
|
+
column_name = name_value.text.strip unless name_value.blank? # finds <Value>...</Value>
|
109
|
+
names_map[column_key] = column_name
|
110
|
+
end
|
111
|
+
names_map
|
112
|
+
end
|
92
113
|
end
|
93
114
|
|
94
115
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
class Report
|
3
|
+
|
4
|
+
# Adds #attributes to the cells we're grabbing, since Xero Report Cells use XML like:
|
5
|
+
# <Cell>
|
6
|
+
# <Value>Interest Income (270)</Value>
|
7
|
+
# <Attributes>
|
8
|
+
# <Attribute>
|
9
|
+
# <Value>e9482110-7245-4a76-bfe2-14500495a076</Value>
|
10
|
+
# <Id>account</Id>
|
11
|
+
# </Attribute>
|
12
|
+
# </Attributes>
|
13
|
+
# </Cell>
|
14
|
+
#
|
15
|
+
# We delegate to the topmost "<Value>" class and decorate with an "attributes" hash
|
16
|
+
# for the "attribute: value" pairs
|
17
|
+
class Cell < SimpleDelegator
|
18
|
+
attr_reader :attributes, :value
|
19
|
+
|
20
|
+
def initialize(value, new_attributes = {})
|
21
|
+
@value = value
|
22
|
+
@attributes = new_attributes
|
23
|
+
super(value)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
class Report
|
3
|
+
class Row
|
4
|
+
COLUMN_METHOD_NAME_RE = /^column\_([0-9])+$/
|
5
|
+
|
6
|
+
attr_accessor :section_name
|
7
|
+
|
8
|
+
def initialize(column_titles, columns, section_name = nil)
|
9
|
+
@columns = columns
|
10
|
+
@column_titles = column_titles
|
11
|
+
@column_titles_underscored = column_titles.map(&:to_s).map(&:underscore)
|
12
|
+
@section_name = section_name
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
return @columns[key] if key.is_a?(Integer)
|
17
|
+
|
18
|
+
[ @column_titles, @column_titles_underscored ].each do |names|
|
19
|
+
if index = names.index(key.to_s)
|
20
|
+
return @columns[index]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(method_name, *args, &block)
|
28
|
+
if method_name =~ COLUMN_METHOD_NAME_RE
|
29
|
+
# support column_#{n} style deprecated API
|
30
|
+
ActiveSupport::Deprecation.warn("XeroGateway: The #column_n API for accessing report cells will be deprecated in a future version. Please use the underscored column title, a hash or array index accessor", caller_locations)
|
31
|
+
@columns[$1.to_i - 1]
|
32
|
+
elsif (column_index = @column_titles_underscored.index(method_name.to_s))
|
33
|
+
@columns[column_index]
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def respond_to_missing?(method_name, *args)
|
40
|
+
(method_name =~ COLUMN_METHOD_NAME_RE) || @column_titles_underscored.include?(method_name.to_s) || super
|
41
|
+
end
|
42
|
+
|
43
|
+
def inspect
|
44
|
+
"<XeroGateway::Report::Row:#{object_id} #{pairs}>"
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def pairs
|
50
|
+
@column_titles.zip(@columns).map do |title, value|
|
51
|
+
"#{title.to_s.underscore}: #{value.inspect}"
|
52
|
+
end.join(" ")
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
data/lib/xero_gateway/version.rb
CHANGED
data/test/unit/contact_test.rb
CHANGED
@@ -80,6 +80,14 @@ class ContactTest < Test::Unit::TestCase
|
|
80
80
|
<PhoneType>MOBILE</PhoneType>
|
81
81
|
</Phone>
|
82
82
|
</Phones>
|
83
|
+
<ContactPersons>
|
84
|
+
<ContactPerson>
|
85
|
+
<FirstName>John</FirstName>
|
86
|
+
<LastName>Smith</LastName>
|
87
|
+
<EmailAddress>john@acme.com</EmailAddress>
|
88
|
+
<IncludeInEmails>true</IncludeInEmails>
|
89
|
+
</ContactPerson>
|
90
|
+
</ContactPersons>
|
83
91
|
<UpdatedDateUTC>2016-08-31T04:55:39.217</UpdatedDateUTC>
|
84
92
|
<IsSupplier>false</IsSupplier>
|
85
93
|
<IsCustomer>false</IsCustomer>
|
@@ -172,6 +180,7 @@ class ContactTest < Test::Unit::TestCase
|
|
172
180
|
contact.phone.number = "12345"
|
173
181
|
contact.is_customer = true
|
174
182
|
contact.is_supplier = true
|
183
|
+
contact.add_contact_person(first_name: 'John', last_name: 'Smith', email_address: 'john@acme.com', include_in_emails: true)
|
175
184
|
|
176
185
|
contact
|
177
186
|
end
|
data/test/unit/gateway_test.rb
CHANGED
@@ -75,6 +75,18 @@ class GatewayTest < Test::Unit::TestCase
|
|
75
75
|
assert result.response_item.first.is_a? XeroGateway::Payment
|
76
76
|
end
|
77
77
|
|
78
|
+
should :get_payroll_calendars do
|
79
|
+
XeroGateway::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("payroll_calendars.xml"), :code => "200"))
|
80
|
+
result = @gateway.get_payroll_calendars
|
81
|
+
assert result.response_item.first.is_a? XeroGateway::PayrollCalendar
|
82
|
+
end
|
83
|
+
|
84
|
+
should :get_pay_runs do
|
85
|
+
XeroGateway::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("pay_runs.xml"), :code => "200"))
|
86
|
+
result = @gateway.get_pay_runs
|
87
|
+
assert result.response_item.is_a? XeroGateway::PayRun
|
88
|
+
end
|
89
|
+
|
78
90
|
should :get_contacts do
|
79
91
|
XeroGateway::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("contacts.xml"), :code => "200"))
|
80
92
|
result = @gateway.get_contacts
|
data/test/unit/invoice_test.rb
CHANGED
@@ -68,6 +68,27 @@ class InvoiceTest < Test::Unit::TestCase
|
|
68
68
|
parsed_invoice = XeroGateway::Invoice.from_xml(invoice_element)
|
69
69
|
assert_equal 'http://example.com?with=params&and=more', parsed_invoice.url
|
70
70
|
end
|
71
|
+
|
72
|
+
should "work with line_item discount rates" do
|
73
|
+
invoice = create_test_invoice
|
74
|
+
invoice.line_items.first.discount_rate = 27
|
75
|
+
invoice_as_xml = invoice.to_xml
|
76
|
+
|
77
|
+
invoice_element = REXML::XPath.first(REXML::Document.new(invoice_as_xml), "/Invoice")
|
78
|
+
result_invoice = XeroGateway::Invoice.from_xml(invoice_element)
|
79
|
+
|
80
|
+
assert_equal(invoice, result_invoice)
|
81
|
+
assert_equal 27, result_invoice.line_items.first.discount_rate
|
82
|
+
end
|
83
|
+
|
84
|
+
should "handle paid-on date" do
|
85
|
+
invoice = create_test_invoice(:fully_paid_on => Date.yesterday)
|
86
|
+
invoice_element = REXML::XPath.first(REXML::Document.new(invoice.to_xml), "/Invoice")
|
87
|
+
result_invoice = XeroGateway::Invoice.from_xml(invoice_element)
|
88
|
+
|
89
|
+
assert_equal(invoice, result_invoice)
|
90
|
+
assert_equal Date.yesterday, result_invoice.fully_paid_on
|
91
|
+
end
|
71
92
|
end
|
72
93
|
|
73
94
|
# Tests the sub_total calculation and that setting it manually doesn't modify the data.
|
@@ -154,6 +175,26 @@ class InvoiceTest < Test::Unit::TestCase
|
|
154
175
|
assert_equal(quantity * line_item.unit_amount, line_item.line_amount)
|
155
176
|
end
|
156
177
|
|
178
|
+
def test_line_amount_discount_calculation
|
179
|
+
invoice = create_test_invoice
|
180
|
+
line_item = invoice.line_items.first
|
181
|
+
line_item.discount_rate = 12.5
|
182
|
+
|
183
|
+
# Make sure that everything adds up to begin with.
|
184
|
+
expected_amount = line_item.quantity * line_item.unit_amount * 0.875
|
185
|
+
assert_equal(expected_amount, line_item.line_amount)
|
186
|
+
|
187
|
+
# Change the line_amount and check that it doesn't modify anything.
|
188
|
+
line_item.line_amount = expected_amount * 10
|
189
|
+
assert_equal(expected_amount, line_item.line_amount)
|
190
|
+
|
191
|
+
# Change the quantity and check that the line_amount has been updated.
|
192
|
+
quantity = line_item.quantity + 2
|
193
|
+
line_item.quantity = quantity
|
194
|
+
assert_not_equal(expected_amount, line_item.line_amount)
|
195
|
+
assert_equal(quantity * line_item.unit_amount * 0.875, line_item.line_amount)
|
196
|
+
end
|
197
|
+
|
157
198
|
# Ensure that the totalling methods don't raise exceptions, even when
|
158
199
|
# invoice.line_items is empty.
|
159
200
|
def test_totalling_methods_when_line_items_empty
|
@@ -286,7 +327,7 @@ class InvoiceTest < Test::Unit::TestCase
|
|
286
327
|
def test_optional_params
|
287
328
|
eur_code = "EUR"
|
288
329
|
eur_rate = 1.80
|
289
|
-
|
330
|
+
|
290
331
|
invoice = create_test_invoice(:url => 'http://example.com', :branding_theme_id => 'a94a78db-5cc6-4e26-a52b-045237e56e6e', :currency_code => eur_code, :currency_rate => eur_rate)
|
291
332
|
assert_equal 'http://example.com', invoice.url
|
292
333
|
assert_equal 'a94a78db-5cc6-4e26-a52b-045237e56e6e', invoice.branding_theme_id
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative '../../test_helper.rb'
|
2
|
+
|
3
|
+
class ReportRowTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "with a sample row" do
|
6
|
+
setup do
|
7
|
+
@row = XeroGateway::Report::Row.new(
|
8
|
+
["Account", "Debit", "Credit"],
|
9
|
+
["Sales (200)", 560_00, 0],
|
10
|
+
"Bank Accounts"
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
should "be able to access using the deprecated column_n API" do
|
15
|
+
ActiveSupport::Deprecation.silence do
|
16
|
+
assert_equal @row.column_1, "Sales (200)"
|
17
|
+
assert_equal @row.column_3, 0
|
18
|
+
|
19
|
+
assert @row.respond_to?(:column_1)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
should "be able to access using an underscored column name" do
|
24
|
+
assert_equal @row.account, "Sales (200)"
|
25
|
+
assert @row.respond_to?(:account)
|
26
|
+
end
|
27
|
+
|
28
|
+
should "be able to access using an array index" do
|
29
|
+
assert_equal @row[0], "Sales (200)"
|
30
|
+
assert_equal @row[1], 560_00
|
31
|
+
end
|
32
|
+
|
33
|
+
should "be able to access using a string index" do
|
34
|
+
assert_equal @row["Account"], "Sales (200)"
|
35
|
+
assert_equal @row["Debit"], 560_00
|
36
|
+
end
|
37
|
+
|
38
|
+
should "be able to access using a symbol index" do
|
39
|
+
assert_equal @row[:account], "Sales (200)"
|
40
|
+
assert_equal @row[:debit], 560_00
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/test/unit/report_test.rb
CHANGED
@@ -19,60 +19,86 @@ class ReportTest < Test::Unit::TestCase
|
|
19
19
|
end
|
20
20
|
|
21
21
|
context :from_xml do
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
context "with a bank statement report" do
|
23
|
+
setup do
|
24
|
+
@report = make_report_from_xml("bank_statement")
|
25
|
+
end
|
26
|
+
|
27
|
+
should "create a bank statement report" do
|
28
|
+
assert @report.is_a?(XeroGateway::Report)
|
29
|
+
assert_equal [], @report.errors
|
30
|
+
assert_equal Date.parse("27 May 2014"), @report.report_date
|
31
|
+
assert_equal "BankStatement", @report.report_id
|
32
|
+
assert_equal "Bank Statement", @report.report_name
|
33
|
+
expected_titles = ["Bank Statement", "Business Bank Account", "Demo Company (NZ)", "From 1 May 2014 to 27 May 2014"]
|
34
|
+
assert_equal expected_titles, @report.report_titles
|
35
|
+
assert_equal "BankStatement", @report.report_type
|
36
|
+
assert_equal Time.parse("2014-05-26 22:36:07 +0000").to_i, @report.updated_at.to_i
|
37
|
+
expected_names = { :column_1=>"Date", :column_2=>"Description", :column_3=>"Reference", :column_4=>"Reconciled", :column_5=>"Source", :column_6=>"Amount", :column_7=>"Balance" }
|
38
|
+
assert_equal expected_names, @report.column_names
|
39
|
+
|
40
|
+
###
|
41
|
+
# REPORT BODY
|
42
|
+
assert @report.body.is_a?(Array)
|
29
43
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
assert_equal Time.parse("2014-05-26 22:36:07 +0000").to_i, @report.updated_at.to_i
|
40
|
-
expected_names = { :column_1=>"Date", :column_2=>"Description", :column_3=>"Reference", :column_4=>"Reconciled", :column_5=>"Source", :column_6=>"Amount", :column_7=>"Balance" }
|
41
|
-
assert_equal expected_names, @report.column_names
|
44
|
+
# First = Opening Balance
|
45
|
+
first_statement = @report.body.first
|
46
|
+
assert_equal "2014-05-01T00:00:00", first_statement.date
|
47
|
+
assert_equal "Opening Balance", first_statement.description
|
48
|
+
assert_equal nil, first_statement.reference
|
49
|
+
assert_equal nil, first_statement.reconciled
|
50
|
+
assert_equal nil, first_statement.source
|
51
|
+
assert_equal nil, first_statement.amount
|
52
|
+
assert_equal "15461.97", first_statement.balance
|
42
53
|
|
43
|
-
|
44
|
-
|
45
|
-
|
54
|
+
# Second = Bank Transaction/Statement
|
55
|
+
second_statement = @report.body.second
|
56
|
+
assert_equal "2014-05-01T00:00:00", second_statement.date
|
57
|
+
assert_equal "Ridgeway Banking Corporation", second_statement.description
|
58
|
+
assert_equal "Fee", second_statement.reference
|
59
|
+
assert_equal "No", second_statement.reconciled
|
60
|
+
assert_equal "Import", second_statement.source
|
61
|
+
assert_equal "-15.00", second_statement.amount
|
62
|
+
assert_equal "15446.97", second_statement.balance
|
63
|
+
|
64
|
+
# Third
|
65
|
+
third_statement = @report.body.third
|
66
|
+
assert_equal nil, third_statement.description.value # no description, but other attributes
|
67
|
+
assert_equal "Eft", third_statement.reference
|
68
|
+
assert_equal "No", third_statement.reconciled
|
69
|
+
assert_equal "Import", third_statement.source
|
70
|
+
assert_equal "-15.75", third_statement.amount
|
71
|
+
assert_equal "15431.22", third_statement.balance
|
72
|
+
end
|
73
|
+
end
|
46
74
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
assert_equal nil, first_statement.column_3
|
52
|
-
assert_equal nil, first_statement.column_4
|
53
|
-
assert_equal nil, first_statement.column_5
|
54
|
-
assert_equal nil, first_statement.column_6
|
55
|
-
assert_equal "15461.97", first_statement.column_7
|
75
|
+
context "with a trial balance report" do
|
76
|
+
setup do
|
77
|
+
@report = make_report_from_xml("trial_balance")
|
78
|
+
end
|
56
79
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
assert_equal "No", second_statement.column_4
|
63
|
-
assert_equal "Import", second_statement.column_5
|
64
|
-
assert_equal "-15.00", second_statement.column_6
|
65
|
-
assert_equal "15446.97", second_statement.column_7
|
80
|
+
should "set attributes on individual cells" do
|
81
|
+
first_statement = @report.body.first
|
82
|
+
assert_equal "Sales (200)", first_statement.account.value
|
83
|
+
assert_equal({ account: "7d05a53d-613d-4eb2-a2fc-dcb6adb80b80" }, first_statement.account.attributes)
|
84
|
+
end
|
66
85
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
assert_equal "No", third_statement.column_4
|
72
|
-
assert_equal "Import", third_statement.column_5
|
73
|
-
assert_equal "-15.75", third_statement.column_6
|
74
|
-
assert_equal "15431.22", third_statement.column_7
|
86
|
+
should "have all rows and section titles" do
|
87
|
+
assert_equal 15, @report.rows.length
|
88
|
+
assert_equal %w(Revenue Expenses Assets Liabilities Equity), @report.rows.map(&:section_name).uniq.compact
|
89
|
+
end
|
75
90
|
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def make_report_from_xml(report_name = "bank_statement")
|
97
|
+
xml_response = get_file("reports/#{report_name}.xml")
|
98
|
+
xml_response.gsub!(/\n +/,'')
|
99
|
+
xml_doc = REXML::Document.new(xml_response)
|
100
|
+
xpath_report = XPath.first(xml_doc, "//Report")
|
101
|
+
XeroGateway::Report.from_xml(xpath_report)
|
76
102
|
end
|
77
103
|
|
78
104
|
end
|
data/xero_gateway.gemspec
CHANGED
@@ -10,7 +10,6 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.email = ["me@nikwakelin.com", "jared@minutedock.com"]
|
11
11
|
s.homepage = "http://github.com/xero-gateway/xero_gateway"
|
12
12
|
s.description = "Enables Ruby based applications to communicate with the Xero API"
|
13
|
-
s.has_rdoc = false
|
14
13
|
s.authors = ["Tim Connor", "Nik Wakelin", "Jared Armstrong"]
|
15
14
|
s.license = "MIT"
|
16
15
|
|
@@ -32,5 +31,6 @@ Gem::Specification.new do |s|
|
|
32
31
|
s.add_development_dependency "mocha"
|
33
32
|
s.add_development_dependency "shoulda"
|
34
33
|
s.add_development_dependency "libxml-ruby", "2.7.0"
|
34
|
+
s.add_development_dependency "byebug"
|
35
35
|
|
36
36
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xero_gateway
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tim Connor
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2019-02-14 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: builder
|
@@ -152,6 +152,20 @@ dependencies:
|
|
152
152
|
- - '='
|
153
153
|
- !ruby/object:Gem::Version
|
154
154
|
version: 2.7.0
|
155
|
+
- !ruby/object:Gem::Dependency
|
156
|
+
name: byebug
|
157
|
+
requirement: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
type: :development
|
163
|
+
prerelease: false
|
164
|
+
version_requirements: !ruby/object:Gem::Requirement
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
version: '0'
|
155
169
|
description: Enables Ruby based applications to communicate with the Xero API
|
156
170
|
email:
|
157
171
|
- me@nikwakelin.com
|
@@ -177,6 +191,7 @@ files:
|
|
177
191
|
- lib/xero_gateway/ca-certificates.crt
|
178
192
|
- lib/xero_gateway/contact.rb
|
179
193
|
- lib/xero_gateway/contact_group.rb
|
194
|
+
- lib/xero_gateway/contact_person.rb
|
180
195
|
- lib/xero_gateway/credit_note.rb
|
181
196
|
- lib/xero_gateway/currency.rb
|
182
197
|
- lib/xero_gateway/dates.rb
|
@@ -195,10 +210,14 @@ files:
|
|
195
210
|
- lib/xero_gateway/oauth.rb
|
196
211
|
- lib/xero_gateway/organisation.rb
|
197
212
|
- lib/xero_gateway/partner_app.rb
|
213
|
+
- lib/xero_gateway/pay_run.rb
|
198
214
|
- lib/xero_gateway/payment.rb
|
215
|
+
- lib/xero_gateway/payroll_calendar.rb
|
199
216
|
- lib/xero_gateway/phone.rb
|
200
217
|
- lib/xero_gateway/private_app.rb
|
201
218
|
- lib/xero_gateway/report.rb
|
219
|
+
- lib/xero_gateway/report/cell.rb
|
220
|
+
- lib/xero_gateway/report/row.rb
|
202
221
|
- lib/xero_gateway/response.rb
|
203
222
|
- lib/xero_gateway/tax_rate.rb
|
204
223
|
- lib/xero_gateway/tracking_category.rb
|
@@ -246,6 +265,7 @@ files:
|
|
246
265
|
- test/unit/oauth_test.rb
|
247
266
|
- test/unit/organisation_test.rb
|
248
267
|
- test/unit/payment_test.rb
|
268
|
+
- test/unit/report/row_test.rb
|
249
269
|
- test/unit/report_test.rb
|
250
270
|
- test/unit/tax_rate_test.rb
|
251
271
|
- test/unit/tracking_category_test.rb
|
@@ -270,7 +290,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
270
290
|
version: '0'
|
271
291
|
requirements: []
|
272
292
|
rubyforge_project:
|
273
|
-
rubygems_version: 2.5.
|
293
|
+
rubygems_version: 2.5.2.3
|
274
294
|
signing_key:
|
275
295
|
specification_version: 4
|
276
296
|
summary: Enables Ruby based applications to communicate with the Xero API
|