double_double 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 (40) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +10 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +38 -0
  7. data/Rakefile +10 -0
  8. data/double_double.gemspec +27 -0
  9. data/lib/double_double/account.rb +85 -0
  10. data/lib/double_double/amount.rb +33 -0
  11. data/lib/double_double/asset.rb +25 -0
  12. data/lib/double_double/credit_amount.rb +9 -0
  13. data/lib/double_double/debit_amount.rb +9 -0
  14. data/lib/double_double/equity.rb +26 -0
  15. data/lib/double_double/expense.rb +26 -0
  16. data/lib/double_double/liability.rb +25 -0
  17. data/lib/double_double/revenue.rb +26 -0
  18. data/lib/double_double/transaction.rb +88 -0
  19. data/lib/double_double/transaction_type.rb +13 -0
  20. data/lib/double_double/version.rb +3 -0
  21. data/lib/double_double.rb +21 -0
  22. data/spec/factories/account_factory.rb +27 -0
  23. data/spec/factories/amount_factory.rb +10 -0
  24. data/spec/factories/transaction_factory.rb +9 -0
  25. data/spec/factories/transaction_type_factory.rb +14 -0
  26. data/spec/models/account_spec.rb +70 -0
  27. data/spec/models/amount_spec.rb +9 -0
  28. data/spec/models/asset_spec.rb +12 -0
  29. data/spec/models/credit_amount_spec.rb +46 -0
  30. data/spec/models/debit_amount_spec.rb +46 -0
  31. data/spec/models/equity_spec.rb +12 -0
  32. data/spec/models/expense_spec.rb +12 -0
  33. data/spec/models/liability_spec.rb +12 -0
  34. data/spec/models/revenue_spec.rb +12 -0
  35. data/spec/models/transaction_spec.rb +101 -0
  36. data/spec/spec_helper.rb +87 -0
  37. data/spec/support/all_account_types.rb +50 -0
  38. data/spec/support/left_side_account_types.rb +125 -0
  39. data/spec/support/right_side_account_types.rb +125 -0
  40. metadata +216 -0
