aloe 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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