xeroizer 0.3.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.bundle/config +2 -2
  2. data/Gemfile +5 -0
  3. data/Rakefile +17 -1
  4. data/VERSION +1 -1
  5. data/lib/xeroizer.rb +5 -1
  6. data/lib/xeroizer/configuration.rb +19 -0
  7. data/lib/xeroizer/generic_application.rb +2 -1
  8. data/lib/xeroizer/logging.rb +8 -0
  9. data/lib/xeroizer/models/account.rb +2 -1
  10. data/lib/xeroizer/models/bank_account.rb +12 -0
  11. data/lib/xeroizer/models/bank_transaction.rb +74 -0
  12. data/lib/xeroizer/models/invoice.rb +17 -12
  13. data/lib/xeroizer/models/item.rb +3 -3
  14. data/lib/xeroizer/models/item_purchase_details.rb +19 -0
  15. data/lib/xeroizer/models/{item_purchase_sale_details.rb → item_sales_details.rb} +5 -3
  16. data/lib/xeroizer/models/line_amount_type.rb +11 -0
  17. data/lib/xeroizer/models/line_item.rb +2 -12
  18. data/lib/xeroizer/models/line_item_sum.rb +21 -0
  19. data/lib/xeroizer/models/payment.rb +2 -6
  20. data/lib/xeroizer/oauth.rb +1 -1
  21. data/lib/xeroizer/record/base.rb +21 -2
  22. data/lib/xeroizer/record/validation_helper.rb +14 -2
  23. data/lib/xeroizer/record/validators/block_validator.rb +22 -0
  24. data/lib/xeroizer/record/validators/validator.rb +14 -5
  25. data/lib/xeroizer/record/xml_helper.rb +24 -7
  26. data/test/acceptance/about_creating_bank_transactions_test.rb +162 -0
  27. data/test/acceptance/about_fetching_bank_transactions_test.rb +56 -0
  28. data/test/acceptance/acceptance_test.rb +53 -0
  29. data/test/acceptance/bank_transaction_reference_data.rb +31 -0
  30. data/test/test_helper.rb +11 -1
  31. data/test/unit/models/bank_transaction_model_parsing_test.rb +131 -0
  32. data/test/unit/models/bank_transaction_test.rb +47 -0
  33. data/test/unit/models/bank_transaction_validation_test.rb +87 -0
  34. data/test/unit/models/contact_test.rb +2 -2
  35. data/test/unit/models/credit_note_test.rb +2 -2
  36. data/test/unit/models/invoice_test.rb +43 -17
  37. data/test/unit/models/line_item_sum_test.rb +24 -0
  38. data/test/unit/models/line_item_test.rb +54 -0
  39. data/test/unit/oauth_config_test.rb +20 -0
  40. data/test/unit/oauth_test.rb +1 -1
  41. data/test/unit/private_application_test.rb +2 -2
  42. data/test/unit/record/base_model_test.rb +2 -2
  43. data/test/unit/record/base_test.rb +38 -1
  44. data/test/unit/record/block_validator_test.rb +125 -0
  45. data/test/unit/record/model_definition_test.rb +2 -2
  46. data/test/unit/record/parse_where_hash_test.rb +2 -2
  47. data/test/unit/record/record_association_test.rb +1 -1
  48. data/test/unit/record/validators_test.rb +51 -3
  49. data/test/unit/record_definition_test.rb +2 -2
  50. data/test/unit/report_definition_test.rb +2 -2
  51. data/test/unit/report_test.rb +1 -1
  52. data/xeroizer.gemspec +60 -6
  53. metadata +124 -66
  54. data/lib/.DS_Store +0 -0
data/.bundle/config CHANGED
@@ -1,2 +1,2 @@
1
- ---
2
- BUNDLE_DISABLE_SHARED_GEMS: "1"
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.3.5
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/item_purchase_sale_details'
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
@@ -25,7 +25,8 @@ module Xeroizer
25
25
  record :Payment
26
26
  record :TaxRate
27
27
  record :TrackingCategory
28
-
28
+ record :BankTransaction
29
+
29
30
  report :AgedPayablesByContact
30
31
  report :AgedReceivablesByContact
31
32
  report :BalanceSheet
@@ -0,0 +1,8 @@
1
+ module Xeroizer
2
+ module Logging
3
+ class DevNullLog; def self.info(what); end; end
4
+ class StdOutLog; def self.info(what); puts what; end; end
5
+
6
+ Log = DevNullLog
7
+ end
8
+ end
@@ -49,6 +49,7 @@ module Xeroizer
49
49
  string :code
50
50
  string :name
51
51
  string :type
52
+ string :class, :internal_name => :account_class
52
53
  string :status
53
54
  string :currency_code
54
55
  string :tax_type
@@ -59,4 +60,4 @@ module Xeroizer
59
60
  end
60
61
 
61
62
  end
62
- end
63
+ end
@@ -0,0 +1,12 @@
1
+ module Xeroizer
2
+ module Record
3
+ class BankAccountModel < BaseModel
4
+ set_permissions :read
5
+ end
6
+
7
+ class BankAccount < Base
8
+ string :account_id
9
+ string :code
10
+ end
11
+ end
12
+ 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 => proc { |invoice| invoice.new_record? }
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 => proc { |invoice| invoice.new_record? }
83
- validates_inclusion_of :line_amount_types, :in => LINE_AMOUNT_TYPES, :unless => proc { |invoice| invoice.new_record? }
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 => proc { |invoice| invoice.approved? }
86
- validates_associated :line_items, :if => proc { |invoice| invoice.approved? }
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?
@@ -16,12 +16,12 @@ module Xeroizer
16
16
  string :code
17
17
  string :description
18
18
 
19
- belongs_to :purchase_details, :model_name => 'ItemPurchaseSaleDetails'
20
- belongs_to :sales_details, :model_name => 'ItemPurchaseSaleDetails'
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 ItemPurchaseSaleDetailsModel < BaseModel
4
+ class ItemSalesDetailsModel < BaseModel
5
+
6
+ set_xml_node_name 'SalesDetails'
5
7
 
6
8
  end
7
9
 
8
- class ItemPurchaseSaleDetails < Base
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
- unless quantity.nil? && unit_amount.nil?
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
 
@@ -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 || @secret)
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
@@ -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
- parse_save_response(parent.http_put(to_xml))
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
- parse_save_response(parent.http_post(to_xml))
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