plutus 0.10.1 → 0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +5 -5
  2. data/README.markdown +172 -99
  3. data/app/assets/javascripts/plutus/application.js +5 -4
  4. data/app/assets/javascripts/plutus/reports.js +5 -0
  5. data/app/assets/stylesheets/bootstrap-theme.min.css +5 -0
  6. data/app/assets/stylesheets/bootstrap.min.css +5 -0
  7. data/app/assets/stylesheets/plutus/application.css +11 -5
  8. data/app/controllers/plutus/accounts_controller.rb +1 -16
  9. data/app/controllers/plutus/application_controller.rb +4 -0
  10. data/app/controllers/plutus/entries_controller.rb +7 -17
  11. data/app/controllers/plutus/reports_controller.rb +40 -0
  12. data/app/models/plutus/account.rb +92 -7
  13. data/app/models/plutus/amount.rb +11 -1
  14. data/app/models/plutus/amounts_extension.rb +31 -6
  15. data/app/models/plutus/asset.rb +21 -18
  16. data/app/models/plutus/entry.rb +28 -26
  17. data/app/models/plutus/equity.rb +22 -19
  18. data/app/models/plutus/expense.rb +21 -18
  19. data/app/models/plutus/liability.rb +27 -19
  20. data/app/models/plutus/revenue.rb +22 -19
  21. data/app/models/plutus/tenancy.rb +5 -1
  22. data/app/views/layouts/plutus/_messages.html.erb +9 -0
  23. data/app/views/layouts/plutus/_navigation.html.erb +19 -0
  24. data/app/views/layouts/plutus/_navigation_links.html.erb +5 -0
  25. data/app/views/layouts/plutus/application.html.erb +19 -0
  26. data/app/views/plutus/accounts/index.html.erb +9 -12
  27. data/app/views/plutus/entries/index.html.erb +22 -11
  28. data/app/views/plutus/reports/_account.html.erb +28 -0
  29. data/app/views/plutus/reports/balance_sheet.html.erb +21 -0
  30. data/app/views/plutus/reports/income_statement.html.erb +24 -0
  31. data/config/routes.rb +6 -3
  32. data/{lib/generators/plutus/templates/migration.rb → db/migrate/20160422010135_create_plutus_tables.rb} +6 -10
  33. data/lib/generators/plutus/add_date_upgrade_generator.rb +11 -0
  34. data/lib/generators/plutus/base_generator.rb +19 -0
  35. data/lib/generators/plutus/plutus_generator.rb +8 -22
  36. data/lib/generators/plutus/templates/add_date_migration.rb +12 -0
  37. data/lib/generators/plutus/templates/tenant_migration.rb +1 -1
  38. data/lib/generators/plutus/templates/update_migration.rb +2 -2
  39. data/lib/generators/plutus/tenancy_generator.rb +2 -17
  40. data/lib/generators/plutus/upgrade_plutus_generator.rb +6 -22
  41. data/lib/plutus.rb +2 -5
  42. data/lib/plutus/engine.rb +5 -0
  43. data/lib/plutus/version.rb +1 -1
  44. data/spec/controllers/accounts_controller_spec.rb +11 -20
  45. data/spec/controllers/entries_controller_spec.rb +11 -20
  46. data/spec/controllers/reports_controller_spec.rb +24 -0
  47. data/spec/factories/tenant_factory.rb +12 -0
  48. data/spec/models/account_spec.rb +79 -16
  49. data/spec/models/amount_spec.rb +6 -1
  50. data/spec/models/debit_amount_spec.rb +1 -1
  51. data/spec/models/entry_spec.rb +108 -29
  52. data/spec/models/tenancy_spec.rb +26 -8
  53. data/spec/routing/accounts_routing_spec.rb +6 -25
  54. data/spec/routing/entries_routing_spec.rb +6 -25
  55. data/spec/spec_helper.rb +5 -0
  56. data/spec/support/account_shared_examples.rb +14 -10
  57. data/spec/support/amount_shared_examples.rb +7 -7
  58. data/spec/support/shoulda_matchers.rb +8 -0
  59. metadata +100 -27
  60. data/app/views/plutus/accounts/show.html.erb +0 -59
  61. data/app/views/plutus/entries/show.html.erb +0 -46
  62. data/spec/schema.rb +0 -31
