keepr 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.travis.yml +9 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +62 -0
  7. data/Rakefile +6 -0
  8. data/ci/Gemfile-rails-4-1 +11 -0
  9. data/ci/Gemfile-rails-4-2 +11 -0
  10. data/keepr.gemspec +31 -0
  11. data/lib/generators/keepr/migration/migration_generator.rb +23 -0
  12. data/lib/generators/keepr/migration/templates/migration.rb +72 -0
  13. data/lib/keepr.rb +15 -0
  14. data/lib/keepr/account.rb +120 -0
  15. data/lib/keepr/active_record_extension.rb +27 -0
  16. data/lib/keepr/cost_center.rb +8 -0
  17. data/lib/keepr/group.rb +33 -0
  18. data/lib/keepr/groups_creator.rb +51 -0
  19. data/lib/keepr/groups_creator/asset.txt +36 -0
  20. data/lib/keepr/groups_creator/liability.txt +28 -0
  21. data/lib/keepr/groups_creator/profit_and_loss.txt +31 -0
  22. data/lib/keepr/journal.rb +48 -0
  23. data/lib/keepr/posting.rb +74 -0
  24. data/lib/keepr/tax.rb +13 -0
  25. data/lib/keepr/version.rb +3 -0
  26. data/spec/account_spec.rb +210 -0
  27. data/spec/active_record_extension_spec.rb +60 -0
  28. data/spec/cost_center_spec.rb +16 -0
  29. data/spec/database.yml +3 -0
  30. data/spec/factories/account.rb +7 -0
  31. data/spec/factories/cost_center.rb +6 -0
  32. data/spec/factories/group.rb +6 -0
  33. data/spec/factories/tax.rb +8 -0
  34. data/spec/group_spec.rb +89 -0
  35. data/spec/groups_creator_spec.rb +45 -0
  36. data/spec/journal_spec.rb +111 -0
  37. data/spec/posting_spec.rb +124 -0
  38. data/spec/spec_helper.rb +58 -0
  39. data/spec/support/document.rb +3 -0
  40. data/spec/support/ledger.rb +3 -0
  41. data/spec/support/spec_migration.rb +16 -0
  42. data/spec/tax_spec.rb +35 -0
  43. metadata +213 -0
