aloe 0.1.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 (74) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/Rakefile +26 -0
  4. data/lib/aloe.rb +14 -0
  5. data/lib/aloe/account.rb +176 -0
  6. data/lib/aloe/account_configuration.rb +21 -0
  7. data/lib/aloe/account_repository.rb +45 -0
  8. data/lib/aloe/engine.rb +6 -0
  9. data/lib/aloe/entry.rb +59 -0
  10. data/lib/aloe/inoperable_account_error.rb +11 -0
  11. data/lib/aloe/insufficient_balance_error.rb +14 -0
  12. data/lib/aloe/invalid_amount_error.rb +3 -0
  13. data/lib/aloe/invalid_currency_error.rb +16 -0
  14. data/lib/aloe/ledger.rb +83 -0
  15. data/lib/aloe/ledger_entry.rb +83 -0
  16. data/lib/aloe/reports/account_history.rb +32 -0
  17. data/lib/aloe/reports/accounts_list.rb +39 -0
  18. data/lib/aloe/transaction.rb +80 -0
  19. data/lib/aloe/transaction_rollback.rb +34 -0
  20. data/lib/aloe/version.rb +3 -0
  21. data/lib/generators/aloe/aloe_generator.rb +17 -0
  22. data/lib/generators/aloe/templates/migration.rb +44 -0
  23. data/lib/tasks/aloe_tasks.rake +24 -0
  24. data/spec/aloe/account_configuration_spec.rb +21 -0
  25. data/spec/aloe/account_integration_spec.rb +144 -0
  26. data/spec/aloe/account_spec.rb +189 -0
  27. data/spec/aloe/accounting_integration_spec.rb +172 -0
  28. data/spec/aloe/entry_spec.rb +43 -0
  29. data/spec/aloe/ledger_entry_spec.rb +105 -0
  30. data/spec/aloe/ledger_spec.rb +98 -0
  31. data/spec/aloe/transaction_rollback_spec.rb +49 -0
  32. data/spec/aloe/transaction_spec.rb +59 -0
  33. data/spec/dummy/Rakefile +6 -0
  34. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  35. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  36. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  37. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  38. data/spec/dummy/app/models/user.rb +2 -0
  39. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  40. data/spec/dummy/bin/bundle +3 -0
  41. data/spec/dummy/bin/rails +4 -0
  42. data/spec/dummy/bin/rake +4 -0
  43. data/spec/dummy/config.ru +4 -0
  44. data/spec/dummy/config/application.rb +23 -0
  45. data/spec/dummy/config/boot.rb +5 -0
  46. data/spec/dummy/config/database.yml +25 -0
  47. data/spec/dummy/config/environment.rb +5 -0
  48. data/spec/dummy/config/environments/development.rb +29 -0
  49. data/spec/dummy/config/environments/production.rb +80 -0
  50. data/spec/dummy/config/environments/test.rb +36 -0
  51. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  52. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  53. data/spec/dummy/config/initializers/inflections.rb +16 -0
  54. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  55. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  56. data/spec/dummy/config/initializers/session_store.rb +3 -0
  57. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  58. data/spec/dummy/config/locales/en.yml +23 -0
  59. data/spec/dummy/config/routes.rb +56 -0
  60. data/spec/dummy/db/development.sqlite3 +0 -0
  61. data/spec/dummy/db/migrate/20131003200954_create_users.rb +8 -0
  62. data/spec/dummy/db/migrate/20131003203647_create_aloe_tables.rb +43 -0
  63. data/spec/dummy/db/schema.rb +63 -0
  64. data/spec/dummy/log/test.log +3308 -0
  65. data/spec/dummy/public/404.html +58 -0
  66. data/spec/dummy/public/422.html +58 -0
  67. data/spec/dummy/public/500.html +57 -0
  68. data/spec/dummy/public/favicon.ico +0 -0
  69. data/spec/dummy/test/fixtures/users.yml +11 -0
  70. data/spec/dummy/test/models/user_test.rb +7 -0
  71. data/spec/spec_helper.rb +19 -0
  72. data/spec/support/account_helpers.rb +17 -0
  73. data/spec/support/user.rb +1 -0
  74. metadata +278 -0
