plutus 0.10.1 → 0.15

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