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.
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +38 -0
- data/Rakefile +10 -0
- data/double_double.gemspec +27 -0
- data/lib/double_double/account.rb +85 -0
- data/lib/double_double/amount.rb +33 -0
- data/lib/double_double/asset.rb +25 -0
- data/lib/double_double/credit_amount.rb +9 -0
- data/lib/double_double/debit_amount.rb +9 -0
- data/lib/double_double/equity.rb +26 -0
- data/lib/double_double/expense.rb +26 -0
- data/lib/double_double/liability.rb +25 -0
- data/lib/double_double/revenue.rb +26 -0
- data/lib/double_double/transaction.rb +88 -0
- data/lib/double_double/transaction_type.rb +13 -0
- data/lib/double_double/version.rb +3 -0
- data/lib/double_double.rb +21 -0
- data/spec/factories/account_factory.rb +27 -0
- data/spec/factories/amount_factory.rb +10 -0
- data/spec/factories/transaction_factory.rb +9 -0
- data/spec/factories/transaction_type_factory.rb +14 -0
- data/spec/models/account_spec.rb +70 -0
- data/spec/models/amount_spec.rb +9 -0
- data/spec/models/asset_spec.rb +12 -0
- data/spec/models/credit_amount_spec.rb +46 -0
- data/spec/models/debit_amount_spec.rb +46 -0
- data/spec/models/equity_spec.rb +12 -0
- data/spec/models/expense_spec.rb +12 -0
- data/spec/models/liability_spec.rb +12 -0
- data/spec/models/revenue_spec.rb +12 -0
- data/spec/models/transaction_spec.rb +101 -0
- data/spec/spec_helper.rb +87 -0
- data/spec/support/all_account_types.rb +50 -0
- data/spec/support/left_side_account_types.rb +125 -0
- data/spec/support/right_side_account_types.rb +125 -0
- 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,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,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,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,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
|