@@ -0,0 +1,13 @@
1
+ module DoubleDouble
2
+
3
+ class TransactionType < ActiveRecord::Base
4
+ self.table_name = 'double_double_transaction_types'
5
+
6
+ has_many :transactions
7
+ attr_accessible :number, :description
8
+
9
+ validates_numericality_of :number, greater_than: 0
10
+ validates_length_of :description, minimum: 6
11
+ end
12
+
13
+ end
@@ -0,0 +1,3 @@
1
+ module DoubleDouble
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,21 @@
1
+ require 'active_record'
2
+ require 'money'
3
+
4
+ require 'double_double/version'
5
+
6
+ # Accounts
7
+ require 'double_double/account'
8
+ require 'double_double/asset'
9
+ require 'double_double/equity'
10
+ require 'double_double/expense'
11
+ require 'double_double/liability'
12
+ require 'double_double/revenue'
13
+
14
+ # Amounts
15
+ require 'double_double/amount'
16
+ require 'double_double/credit_amount'
17
+ require 'double_double/debit_amount'
18
+
19
+ # Transactions
20
+ require 'double_double/transaction'
21
+ require 'double_double/transaction_type'
@@ -0,0 +1,27 @@
1
+ FactoryGirl.define do
2
+ factory :account, :class => DoubleDouble::Account do |account|
3
+ account.name { FactoryGirl.generate(:account_name) }
4
+ account.number { FactoryGirl.generate(:account_number)}
5
+ account.contra false
6
+
7
+ factory :asset, :class => DoubleDouble::Asset
8
+ factory :equity, :class => DoubleDouble::Equity
9
+ factory :expense, :class => DoubleDouble::Expense
10
+ factory :liability, :class => DoubleDouble::Liability
11
+ factory :revenue, :class => DoubleDouble::Revenue
12
+
13
+ factory :not_asset, :class => DoubleDouble::Liability
14
+ factory :not_equity, :class => DoubleDouble::Asset
15
+ factory :not_expense, :class => DoubleDouble::Liability
16
+ factory :not_liability, :class => DoubleDouble::Asset
17
+ factory :not_revenue, :class => DoubleDouble::Asset
18
+ end
19
+
20
+ sequence :account_name do |n|
21
+ "account name #{n}"
22
+ end
23
+
24
+ sequence :account_number do |n|
25
+ 8000 + n
26
+ end
27
+ end
@@ -0,0 +1,10 @@
1
+ FactoryGirl.define do
2
+ factory :amount, :class => DoubleDouble::Amount do |amount|
3
+ end
4
+
5
+ factory :credit_amt, :class => DoubleDouble::CreditAmount do
6
+ end
7
+
8
+ factory :debit_amt, :class => DoubleDouble::DebitAmount do
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ FactoryGirl.define do
2
+ factory :transaction, :class => DoubleDouble::Transaction do
3
+ description { FactoryGirl.generate(:transaction_type_description) }
4
+ end
5
+
6
+ sequence :transaction_description do |n|
7
+ "transaction description #{n}"
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ FactoryGirl.define do
2
+ factory :transaction_type, :class => DoubleDouble::TransactionType do |type|
3
+ type.description { FactoryGirl.generate(:transaction_type_description) }
4
+ type.number { FactoryGirl.generate(:transaction_type_number) }
5
+ end
6
+
7
+ sequence :transaction_type_description do |n|
8
+ "transaction type description #{n}"
9
+ end
10
+
11
+ sequence :transaction_type_number do |n|
12
+ 9000 + n
13
+ end
14
+ end
@@ -0,0 +1,70 @@
1
+ module DoubleDouble
2
+ describe Account do
3
+
4
+ it "should not allow creating an account without a subtype" do
5
+ account = FactoryGirl.build(:account)
6
+ account.should_not be_valid
7
+ end
8
+
9
+ it "should be unique per name" do
10
+ FactoryGirl.create(:asset, :name => "Test1")
11
+ account = FactoryGirl.build(:asset, :name => "Test1")
12
+ account.should_not be_valid
13
+ account.errors[:name].should == ["has already been taken"]
14
+ end
15
+
16
+ it "should be unique per number" do
17
+ FactoryGirl.create(:asset, :number => 88)
18
+ account = FactoryGirl.build(:asset, :number => 88)
19
+ account.should_not be_valid
20
+ account.errors[:number].should == ["has already been taken"]
21
+ end
22
+
23
+ it "should not have a balance method" do
24
+ lambda{Account.balance}.should raise_error(NoMethodError)
25
+ end
26
+
27
+ it "should have a trial balance" do
28
+ Account.should respond_to(:trial_balance)
29
+ Account.trial_balance.should be_kind_of(Money)
30
+ end
31
+
32
+ it "should report a trial balance of 0 with correct transactions (with a contrived example of transactions)" do
33
+ # credit accounts
34
+ liability = FactoryGirl.create(:liability)
35
+ equity = FactoryGirl.create(:equity)
36
+ revenue = FactoryGirl.create(:revenue)
37
+ contra_asset = FactoryGirl.create(:asset, :contra => true)
38
+ contra_expense = FactoryGirl.create(:expense, :contra => true)
39
+ # debit accounts
40
+ asset = FactoryGirl.create(:asset)
41
+ expense = FactoryGirl.create(:expense)
42
+ contra_liability = FactoryGirl.create(:liability, :contra => true)
43
+ contra_equity = FactoryGirl.create(:equity, :contra => true)
44
+ contra_revenue = FactoryGirl.create(:revenue, :contra => true)
45
+
46
+ t = FactoryGirl.build(:transaction)
47
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: 100_000, account: liability)
48
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: 100_000, account: asset)
49
+ t.save
50
+ t = FactoryGirl.build(:transaction)
51
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: 1_000, account: equity)
52
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: 1_000, account: expense)
53
+ t.save
54
+ t = FactoryGirl.build(:transaction)
55
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: 40_404, account: revenue)
56
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: 40_404, account: contra_liability)
57
+ t.save
58
+ t = FactoryGirl.build(:transaction)
59
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: 2, account: contra_asset)
60
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: 2, account: contra_equity)
61
+ t.save
62
+ t = FactoryGirl.build(:transaction)
63
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: 333, account: contra_expense)
64
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: 333, account: contra_revenue)
65
+ t.save
66
+
67
+ Account.trial_balance.should == 0
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,9 @@
1
+ module DoubleDouble
2
+ describe Amount do
3
+
4
+ it "should not allow creating an amount without a subtype" do
5
+ amount = FactoryGirl.build(:amount)
6
+ amount.should_not be_valid
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module DoubleDouble
2
+ describe Asset do
3
+
4
+ it_behaves_like "all account types" do
5
+ let(:account_type) {:asset}
6
+ end
7
+
8
+ it_behaves_like "a left side account type" do
9
+ let(:left_side_account_type) {:asset}
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,46 @@
1
+ module DoubleDouble
2
+ describe CreditAmount do
3
+
4
+ it "should not be valid without an amount" do
5
+ expect {
6
+ FactoryGirl.build(:credit_amt, amount: nil, transaction: FactoryGirl.build(:transaction))
7
+ }.to raise_error(ArgumentError)
8
+ end
9
+
10
+ it "should not be valid without a transaction" do
11
+ acct = FactoryGirl.create(:asset)
12
+ credit_amount = FactoryGirl.build(:credit_amt, transaction: nil, account: acct, amount: Money.new(20))
13
+ credit_amount.should_not be_valid
14
+ end
15
+
16
+ it "should not be valid without an account" do
17
+ t = FactoryGirl.build(:transaction)
18
+ credit_amount = FactoryGirl.build(:credit_amt, account: nil, transaction: t, amount: Money.new(20))
19
+ credit_amount.should_not be_valid
20
+ end
21
+
22
+ it "should be sensitive to project_id when calculating balances, if supplied" do
23
+ acct_1 = FactoryGirl.create(:asset)
24
+ other_acct = FactoryGirl.create(:not_asset)
25
+ t = FactoryGirl.build(:transaction)
26
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(123), account: acct_1, project_id: 77)
27
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(123), account: other_acct)
28
+ t.save
29
+ t = FactoryGirl.build(:transaction)
30
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(321), account: acct_1, project_id: 77)
31
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(321), account: other_acct)
32
+ t.save
33
+ t = FactoryGirl.build(:transaction)
34
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(275), account: acct_1, project_id: 82)
35
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(275), account: other_acct)
36
+ t.save
37
+ t = FactoryGirl.build(:transaction)
38
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(999), account: acct_1)
39
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(999), account: other_acct)
40
+ t.save
41
+ acct_1.credits_balance({project_id: 77}).should == Money.new(123 + 321)
42
+ acct_1.credits_balance({project_id: 82}).should == Money.new(275)
43
+ acct_1.credits_balance.should == Money.new(123 + 321 + 275 + 999)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,46 @@
1
+ module DoubleDouble
2
+ describe DebitAmount do
3
+
4
+ it "should not be valid without an amount" do
5
+ expect {
6
+ FactoryGirl.build(:debit_amt, amount: nil, transaction: FactoryGirl.build(:transaction))
7
+ }.to raise_error(ArgumentError)
8
+ end
9
+
10
+ it "should not be valid without a transaction" do
11
+ acct = FactoryGirl.create(:asset)
12
+ debit_amount = FactoryGirl.build(:debit_amt, transaction: nil, account: acct, amount: Money.new(20))
13
+ debit_amount.should_not be_valid
14
+ end
15
+
16
+ it "should not be valid without an account" do
17
+ t = FactoryGirl.build(:transaction)
18
+ debit_amount = FactoryGirl.build(:debit_amt, account: nil, transaction: t, amount: Money.new(20))
19
+ debit_amount.should_not be_valid
20
+ end
21
+
22
+ it "should be sensitive to project_id when calculating balances, if supplied" do
23
+ acct_1 = FactoryGirl.create(:asset)
24
+ other_acct = FactoryGirl.create(:not_asset)
25
+ t = FactoryGirl.build(:transaction)
26
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(123), account: acct_1, project_id: 77)
27
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(123), account: other_acct)
28
+ t.save
29
+ t = FactoryGirl.build(:transaction)
30
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(321), account: acct_1, project_id: 77)
31
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(321), account: other_acct)
32
+ t.save
33
+ t = FactoryGirl.build(:transaction)
34
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(275), account: acct_1, project_id: 82)
35
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(275), account: other_acct)
36
+ t.save
37
+ t = FactoryGirl.build(:transaction)
38
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(999), account: acct_1)
39
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(999), account: other_acct)
40
+ t.save
41
+ acct_1.debits_balance({project_id: 77}).should == Money.new(123 + 321)
42
+ acct_1.debits_balance({project_id: 82}).should == Money.new(275)
43
+ acct_1.debits_balance.should == Money.new(123 + 321 + 275 + 999)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,12 @@
1
+ module DoubleDouble
2
+ describe Equity do
3
+
4
+ it_behaves_like "all account types" do
5
+ let(:account_type) {:equity}
6
+ end
7
+
8
+ it_behaves_like "a right side account type" do
9
+ let(:right_side_account_type) {:equity}
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module DoubleDouble
2
+ describe Expense do
3
+
4
+ it_behaves_like "all account types" do
5
+ let(:account_type) {:expense}
6
+ end
7
+
8
+ it_behaves_like "a left side account type" do
9
+ let(:left_side_account_type) {:expense}
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module DoubleDouble
2
+ describe Liability do
3
+
4
+ it_behaves_like "all account types" do
5
+ let(:account_type) {:liability}
6
+ end
7
+
8
+ it_behaves_like "a right side account type" do
9
+ let(:right_side_account_type) {:liability}
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module DoubleDouble
2
+ describe Revenue do
3
+
4
+ it_behaves_like "all account types" do
5
+ let(:account_type) {:revenue}
6
+ end
7
+
8
+ it_behaves_like "a right side account type" do
9
+ let(:right_side_account_type) {:revenue}
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,101 @@
1
+ module DoubleDouble
2
+ describe Transaction do
3
+
4
+ before(:each) do
5
+ @acct = FactoryGirl.create(:asset)
6
+ @other_acct = FactoryGirl.create(:not_asset)
7
+ end
8
+
9
+ it "should create a transaction" do
10
+ -> {
11
+ t = FactoryGirl.build(:transaction)
12
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(123), account: @acct)
13
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(123), account: @other_acct)
14
+ t.save!
15
+ }.should change(DoubleDouble::Transaction, :count).by(1)
16
+ end
17
+
18
+ it "should not be valid without a credit amount" do
19
+ t = FactoryGirl.build(:transaction)
20
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(123), account: @other_acct)
21
+ t.should_not be_valid
22
+ t.errors['base'].should include("Transaction must have at least one credit amount")
23
+ end
24
+
25
+ it "should not be valid with an invalid credit amount" do
26
+ -> {
27
+ t = FactoryGirl.build(:transaction)
28
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: nil, account: @acct)
29
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(123), account: @other_acct)
30
+ t.save
31
+ }.should raise_error(ArgumentError)
32
+ end
33
+
34
+ it "should not be valid without a debit amount" do
35
+ t = FactoryGirl.build(:transaction)
36
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(123), account: @acct)
37
+ t.should_not be_valid
38
+ t.errors['base'].should include("Transaction must have at least one debit amount")
39
+ end
40
+
41
+ it "should not be valid with an invalid debit amount" do
42
+ -> {
43
+ t = FactoryGirl.build(:transaction)
44
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(123), account: @acct)
45
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: nil, account: @other_acct)
46
+ t.save
47
+ }.should raise_error(ArgumentError)
48
+ end
49
+
50
+ it "should not be valid without a description" do
51
+ t = FactoryGirl.build(:transaction, description: nil)
52
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(123), account: @acct)
53
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(123), account: @other_acct)
54
+ t.save
55
+ t.should_not be_valid
56
+ t.errors[:description].should == ["can't be blank"]
57
+ end
58
+
59
+ it "should require the debit and credit amounts to cancel" do
60
+ t = FactoryGirl.build(:transaction)
61
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(555), account: @acct)
62
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(666), account: @other_acct)
63
+ t.save
64
+ t.should_not be_valid
65
+ t.errors['base'].should == ["The credit and debit amounts are not equal"]
66
+ end
67
+
68
+ it "should have a polymorphic commercial document associations" do
69
+ mock_document = FactoryGirl.create(:asset) # one would never do this, but it allows us to not require a migration for the test
70
+
71
+ t = FactoryGirl.build(:transaction)
72
+ t.credit_amounts << FactoryGirl.create(:credit_amt, transaction: t, amount: Money.new(123), account: @acct)
73
+ t.debit_amounts << FactoryGirl.create(:debit_amt, transaction: t, amount: Money.new(123), account: @other_acct)
74
+ t.commercial_document = mock_document
75
+ t.save
76
+
77
+ saved_transaction = Transaction.find(t.id)
78
+ saved_transaction.commercial_document.should == mock_document
79
+ end
80
+
81
+ it "should allow building a transaction and credit and debits with a hash" do
82
+ FactoryGirl.create(:asset, :name => "Accounts Receivable")
83
+ FactoryGirl.create(:revenue, :name => "Sales Revenue")
84
+ FactoryGirl.create(:liability, :name => "Sales Tax Payable")
85
+ mock_document = FactoryGirl.create(:asset)
86
+ transaction = Transaction.build(
87
+ description: "Sold some widgets",
88
+ commercial_document: mock_document,
89
+ debits: [
90
+ {account: "Accounts Receivable", amount: 50}],
91
+ credits: [
92
+ {account: "Sales Revenue", amount: 45},
93
+ {account: "Sales Tax Payable", amount: 5}])
94
+ transaction.should be_valid
95
+ transaction.save
96
+ saved_transaction = DoubleDouble::Transaction.find(transaction.id)
97
+ saved_transaction.commercial_document.should == mock_document
98
+ end
99
+
100
+ end
101
+ end