xeroizer 0.3.5 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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