@@ -4,30 +4,36 @@ module Plutus
4
4
  describe Account do
5
5
  let(:account) { FactoryGirl.build(:account) }
6
6
  subject { account }
7
-
8
- it { should_not be_valid } # must construct a child type instead
7
+
8
+ it { is_expected.not_to be_valid } # must construct a child type instead
9
9
 
10
10
  describe "when using a child type" do
11
11
  let(:account) { FactoryGirl.create(:account, type: "Finance::Asset") }
12
- it { should be_valid }
13
-
12
+ it { is_expected.to be_valid }
13
+
14
14
  it "should be unique per name" do
15
15
  conflict = FactoryGirl.build(:account, name: account.name, type: account.type)
16
- conflict.should_not be_valid
17
- conflict.errors[:name].should == ["has already been taken"]
16
+ expect(conflict).not_to be_valid
17
+ expect(conflict.errors[:name]).to eq(["has already been taken"])
18
18
  end
19
19
  end
20
-
21
- it { should_not respond_to(:balance) }
22
-
20
+
21
+ it "calling the instance method #balance should raise a NoMethodError" do
22
+ expect { subject.balance }.to raise_error NoMethodError, "undefined method 'balance'"
23
+ end
24
+
25
+ it "calling the class method ::balance should raise a NoMethodError" do
26
+ expect { subject.class.balance }.to raise_error NoMethodError, "undefined method 'balance'"
27
+ end
28
+
23
29
  describe ".trial_balance" do
24
30
  subject { Account.trial_balance }
25
- it { should be_kind_of BigDecimal }
26
-
31
+ it { is_expected.to be_kind_of BigDecimal }
32
+
27
33
  context "when given no entries" do
28
- it { should == 0 }
34
+ it { is_expected.to eq(0) }
29
35
  end
30
-
36
+
31
37
  context "when given correct entries" do
32
38
  before {
33
39
  # credit accounts
@@ -57,14 +63,71 @@ module Plutus
57
63
  da5 = FactoryGirl.build(:debit_amount, :account => contra_revenue, :amount => 333)
58
64
 
59
65
  FactoryGirl.create(:entry, :credit_amounts => [ca1], :debit_amounts => [da1])
60
- FactoryGirl.create(:entry, :credit_amounts => [ca2], :debit_amounts => [da2])
66
+ FactoryGirl.create(:entry, :credit_amounts => [ca2], :debit_amounts => [da2])
61
67
  FactoryGirl.create(:entry, :credit_amounts => [ca3], :debit_amounts => [da3])
62
68
  FactoryGirl.create(:entry, :credit_amounts => [ca4], :debit_amounts => [da4])
63
69
  FactoryGirl.create(:entry, :credit_amounts => [ca5], :debit_amounts => [da5])
64
70
  }
65
-
66
- it { should == 0 }
71
+
72
+ it { is_expected.to eq(0) }
67
73
  end
68
74
  end