@@ -0,0 +1,60 @@
1
+ require 'spec_helper'
2
+
3
+ describe Keepr::ActiveRecordExtension do
4
+ let!(:account_1000) { FactoryGirl.create(:account, :number => 1000, :kind => :asset) }
5
+ let!(:account_1200) { FactoryGirl.create(:account, :number => 1200, :kind => :asset) }
6
+
7
+ describe 'ledger with associated account' do
8
+ subject do
9
+ ledger = Ledger.create! :bank_name => 'Sparkasse'
10
+ account_1200.update_attributes! :accountable => ledger
11
+ ledger
12
+ end
13
+
14
+ it { expect(subject.keepr_account).to be_present }
15
+ end
16
+
17
+ describe 'Document with associated journal' do
18
+ subject do
19
+ document = Document.create! :number => 'RE-2013-10-12345'
20
+ Keepr::Journal.create! :accountable => document,
21
+ :keepr_postings_attributes => [
22
+ { :keepr_account => account_1000, :amount => 100.99, :side => 'debit' },
23
+ { :keepr_account => account_1200, :amount => 100.99, :side => 'credit' }
24
+ ]
25
+ document
26
+ end
27
+
28
+ it 'has 1 keepr_journal' do
29
+ expect(subject.keepr_journals.size).to eq(1)
30
+ end
31
+ it { is_expected.to be_keepr_booked }
32
+ end
33
+
34
+ describe 'scopes' do
35
+ let!(:unbooked_document) { Document.create! :number => 'Unbooked' }
36
+ let!(:booked_document) {
37
+ document = Document.create! :number => 'Booked'
38
+ Keepr::Journal.create! :accountable => document,
39
+ :keepr_postings_attributes => [
40
+ { :keepr_account => account_1000, :amount => 100.99, :side => 'debit' },
41
+ { :keepr_account => account_1200, :amount => 100.99, :side => 'credit' }
42
+ ]
43
+ document
44
+ }
45
+
46
+ describe :keepr_booked do
47
+ subject { Document.keepr_booked }
48
+
49
+ it { is_expected.to include(booked_document) }
50
+ it { is_expected.not_to include(unbooked_document) }
51
+ end
52
+
53
+ describe :keepr_unbooked do
54
+ subject { Document.keepr_unbooked }
55
+
56
+ it { is_expected.to include(unbooked_document) }
57
+ it { is_expected.not_to include(booked_document) }
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe Keepr::CostCenter do
4
+ let(:cost_center) { FactoryGirl.create(:cost_center) }
5
+ let(:account) { FactoryGirl.create(:account, :number => 8400, :kind => :revenue) }
6
+
7
+ it 'should have postings' do
8
+ posting = Keepr::Posting.create! :amount => 10,
9
+ :side => 'debit',
10
+ :keepr_account => account,
11
+ :keepr_cost_center => cost_center,
12
+ :keepr_journal_id => 42
13
+
14
+ expect(cost_center.keepr_postings).to eq([posting])
15
+ end
16
+ end
data/spec/database.yml ADDED
@@ -0,0 +1,3 @@
1
+ sqlite:
2
+ adapter: sqlite3
3
+ database: ":memory:"
@@ -0,0 +1,7 @@
1
+ FactoryGirl.define do
2
+ factory :account, class: Keepr::Account do
3
+ number 1000
4
+ kind :asset
5
+ name 'Foo'
6
+ end
7
+ end
@@ -0,0 +1,6 @@
1
+ FactoryGirl.define do
2
+ factory :cost_center, class: Keepr::CostCenter do
3
+ number 'FZ1'
4
+ name 'Kleintransporter'
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ FactoryGirl.define do
2
+ factory :group, class: Keepr::Group do
3
+ target :asset
4
+ name 'Foo'
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ FactoryGirl.define do
2
+ factory :tax, class: Keepr::Tax do
3
+ name 'USt19'
4
+ description 'Umsatzsteuer 19%'
5
+ value 19.0
6
+ keepr_account { FactoryGirl.create :account, :number => 1776 }
7
+ end
8
+ end
@@ -0,0 +1,89 @@
1
+ require 'spec_helper'
2
+
3
+ describe Keepr::Group do
4
+ describe :get_from_parent do
5
+ it 'should preset parent' do
6
+ root = FactoryGirl.create :group, :target => :asset
7
+ child = root.children.create! :name => 'Bar'
8
+
9
+ expect(child.target).to eq('asset')
10
+ end
11
+ end
12
+
13
+ describe :keepr_accounts do
14
+ it 'should not destroy if there are accounts' do
15
+ group = FactoryGirl.create :group
16
+ account = FactoryGirl.create :account, :number => 1000, :keepr_group => group
17
+
18
+ expect { group.destroy }.to_not change { Keepr::Group.count }
19
+ expect(group.destroy).to eq(false)
20
+ expect(group.reload).to eq(group)
21
+ end
22
+
23
+ it 'should destroy if there are no accounts' do
24
+ group = FactoryGirl.create :group
25
+
26
+ expect { group.destroy }.to change { Keepr::Group.count }.by(-1)
27
+ end
28
+ end
29
+
30
+ describe :keepr_postings do
31
+ # Simple asset group hierarchy
32
+ let(:group_1) { FactoryGirl.create :group, :target => :asset }
33
+ let(:group_1_1) { FactoryGirl.create :group, :target => :asset, :parent => group_1 }
34
+ let(:group_1_1_1) { FactoryGirl.create :group, :target => :asset, :parent => group_1_1 }
35
+
36
+ # Group for P&L accounts
37
+ let(:group_2) { FactoryGirl.create :group, :target => :profit_and_loss }
38
+
39
+ # Group for balance result
40
+ let(:group_result){ FactoryGirl.create :group, :target => :liability, :is_result => true }
41
+
42
+ # Accounts
43
+ let(:account_1a) { FactoryGirl.create :account, :number => '0001', :keepr_group => group_1_1_1 }
44
+ let(:account_1b) { FactoryGirl.create :account, :number => '0011', :keepr_group => group_1_1_1 }
45
+ let(:account_1c) { FactoryGirl.create :account, :number => '0111', :keepr_group => group_1_1_1 }
46
+
47
+ let(:account_2) { FactoryGirl.create :account, :number => '8400', :keepr_group => group_2, :kind => :revenue }
48
+
49
+ # Journals
50
+ let!(:journal1) { Keepr::Journal.create! :keepr_postings_attributes => [
51
+ { :keepr_account => account_1a, :amount => 100.99, :side => 'debit' },
52
+ { :keepr_account => account_2, :amount => 100.99, :side => 'credit' }
53
+ ]
54
+ }
55
+ let!(:journal2) { Keepr::Journal.create! :keepr_postings_attributes => [
56
+ { :keepr_account => account_1b, :amount => 100.99, :side => 'debit' },
57
+ { :keepr_account => account_2, :amount => 100.99, :side => 'credit' }
58
+ ]
59
+ }
60
+ let!(:journal3) { Keepr::Journal.create! :keepr_postings_attributes => [
61
+ { :keepr_account => account_1c, :amount => 100.99, :side => 'debit' },
62
+ { :keepr_account => account_2, :amount => 100.99, :side => 'credit' }
63
+ ]
64
+ }
65
+
66
+ context 'for normal groups' do
67
+ it "should return postings of all accounts within the group" do
68
+ postings_1 = [journal1.debit_postings.first, journal2.debit_postings.first, journal3.debit_postings.first]
69
+ expect(group_1.keepr_postings).to eq(postings_1)
70
+ expect(group_1_1.keepr_postings).to eq(postings_1)
71
+ expect(group_1_1_1.keepr_postings).to eq(postings_1)
72
+
73
+ postings_2 = [journal1.credit_postings.first, journal2.credit_postings.first, journal3.credit_postings.first]
74
+ expect(group_2.keepr_postings).to eq(postings_2)
75
+ end
76
+ end
77
+
78
+ context "for result group" do
79
+ it "should return postings for P&L accounts" do
80
+ result_postings = [ journal1.credit_postings.first,
81
+ journal2.credit_postings.first,
82
+ journal3.credit_postings.first
83
+ ]
84
+
85
+ expect(group_result.keepr_postings).to eq(result_postings)
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe Keepr::GroupsCreator do
4
+ context 'balance groups' do
5
+ before :each do
6
+ Keepr::GroupsCreator.new(:balance).run
7
+ end
8
+
9
+ it "should create groups" do
10
+ expect(Keepr::Group.count).to eq(64)
11
+ expect(Keepr::Group.asset.count).to eq(36)
12
+ expect(Keepr::Group.liability.count).to eq(28)
13
+
14
+ compare_with_source(Keepr::Group.asset, 'asset.txt')
15
+ compare_with_source(Keepr::Group.liability, 'liability.txt')
16
+ end
17
+
18
+ it "should create result group" do
19
+ expect(Keepr::Group.result).to be_a(Keepr::Group)
20
+ end
21
+ end
22
+
23
+ context 'profit & loss groups' do
24
+ before :each do
25
+ Keepr::GroupsCreator.new(:profit_and_loss).run
26
+ end
27
+
28
+ it "should create profit & loss groups" do
29
+ expect(Keepr::Group.count).to eq(31)
30
+ expect(Keepr::Group.profit_and_loss.count).to eq(31)
31
+
32
+ compare_with_source(Keepr::Group.profit_and_loss, 'profit_and_loss.txt')
33
+ end
34
+ end
35
+
36
+ private
37
+ def compare_with_source(scope, filename)
38
+ full_filename = File.join(File.dirname(__FILE__), "../lib/keepr/groups_creator/#{filename}")
39
+ source = File.read(full_filename)
40
+
41
+ lines = scope.find_each.map { |g| "#{' ' * g.depth * 2}#{g.number} #{g.name}\n" }.join
42
+
43
+ expect(lines).to eq(source)
44
+ end
45
+ end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+
3
+ describe Keepr::Journal do
4
+ let!(:account_1000) { FactoryGirl.create(:account, :number => 1000, :kind => :asset) }
5
+ let!(:account_1200) { FactoryGirl.create(:account, :number => 1200, :kind => :asset) }
6
+ let!(:account_1210) { FactoryGirl.create(:account, :number => 1210, :kind => :asset) }
7
+ let!(:account_4910) { FactoryGirl.create(:account, :number => 4910, :kind => :expense) }
8
+ let!(:account_4920) { FactoryGirl.create(:account, :number => 4920, :kind => :expense) }
9
+ let!(:account_1576) { FactoryGirl.create(:account, :number => 1576, :kind => :asset) }
10
+ let!(:account_1600) { FactoryGirl.create(:account, :number => 1600, :kind => :liability) }
11
+
12
+ let :simple_journal do
13
+ Keepr::Journal.create :keepr_postings_attributes => [
14
+ { :keepr_account => account_1000, :amount => 100.99, :side => 'debit' },
15
+ { :keepr_account => account_1200, :amount => 100.99, :side => 'credit' }
16
+ ]
17
+ end
18
+
19
+ let :complex_journal do
20
+ Keepr::Journal.create :keepr_postings_attributes => [
21
+ { :keepr_account => account_4920, :amount => 8.40, :side => 'debit' },
22
+ { :keepr_account => account_1576, :amount => 1.60, :side => 'debit' },
23
+ { :keepr_account => account_1600, :amount => 10.00, :side => 'credit' }
24
+ ]
25
+ end
26
+
27
+ describe :initialization do
28
+ context 'with date missing' do
29
+ it 'should set date to today' do
30
+ expect(Keepr::Journal.new.date).to eq(Date.today)
31
+ end
32
+ end
33
+
34
+ context 'with date given' do
35
+ it 'should not modify the date' do
36
+ old_date = Date.new(2013,10,1)
37
+ expect(Keepr::Journal.new(:date => old_date).date).to eq(old_date)
38
+ end
39
+ end
40
+ end
41
+
42
+ describe :validation do
43
+ it 'should success for valid journals' do
44
+ expect(simple_journal).to be_valid
45
+ expect(complex_journal).to be_valid
46
+ end
47
+
48
+ it 'should accept journal with postings marked for destruction' do
49
+ complex_journal.keepr_postings.first.mark_for_destruction
50
+ complex_journal.keepr_postings.build :keepr_account => account_4910, :amount => 8.4, :side => 'debit'
51
+
52
+ expect(complex_journal).to be_valid
53
+ end
54
+
55
+ it 'should fail for journal with only one posting' do
56
+ journal = Keepr::Journal.create :keepr_postings_attributes => [
57
+ { :keepr_account => account_4920, :amount => 8.40, :side => 'debit' }
58
+ ]
59
+ expect(journal).not_to be_valid
60
+ end
61
+
62
+ it 'should fail for booking the same account twice' do
63
+ journal = Keepr::Journal.create :keepr_postings_attributes => [
64
+ { :keepr_account => account_1000, :amount => 10, :side => 'debit' },
65
+ { :keepr_account => account_1000, :amount => 10, :side => 'credit' }
66
+ ]
67
+ expect(journal).not_to be_valid
68
+ end
69
+
70
+ it 'should fail for unbalanced journal' do
71
+ journal = Keepr::Journal.create :keepr_postings_attributes => [
72
+ { :keepr_account => account_1000, :amount => 10, :side => 'debit' },
73
+ { :keepr_account => account_1200, :amount => 10, :side => 'debit' }
74
+ ]
75
+ expect(journal).not_to be_valid
76
+ end
77
+ end
78
+
79
+ describe :postings do
80
+ it 'should return postings' do
81
+ expect(simple_journal.keepr_postings.size).to eq(2)
82
+ expect(complex_journal.keepr_postings.size).to eq(3)
83
+ end
84
+
85
+ it 'should order postings' do
86
+ expect(simple_journal.keepr_postings.map(&:side)).to eq(['debit','credit'])
87
+ expect(complex_journal.keepr_postings.map(&:side)).to eq(['debit','debit','credit'])
88
+ end
89
+ end
90
+
91
+ describe :credit_postings do
92
+ it 'should return postings with positive amount' do
93
+ expect(simple_journal.credit_postings.size).to eq(1)
94
+ expect(complex_journal.credit_postings.size).to eq(1)
95
+ end
96
+ end
97
+
98
+ describe :debit_postings do
99
+ it 'should return postings with negative amount' do
100
+ expect(simple_journal.debit_postings.size).to eq(1)
101
+ expect(complex_journal.debit_postings.size).to eq(2)
102
+ end
103
+ end
104
+
105
+ describe :amount do
106
+ it 'should return absolute amount' do
107
+ expect(simple_journal.amount).to eq(100.99)
108
+ expect(complex_journal.amount).to eq(10)
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,124 @@
1
+ require 'spec_helper'
2
+
3
+ describe Keepr::Posting do
4
+ let!(:account_1000) { FactoryGirl.create(:account, :number => 1000, :kind => :asset) }
5
+
6
+ describe 'side/amount' do
7
+ it 'should handle empty object' do
8
+ posting = Keepr::Posting.new
9
+ expect(posting.amount).to be_nil
10
+ expect(posting.side).to be_nil
11
+ end
12
+
13
+ it 'should set credit amount' do
14
+ posting = Keepr::Posting.new :amount => 10, :side => 'credit'
15
+
16
+ expect(posting).to be_credit
17
+ expect(posting.amount).to eq(10)
18
+ expect(posting.raw_amount).to eq(-10)
19
+ end
20
+
21
+ it 'should set debit amount' do
22
+ posting = Keepr::Posting.new :amount => 10, :side => 'debit'
23
+
24
+ expect(posting).to be_debit
25
+ expect(posting.amount).to eq(10)
26
+ expect(posting.raw_amount).to eq(10)
27
+ end
28
+
29
+ it 'should set side and amount in different steps' do
30
+ posting = Keepr::Posting.new
31
+
32
+ posting.side = 'credit'
33
+ expect(posting).to be_credit
34
+ expect(posting.amount).to be_nil
35
+
36
+ posting.amount = 10
37
+ expect(posting).to be_credit
38
+ expect(posting.amount).to eq(10)
39
+ end
40
+
41
+ it 'should change to credit' do
42
+ posting = Keepr::Posting.new :amount => 10, :side => 'debit'
43
+ posting.side = 'credit'
44
+
45
+ expect(posting).to be_credit
46
+ expect(posting.amount).to eq(10)
47
+ end
48
+
49
+ it 'should change to debit' do
50
+ posting = Keepr::Posting.new :amount => 10, :side => 'credit'
51
+ posting.side = 'debit'
52
+
53
+ expect(posting).to be_debit
54
+ expect(posting.amount).to eq(10)
55
+ end
56
+
57
+ it 'should default to debit' do
58
+ posting = Keepr::Posting.new :amount => 10
59
+
60
+ expect(posting).to be_debit
61
+ expect(posting.amount).to eq(10)
62
+ end
63
+
64
+ it 'should handle string amount' do
65
+ posting = Keepr::Posting.new :amount => '0.5'
66
+
67
+ expect(posting).to be_debit
68
+ expect(posting.amount).to eq(0.5)
69
+ end
70
+
71
+ it 'should recognized saved debit posting' do
72
+ posting = Keepr::Posting.create!(:amount => 10, :side => 'debit', :keepr_account => account_1000, :keepr_journal_id => 42)
73
+ posting.reload
74
+
75
+ expect(posting).to be_debit
76
+ expect(posting.amount).to eq(10)
77
+ end
78
+
79
+ it 'should recognized saved credit posting' do
80
+ posting = Keepr::Posting.create!(:amount => 10, :side => 'credit', :keepr_account => account_1000, :keepr_journal_id => 42)
81
+ posting.reload
82
+
83
+ expect(posting).to be_credit
84
+ expect(posting.amount).to eq(10)
85
+ end
86
+
87
+ it 'should fail for negative amount' do
88
+ expect {
89
+ Keepr::Posting.new(:amount => -10)
90
+ }.to raise_error(ArgumentError)
91
+ end
92
+
93
+ it 'should fail for unknown side' do
94
+ expect {
95
+ Keepr::Posting.new(:side => 'foo')
96
+ }.to raise_error(ArgumentError)
97
+ end
98
+ end
99
+
100
+ describe 'scopes' do
101
+ let!(:debit_posting) { Keepr::Posting.create!(:amount => 10, :side => 'debit', :keepr_account => account_1000, :keepr_journal_id => 42) }
102
+ let!(:credit_posting) { Keepr::Posting.create!(:amount => 10, :side => 'credit', :keepr_account => account_1000, :keepr_journal_id => 42) }
103
+
104
+ it 'should filter' do
105
+ expect(account_1000.keepr_postings.debits).to eq([debit_posting])
106
+ expect(account_1000.keepr_postings.credits).to eq([credit_posting])
107
+ end
108
+ end
109
+
110
+ describe 'cost_center handling' do
111
+ let!(:cost_center) { FactoryGirl.create(:cost_center) }
112
+ let!(:account_8400) { FactoryGirl.create(:account, :number => 8400, :kind => :revenue) }
113
+
114
+ it "should allow cost_center" do
115
+ posting = Keepr::Posting.new :keepr_account => account_8400, :amount => 100, :keepr_cost_center => cost_center
116
+ expect(posting).to be_valid
117
+ end
118
+
119
+ it "should not allow cost_center" do
120
+ posting = Keepr::Posting.new :keepr_account => account_1000, :amount => 100, :keepr_cost_center => cost_center
121
+ expect(posting).to_not be_valid
122
+ end
123
+ end
124
+ end