xeroizer 0.3.5 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.bundle/config +2 -2
- data/Gemfile +5 -0
- data/Rakefile +17 -1
- data/VERSION +1 -1
- data/lib/xeroizer.rb +5 -1
- data/lib/xeroizer/configuration.rb +19 -0
- data/lib/xeroizer/generic_application.rb +2 -1
- data/lib/xeroizer/logging.rb +8 -0
- data/lib/xeroizer/models/account.rb +2 -1
- data/lib/xeroizer/models/bank_account.rb +12 -0
- data/lib/xeroizer/models/bank_transaction.rb +74 -0
- data/lib/xeroizer/models/invoice.rb +17 -12
- data/lib/xeroizer/models/item.rb +3 -3
- data/lib/xeroizer/models/item_purchase_details.rb +19 -0
- data/lib/xeroizer/models/{item_purchase_sale_details.rb → item_sales_details.rb} +5 -3
- data/lib/xeroizer/models/line_amount_type.rb +11 -0
- data/lib/xeroizer/models/line_item.rb +2 -12
- data/lib/xeroizer/models/line_item_sum.rb +21 -0
- data/lib/xeroizer/models/payment.rb +2 -6
- data/lib/xeroizer/oauth.rb +1 -1
- data/lib/xeroizer/record/base.rb +21 -2
- data/lib/xeroizer/record/validation_helper.rb +14 -2
- data/lib/xeroizer/record/validators/block_validator.rb +22 -0
- data/lib/xeroizer/record/validators/validator.rb +14 -5
- data/lib/xeroizer/record/xml_helper.rb +24 -7
- data/test/acceptance/about_creating_bank_transactions_test.rb +162 -0
- data/test/acceptance/about_fetching_bank_transactions_test.rb +56 -0
- data/test/acceptance/acceptance_test.rb +53 -0
- data/test/acceptance/bank_transaction_reference_data.rb +31 -0
- data/test/test_helper.rb +11 -1
- data/test/unit/models/bank_transaction_model_parsing_test.rb +131 -0
- data/test/unit/models/bank_transaction_test.rb +47 -0
- data/test/unit/models/bank_transaction_validation_test.rb +87 -0
- data/test/unit/models/contact_test.rb +2 -2
- data/test/unit/models/credit_note_test.rb +2 -2
- data/test/unit/models/invoice_test.rb +43 -17
- data/test/unit/models/line_item_sum_test.rb +24 -0
- data/test/unit/models/line_item_test.rb +54 -0
- data/test/unit/oauth_config_test.rb +20 -0
- data/test/unit/oauth_test.rb +1 -1
- data/test/unit/private_application_test.rb +2 -2
- data/test/unit/record/base_model_test.rb +2 -2
- data/test/unit/record/base_test.rb +38 -1
- data/test/unit/record/block_validator_test.rb +125 -0
- data/test/unit/record/model_definition_test.rb +2 -2
- data/test/unit/record/parse_where_hash_test.rb +2 -2
- data/test/unit/record/record_association_test.rb +1 -1
- data/test/unit/record/validators_test.rb +51 -3
- data/test/unit/record_definition_test.rb +2 -2
- data/test/unit/report_definition_test.rb +2 -2
- data/test/unit/report_test.rb +1 -1
- data/xeroizer.gemspec +60 -6
- metadata +124 -66
- data/lib/.DS_Store +0 -0
data/.bundle/config
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
---
|
2
|
-
|
1
|
+
--- {}
|
2
|
+
|
data/Gemfile
CHANGED
@@ -4,9 +4,14 @@ gem 'builder', '>= 2.1.2'
|
|
4
4
|
gem 'oauth', '>= 0.3.6'
|
5
5
|
gem 'activesupport'
|
6
6
|
gem 'nokogiri'
|
7
|
+
gem 'i18n'
|
8
|
+
gem 'yard'
|
7
9
|
|
8
10
|
group :test do
|
9
11
|
gem 'mocha'
|
10
12
|
gem 'shoulda'
|
11
13
|
gem "jeweler", "~> 1.5.2"
|
14
|
+
gem "rest-client"
|
15
|
+
gem "turn"
|
16
|
+
gem "ansi"
|
12
17
|
end
|
data/Rakefile
CHANGED
@@ -28,11 +28,27 @@ task :default => :test
|
|
28
28
|
|
29
29
|
desc 'Test the xero gateway.'
|
30
30
|
Rake::TestTask.new(:test) do |t|
|
31
|
-
t.libs << 'lib'
|
31
|
+
t.libs << ['lib', 'test']
|
32
32
|
t.pattern = 'test/**/*_test.rb'
|
33
33
|
t.verbose = true
|
34
34
|
end
|
35
35
|
|
36
|
+
namespace :test do
|
37
|
+
desc 'Run acceptance/integration tests'
|
38
|
+
Rake::TestTask.new(:acceptance) do |t|
|
39
|
+
t.libs << ['lib', 'test']
|
40
|
+
t.pattern = 'test/acceptance/**/*_test.rb'
|
41
|
+
t.verbose = true
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'Run unit tests'
|
45
|
+
Rake::TestTask.new(:unit) do |t|
|
46
|
+
t.libs << ['lib', 'test']
|
47
|
+
t.pattern = 'test/unit/**/*_test.rb'
|
48
|
+
t.verbose = true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
36
52
|
YARD::Rake::YardocTask.new do |t|
|
37
53
|
# t.files = ['lib/**/*.rb', OTHER_PATHS] # optional
|
38
54
|
# t.options = ['--any', '--extra', '--opts'] # optional
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/lib/xeroizer.rb
CHANGED
@@ -23,11 +23,14 @@ require 'xeroizer/exceptions'
|
|
23
23
|
|
24
24
|
require 'xeroizer/record/base_model'
|
25
25
|
require 'xeroizer/record/base'
|
26
|
+
require 'xeroizer/configuration'
|
26
27
|
|
27
28
|
# Include models
|
28
29
|
require 'xeroizer/models/account'
|
29
30
|
require 'xeroizer/models/address'
|
30
31
|
require 'xeroizer/models/branding_theme'
|
32
|
+
require 'xeroizer/models/bank_transaction'
|
33
|
+
require 'xeroizer/models/bank_account'
|
31
34
|
require 'xeroizer/models/contact'
|
32
35
|
require 'xeroizer/models/contact_group'
|
33
36
|
require 'xeroizer/models/credit_note'
|
@@ -35,7 +38,8 @@ require 'xeroizer/models/currency'
|
|
35
38
|
require 'xeroizer/models/employee'
|
36
39
|
require 'xeroizer/models/invoice'
|
37
40
|
require 'xeroizer/models/item'
|
38
|
-
require 'xeroizer/models/
|
41
|
+
require 'xeroizer/models/item_purchase_details'
|
42
|
+
require 'xeroizer/models/item_sales_details'
|
39
43
|
require 'xeroizer/models/journal'
|
40
44
|
require 'xeroizer/models/journal_line'
|
41
45
|
require 'xeroizer/models/line_item'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Xeroizer
|
2
|
+
OAuthCredentials = Struct.new "OAuthCredentials", :consumer_key, :consumer_secret, :key_file
|
3
|
+
|
4
|
+
class OAuthConfig
|
5
|
+
class << self
|
6
|
+
def load yaml_text
|
7
|
+
require "yaml"
|
8
|
+
yaml = YAML.load yaml_text
|
9
|
+
consumer_credential = yaml["consumer"]
|
10
|
+
|
11
|
+
OAuthCredentials.new(
|
12
|
+
consumer_credential["key"],
|
13
|
+
consumer_credential["secret"],
|
14
|
+
consumer_credential["key_file"]
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'xeroizer/models/line_item'
|
2
|
+
require 'xeroizer/models/line_item_sum'
|
3
|
+
|
4
|
+
module Xeroizer
|
5
|
+
module Record
|
6
|
+
class BankTransactionModel < BaseModel
|
7
|
+
set_permissions :read
|
8
|
+
end
|
9
|
+
|
10
|
+
class BankTransaction < Base
|
11
|
+
def initialize(parent)
|
12
|
+
super parent
|
13
|
+
self.line_amount_types = "Exclusive"
|
14
|
+
end
|
15
|
+
|
16
|
+
set_primary_key :bank_transaction_id
|
17
|
+
string :type
|
18
|
+
date :date
|
19
|
+
|
20
|
+
date :updated_date_utc, :api_name => "UpdatedDateUTC"
|
21
|
+
date :fully_paid_on_date
|
22
|
+
string :bank_transaction_id, :api_name => "BankTransactionID"
|
23
|
+
boolean :is_reconciled
|
24
|
+
|
25
|
+
alias_method :reconciled?, :is_reconciled
|
26
|
+
|
27
|
+
belongs_to :contact, :model_name => 'Contact'
|
28
|
+
string :line_amount_types
|
29
|
+
has_many :line_items, :model_name => 'LineItem'
|
30
|
+
belongs_to :bank_account, :model_name => 'BankAccount'
|
31
|
+
|
32
|
+
validates_inclusion_of :line_amount_types,
|
33
|
+
:in => Xeroizer::Record::LINE_AMOUNT_TYPES, :allow_blanks => false
|
34
|
+
|
35
|
+
validates_inclusion_of :type,
|
36
|
+
:in => %w{SPEND RECEIVE}, :allow_blanks => false,
|
37
|
+
:message => "Invalid type. Expected either SPEND or RECEIVE."
|
38
|
+
|
39
|
+
validates_presence_of :contact, :bank_account, :allow_blanks => false
|
40
|
+
|
41
|
+
validates :line_items, :message => "Invalid line items. Must supply at least one." do
|
42
|
+
self.line_items.size > 0
|
43
|
+
end
|
44
|
+
|
45
|
+
def sub_total=(value); raise SettingTotalDirectlyNotSupported.new(:sub_total); end
|
46
|
+
def total_tax=(value); raise SettingTotalDirectlyNotSupported.new(:total_tax); end
|
47
|
+
def total=(value); raise SettingTotalDirectlyNotSupported.new(:total); end
|
48
|
+
|
49
|
+
def total; sub_total + total_tax; end
|
50
|
+
|
51
|
+
def sub_total
|
52
|
+
if ought_to_recalculate_totals?
|
53
|
+
result = LineItemSum.sub_total(self.line_items)
|
54
|
+
result -= total_tax if line_amount_types == 'Inclusive'
|
55
|
+
result
|
56
|
+
else
|
57
|
+
attributes[:sub_total]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def total_tax
|
62
|
+
return ought_to_recalculate_totals? ?
|
63
|
+
LineItemSum.total_tax(self.line_items) :
|
64
|
+
attributes[:total_tax]
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def ought_to_recalculate_totals?
|
70
|
+
new_record? || line_items && line_items.size > 0
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -40,13 +40,6 @@ module Xeroizer
|
|
40
40
|
} unless defined?(INVOICE_STATUS)
|
41
41
|
INVOICE_STATUSES = INVOICE_STATUS.keys.sort
|
42
42
|
|
43
|
-
LINE_AMOUNT_TYPE = {
|
44
|
-
"Inclusive" => 'CreditNote lines are inclusive tax',
|
45
|
-
"Exclusive" => 'CreditNote lines are exclusive of tax (default)',
|
46
|
-
"NoTax" => 'CreditNotes lines have no tax'
|
47
|
-
} unless defined?(LINE_AMOUNT_TYPE)
|
48
|
-
LINE_AMOUNT_TYPES = LINE_AMOUNT_TYPE.keys.sort
|
49
|
-
|
50
43
|
set_primary_key :invoice_id
|
51
44
|
set_possible_primary_keys :invoice_id, :invoice_number
|
52
45
|
list_contains_summary_only true
|
@@ -77,15 +70,27 @@ module Xeroizer
|
|
77
70
|
has_many :payments
|
78
71
|
has_many :credit_notes
|
79
72
|
|
80
|
-
validates_presence_of :date, :due_date, :unless =>
|
73
|
+
validates_presence_of :date, :due_date, :unless => :new_record?
|
81
74
|
validates_inclusion_of :type, :in => INVOICE_TYPES
|
82
|
-
validates_inclusion_of :status, :in => INVOICE_STATUSES, :unless =>
|
83
|
-
validates_inclusion_of :line_amount_types, :in => LINE_AMOUNT_TYPES, :unless =>
|
75
|
+
validates_inclusion_of :status, :in => INVOICE_STATUSES, :unless => :new_record?
|
76
|
+
validates_inclusion_of :line_amount_types, :in => LINE_AMOUNT_TYPES, :unless => :new_record?
|
84
77
|
validates_associated :contact
|
85
|
-
validates_associated :line_items, :allow_blanks => true, :unless =>
|
86
|
-
validates_associated :line_items, :if =>
|
78
|
+
validates_associated :line_items, :allow_blanks => true, :unless => :approved?
|
79
|
+
validates_associated :line_items, :if => :approved?
|
87
80
|
|
88
81
|
public
|
82
|
+
|
83
|
+
# Access the contact name without forcing a download of
|
84
|
+
# an incomplete, summary invoice.
|
85
|
+
def contact_name
|
86
|
+
attributes[:contact] && attributes[:contact][:name]
|
87
|
+
end
|
88
|
+
|
89
|
+
# Access the contact ID without forcing a download of an
|
90
|
+
# incomplete, summary invoice.
|
91
|
+
def contact_id
|
92
|
+
attributes[:contact] && attributes[:contact][:contact_id]
|
93
|
+
end
|
89
94
|
|
90
95
|
# Helper method to check if the invoice has been approved.
|
91
96
|
def approved?
|
data/lib/xeroizer/models/item.rb
CHANGED
@@ -16,12 +16,12 @@ module Xeroizer
|
|
16
16
|
string :code
|
17
17
|
string :description
|
18
18
|
|
19
|
-
belongs_to :purchase_details, :model_name => '
|
20
|
-
belongs_to :sales_details, :model_name => '
|
19
|
+
belongs_to :purchase_details, :model_name => 'ItemPurchaseDetails'
|
20
|
+
belongs_to :sales_details, :model_name => 'ItemSalesDetails'
|
21
21
|
|
22
22
|
validates_presence_of :code
|
23
23
|
|
24
24
|
end
|
25
25
|
|
26
26
|
end
|
27
|
-
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Xeroizer
|
2
|
+
module Record
|
3
|
+
|
4
|
+
class ItemPurchaseDetailsModel < BaseModel
|
5
|
+
|
6
|
+
set_xml_node_name 'PurchaseDetails'
|
7
|
+
|
8
|
+
end
|
9
|
+
|
10
|
+
class ItemPurchaseDetails < Base
|
11
|
+
|
12
|
+
decimal :unit_price
|
13
|
+
string :account_code
|
14
|
+
string :tax_type
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -1,11 +1,13 @@
|
|
1
1
|
module Xeroizer
|
2
2
|
module Record
|
3
3
|
|
4
|
-
class
|
4
|
+
class ItemSalesDetailsModel < BaseModel
|
5
|
+
|
6
|
+
set_xml_node_name 'SalesDetails'
|
5
7
|
|
6
8
|
end
|
7
9
|
|
8
|
-
class
|
10
|
+
class ItemSalesDetails < Base
|
9
11
|
|
10
12
|
decimal :unit_price
|
11
13
|
string :account_code
|
@@ -14,4 +16,4 @@ module Xeroizer
|
|
14
16
|
end
|
15
17
|
|
16
18
|
end
|
17
|
-
end
|
19
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Xeroizer
|
2
|
+
module Record
|
3
|
+
line_amount_type = {
|
4
|
+
"Inclusive" => 'Line item amounts are inclusive of tax',
|
5
|
+
"Exclusive" => 'Line item amounts are exclusive of tax (default)',
|
6
|
+
"NoTax" => 'Line item amounts have no tax'
|
7
|
+
}
|
8
|
+
|
9
|
+
LINE_AMOUNT_TYPES = line_amount_type.keys.sort
|
10
|
+
end
|
11
|
+
end
|
@@ -1,21 +1,13 @@
|
|
1
1
|
require 'xeroizer/models/account'
|
2
|
+
require 'xeroizer/models/line_amount_type'
|
2
3
|
|
3
4
|
module Xeroizer
|
4
5
|
module Record
|
5
|
-
|
6
6
|
class LineItemModel < BaseModel
|
7
7
|
|
8
8
|
end
|
9
9
|
|
10
10
|
class LineItem < Base
|
11
|
-
|
12
|
-
LINE_AMOUNT_TYPE = {
|
13
|
-
"Inclusive" => 'CreditNote lines are inclusive tax',
|
14
|
-
"Exclusive" => 'CreditNote lines are exclusive of tax (default)',
|
15
|
-
"NoTax" => 'CreditNotes lines have no tax'
|
16
|
-
} unless defined?(LINE_AMOUNT_TYPE)
|
17
|
-
LINE_AMOUNT_TYPES = LINE_AMOUNT_TYPE.keys.sort
|
18
|
-
|
19
11
|
TAX_TYPE = Account::TAX_TYPE unless defined?(TAX_TYPE)
|
20
12
|
|
21
13
|
string :item_code
|
@@ -35,9 +27,7 @@ module Xeroizer
|
|
35
27
|
# Calculate the line_total (if there is a quantity and unit_amount).
|
36
28
|
# Description-only lines have been allowed since Xero V2.09.
|
37
29
|
def line_amount
|
38
|
-
|
39
|
-
quantity * unit_amount
|
40
|
-
end
|
30
|
+
BigDecimal((quantity * unit_amount).to_s).round(2) if quantity && unit_amount
|
41
31
|
end
|
42
32
|
|
43
33
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Xeroizer
|
2
|
+
module Record
|
3
|
+
class LineItemSum
|
4
|
+
def self.total(line_items)
|
5
|
+
sub_total(line_items) + total_tax(line_items)
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.sub_total(line_items)
|
9
|
+
line_items.inject(BigDecimal("0")) do |sum, item|
|
10
|
+
sum += BigDecimal(item.line_amount.to_s).round(2)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.total_tax(line_items)
|
15
|
+
line_items.inject(BigDecimal("0")) do |sum, item|
|
16
|
+
sum += BigDecimal(item.tax_amount.to_s).round(2)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -3,6 +3,7 @@ module Xeroizer
|
|
3
3
|
|
4
4
|
class PaymentModel < BaseModel
|
5
5
|
|
6
|
+
set_xml_root_name 'Payments'
|
6
7
|
set_permissions :read, :write
|
7
8
|
|
8
9
|
end
|
@@ -16,12 +17,7 @@ module Xeroizer
|
|
16
17
|
decimal :amount
|
17
18
|
decimal :currency_rate
|
18
19
|
string :reference
|
19
|
-
|
20
|
-
guid :invoice_id
|
21
|
-
string :invoice_number
|
22
|
-
guid :account_id
|
23
|
-
string :code
|
24
|
-
|
20
|
+
|
25
21
|
belongs_to :account
|
26
22
|
belongs_to :invoice
|
27
23
|
|
data/lib/xeroizer/oauth.rb
CHANGED
@@ -97,7 +97,7 @@ module Xeroizer
|
|
97
97
|
# Renew an access token from a previously authorised token for a
|
98
98
|
# PARTNER application.
|
99
99
|
def renew_access_token(atoken = nil, asecret = nil, session_handle = nil)
|
100
|
-
old_token = ::OAuth::RequestToken.new(consumer, atoken || @atoken, asecret || @
|
100
|
+
old_token = ::OAuth::RequestToken.new(consumer, atoken || @atoken, asecret || @asecret)
|
101
101
|
access_token = old_token.get_access_token({
|
102
102
|
:oauth_session_handle => (session_handle || @session_handle),
|
103
103
|
:token => old_token
|
data/lib/xeroizer/record/base.rb
CHANGED
@@ -2,6 +2,7 @@ require 'xeroizer/record/model_definition_helper'
|
|
2
2
|
require 'xeroizer/record/record_association_helper'
|
3
3
|
require 'xeroizer/record/validation_helper'
|
4
4
|
require 'xeroizer/record/xml_helper'
|
5
|
+
require 'xeroizer/logging'
|
5
6
|
|
6
7
|
module Xeroizer
|
7
8
|
module Record
|
@@ -96,7 +97,13 @@ module Xeroizer
|
|
96
97
|
|
97
98
|
# Attempt to create a new record.
|
98
99
|
def create
|
99
|
-
|
100
|
+
request = to_xml
|
101
|
+
log "[CREATE SENT] (#{__FILE__}:#{__LINE__}) #{request}"
|
102
|
+
|
103
|
+
response = parent.http_put(request)
|
104
|
+
log "[CREATE RECEIVED] (#{__FILE__}:#{__LINE__}) #{response}"
|
105
|
+
|
106
|
+
parse_save_response(response)
|
100
107
|
end
|
101
108
|
|
102
109
|
# Attempt to update an existing record.
|
@@ -105,7 +112,15 @@ module Xeroizer
|
|
105
112
|
raise RecordKeyMustBeDefined.new(self.class.possible_primary_keys)
|
106
113
|
end
|
107
114
|
|
108
|
-
|
115
|
+
request = to_xml
|
116
|
+
|
117
|
+
log "[UPDATE SENT] (#{__FILE__}:#{__LINE__}) \r\n#{request}"
|
118
|
+
|
119
|
+
response = parent.http_post(request)
|
120
|
+
|
121
|
+
log "[UPDATE RECEIVED] (#{__FILE__}:#{__LINE__}) \r\n#{response}"
|
122
|
+
|
123
|
+
parse_save_response(response)
|
109
124
|
end
|
110
125
|
|
111
126
|
# Parse the response from a create/update request.
|
@@ -117,6 +132,10 @@ module Xeroizer
|
|
117
132
|
end
|
118
133
|
self
|
119
134
|
end
|
135
|
+
|
136
|
+
def log(what)
|
137
|
+
Xeroizer::Logging::Log.info what
|
138
|
+
end
|
120
139
|
|
121
140
|
end
|
122
141
|
|