@@ -0,0 +1,189 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Aloe::Account do
6
+ describe "#configuration" do
7
+ it "returns AccountConfiguration instance by default" do
8
+ subject.configuration.should be_kind_of Aloe::AccountConfiguration
9
+ end
10
+ end
11
+
12
+ describe "#allow_negative_balance" do
13
+ it "delegates request to configuration" do
14
+ configuration = double "account configuration"
15
+ subject.stub(:configuration).and_return configuration
16
+ configuration.should_receive(:allow_negative_balance).and_return true
17
+ subject.allow_negative_balance.should be_true
18
+ end
19
+ end
20
+
21
+ describe "#balance" do
22
+ it "returns instance of Money value" do
23
+ subject.currency = "CZK"
24
+ subject.balance = 1234
25
+ subject.balance.should eq Money.new(1234, "CZK")
26
+ end
27
+ end
28
+
29
+ describe "#balance_of?" do
30
+ before(:each) do
31
+ subject.balance = 500
32
+ end
33
+
34
+ context "given fixnum cents" do
35
+ it "returns true when available balance is more than or equals to given number" do
36
+ subject.balance_of?(500).should be_true
37
+ subject.balance_of?(35).should be_true
38
+ end
39
+
40
+ it "returns false when available balance is less than given number" do
41
+ subject.balance_of?(501).should be_false
42
+ subject.balance_of?(990).should be_false
43
+ end
44
+ end
45
+
46
+ context "given Money instance" do
47
+ it "returns true when available balance is more than or equals to given number" do
48
+ subject.balance_of?(Money.new(500)).should be_true
49
+ subject.balance_of?(Money.new(35)).should be_true
50
+ end
51
+
52
+ it "returns false when available balance is less than given number" do
53
+ subject.balance_of?(Money.new(501)).should be_false
54
+ subject.balance_of?(Money.new(990)).should be_false
55
+ end
56
+
57
+ end
58
+ end
59
+
60
+ describe "#closeable?" do
61
+ context "given the balance is zero" do
62
+ it "returns true" do
63
+ subject.should be_closeable
64
+ end
65
+ end
66
+
67
+ context "given the balance is non-zero" do
68
+ it "returns false" do
69
+ subject.stub(:balance).and_return Money.new 100
70
+ subject.should_not be_closeable
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "#create_entry" do
76
+ before do
77
+ subject.currency = "GBP"
78
+ subject.save!
79
+ end
80
+
81
+ it "creates entry" do
82
+ subject.create_entry 500
83
+ subject.entries.size.should eq 1
84
+ end
85
+
86
+ it "modifies the balance" do
87
+ subject.create_entry 500
88
+ subject.balance.should eq Money.new(500, "GBP")
89
+ end
90
+
91
+ it "returns created entry" do
92
+ entry = subject.create_entry(500)
93
+ entry.should be_kind_of(Aloe::Entry)
94
+ entry.amount.should eq Money.new(500, :GBP)
95
+ end
96
+ end
97
+
98
+ describe "#currency?" do
99
+ it "checks whether account is of asked currency" do
100
+ subject.currency = "GBP"
101
+ subject.currency?(:GBP).should be_true
102
+ subject.currency?("GBP").should be_true
103
+ subject.currency?("USD").should be_false
104
+ end
105
+ end
106
+
107
+ describe "#rollback_all" do
108
+ it "calls rollback on all transactions" do
109
+ t1 = double("t1")
110
+ t2 = double("t2")
111
+ entries = [double("e1", transaction: t1), double("e2", transaction: t2)]
112
+ subject.stub(:entries).and_return entries
113
+ t1.should_receive(:rollback)
114
+ t2.should_receive(:rollback)
115
+ subject.rollback_all
116
+ end
117
+ end
118
+
119
+ describe "#balance_at" do
120
+ before do
121
+ subject.stub(:currency).and_return 'GBP'
122
+ subject.stub_chain(:entries, :where).and_return entries
123
+ end
124
+
125
+ context 'given date before the account was created' do
126
+ let(:entries) { [] }
127
+
128
+ before do
129
+ subject.stub(:created_at).and_return Time.now
130
+ end
131
+
132
+ it "should have nil balance before it was created" do
133
+ subject.balance_at(1.year.ago).should be_nil
134
+ end
135
+ end
136
+
137
+ context 'given there are entries and correct date given' do
138
+ let(:balance) { 84920123 }
139
+ let(:amounts) { [4858, -75785, 5951, -9958, 99485, 885496, 9983] }
140
+ let(:entries) do
141
+ amounts.map do |amount|
142
+ double "Entry", amount: amount
143
+ end
144
+ end
145
+
146
+ before do
147
+ subject.stub(:balance).and_return balance
148
+ subject.stub(:created_at).and_return 30.days.ago
149
+ end
150
+
151
+ it "should give the correct past balance" do
152
+ subject.balance_at(29.days.ago).should eq(balance - amounts.sum)
153
+ end
154
+ end
155
+ end
156
+
157
+ describe "#turnover" do
158
+ before do
159
+ subject.stub_chain(:entries, :where).and_return entries
160
+ subject.stub(:currency).and_return 'GBP'
161
+ end
162
+
163
+ context "given there are any entries" do
164
+ let(:amounts) { [1234, 500, -1234, 600, -100] }
165
+ let(:entries) do
166
+ amounts.map do |amount|
167
+ double "Entry", amount: amount
168
+ end
169
+ end
170
+
171
+ it "returns turnover over given period of time" do
172
+ subject.turnover(1.month.ago..Time.now).should eq 1000
173
+ end
174
+ end
175
+
176
+ context "given there are no entries" do
177
+ let(:entries) { [] }
178
+
179
+ before do
180
+ subject.stub_chain(:entries, :where).and_return entries
181
+ end
182
+
183
+ it "returns Money instance with 0" do
184
+ zero = Money.new(0, 'GBP')
185
+ subject.turnover(1.month.ago..Time.now).should eq zero
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,172 @@
1
+ # encoding: utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe "Aloe integration spec", integration: true do
6
+ describe "Aloe::Account" do
7
+ it "does not allow duplicate names" do
8
+ create_account 'Test', :GBP
9
+ account = Aloe::Account.new name: "Test", currency: "GBP"
10
+ account.should_not be_valid
11
+ end
12
+
13
+ it "does not allow duplicate owners" do
14
+ user = User.create!
15
+ previous_account = create_account user, :GBP
16
+ account = Aloe::Account.new owner: user, currency: "GBP"
17
+ account.should_not be_valid
18
+ end
19
+ end
20
+
21
+ describe "Aloe::Ledger.find_account" do
22
+ context "given an account exists" do
23
+ let(:user) { User.create! }
24
+
25
+ it "returns given account" do
26
+ create_account user, :GBP
27
+ account = Aloe::Ledger.find_account(user, "GBP")
28
+ account.should be_kind_of Aloe::Account
29
+ account.owner.should eq user
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "posting to a ledger" do
35
+ let(:from_account) { create_account 'Debit', :GBP }
36
+ let(:to_account) { create_account 'Credit', :GBP }
37
+ let(:money) { Money.new 5000, "GBP" }
38
+
39
+ it "manipulates balances" do
40
+ Aloe::Ledger.create_entry money, from: from_account, to: to_account,
41
+ type: 'deposit'
42
+ to_account.balance.should eq money
43
+ from_account.balance.should eq -money
44
+ end
45
+
46
+ context "given different currency" do
47
+ it "raises exception when given amount is of different currency" do
48
+ money = Money.new 5000, "USD"
49
+ lambda do
50
+ Aloe::Ledger.create_entry money, from: from_account,
51
+ to: to_account,
52
+ type: 'deposit'
53
+ end.should raise_error Aloe::InvalidCurrencyError
54
+ end
55
+ end
56
+
57
+ context "given that accounts are of different currency" do
58
+ let(:to_account) { create_account 'Credit', :USD }
59
+
60
+ it "raises exception when given amount is of different currency" do
61
+ lambda do
62
+ Aloe::Ledger.create_entry money, from: from_account, to: to_account,
63
+ type: 'deposit'
64
+ end.should raise_error Aloe::InvalidCurrencyError
65
+ end
66
+ end
67
+
68
+ context "given debit account cannot have negative balance" do
69
+ let(:configuration) { Aloe::AccountConfiguration.new allow_negative_balance: false }
70
+ let(:from_account) { create_account 'Debit', :GBP, configuration: configuration }
71
+
72
+ it "raises exception" do
73
+ lambda do
74
+ Aloe::Ledger.create_entry money, from: from_account, to: to_account,
75
+ type: 'deposit'
76
+ end.should raise_error Aloe::InsufficientBalanceError
77
+ to_account.balance.should be_zero
78
+ from_account.balance.should be_zero
79
+ end
80
+ end
81
+
82
+ context "given debit account is closed" do
83
+ let(:from_account) { create_account 'Debit', :GBP }
84
+
85
+ before do
86
+ from_account.close!
87
+ end
88
+
89
+ it "manipulates balances" do
90
+ lambda do
91
+ Aloe::Ledger.create_entry money, from: from_account,
92
+ to: to_account,
93
+ type: 'deposit'
94
+ end.should raise_error Aloe::InoperableAccountError
95
+ to_account.balance.should be_zero
96
+ from_account.balance.should be_zero
97
+ end
98
+ end
99
+
100
+ context "given debit account is suspended" do
101
+ let(:from_account) { create_account 'Debit', :GBP }
102
+
103
+ before do
104
+ from_account.suspend!
105
+ end
106
+
107
+ it "manipulates balances" do
108
+ lambda do
109
+ Aloe::Ledger.create_entry money, from: from_account,
110
+ to: to_account,
111
+ type: 'deposit'
112
+ end.should raise_error Aloe::InoperableAccountError
113
+ to_account.balance.should be_zero
114
+ from_account.balance.should be_zero
115
+ end
116
+ end
117
+ end
118
+
119
+ describe "rolling back transactions" do
120
+ let(:from_account) { create_account 'Debit', :GBP }
121
+ let(:to_account) { create_account 'Credit', :GBP }
122
+ let(:money) { Money.new 5000, "GBP" }
123
+
124
+ before do
125
+ @transaction = Aloe::Ledger.create_entry money,
126
+ from: from_account, to: to_account,
127
+ type: 'deposit'
128
+ end
129
+
130
+ it "creates balancing transaction" do
131
+ @transaction.rollback
132
+ from_account.entries.size.should eq 2
133
+ to_account.entries.size.should eq 2
134
+ Aloe::Transaction.last.type.should eq Aloe::ROLLBACK_TRANSACTION
135
+ Aloe::Transaction.last.type.should eq Aloe::ROLLBACK_TRANSACTION
136
+ end
137
+
138
+ it "balances accounts" do
139
+ @transaction.rollback
140
+ from_account.reload.balance.should be_zero
141
+ to_account.reload.balance.should be_zero
142
+ end
143
+ end
144
+
145
+ describe "handling concurent transactions" do
146
+ let(:configuration) { Aloe::AccountConfiguration.new allow_negative_balance: false }
147
+ let(:from_account) { create_account 'Debit', :GBP, configuration: configuration }
148
+ let(:to_account) { create_account 'Credit', :GBP, configuration: configuration }
149
+ let(:source) { create_account 'Source', :GBP }
150
+ let(:money) { Money.new 5000, "GBP" }
151
+
152
+ before do
153
+ Aloe::Ledger.create_entry money, from: source,
154
+ to: from_account,
155
+ type: 'deposit'
156
+ end
157
+
158
+ it "shouldn't allow concurent transactions" do
159
+ from_account_copy = Aloe::Account.find(from_account.id)
160
+ Aloe::Ledger.create_entry money, from: from_account,
161
+ to: to_account,
162
+ type: 'deposit'
163
+ expect do
164
+ Aloe::Ledger.create_entry money, from: from_account_copy,
165
+ to: to_account,
166
+ type: 'deposit'
167
+ end.to raise_error(Aloe::InsufficientBalanceError)
168
+ to_account.balance.should eq money
169
+ end
170
+ end
171
+ end
172
+
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Aloe::Entry do
6
+ it "takes amount in fixnum" do
7
+ subject.amount = 1234
8
+ subject.read_attribute(:amount).should eq 1234
9
+ end
10
+
11
+ it "delegates #currency to #account" do
12
+ subject.stub(:account).and_return(double "Account", currency: "GBP")
13
+ subject.currency.should eq "GBP"
14
+ end
15
+
16
+ describe '#amount' do
17
+ it 'returns Money instance' do
18
+ subject.send :write_attribute, :amount, 1234
19
+ subject.stub(:account).and_return(double 'Account', currency: 'GBP')
20
+ subject.amount.should eq Money.new(1234, 'GBP')
21
+ end
22
+ end
23
+
24
+ describe '#withdrawal?' do
25
+ before do
26
+ subject.stub(:account).and_return(double 'Account', currency: 'GBP')
27
+ end
28
+
29
+ context 'given the amount is negative' do
30
+ it 'returns true' do
31
+ subject.send :write_attribute, :amount, -1234
32
+ subject.should be_withdrawal
33
+ end
34
+ end
35
+
36
+ context 'given the amount is positive' do
37
+ it 'returns false' do
38
+ subject.send :write_attribute, :amount, 1234
39
+ subject.should_not be_withdrawal
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,105 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Aloe::LedgerEntry do
6
+ describe ".create_entry" do
7
+ let(:debit_account) { double 'DebitAccount', currency?: true,
8
+ create_entry: debit_entry, open?: true }
9
+ let(:credit_account) { double 'CreditAccount', currency?: true,
10
+ create_entry: credit_entry, open?: true }
11
+ let(:credit_entry) { double 'CreditEntry' }
12
+ let(:debit_entry) { double 'DebitEntry' }
13
+ let(:transaction) { double 'Transaction' }
14
+ let(:amount) { Money.new 5000, :GBP }
15
+ let(:options) { { from: debit_account, to: credit_account, type: 123 } }
16
+ subject { Aloe::LedgerEntry.new amount, options }
17
+
18
+ before do
19
+ Aloe::Transaction.stub(:create!)
20
+ end
21
+
22
+ it "checks if credit and debit accounts are of given currency" do
23
+ debit_account.should_receive(:currency?).with(amount.currency).and_return true
24
+ credit_account.should_receive(:currency?).with(amount.currency).and_return true
25
+ subject.create!
26
+ end
27
+
28
+ it "creates entry on debit and credit account" do
29
+ debit_account.should_receive(:create_entry).with(-5000)
30
+ credit_account.should_receive(:create_entry).with(5000)
31
+ subject.create!
32
+ end
33
+
34
+ it "creates transaction" do
35
+ Aloe::Transaction
36
+ .should_receive(:create!)
37
+ .with(credit_entry: credit_entry, debit_entry: debit_entry, category: 123)
38
+ .and_return transaction
39
+ subject.create!.should eq transaction
40
+ end
41
+
42
+ context "account owners given instead of accounts" do
43
+ let(:debit_owner) { double "debit owner" }
44
+ let(:credit_owner) { double "credit owner" }
45
+
46
+ it "finds accounts" do
47
+ Aloe::Ledger
48
+ .should_receive(:find_account)
49
+ .with(credit_owner, amount.currency)
50
+ .and_return credit_account
51
+ Aloe::Ledger
52
+ .should_receive(:find_account)
53
+ .with(debit_owner, amount.currency)
54
+ .and_return debit_account
55
+ Aloe::LedgerEntry.new(amount, from: debit_owner, to: credit_owner,
56
+ type: 123).create!
57
+ end
58
+ end
59
+
60
+ context "given different currency" do
61
+ let(:amount) { Money.new 5000, :CZK }
62
+
63
+ it "raises InvalidCurrencyError if given different currency" do
64
+ lambda do
65
+ debit_account.stub(:currency?).and_return false
66
+ subject.create!
67
+ end.should raise_error(Aloe::InvalidCurrencyError)
68
+ end
69
+ end
70
+
71
+ context "given 0 amount" do
72
+ let(:amount) { 0 }
73
+
74
+ it "raises InvalidAmountError" do
75
+ lambda do
76
+ subject.create!
77
+ end.should raise_error(Aloe::InvalidAmountError)
78
+ end
79
+ end
80
+
81
+ context "given closed or freezed debit amount" do
82
+ before do
83
+ debit_account.stub(:open?).and_return false
84
+ end
85
+
86
+ it "raises InoperableAccountError" do
87
+ lambda do
88
+ subject.create!
89
+ end.should raise_error(Aloe::InoperableAccountError)
90
+ end
91
+ end
92
+
93
+ context "given closed or freezed credit amount" do
94
+ before do
95
+ credit_account.stub(:open?).and_return false
96
+ end
97
+
98
+ it "raises InoperableAccountError" do
99
+ lambda do
100
+ subject.create!
101
+ end.should raise_error(Aloe::InoperableAccountError)
102
+ end
103
+ end
104
+ end
105
+ end