xero_gateway 2.0.14 → 2.0.15
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/lib/xero_gateway/accounts_list.rb +0 -4
- data/lib/xero_gateway/bank_transaction.rb +5 -5
- data/lib/xero_gateway/contact.rb +3 -6
- data/lib/xero_gateway/credit_note.rb +1 -3
- data/lib/xero_gateway/exceptions.rb +5 -0
- data/lib/xero_gateway/gateway.rb +83 -0
- data/lib/xero_gateway/http.rb +1 -0
- data/lib/xero_gateway/invoice.rb +2 -4
- data/lib/xero_gateway/journal_line.rb +102 -0
- data/lib/xero_gateway/line_item_calculations.rb +0 -4
- data/lib/xero_gateway/manual_journal.rb +163 -0
- data/lib/xero_gateway/response.rb +2 -0
- data/lib/xero_gateway.rb +2 -0
- data/test/integration/accounts_list_test.rb +4 -4
- data/test/integration/create_manual_journal_test.rb +35 -0
- data/test/integration/get_bank_transaction_test.rb +1 -0
- data/test/integration/get_manual_journal_test.rb +50 -0
- data/test/integration/get_manual_journals_test.rb +88 -0
- data/test/integration/update_manual_journal_test.rb +31 -0
- data/test/test_helper.rb +38 -0
- data/test/unit/bank_transaction_test.rb +13 -1
- data/test/unit/credit_note_test.rb +1 -1
- data/test/unit/gateway_test.rb +26 -2
- data/test/unit/invoice_test.rb +1 -1
- data/test/unit/manual_journal_test.rb +93 -0
- data/xero_gateway.gemspec +2 -2
- metadata +82 -41
data/Rakefile
CHANGED
@@ -3,8 +3,6 @@ module XeroGateway
|
|
3
3
|
include Dates
|
4
4
|
include LineItemCalculations
|
5
5
|
|
6
|
-
class NoGatewayError < Error; end
|
7
|
-
|
8
6
|
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)
|
9
7
|
|
10
8
|
TYPES = {
|
@@ -27,7 +25,7 @@ module XeroGateway
|
|
27
25
|
attr_accessor :line_items_downloaded
|
28
26
|
|
29
27
|
# accessible fields
|
30
|
-
attr_accessor :bank_transaction_id, :type, :date, :reference, :status, :contact, :line_items, :bank_account, :url
|
28
|
+
attr_accessor :bank_transaction_id, :type, :date, :reference, :status, :contact, :line_items, :bank_account, :url, :is_reconciled
|
31
29
|
|
32
30
|
def initialize(params = {})
|
33
31
|
@errors ||= []
|
@@ -128,11 +126,12 @@ module XeroGateway
|
|
128
126
|
b.BankTransactionID bank_transaction_id if bank_transaction_id
|
129
127
|
b.Type type
|
130
128
|
# b.CurrencyCode self.currency_code if self.currency_code
|
131
|
-
contact.to_xml(b)
|
132
|
-
bank_account.to_xml(b, :name => 'BankAccount')
|
129
|
+
contact.to_xml(b) if contact
|
130
|
+
bank_account.to_xml(b, :name => 'BankAccount') if bank_account
|
133
131
|
b.Date BankTransaction.format_date(date || Date.today)
|
134
132
|
b.Status status if status
|
135
133
|
b.Reference reference if reference
|
134
|
+
b.IsReconciled true if self.is_reconciled
|
136
135
|
b.LineItems {
|
137
136
|
self.line_items.each do |line_item|
|
138
137
|
line_item.to_xml(b)
|
@@ -165,6 +164,7 @@ module XeroGateway
|
|
165
164
|
# when "AmountPaid" then invoice.amount_paid = BigDecimal.new(element.text)
|
166
165
|
# when "AmountCredited" then invoice.amount_credited = BigDecimal.new(element.text)
|
167
166
|
# when "SentToContact" then invoice.sent_to_contact = (element.text.strip.downcase == "true")
|
167
|
+
when "IsReconciled" then bank_transaction.is_reconciled = (element.text.strip.downcase == "true")
|
168
168
|
when "Url" then bank_transaction.url = element.text
|
169
169
|
end
|
170
170
|
end
|
data/lib/xero_gateway/contact.rb
CHANGED
@@ -1,10 +1,7 @@
|
|
1
1
|
module XeroGateway
|
2
2
|
class Contact
|
3
3
|
include Dates
|
4
|
-
|
5
|
-
class Error < RuntimeError; end
|
6
|
-
class NoGatewayError < Error; end
|
7
|
-
|
4
|
+
|
8
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)
|
9
6
|
|
10
7
|
CONTACT_STATUS = {
|
@@ -126,14 +123,14 @@ module XeroGateway
|
|
126
123
|
end
|
127
124
|
|
128
125
|
# Creates this contact record (using gateway.create_contact) with the associated gateway.
|
129
|
-
# If no gateway set, raise a
|
126
|
+
# If no gateway set, raise a NoGatewayError exception.
|
130
127
|
def create
|
131
128
|
raise NoGatewayError unless gateway
|
132
129
|
gateway.create_contact(self)
|
133
130
|
end
|
134
131
|
|
135
132
|
# Creates this contact record (using gateway.update_contact) with the associated gateway.
|
136
|
-
# If no gateway set, raise a
|
133
|
+
# If no gateway set, raise a NoGatewayError exception.
|
137
134
|
def update
|
138
135
|
raise NoGatewayError unless gateway
|
139
136
|
gateway.update_contact(self)
|
@@ -4,8 +4,6 @@ module XeroGateway
|
|
4
4
|
include Money
|
5
5
|
include LineItemCalculations
|
6
6
|
|
7
|
-
class NoGatewayError < Error; end
|
8
|
-
|
9
7
|
CREDIT_NOTE_TYPE = {
|
10
8
|
'ACCRECCREDIT' => 'Accounts Receivable',
|
11
9
|
'ACCPAYCREDIT' => 'Accounts Payable'
|
@@ -163,7 +161,7 @@ module XeroGateway
|
|
163
161
|
end
|
164
162
|
|
165
163
|
# Creates this credit_note record (using gateway.create_credit_note) with the associated gateway.
|
166
|
-
# If no gateway set, raise a
|
164
|
+
# If no gateway set, raise a NoGatewayError exception.
|
167
165
|
def create
|
168
166
|
raise NoGatewayError unless gateway
|
169
167
|
gateway.create_credit_note(self)
|
@@ -1,4 +1,8 @@
|
|
1
1
|
module XeroGateway
|
2
|
+
class NoGatewayError < StandardError; end
|
3
|
+
class AccountsListNotLoadedError < StandardError; end
|
4
|
+
class InvalidLineItemError < StandardError; end
|
5
|
+
|
2
6
|
class ApiException < StandardError
|
3
7
|
|
4
8
|
def initialize(type, message, request_xml, response_xml)
|
@@ -43,4 +47,5 @@ module XeroGateway
|
|
43
47
|
class InvoiceNotFoundError < StandardError; end
|
44
48
|
class BankTransactionNotFoundError < StandardError; end
|
45
49
|
class CreditNoteNotFoundError < StandardError; end
|
50
|
+
class ManualJournalNotFoundError < StandardError; end
|
46
51
|
end
|
data/lib/xero_gateway/gateway.rb
CHANGED
@@ -418,6 +418,56 @@ module XeroGateway
|
|
418
418
|
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/BankTransaction'})
|
419
419
|
end
|
420
420
|
|
421
|
+
# Creates a manual journal in Xero based on a manual journal object.
|
422
|
+
#
|
423
|
+
# Manual journal and line item totals are calculated automatically.
|
424
|
+
#
|
425
|
+
# Usage : # TODO
|
426
|
+
|
427
|
+
def create_manual_journal(manual_journal)
|
428
|
+
save_manual_journal(manual_journal)
|
429
|
+
end
|
430
|
+
|
431
|
+
#
|
432
|
+
# Updates an existing Xero manual journal
|
433
|
+
#
|
434
|
+
# Usage :
|
435
|
+
#
|
436
|
+
# manual_journal = xero_gateway.get_manual_journal(some_manual_journal_id)
|
437
|
+
#
|
438
|
+
# xero_gateway.update_manual_journal(manual_journal)
|
439
|
+
def update_manual_journal(manual_journal)
|
440
|
+
raise "manual_journal_id is required for updating manual journals" if manual_journal.manual_journal_id.nil?
|
441
|
+
save_manual_journal(manual_journal)
|
442
|
+
end
|
443
|
+
|
444
|
+
# Retrieves all manual journals from Xero
|
445
|
+
#
|
446
|
+
# Usage : get_manual_journal
|
447
|
+
# getmanual_journal(:manual_journal_id => " 297c2dc5-cc47-4afd-8ec8-74990b8761e9")
|
448
|
+
#
|
449
|
+
# Note : modified_since is in UTC format (i.e. Brisbane is UTC+10)
|
450
|
+
def get_manual_journals(options = {})
|
451
|
+
request_params = {}
|
452
|
+
request_params[:ManualJournalID] = options[:manual_journal_id] if options[:manual_journal_id]
|
453
|
+
request_params[:ModifiedAfter] = options[:modified_since] if options[:modified_since]
|
454
|
+
|
455
|
+
response_xml = http_get(@client, "#{@xero_url}/ManualJournals", request_params)
|
456
|
+
|
457
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/ManualJournals'})
|
458
|
+
end
|
459
|
+
|
460
|
+
# Retrieves a single manual journal
|
461
|
+
#
|
462
|
+
# Usage : get_manual_journal("297c2dc5-cc47-4afd-8ec8-74990b8761e9") # By ID
|
463
|
+
# get_manual_journal("OIT-12345") # By number
|
464
|
+
def get_manual_journal(manual_journal_id)
|
465
|
+
request_params = {}
|
466
|
+
url = "#{@xero_url}/ManualJournals/#{URI.escape(manual_journal_id)}"
|
467
|
+
response_xml = http_get(@client, url, request_params)
|
468
|
+
parse_response(response_xml, {:request_params => request_params}, {:request_signature => 'GET/ManualJournal'})
|
469
|
+
end
|
470
|
+
|
421
471
|
#
|
422
472
|
# Gets all accounts for a specific organization in Xero.
|
423
473
|
#
|
@@ -555,6 +605,33 @@ module XeroGateway
|
|
555
605
|
response
|
556
606
|
end
|
557
607
|
|
608
|
+
# Create or update a manual journal record based on if it has an manual_journal_id.
|
609
|
+
def save_manual_journal(manual_journal)
|
610
|
+
request_xml = manual_journal.to_xml
|
611
|
+
response_xml = nil
|
612
|
+
create_or_save = nil
|
613
|
+
|
614
|
+
if manual_journal.manual_journal_id.nil?
|
615
|
+
# Create new manual journal record.
|
616
|
+
response_xml = http_put(@client, "#{@xero_url}/ManualJournals", request_xml, {})
|
617
|
+
create_or_save = :create
|
618
|
+
else
|
619
|
+
# Update existing manual journal record.
|
620
|
+
response_xml = http_post(@client, "#{@xero_url}/ManualJournals", request_xml, {})
|
621
|
+
create_or_save = :save
|
622
|
+
end
|
623
|
+
|
624
|
+
response = parse_response(response_xml, {:request_xml => request_xml}, {:request_signature => "#{create_or_save == :create ? 'PUT' : 'POST'}/ManualJournals"})
|
625
|
+
|
626
|
+
# Xero returns manual journals inside an <ManualJournals> tag, even though there's only ever
|
627
|
+
# one for this request
|
628
|
+
response.response_item = response.manual_journals.first
|
629
|
+
|
630
|
+
manual_journal.manual_journal_id = response.manual_journal.manual_journal_id if response.success? && response.manual_journal && response.manual_journal.manual_journal_id
|
631
|
+
|
632
|
+
response
|
633
|
+
end
|
634
|
+
|
558
635
|
def parse_response(raw_response, request = {}, options = {})
|
559
636
|
|
560
637
|
response = XeroGateway::Response.new
|
@@ -576,12 +653,18 @@ module XeroGateway
|
|
576
653
|
when "Invoice" then response.response_item = Invoice.from_xml(element, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"})
|
577
654
|
when "BankTransaction"
|
578
655
|
response.response_item = BankTransaction.from_xml(element, self, {:line_items_downloaded => options[:request_signature] != "GET/BankTransactions"})
|
656
|
+
when "ManualJournal"
|
657
|
+
response.response_item = ManualJournal.from_xml(element, self, {:journal_lines_downloaded => options[:request_signature] != "GET/ManualJournals"})
|
579
658
|
when "Contacts" then element.children.each {|child| response.response_item << Contact.from_xml(child, self) }
|
580
659
|
when "Invoices" then element.children.each {|child| response.response_item << Invoice.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/Invoices"}) }
|
581
660
|
when "BankTransactions"
|
582
661
|
element.children.each do |child|
|
583
662
|
response.response_item << BankTransaction.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/BankTransactions"})
|
584
663
|
end
|
664
|
+
when "ManualJournals"
|
665
|
+
element.children.each do |child|
|
666
|
+
response.response_item << ManualJournal.from_xml(child, self, {:journal_lines_downloaded => options[:request_signature] != "GET/ManualJournals"})
|
667
|
+
end
|
585
668
|
when "CreditNotes" then element.children.each {|child| response.response_item << CreditNote.from_xml(child, self, {:line_items_downloaded => options[:request_signature] != "GET/CreditNotes"}) }
|
586
669
|
when "Accounts" then element.children.each {|child| response.response_item << Account.from_xml(child) }
|
587
670
|
when "TaxRates" then element.children.each {|child| response.response_item << TaxRate.from_xml(child) }
|
data/lib/xero_gateway/http.rb
CHANGED
@@ -125,6 +125,7 @@ module XeroGateway
|
|
125
125
|
def handle_object_not_found!(response, request_url)
|
126
126
|
case(request_url)
|
127
127
|
when /Invoices/ then raise InvoiceNotFoundError.new("Invoice not found in Xero.")
|
128
|
+
when /BankTransactions/ then raise BankTransactionNotFoundError.new("Bank Transaction not found in Xero.")
|
128
129
|
when /CreditNotes/ then raise CreditNoteNotFoundError.new("Credit Note not found in Xero.")
|
129
130
|
else raise ObjectNotFound.new(request_url)
|
130
131
|
end
|
data/lib/xero_gateway/invoice.rb
CHANGED
@@ -4,8 +4,6 @@ module XeroGateway
|
|
4
4
|
include Money
|
5
5
|
include LineItemCalculations
|
6
6
|
|
7
|
-
class NoGatewayError < Error; end
|
8
|
-
|
9
7
|
INVOICE_TYPE = {
|
10
8
|
'ACCREC' => 'Accounts Receivable',
|
11
9
|
'ACCPAY' => 'Accounts Payable'
|
@@ -165,14 +163,14 @@ module XeroGateway
|
|
165
163
|
end
|
166
164
|
|
167
165
|
# Creates this invoice record (using gateway.create_invoice) with the associated gateway.
|
168
|
-
# If no gateway set, raise a
|
166
|
+
# If no gateway set, raise a NoGatewayError exception.
|
169
167
|
def create
|
170
168
|
raise NoGatewayError unless gateway
|
171
169
|
gateway.create_invoice(self)
|
172
170
|
end
|
173
171
|
|
174
172
|
# Updates this invoice record (using gateway.update_invoice) with the associated gateway.
|
175
|
-
# If no gateway set, raise a
|
173
|
+
# If no gateway set, raise a NoGatewayError exception.
|
176
174
|
def update
|
177
175
|
raise NoGatewayError unless gateway
|
178
176
|
gateway.update_invoice(self)
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'account')
|
2
|
+
|
3
|
+
module XeroGateway
|
4
|
+
class JournalLine
|
5
|
+
include Money
|
6
|
+
|
7
|
+
TAX_TYPE = Account::TAX_TYPE unless defined?(TAX_TYPE)
|
8
|
+
|
9
|
+
# Any errors that occurred when the #valid? method called.
|
10
|
+
attr_reader :errors
|
11
|
+
|
12
|
+
# All accessible fields
|
13
|
+
attr_accessor :journal_line_id, :line_amount, :account_code, :description, :tax_type, :tracking
|
14
|
+
|
15
|
+
def initialize(params = {})
|
16
|
+
@errors ||= []
|
17
|
+
@tracking ||= []
|
18
|
+
|
19
|
+
params.each do |k,v|
|
20
|
+
self.send("#{k}=", v)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Validate the JournalLineItem record according to what will be valid by the gateway.
|
25
|
+
#
|
26
|
+
# Usage:
|
27
|
+
# journal_line_item.valid? # Returns true/false
|
28
|
+
#
|
29
|
+
# Additionally sets journal_line_item.errors array to an array of field/error.
|
30
|
+
def valid?
|
31
|
+
@errors = []
|
32
|
+
|
33
|
+
if !journal_line_id.nil? && journal_line_id !~ GUID_REGEX
|
34
|
+
@errors << ['journal_line_id', 'must be blank or a valid Xero GUID']
|
35
|
+
end
|
36
|
+
|
37
|
+
unless line_amount
|
38
|
+
@errors << ['line_amount', "can't be blank"]
|
39
|
+
end
|
40
|
+
|
41
|
+
unless account_code
|
42
|
+
@errors << ['account_code', "can't be blank"]
|
43
|
+
end
|
44
|
+
|
45
|
+
@errors.size == 0
|
46
|
+
end
|
47
|
+
|
48
|
+
def has_tracking?
|
49
|
+
return false if tracking.nil?
|
50
|
+
|
51
|
+
if tracking.is_a?(Array)
|
52
|
+
return tracking.any?
|
53
|
+
else
|
54
|
+
return tracking.is_a?(TrackingCategory)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_xml(b = Builder::XmlMarkup.new)
|
59
|
+
b.JournalLine {
|
60
|
+
b.LineAmount line_amount # mandatory
|
61
|
+
b.AccountCode account_code # mandatory
|
62
|
+
b.Description description if description # optional
|
63
|
+
b.TaxType tax_type if tax_type # optional
|
64
|
+
if has_tracking?
|
65
|
+
b.Tracking { # optional
|
66
|
+
# Due to strange retardness in the Xero API, the XML structure for a tracking category within
|
67
|
+
# an invoice is different to a standalone tracking category.
|
68
|
+
# This means rather than going category.to_xml we need to call the special category.to_xml_for_invoice_messages
|
69
|
+
(tracking.is_a?(TrackingCategory) ? [tracking] : tracking).each do |category|
|
70
|
+
category.to_xml_for_invoice_messages(b)
|
71
|
+
end
|
72
|
+
}
|
73
|
+
end
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.from_xml(journal_line_element)
|
78
|
+
journal_line = JournalLine.new
|
79
|
+
journal_line_element.children.each do |element|
|
80
|
+
case(element.name)
|
81
|
+
when "LineAmount" then journal_line.line_amount = BigDecimal.new(element.text)
|
82
|
+
when "AccountCode" then journal_line.account_code = element.text
|
83
|
+
when "JournalLineID" then journal_line.journal_line_id = element.text
|
84
|
+
when "Description" then journal_line.description = element.text
|
85
|
+
when "TaxType" then journal_line.tax_type = element.text
|
86
|
+
when "Tracking" then
|
87
|
+
element.children.each do | tracking_element |
|
88
|
+
journal_line.tracking << TrackingCategory.from_xml(tracking_element)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
journal_line
|
93
|
+
end
|
94
|
+
|
95
|
+
def ==(other)
|
96
|
+
[:description, :line_amount, :account_code, :tax_type].each do |field|
|
97
|
+
return false if send(field) != other.send(field)
|
98
|
+
end
|
99
|
+
return true
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module XeroGateway
|
2
|
+
class ManualJournal
|
3
|
+
include Dates
|
4
|
+
|
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
|
+
|
7
|
+
STATUSES = {
|
8
|
+
'DRAFT' => 'Draft Manual Journal',
|
9
|
+
'POSTED' => 'Posted Manual Journal',
|
10
|
+
'DELETED' => 'Deleted Draft Manual Journal',
|
11
|
+
'VOIDED' => 'Voided Posted Manual Journal'
|
12
|
+
} unless defined?(STATUSES)
|
13
|
+
|
14
|
+
# Xero::Gateway associated with this invoice.
|
15
|
+
attr_accessor :gateway
|
16
|
+
|
17
|
+
# Any errors that occurred when the #valid? method called.
|
18
|
+
attr_reader :errors
|
19
|
+
|
20
|
+
# Represents whether the journal lines have been downloaded when getting from GET /API.XRO/2.0/ManualJournals
|
21
|
+
attr_accessor :journal_lines_downloaded
|
22
|
+
|
23
|
+
# accessible fields
|
24
|
+
attr_accessor :manual_journal_id, :narration, :date, :status, :journal_lines, :url, :show_on_cash_basis_reports
|
25
|
+
|
26
|
+
def initialize(params = {})
|
27
|
+
@errors ||= []
|
28
|
+
@payments ||= []
|
29
|
+
|
30
|
+
# Check if the line items have been downloaded.
|
31
|
+
@journal_lines_downloaded = (params.delete(:journal_lines_downloaded) == true)
|
32
|
+
|
33
|
+
params.each do |k,v|
|
34
|
+
self.send("#{k}=", v)
|
35
|
+
end
|
36
|
+
|
37
|
+
@journal_lines ||= []
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(other)
|
41
|
+
['narration', 'status', 'journal_lines', 'show_on_cash_basis_reports'].each do |field|
|
42
|
+
return false if send(field) != other.send(field)
|
43
|
+
end
|
44
|
+
|
45
|
+
["date"].each do |field|
|
46
|
+
return false if send(field).to_s != other.send(field).to_s
|
47
|
+
end
|
48
|
+
return true
|
49
|
+
end
|
50
|
+
|
51
|
+
# Validate the ManualJournal record according to what will be valid by the gateway.
|
52
|
+
#
|
53
|
+
# Usage:
|
54
|
+
# manual_journal.valid? # Returns true/false
|
55
|
+
#
|
56
|
+
# Additionally sets manual_journal.errors array to an array of field/error.
|
57
|
+
def valid?
|
58
|
+
@errors = []
|
59
|
+
|
60
|
+
if !manual_journal_id.nil? && manual_journal_id !~ GUID_REGEX
|
61
|
+
@errors << ['manual_journal_id', 'must be blank or a valid Xero GUID']
|
62
|
+
end
|
63
|
+
|
64
|
+
if narration.blank?
|
65
|
+
@errors << ['narration', "can't be blank"]
|
66
|
+
end
|
67
|
+
|
68
|
+
unless date
|
69
|
+
@errors << ['date', "can't be blank"]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Make sure all journal_items are valid.
|
73
|
+
unless journal_lines.all? { | journal_line | journal_line.valid? }
|
74
|
+
@errors << ['journal_lines', "at least one journal line invalid"]
|
75
|
+
end
|
76
|
+
|
77
|
+
# make sure there are at least 2 journal lines
|
78
|
+
unless journal_lines.length > 1
|
79
|
+
@errors << ['journal_lines', "journal must contain at least two individual journal lines"]
|
80
|
+
end
|
81
|
+
|
82
|
+
if journal_lines.length > 100
|
83
|
+
@errors << ['journal_lines', "journal must contain less than one hundred journal lines"]
|
84
|
+
end
|
85
|
+
|
86
|
+
unless journal_lines.sum(&:line_amount).to_f == 0.0
|
87
|
+
@errors << ['journal_lines', "the total debits must be equal to total credits"]
|
88
|
+
end
|
89
|
+
|
90
|
+
@errors.size == 0
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
def journal_lines_downloaded?
|
95
|
+
@journal_lines_downloaded
|
96
|
+
end
|
97
|
+
|
98
|
+
# If line items are not downloaded, then attempt a download now (if this record was found to begin with).
|
99
|
+
def journal_lines
|
100
|
+
if journal_lines_downloaded?
|
101
|
+
@journal_lines
|
102
|
+
|
103
|
+
elsif manual_journal_id =~ GUID_REGEX && @gateway
|
104
|
+
# There is a manual_journal_id so we can assume this record was loaded from Xero.
|
105
|
+
# Let's attempt to download the journal_line records (if there is a gateway)
|
106
|
+
|
107
|
+
response = @gateway.get_manual_journal(manual_journal_id)
|
108
|
+
raise ManualJournalNotFoundError, "Manual Journal with ID #{manual_journal_id} not found in Xero." unless response.success? && response.manual_journal.is_a?(XeroGateway::ManualJournal)
|
109
|
+
|
110
|
+
@journal_lines = response.manual_journal.journal_lines
|
111
|
+
@journal_lines_downloaded = true
|
112
|
+
|
113
|
+
@journal_lines
|
114
|
+
|
115
|
+
# Otherwise, this is a new manual journal, so return the journal_lines reference.
|
116
|
+
else
|
117
|
+
@journal_lines
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_xml(b = Builder::XmlMarkup.new)
|
122
|
+
b.ManualJournal {
|
123
|
+
b.ManualJournalID manual_journal_id if manual_journal_id
|
124
|
+
b.Narration narration
|
125
|
+
b.JournalLines {
|
126
|
+
self.journal_lines.each do |journal_line|
|
127
|
+
journal_line.to_xml(b)
|
128
|
+
end
|
129
|
+
}
|
130
|
+
b.Date ManualJournal.format_date(date || Date.today)
|
131
|
+
b.Status status if status
|
132
|
+
b.Url url if url
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.from_xml(manual_journal_element, gateway = nil, options = {})
|
137
|
+
manual_journal = ManualJournal.new(options.merge({:gateway => gateway}))
|
138
|
+
manual_journal_element.children.each do |element|
|
139
|
+
case(element.name)
|
140
|
+
when "ManualJournalID" then manual_journal.manual_journal_id = element.text
|
141
|
+
when "Date" then manual_journal.date = parse_date(element.text)
|
142
|
+
when "Status" then manual_journal.status = element.text
|
143
|
+
when "Narration" then manual_journal.narration = element.text
|
144
|
+
when "JournalLines" then element.children.each {|journal_line| manual_journal.journal_lines_downloaded = true; manual_journal.journal_lines << JournalLine.from_xml(journal_line) }
|
145
|
+
when "Url" then manual_journal.url = element.text
|
146
|
+
end
|
147
|
+
end
|
148
|
+
manual_journal
|
149
|
+
end # from_xml
|
150
|
+
|
151
|
+
def add_journal_line(params = {})
|
152
|
+
journal_line = nil
|
153
|
+
case params
|
154
|
+
when Hash then journal_line = JournalLine.new(params)
|
155
|
+
when JournalLine then journal_line = params
|
156
|
+
else raise InvalidLineItemError
|
157
|
+
end
|
158
|
+
@journal_lines << journal_line
|
159
|
+
journal_line
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
@@ -9,11 +9,13 @@ module XeroGateway
|
|
9
9
|
alias_method :invoice, :response_item
|
10
10
|
alias_method :credit_note, :response_item
|
11
11
|
alias_method :bank_transaction, :response_item
|
12
|
+
alias_method :manual_journal, :response_item
|
12
13
|
alias_method :contact, :response_item
|
13
14
|
alias_method :organisation, :response_item
|
14
15
|
alias_method :invoices, :array_wrapped_response_item
|
15
16
|
alias_method :credit_notes, :array_wrapped_response_item
|
16
17
|
alias_method :bank_transactions, :array_wrapped_response_item
|
18
|
+
alias_method :manual_journals, :array_wrapped_response_item
|
17
19
|
alias_method :contacts, :array_wrapped_response_item
|
18
20
|
alias_method :accounts, :array_wrapped_response_item
|
19
21
|
alias_method :tracking_categories, :array_wrapped_response_item
|
data/lib/xero_gateway.rb
CHANGED
@@ -26,6 +26,8 @@ require File.join(File.dirname(__FILE__), 'xero_gateway', 'payment')
|
|
26
26
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'invoice')
|
27
27
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'bank_transaction')
|
28
28
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'credit_note')
|
29
|
+
require File.join(File.dirname(__FILE__), 'xero_gateway', 'journal_line')
|
30
|
+
require File.join(File.dirname(__FILE__), 'xero_gateway', 'manual_journal')
|
29
31
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'address')
|
30
32
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'phone')
|
31
33
|
require File.join(File.dirname(__FILE__), 'xero_gateway', 'organisation')
|
@@ -21,10 +21,10 @@ class AccountsListTest < Test::Unit::TestCase
|
|
21
21
|
def test_raise_error_on_not_loaded
|
22
22
|
accounts_list = @gateway.get_accounts_list(false)
|
23
23
|
assert_equal(false, accounts_list.loaded?)
|
24
|
-
assert_raise(XeroGateway::
|
25
|
-
assert_raise(XeroGateway::
|
26
|
-
assert_raise(XeroGateway::
|
27
|
-
assert_raise(XeroGateway::
|
24
|
+
assert_raise(XeroGateway::AccountsListNotLoadedError) { accounts_list[200] }
|
25
|
+
assert_raise(XeroGateway::AccountsListNotLoadedError) { accounts_list.find_by_code(200) }
|
26
|
+
assert_raise(XeroGateway::AccountsListNotLoadedError) { accounts_list.find_all_by_type('EXPENSE') }
|
27
|
+
assert_raise(XeroGateway::AccountsListNotLoadedError) { accounts_list.find_all_by_tax_type('OUTPUT') }
|
28
28
|
end
|
29
29
|
|
30
30
|
# Test simple lookup by account code (from cache).
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class CreateManualJournalTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@gateway = XeroGateway::Gateway.new(CONSUMER_KEY, CONSUMER_SECRET)
|
8
|
+
|
9
|
+
if STUB_XERO_CALLS
|
10
|
+
@gateway.xero_url = "DUMMY_URL"
|
11
|
+
|
12
|
+
@gateway.stubs(:http_put).with {|client, url, body, params| url =~ /ManualJournals$/ }.returns(get_file_as_string("create_manual_journal.xml"))
|
13
|
+
@gateway.stubs(:http_post).with {|client, url, body, params| url =~ /ManualJournals$/ }.returns(get_file_as_string("manual_journal.xml"))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_create_manual_journal
|
18
|
+
example_manual_journal = create_test_manual_journal.dup
|
19
|
+
|
20
|
+
result = @gateway.create_manual_journal(example_manual_journal)
|
21
|
+
assert_kind_of XeroGateway::Response, result
|
22
|
+
assert result.success?
|
23
|
+
assert !result.request_xml.nil?
|
24
|
+
assert !result.response_xml.nil?
|
25
|
+
assert !result.manual_journal.manual_journal_id.nil?
|
26
|
+
assert example_manual_journal.manual_journal_id =~ GUID_REGEX
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_create_manual_journal_valid
|
30
|
+
example_manual_journal = create_test_manual_journal.dup
|
31
|
+
assert_equal true, example_manual_journal.valid?,
|
32
|
+
"manual_journal is invalid - errors:\n\t#{example_manual_journal.errors.map { | error | "#{error[0]} #{error[1]}"}.join("\n\t")}"
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -25,6 +25,7 @@ class GetBankTransactionTest < Test::Unit::TestCase
|
|
25
25
|
assert !result.response_xml.nil?
|
26
26
|
assert_equal result.bank_transaction.bank_transaction_id, bank_transaction.bank_transaction_id
|
27
27
|
assert_equal result.bank_transaction.reference, bank_transaction.reference
|
28
|
+
assert result.bank_transaction.is_reconciled
|
28
29
|
|
29
30
|
result = @gateway.get_bank_transaction(bank_transaction.bank_transaction_id)
|
30
31
|
assert result.success?
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class GetManualJournalTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@gateway = XeroGateway::Gateway.new(CONSUMER_KEY, CONSUMER_SECRET)
|
8
|
+
|
9
|
+
if STUB_XERO_CALLS
|
10
|
+
@gateway.xero_url = "DUMMY_URL"
|
11
|
+
|
12
|
+
@gateway.stubs(:http_get).with {|client, url, params| url =~ /ManualJournals(\/[0-9a-z\-]+)?$/i }.returns(get_file_as_string("manual_journal.xml"))
|
13
|
+
@gateway.stubs(:http_put).with {|client, url, body, params| url =~ /ManualJournals$/ }.returns(get_file_as_string("create_manual_journal.xml"))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_get_manual_journal
|
18
|
+
# Make sure there is a manual journal in Xero to retrieve
|
19
|
+
response = @gateway.create_manual_journal(create_test_manual_journal)
|
20
|
+
manual_journal = response.manual_journal
|
21
|
+
|
22
|
+
result = @gateway.get_manual_journal(manual_journal.manual_journal_id)
|
23
|
+
assert result.success?
|
24
|
+
assert !result.request_params.nil?
|
25
|
+
assert !result.response_xml.nil?
|
26
|
+
assert_equal result.manual_journal.manual_journal_id, manual_journal.manual_journal_id
|
27
|
+
assert_equal result.manual_journal.narration, manual_journal.narration
|
28
|
+
|
29
|
+
result = @gateway.get_manual_journal(manual_journal.manual_journal_id)
|
30
|
+
assert result.success?
|
31
|
+
assert !result.request_params.nil?
|
32
|
+
assert !result.response_xml.nil?
|
33
|
+
assert_equal result.manual_journal.manual_journal_id, manual_journal.manual_journal_id
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_journal_lines_downloaded_set_correctly
|
37
|
+
# Make sure there is a manual journal in Xero to retrieve.
|
38
|
+
example_manual_journal = @gateway.create_manual_journal(create_test_manual_journal).manual_journal
|
39
|
+
|
40
|
+
# No line items.
|
41
|
+
response = @gateway.get_manual_journal(example_manual_journal.manual_journal_id)
|
42
|
+
assert response.success?
|
43
|
+
|
44
|
+
manual_journal = response.manual_journal
|
45
|
+
assert_kind_of(XeroGateway::JournalLine, manual_journal.journal_lines.first)
|
46
|
+
assert_kind_of(XeroGateway::ManualJournal, manual_journal)
|
47
|
+
assert manual_journal.journal_lines_downloaded?
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class GetManualJournalsTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
INVALID_MANUAL_JOURNAL_ID = "99999999-9999-9999-9999-999999999999" unless defined?(INVALID_MANUAL_JOURNAL_ID)
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@gateway = XeroGateway::Gateway.new(CONSUMER_KEY, CONSUMER_SECRET)
|
10
|
+
|
11
|
+
if STUB_XERO_CALLS
|
12
|
+
@gateway.xero_url = "DUMMY_URL"
|
13
|
+
|
14
|
+
@gateway.stubs(:http_get).with {|client, url, params| url =~ /ManualJournals(\/[0-9a-z\-]+)?$/i }.returns(get_file_as_string("manual_journals.xml"))
|
15
|
+
@gateway.stubs(:http_put).with {|client, url, body, params| url =~ /ManualJournals$/ }.returns(get_file_as_string("create_manual_journal.xml"))
|
16
|
+
|
17
|
+
# Get a manual journal with an invalid ID.
|
18
|
+
@gateway.stubs(:http_get).with {|client, url, params| url =~ Regexp.new("ManualJournals/#{INVALID_MANUAL_JOURNAL_ID}") }.returns(get_file_as_string("manual_journal_not_found_error.xml"))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_get_manual_journals
|
23
|
+
# Make sure there is a manual journal in Xero to retrieve
|
24
|
+
manual_journal = @gateway.create_manual_journal(create_test_manual_journal).manual_journal
|
25
|
+
|
26
|
+
result = @gateway.get_manual_journals
|
27
|
+
assert result.success?
|
28
|
+
assert !result.request_params.nil?
|
29
|
+
assert !result.response_xml.nil?
|
30
|
+
assert result.manual_journals.collect {|i| i.narration}.include?(manual_journal.narration)
|
31
|
+
assert result.manual_journals.collect {|i| i.manual_journal_id}.include?(manual_journal.manual_journal_id)
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_get_manual_journals_with_modified_since_date
|
35
|
+
# Create a test manual journal
|
36
|
+
@gateway.create_manual_journal(create_test_manual_journal)
|
37
|
+
|
38
|
+
# Check that it is returned
|
39
|
+
result = @gateway.get_manual_journals(:modified_since => Date.today - 1)
|
40
|
+
assert result.success?
|
41
|
+
assert !result.request_params.nil?
|
42
|
+
assert !result.response_xml.nil?
|
43
|
+
assert result.request_params.keys.include?(:ModifiedAfter) # make sure the flag was sent
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_journal_lines_downloaded_set_correctly
|
47
|
+
# No line items.
|
48
|
+
response = @gateway.get_manual_journals
|
49
|
+
assert_equal(true, response.success?)
|
50
|
+
|
51
|
+
manual_journal = response.manual_journals.first
|
52
|
+
assert_kind_of(XeroGateway::ManualJournal, manual_journal)
|
53
|
+
assert !manual_journal.journal_lines_downloaded?
|
54
|
+
end
|
55
|
+
|
56
|
+
# Make sure that a reference to gateway is passed when the get_manual_journals response is parsed.
|
57
|
+
def test_get_manual_journals_gateway_reference
|
58
|
+
result = @gateway.get_manual_journals
|
59
|
+
assert(result.success?)
|
60
|
+
assert_not_equal(0, result.manual_journals.size)
|
61
|
+
|
62
|
+
result.manual_journals.each do |manual_journal|
|
63
|
+
assert(manual_journal.gateway === @gateway)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Test to make sure that we correctly error when a manual journal doesn't have an ID.
|
68
|
+
# This should usually never be ecountered.
|
69
|
+
def test_to_ensure_that_a_manual_journal_with_invalid_id_errors
|
70
|
+
# Make sure there is a manual journal to retrieve, even though we will mangle it later.
|
71
|
+
manual_journal = @gateway.create_manual_journal(create_test_manual_journal).manual_journal
|
72
|
+
|
73
|
+
result = @gateway.get_manual_journals
|
74
|
+
assert_equal(true, result.success?)
|
75
|
+
|
76
|
+
manual_journal = result.manual_journals.first
|
77
|
+
assert !manual_journal.journal_lines_downloaded?
|
78
|
+
|
79
|
+
# Mangle invoice_id to invalid one.
|
80
|
+
manual_journal.manual_journal_id = INVALID_MANUAL_JOURNAL_ID
|
81
|
+
|
82
|
+
# Make sure we fail here.
|
83
|
+
journal_lines = nil
|
84
|
+
assert_raise(XeroGateway::ManualJournalNotFoundError) { journal_lines = manual_journal.journal_lines }
|
85
|
+
assert_nil(journal_lines)
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../test_helper'
|
2
|
+
|
3
|
+
class UpdateManualJournalTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@gateway = XeroGateway::Gateway.new(CONSUMER_KEY, CONSUMER_SECRET)
|
8
|
+
|
9
|
+
if STUB_XERO_CALLS
|
10
|
+
@gateway.xero_url = "DUMMY_URL"
|
11
|
+
|
12
|
+
@gateway.stubs(:http_put).with {|client, url, body, params| url =~ /ManualJournals$/ }.returns(get_file_as_string("create_manual_journal.xml"))
|
13
|
+
@gateway.stubs(:http_post).with {|client, url, body, params| url =~ /ManualJournals$/ }.returns(get_file_as_string("manual_journal.xml"))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_update_manual_journal
|
18
|
+
manual_journal = @gateway.create_manual_journal(create_test_manual_journal).manual_journal
|
19
|
+
|
20
|
+
today = Date.today
|
21
|
+
manual_journal.date = today
|
22
|
+
|
23
|
+
result = @gateway.update_manual_journal(manual_journal)
|
24
|
+
|
25
|
+
assert result.success?
|
26
|
+
assert !result.request_xml.nil?
|
27
|
+
assert !result.response_xml.nil?
|
28
|
+
assert_equal manual_journal.manual_journal_id, result.manual_journal.manual_journal_id
|
29
|
+
assert_equal today, result.manual_journal.date if !STUB_XERO_CALLS
|
30
|
+
end
|
31
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -176,4 +176,42 @@ module TestHelper
|
|
176
176
|
contact
|
177
177
|
end
|
178
178
|
|
179
|
+
def create_test_manual_journal(params={}, journal_line_params={})
|
180
|
+
params = {
|
181
|
+
:date => Date.today,
|
182
|
+
:narration => 'test narration',
|
183
|
+
:status => 'POSTED'
|
184
|
+
}.merge(params)
|
185
|
+
manual_journal = XeroGateway::ManualJournal.new(params)
|
186
|
+
add_test_journal_lines(manual_journal, journal_line_params)
|
187
|
+
end
|
188
|
+
|
189
|
+
def add_test_journal_lines(manual_journal, journal_line_params)
|
190
|
+
if journal_line_params
|
191
|
+
journal_line_params = [journal_line_params].flatten # always use an array, even if only a single hash passed in
|
192
|
+
|
193
|
+
# At least one line item, make first have some defaults.
|
194
|
+
journal_line_params << {} if journal_line_params.size == 0
|
195
|
+
journal_line_params[0] = {
|
196
|
+
:description => "FIRST LINE",
|
197
|
+
:account_code => "200",
|
198
|
+
:line_amount => BigDecimal.new("100"),
|
199
|
+
:tracking => XeroGateway::TrackingCategory.new(:name => "blah", :options => "hello")
|
200
|
+
}.merge(journal_line_params[0])
|
201
|
+
params_line_1 = journal_line_params[1] || {}
|
202
|
+
journal_line_params[1] = {
|
203
|
+
:description => "SECOND LINE",
|
204
|
+
:account_code => "200",
|
205
|
+
:line_amount => BigDecimal.new("-100"),
|
206
|
+
:tracking => XeroGateway::TrackingCategory.new(:name => "blah2", :options => "hello2")
|
207
|
+
}.merge(params_line_1)
|
208
|
+
|
209
|
+
# Create line_items from line_item_params
|
210
|
+
journal_line_params.each do |journal_line|
|
211
|
+
manual_journal.add_journal_line(journal_line)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
manual_journal
|
215
|
+
end
|
216
|
+
|
179
217
|
end
|
@@ -65,7 +65,7 @@ class BankTransactionTest < Test::Unit::TestCase
|
|
65
65
|
|
66
66
|
# Test that pushing anything else into add_line_item fails.
|
67
67
|
["invalid", 100, nil, []].each do | invalid_object |
|
68
|
-
assert_raise(XeroGateway::
|
68
|
+
assert_raise(XeroGateway::InvalidLineItemError) { @bank_transaction.add_line_item(invalid_object) }
|
69
69
|
assert_equal(2, @bank_transaction.line_items.size)
|
70
70
|
end
|
71
71
|
end
|
@@ -100,6 +100,18 @@ class BankTransactionTest < Test::Unit::TestCase
|
|
100
100
|
parsed_bank_transaction = XeroGateway::BankTransaction.from_xml(bank_transaction_element)
|
101
101
|
assert_equal 'http://example.com?with=params&and=more', parsed_bank_transaction.url
|
102
102
|
end
|
103
|
+
|
104
|
+
should "ignore missing contact" do
|
105
|
+
bank_transaction = create_test_bank_transaction
|
106
|
+
bank_transaction.contact = nil
|
107
|
+
bank_transaction.to_xml
|
108
|
+
end
|
109
|
+
|
110
|
+
should "ignore missing bank account" do
|
111
|
+
bank_transaction = create_test_bank_transaction
|
112
|
+
bank_transaction.bank_account = nil
|
113
|
+
bank_transaction.to_xml
|
114
|
+
end
|
103
115
|
end
|
104
116
|
|
105
117
|
private
|
@@ -215,7 +215,7 @@ class CreditNoteTest < Test::Unit::TestCase
|
|
215
215
|
|
216
216
|
# Test that pushing anything else into add_line_item fails.
|
217
217
|
["invalid", 100, nil, []].each do | invalid_object |
|
218
|
-
assert_raise(XeroGateway::
|
218
|
+
assert_raise(XeroGateway::InvalidLineItemError) { credit_note.add_line_item(invalid_object) }
|
219
219
|
assert_equal(2, credit_note.line_items.size)
|
220
220
|
end
|
221
221
|
end
|
data/test/unit/gateway_test.rb
CHANGED
@@ -7,7 +7,7 @@ class GatewayTest < Test::Unit::TestCase
|
|
7
7
|
@gateway = XeroGateway::Gateway.new(CONSUMER_KEY, CONSUMER_SECRET)
|
8
8
|
end
|
9
9
|
|
10
|
-
context "with
|
10
|
+
context "with error handling" do
|
11
11
|
|
12
12
|
should "handle token expired" do
|
13
13
|
XeroGateway::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("token_expired"), :code => "401"))
|
@@ -56,7 +56,31 @@ class GatewayTest < Test::Unit::TestCase
|
|
56
56
|
@gateway.create_invoice(XeroGateway::Invoice.new)
|
57
57
|
end
|
58
58
|
end
|
59
|
-
|
59
|
+
|
60
|
+
should "handle invoices not found" do
|
61
|
+
XeroGateway::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("api_exception.xml"), :code => "404"))
|
62
|
+
|
63
|
+
assert_raises XeroGateway::InvoiceNotFoundError do
|
64
|
+
@gateway.get_invoice('unknown-invoice-id')
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
should "handle bank transactions not found" do
|
69
|
+
XeroGateway::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("api_exception.xml"), :code => "404"))
|
70
|
+
|
71
|
+
assert_raises XeroGateway::BankTransactionNotFoundError do
|
72
|
+
@gateway.get_bank_transaction('unknown-bank-transaction-id')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
should "handle credit notes not found" do
|
77
|
+
XeroGateway::OAuth.any_instance.stubs(:get).returns(stub(:plain_body => get_file_as_string("api_exception.xml"), :code => "404"))
|
78
|
+
|
79
|
+
assert_raises XeroGateway::CreditNoteNotFoundError do
|
80
|
+
@gateway.get_credit_note('unknown-credit-note-id')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
60
84
|
should "handle random root elements" do
|
61
85
|
XeroGateway::OAuth.any_instance.stubs(:put).returns(stub(:plain_body => "<RandomRootElement></RandomRootElement>", :code => "200"))
|
62
86
|
|
data/test/unit/invoice_test.rb
CHANGED
@@ -229,7 +229,7 @@ class InvoiceTest < Test::Unit::TestCase
|
|
229
229
|
|
230
230
|
# Test that pushing anything else into add_line_item fails.
|
231
231
|
["invalid", 100, nil, []].each do | invalid_object |
|
232
|
-
assert_raise(XeroGateway::
|
232
|
+
assert_raise(XeroGateway::InvalidLineItemError) { invoice.add_line_item(invalid_object) }
|
233
233
|
assert_equal(2, invoice.line_items.size)
|
234
234
|
end
|
235
235
|
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../test_helper.rb')
|
2
|
+
|
3
|
+
class ManualJournalTest < Test::Unit::TestCase
|
4
|
+
include TestHelper
|
5
|
+
|
6
|
+
context "creating test manual journals" do
|
7
|
+
should "work" do
|
8
|
+
manual_journal = create_test_manual_journal
|
9
|
+
|
10
|
+
# test transaction defaults
|
11
|
+
assert_equal 'POSTED', manual_journal.status
|
12
|
+
assert_kind_of Date, manual_journal.date
|
13
|
+
assert_equal 'test narration', manual_journal.narration
|
14
|
+
|
15
|
+
# Test the journal_line defaults.
|
16
|
+
journal_line = manual_journal.journal_lines.first
|
17
|
+
assert_equal('FIRST LINE', journal_line.description)
|
18
|
+
assert_equal('200', journal_line.account_code)
|
19
|
+
assert_equal(BigDecimal.new('100'), journal_line.line_amount)
|
20
|
+
end
|
21
|
+
|
22
|
+
should "allow overriding transaction defaults" do
|
23
|
+
assert_equal 'DRAFT', create_test_manual_journal(:status => 'DRAFT').status
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "adding journal lines" do
|
28
|
+
setup do
|
29
|
+
@manual_journal = create_test_manual_journal
|
30
|
+
end
|
31
|
+
|
32
|
+
should "work" do
|
33
|
+
assert_equal 2, @manual_journal.journal_lines.size
|
34
|
+
assert @manual_journal.valid?
|
35
|
+
|
36
|
+
journal_line_params = {:description => "Test Item 1", :line_amount => 100, :account_code => '200'}
|
37
|
+
|
38
|
+
# Test adding line item by hash
|
39
|
+
journal_line = @manual_journal.add_journal_line(journal_line_params)
|
40
|
+
assert_kind_of(XeroGateway::JournalLine, journal_line)
|
41
|
+
assert_equal(journal_line_params[:description], journal_line.description)
|
42
|
+
assert_equal(journal_line_params[:line_amount], journal_line.line_amount)
|
43
|
+
assert_equal(3, @manual_journal.journal_lines.size)
|
44
|
+
|
45
|
+
# Test adding line item by XeroGateway::JournalLine
|
46
|
+
journal_line = @manual_journal.add_journal_line(journal_line_params)
|
47
|
+
assert_kind_of(XeroGateway::JournalLine, journal_line)
|
48
|
+
assert_equal(journal_line_params[:description], journal_line.description)
|
49
|
+
assert_equal(journal_line_params[:line_amount], journal_line.line_amount)
|
50
|
+
assert_equal(4, @manual_journal.journal_lines.size)
|
51
|
+
|
52
|
+
# Test that having only 1 journal line fails.
|
53
|
+
@manual_journal.journal_lines = []
|
54
|
+
@manual_journal.add_journal_line(journal_line_params)
|
55
|
+
assert !@manual_journal.valid?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
context "building and parsing XML" do
|
61
|
+
should "work vice versa" do
|
62
|
+
manual_journal = create_test_manual_journal
|
63
|
+
manual_journal_as_xml = manual_journal.to_xml
|
64
|
+
manual_journal_element = REXML::XPath.first(REXML::Document.new(manual_journal_as_xml), "/ManualJournal")
|
65
|
+
|
66
|
+
# checking for mandatory fields
|
67
|
+
assert_xml_field manual_journal_element, 'Date'
|
68
|
+
assert_xml_field manual_journal_element, 'Narration', :value => 'test narration'
|
69
|
+
assert_xml_field manual_journal_element, 'Status', :value => 'POSTED'
|
70
|
+
|
71
|
+
parsed_manual_journal = XeroGateway::ManualJournal.from_xml(manual_journal_element)
|
72
|
+
assert_equal(manual_journal, parsed_manual_journal)
|
73
|
+
end
|
74
|
+
|
75
|
+
should "work for optional params" do
|
76
|
+
manual_journal = create_test_manual_journal(:url => 'http://example.com?with=params&and=more')
|
77
|
+
manual_journal_element = REXML::XPath.first(REXML::Document.new(manual_journal.to_xml), "/ManualJournal")
|
78
|
+
|
79
|
+
assert_xml_field manual_journal_element, 'Url', :value => 'http://example.com\?with=params&and=more'
|
80
|
+
|
81
|
+
parsed_manual_journal = XeroGateway::ManualJournal.from_xml(manual_journal_element)
|
82
|
+
assert_equal 'http://example.com?with=params&and=more', parsed_manual_journal.url
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def assert_xml_field(xml, field_name, options={})
|
89
|
+
assert_match /#{field_name}/, xml.to_s, "Didn't find the field #{field_name} in the XML document!"
|
90
|
+
assert_match /#{field_name}.*#{options[:value]}.*#{field_name}/, xml.to_s, "The field #{field_name} was expected to be '#{options[:value]}'!" if options[:value]
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
data/xero_gateway.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "xero_gateway"
|
3
|
-
s.version = "2.0.
|
4
|
-
s.date = "2012-
|
3
|
+
s.version = "2.0.15"
|
4
|
+
s.date = "2012-06-15"
|
5
5
|
s.summary = "Enables ruby based applications to communicate with the Xero API"
|
6
6
|
s.email = "tim@connorsoftware.com"
|
7
7
|
s.homepage = "http://github.com/tlconnor/xero_gateway"
|
metadata
CHANGED
@@ -1,56 +1,79 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: xero_gateway
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 17
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
- 15
|
10
|
+
version: 2.0.15
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Tim Connor
|
9
14
|
- Nik Wakelin
|
10
15
|
autorequire:
|
11
16
|
bindir: bin
|
12
17
|
cert_chain: []
|
13
|
-
|
14
|
-
|
15
|
-
|
18
|
+
|
19
|
+
date: 2012-06-15 00:00:00 -07:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
16
23
|
name: builder
|
17
|
-
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
18
26
|
none: false
|
19
|
-
requirements:
|
20
|
-
- -
|
21
|
-
- !ruby/object:Gem::Version
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 15
|
31
|
+
segments:
|
32
|
+
- 2
|
33
|
+
- 1
|
34
|
+
- 2
|
22
35
|
version: 2.1.2
|
23
36
|
type: :runtime
|
24
|
-
|
25
|
-
|
26
|
-
- !ruby/object:Gem::Dependency
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
27
39
|
name: oauth
|
28
|
-
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
29
42
|
none: false
|
30
|
-
requirements:
|
31
|
-
- -
|
32
|
-
- !ruby/object:Gem::Version
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 31
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
- 3
|
50
|
+
- 6
|
33
51
|
version: 0.3.6
|
34
52
|
type: :runtime
|
35
|
-
|
36
|
-
|
37
|
-
- !ruby/object:Gem::Dependency
|
53
|
+
version_requirements: *id002
|
54
|
+
- !ruby/object:Gem::Dependency
|
38
55
|
name: activesupport
|
39
|
-
|
56
|
+
prerelease: false
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
40
58
|
none: false
|
41
|
-
requirements:
|
42
|
-
- -
|
43
|
-
- !ruby/object:Gem::Version
|
44
|
-
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
45
66
|
type: :runtime
|
46
|
-
|
47
|
-
version_requirements: *70356902600700
|
67
|
+
version_requirements: *id003
|
48
68
|
description: Enables ruby based applications to communicate with the Xero API
|
49
69
|
email: tim@connorsoftware.com
|
50
70
|
executables: []
|
71
|
+
|
51
72
|
extensions: []
|
73
|
+
|
52
74
|
extra_rdoc_files: []
|
53
|
-
|
75
|
+
|
76
|
+
files:
|
54
77
|
- Gemfile
|
55
78
|
- LICENSE
|
56
79
|
- Rakefile
|
@@ -74,8 +97,10 @@ files:
|
|
74
97
|
- lib/xero_gateway/http.rb
|
75
98
|
- lib/xero_gateway/http_encoding_helper.rb
|
76
99
|
- lib/xero_gateway/invoice.rb
|
100
|
+
- lib/xero_gateway/journal_line.rb
|
77
101
|
- lib/xero_gateway/line_item.rb
|
78
102
|
- lib/xero_gateway/line_item_calculations.rb
|
103
|
+
- lib/xero_gateway/manual_journal.rb
|
79
104
|
- lib/xero_gateway/money.rb
|
80
105
|
- lib/xero_gateway/oauth.rb
|
81
106
|
- lib/xero_gateway/organisation.rb
|
@@ -92,6 +117,7 @@ files:
|
|
92
117
|
- test/integration/create_contact_test.rb
|
93
118
|
- test/integration/create_credit_note_test.rb
|
94
119
|
- test/integration/create_invoice_test.rb
|
120
|
+
- test/integration/create_manual_journal_test.rb
|
95
121
|
- test/integration/get_accounts_test.rb
|
96
122
|
- test/integration/get_bank_transaction_test.rb
|
97
123
|
- test/integration/get_bank_transactions_test.rb
|
@@ -102,12 +128,15 @@ files:
|
|
102
128
|
- test/integration/get_currencies_test.rb
|
103
129
|
- test/integration/get_invoice_test.rb
|
104
130
|
- test/integration/get_invoices_test.rb
|
131
|
+
- test/integration/get_manual_journal_test.rb
|
132
|
+
- test/integration/get_manual_journals_test.rb
|
105
133
|
- test/integration/get_organisation_test.rb
|
106
134
|
- test/integration/get_tax_rates_test.rb
|
107
135
|
- test/integration/get_tracking_categories_test.rb
|
108
136
|
- test/integration/update_bank_transaction_test.rb
|
109
137
|
- test/integration/update_contact_test.rb
|
110
138
|
- test/integration/update_invoice_test.rb
|
139
|
+
- test/integration/update_manual_journal_test.rb
|
111
140
|
- test/test_helper.rb
|
112
141
|
- test/unit/account_test.rb
|
113
142
|
- test/unit/bank_transaction_test.rb
|
@@ -116,33 +145,45 @@ files:
|
|
116
145
|
- test/unit/currency_test.rb
|
117
146
|
- test/unit/gateway_test.rb
|
118
147
|
- test/unit/invoice_test.rb
|
148
|
+
- test/unit/manual_journal_test.rb
|
119
149
|
- test/unit/oauth_test.rb
|
120
150
|
- test/unit/organisation_test.rb
|
121
151
|
- test/unit/tax_rate_test.rb
|
122
152
|
- test/unit/tracking_category_test.rb
|
123
153
|
- lib/xero_gateway/ca-certificates.crt
|
154
|
+
has_rdoc: true
|
124
155
|
homepage: http://github.com/tlconnor/xero_gateway
|
125
156
|
licenses: []
|
157
|
+
|
126
158
|
post_install_message:
|
127
159
|
rdoc_options: []
|
128
|
-
|
160
|
+
|
161
|
+
require_paths:
|
129
162
|
- lib
|
130
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
163
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
164
|
none: false
|
132
|
-
requirements:
|
133
|
-
- -
|
134
|
-
- !ruby/object:Gem::Version
|
135
|
-
|
136
|
-
|
165
|
+
requirements:
|
166
|
+
- - ">="
|
167
|
+
- !ruby/object:Gem::Version
|
168
|
+
hash: 3
|
169
|
+
segments:
|
170
|
+
- 0
|
171
|
+
version: "0"
|
172
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
173
|
none: false
|
138
|
-
requirements:
|
139
|
-
- -
|
140
|
-
- !ruby/object:Gem::Version
|
141
|
-
|
174
|
+
requirements:
|
175
|
+
- - ">="
|
176
|
+
- !ruby/object:Gem::Version
|
177
|
+
hash: 3
|
178
|
+
segments:
|
179
|
+
- 0
|
180
|
+
version: "0"
|
142
181
|
requirements: []
|
182
|
+
|
143
183
|
rubyforge_project:
|
144
|
-
rubygems_version: 1.
|
184
|
+
rubygems_version: 1.6.2
|
145
185
|
signing_key:
|
146
186
|
specification_version: 3
|
147
187
|
summary: Enables ruby based applications to communicate with the Xero API
|
148
188
|
test_files: []
|
189
|
+
|