75
+
76
+ describe "#amounts" do
77
+ it "returns all credit and debit amounts" do
78
+ equity = FactoryGirl.create(:equity)
79
+ asset = FactoryGirl.create(:asset)
80
+ expense = FactoryGirl.create(:expense)
81
+
82
+ investment = Entry.new(
83
+ description: "Initial investment",
84
+ date: Date.today,
85
+ debits: [{ account_name: equity.name, amount: 1000 }],
86
+ credits: [{ account_name: asset.name, amount: 1000 }],
87
+ )
88
+ investment.save
89
+
90
+ purchase = Entry.new(
91
+ description: "First computer",
92
+ date: Date.today,
93
+ debits: [{ account_name: asset.name, amount: 900 }],
94
+ credits: [{ account_name: expense.name, amount: 900 }],
95
+ )
96
+ purchase.save
97
+
98
+ expect(equity.amounts.size).to eq 1
99
+ expect(asset.amounts.size).to eq 2
100
+ expect(expense.amounts.size).to eq 1
101
+ end
102
+ end
103
+
104
+ describe "#entries" do
105
+ it "returns all credit and debit entries" do
106
+ equity = FactoryGirl.create(:equity)
107
+ asset = FactoryGirl.create(:asset)
108
+ expense = FactoryGirl.create(:expense)
109
+
110
+ investment = Entry.new(
111
+ description: "Initial investment",
112
+ date: Date.today,
113
+ debits: [{ account_name: equity.name, amount: 1000 }],
114
+ credits: [{ account_name: asset.name, amount: 1000 }],
115
+ )
116
+ investment.save
117
+
118
+ purchase = Entry.new(
119
+ description: "First computer",
120
+ date: Date.today,
121
+ debits: [{ account_name: asset.name, amount: 900 }],
122
+ credits: [{ account_name: expense.name, amount: 900 }],
123
+ )
124
+ purchase.save
125
+
126
+ expect(equity.entries.size).to eq 1
127
+ expect(asset.entries.size).to eq 2
128
+ expect(expense.entries.size).to eq 1
129
+ end
130
+ end
131
+
69
132
  end
70
133
  end
@@ -2,7 +2,12 @@ require 'spec_helper'
2
2
 
3
3
  module Plutus
4
4
  describe Amount do
5
+
6
+ describe "attributes" do
7
+ it { is_expected.to delegate_method(:name).to(:account).with_prefix }
8
+ end
9
+
5
10
  subject { FactoryGirl.build(:amount) }
6
- it { should_not be_valid } # construct a child class instead
11
+ it { is_expected.not_to be_valid } # construct a child class instead
7
12
  end
8
13
  end
@@ -4,4 +4,4 @@ module Plutus
4
4
  describe DebitAmount do
5
5
  it_behaves_like 'a Plutus::Amount subtype', kind: :debit_amount
6
6
  end
7
- end
7
+ end
@@ -5,15 +5,15 @@ module Plutus
5
5
  let(:entry) { FactoryGirl.build(:entry) }
6
6
  subject { entry }
7
7
 
8
- it { should_not be_valid }
8
+ it { is_expected.not_to be_valid }
9
9
 
10
10
  context "with credit and debit" do
11
11
  let(:entry) { FactoryGirl.build(:entry_with_credit_and_debit) }
12
- it { should be_valid }
13
-
12
+ it { is_expected.to be_valid }
13
+
14
14
  it "should require a description" do
15
15
  entry.description = nil
16
- entry.should_not be_valid
16
+ expect(entry).not_to be_valid
17
17
  end
18
18
  end
19
19
 
@@ -21,13 +21,13 @@ module Plutus
21
21
  before {
22
22
  entry.debit_amounts << FactoryGirl.build(:debit_amount, entry: entry)
23
23
  }
24
- it { should_not be_valid }
24
+ it { is_expected.not_to be_valid }
25
25
 
26
26
  context "with an invalid credit" do
27
27
  before {
28
28
  entry.credit_amounts << FactoryGirl.build(:credit_amount, entry: entry, amount: nil)
29
29
  }
30
- it { should_not be_valid }
30
+ it { is_expected.not_to be_valid }
31
31
  end
32
32
  end
33
33
 
@@ -35,29 +35,47 @@ module Plutus
35
35
  before {
36
36
  entry.credit_amounts << FactoryGirl.build(:credit_amount, entry: entry)
37
37
  }
38
- it { should_not be_valid }
38
+ it { is_expected.not_to be_valid }
39
39
 
40
40
  context "with an invalid debit" do
41
41
  before {
42
42
  entry.debit_amounts << FactoryGirl.build(:debit_amount, entry: entry, amount: nil)
43
43
  }
