keepr 0.0.1

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 (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