44
- it { should_not be_valid }
44
+ it { is_expected.not_to be_valid }
45
+ end
46
+ end
47
+
48
+ context "without a date" do
49
+ let(:entry) { FactoryGirl.build(:entry_with_credit_and_debit, date: nil) }
50
+
51
+ context "should assign a default date before being saved" do
52
+ before { entry.save! }
53
+ its(:date) { is_expected.to eq(Time.now.to_date) }
45
54
  end
46
55
  end
47
56
 
48
57
  it "should require the debit and credit amounts to cancel" do
49
58
  entry.credit_amounts << FactoryGirl.build(:credit_amount, :amount => 100, :entry => entry)
50
59
  entry.debit_amounts << FactoryGirl.build(:debit_amount, :amount => 200, :entry => entry)
51
- entry.should_not be_valid
52
- entry.errors['base'].should == ["The credit and debit amounts are not equal"]
60
+ expect(entry).not_to be_valid
61
+ expect(entry.errors['base']).to eq(["The credit and debit amounts are not equal"])
53
62
  end
54
63
 
55
64
  it "should require the debit and credit amounts to cancel even with fractions" do
56
65
  entry = FactoryGirl.build(:entry)
57
66
  entry.credit_amounts << FactoryGirl.build(:credit_amount, :amount => 100.1, :entry => entry)
58
67
  entry.debit_amounts << FactoryGirl.build(:debit_amount, :amount => 100.2, :entry => entry)
59
- entry.should_not be_valid
60
- entry.errors['base'].should == ["The credit and debit amounts are not equal"]
68
+ expect(entry).not_to be_valid
69
+ expect(entry.errors['base']).to eq(["The credit and debit amounts are not equal"])
70
+ end
71
+
72
+ it "should ignore debit and credit amounts marked for destruction to cancel" do
73
+ entry.credit_amounts << FactoryGirl.build(:credit_amount, :amount => 100, :entry => entry)
74
+ debit_amount = FactoryGirl.build(:debit_amount, :amount => 100, :entry => entry)
75
+ debit_amount.mark_for_destruction
76
+ entry.debit_amounts << debit_amount
77
+ expect(entry).not_to be_valid
78
+ expect(entry.errors['base']).to eq(["The credit and debit amounts are not equal"])
61
79
  end
62
80
 
63
81
  it "should have a polymorphic commercial document associations" do
@@ -65,26 +83,87 @@ module Plutus
65
83
  entry = FactoryGirl.build(:entry_with_credit_and_debit, commercial_document: mock_document)
66
84
  entry.save!
67
85
  saved_entry = Entry.find(entry.id)
68
- saved_entry.commercial_document.should == mock_document
86
+ expect(saved_entry.commercial_document).to eq(mock_document)
69
87
  end
70
88
 
71
- it "should allow building an entry and credit and debits with a hash" do
72
- FactoryGirl.create(:asset, name: "Accounts Receivable")
73
- FactoryGirl.create(:revenue, name: "Sales Revenue")
74
- FactoryGirl.create(:liability, name: "Sales Tax Payable")
75
- mock_document = FactoryGirl.create(:asset)
76
- entry = Entry.build(
77
- description: "Sold some widgets",
78
- commercial_document: mock_document,
79
- debits: [
80
- {account: "Accounts Receivable", amount: 50}],
81
- credits: [
82
- {account: "Sales Revenue", amount: 45},
83
- {account: "Sales Tax Payable", amount: 5}])
84
- entry.save!
89
+ context "given a set of accounts" do
90
+ let(:mock_document) { FactoryGirl.create(:asset) }
91
+ let!(:accounts_receivable) { FactoryGirl.create(:asset, name: "Accounts Receivable") }
92
+ let!(:sales_revenue) { FactoryGirl.create(:revenue, name: "Sales Revenue") }
93
+ let!(:sales_tax_payable) { FactoryGirl.create(:liability, name: "Sales Tax Payable") }
85
94
 
86
- saved_entry = Entry.find(entry.id)
87
- saved_entry.commercial_document.should == mock_document
95
+ shared_examples_for 'a built-from-hash Plutus::Entry' do
96
+ its(:credit_amounts) { is_expected.not_to be_empty }
97
+ its(:debit_amounts) { is_expected.not_to be_empty }
98
+ it { is_expected.to be_valid }
99
+
100
+ context "when saved" do
101
+ before { entry.save! }
102
+ its(:id) { is_expected.not_to be_nil }
103
+
104
+ context "when reloaded" do
105
+ let(:saved_transaction) { Entry.find(entry.id) }
106
+ subject { saved_transaction }
107
+ it("should have the correct commercial document") {
108
+ saved_transaction.commercial_document == mock_document
109
+ }
110
+ end
111
+ end
112
+ end
113
+
114
+ describe ".new" do
115
+ let(:entry) { Entry.new(hash) }
116
+ subject { entry }
117
+
118
+ context "when given a credit/debits hash with :account => Account" do
119
+ let(:hash) {
120
+ {
121
+ description: "Sold some widgets",
122
+ commercial_document: mock_document,
123
+ debits: [{account: accounts_receivable, amount: 50}],
124
+ credits: [
125
+ {account: sales_revenue, amount: 45},
126
+ {account: sales_tax_payable, amount: 5}
127
+ ]
128
+ }
129
+ }
130
+ include_examples 'a built-from-hash Plutus::Entry'
131
+ end
132
+
133
+ context "when given a credit/debits hash with :account_name => String" do
134
+ let(:hash) {
135
+ {
136
+ description: "Sold some widgets",
137
+ commercial_document: mock_document,
138
+ debits: [{account_name: accounts_receivable.name, amount: 50}],
139
+ credits: [
140
+ {account_name: sales_revenue.name, amount: 45},
141
+ {account_name: sales_tax_payable.name, amount: 5}
142
+ ]
143
+ }
144
+ }
145
+ include_examples 'a built-from-hash Plutus::Entry'
146
+ end
147
+ end
148
+
149
+ describe ".build" do
150
+ let(:entry) { Entry.build(hash) }
151
+ subject { entry }
152
+
153
+ before { ::ActiveSupport::Deprecation.silenced = true }
154
+ after { ::ActiveSupport::Deprecation.silenced = false }
155
+
156
+ context "when used at all" do
157
+ let(:hash) { Hash.new }
158
+
159
+ it("should be deprecated") {
160
+ # .build is the only thing deprecated
161
+ expect(::ActiveSupport::Deprecation).to receive(:warn).once
162
+ entry
163
+ }
164
+ end
165
+
166
+ end
88
167
  end
89
168
 
90
169
  end
@@ -3,12 +3,27 @@ require 'spec_helper'
3
3
  module Plutus
4
4
  describe Account do
5
5
  describe 'tenancy support' do
6
+
7
+ before(:all) do
8
+ m = ActiveRecord::Migration.new
9
+ m.verbose = false
10
+ m.create_table :plutus_tenants do |t|
11
+ t.string :name
12
+ end
13
+ end
14
+
15
+ after :all do
16
+ m = ActiveRecord::Migration.new
17
+ m.verbose = false
18
+ m.drop_table :plutus_tenants
19
+ end
20
+
6
21
  before(:each) do
7
22
  ActiveSupportHelpers.clear_model('Account')
8
23
  ActiveSupportHelpers.clear_model('Asset')
9
24
 
10
25
  Plutus.enable_tenancy = true
11
- Plutus.tenant_class = 'Plutus::Entry'
26
+ Plutus.tenant_class = 'Plutus::Tenant'
12
27
 
13
28
  FactoryGirlHelpers.reload()
14
29
  Plutus::Asset.new
@@ -27,18 +42,21 @@ module Plutus
27
42
  end
28
43
 
29
44
  it 'validate uniqueness of name scoped to tenant' do
30
- account = FactoryGirl.create(:asset, tenant_id: 10)
45
+ tenant = FactoryGirl.create(:tenant)
46
+ account = FactoryGirl.create(:asset, tenant: tenant)
31
47
 
32
- record = FactoryGirl.build(:asset, name: account.name, tenant_id: 10)
33
- record.should_not be_valid
34
- record.errors[:name].should == ['has already been taken']
48
+ record = FactoryGirl.build(:asset, name: account.name, tenant: tenant)
49
+ expect(record).not_to be_valid
50
+ expect(record.errors[:name]).to eq(['has already been taken'])
35
51
  end
36
52
 
37
53
  it 'allows same name scoped under a different tenant' do
38
- account = FactoryGirl.create(:asset, tenant_id: 10)
54
+ tenant_1 = FactoryGirl.create(:tenant)
55
+ tenant_2 = FactoryGirl.create(:tenant)
56
+ account = FactoryGirl.create(:asset, tenant: tenant_1)
39
57
 
40
- record = FactoryGirl.build(:asset, name: account.name, tenant_id: 11)
41
- record.should be_valid
58
+ record = FactoryGirl.build(:asset, name: account.name, tenant: tenant_2)
59
+ expect(record).to be_valid
42
60
  end
43
61
  end
44
62
  end
@@ -2,31 +2,12 @@ require 'spec_helper'
2
2
 
3
3
  module Plutus
4
4
  describe AccountsController do
5
- # Run these tests if you enable routing in your rails app. See README
6
- #describe "routing" do
7
- #it "recognizes and generates #index" do
8
- #{ :get => "/accounts" }.should route_to(:controller => "accounts", :action => "index")
9
- #end
5
+ routes { Plutus::Engine.routes }
10
6
 
11
- #it "recognizes and generates #show" do
12
- #{ :get => "/accounts/1" }.should route_to(:controller => "accounts", :action => "show", :id => "1")
13
- #end
14
-
15
- #it "recognizes and generates #edit" do
16
- #{ :get => "/accounts/1/edit" }.should_not be_routable
17
- #end
18
-
19
- #it "recognizes and generates #create" do
20
- #{ :post => "/accounts" }.should_not be_routable
21
- #end
22
-
23
- #it "recognizes and generates #update" do
24
- #{ :put => "/accounts/1" }.should_not be_routable
25
- #end
26
-
27
- #it "recognizes and generates #destroy" do
28
- #{ :delete => "/accounts/1" }.should_not be_routable
29
- #end
30
- #end
7
+ describe "routing" do
8
+ it "recognizes and generates #index" do
9
+ expect(:get => "/accounts").to route_to(:controller => "plutus/accounts", :action => "index")
10
+ end
11
+ end
31
12
  end
32
13
  end
@@ -2,31 +2,12 @@ require 'spec_helper'
2
2
 
3
3
  module Plutus
4
4
  describe EntriesController do
5
- # Run these tests if you enable routing in your rails app. See README
6
- #describe "routing" do
7
- #it "recognizes and generates #index" do
8
- #{ :get => "/entries" }.should route_to(:controller => "entries", :action => "index")
9
- #end
5
+ routes { Plutus::Engine.routes }
10
6
 
11
- #it "recognizes and generates #show" do
12
- #{ :get => "/entries/1" }.should route_to(:controller => "entries", :action => "show", :id => "1")
13
- #end
14
-
15
- #it "recognizes and generates #edit" do
16
- #{ :get => "/entries/1/edit" }.should_not be_routable
17
- #end
18
-
19
- #it "recognizes and generates #create" do
20
- #{ :post => "/entries" }.should_not be_routable
21
- #end
22
-
23
- #it "recognizes and generates #update" do
24
- #{ :put => "/entries/1" }.should_not be_routable
25
- #end
26
-
27
- #it "recognizes and generates #destroy" do
28
- #{ :delete => "/entries/1" }.should_not be_routable
29
- #end
30
- #end
7
+ describe "routing" do
8
+ it "recognizes and generates #index" do
9
+ expect(:get => "/entries").to route_to(:controller => "plutus/entries", :action => "index")
10
+ end
11
+ end
31
12
  end
32
13
  end