double_double 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .DS_Store
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format progress
3
+ -r ./spec/spec_helper.rb
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby-19mode
6
+ - rbx-19mode
7
+ - ruby-head
8
+ env:
9
+ - JRUBY_OPTS="--1.9"
10
+ script: rake
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in double_double.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Mike Herrera
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # double_double
2
+ [![Build Status](https://travis-ci.org/crftr/double_double.png)](https://travis-ci.org/crftr/double_double)
3
+
4
+ A double-entry accounting system. Likely to change heavily in the coming days.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'double_double'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install double_double
19
+
20
+ ## Usage
21
+
22
+ TODO: Write usage instructions here
23
+
24
+ ## Name
25
+
26
+ The double double is the flagship In-N-Out cheeseburger. Ask a southern-Californian.
27
+
28
+ ## Contributing
29
+
30
+ 1. Fork it
31
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
32
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
33
+ 4. Push to the branch (`git push origin my-new-feature`)
34
+ 5. Create new Pull Request
35
+
36
+ ## Notes
37
+
38
+ double_double was heavily influenced by mbulat's plutus project and regularly working with quickbooks.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'run specs'
5
+ RSpec::Core::RakeTask.new do |t|
6
+ t.rspec_opts = ["--color", "--format progress", "-r ./spec/spec_helper.rb"]
7
+ t.pattern = 'spec/**/*_spec.rb'
8
+ end
9
+
10
+ task default: :spec
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'double_double/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'double_double'
8
+ gem.version = DoubleDouble::VERSION
9
+ gem.authors = ['Mike Herrera']
10
+ gem.email = ['git@devoplabs.com']
11
+ gem.description = %q{A double-entry accural accounting system}
12
+ gem.summary = %q{A double-entry accural accounting system. All currency amounts are stored using the Money gem.}
13
+ gem.homepage = 'https://github.com/crftr/double_double'
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features|account_types)/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_dependency('money', '~> 5.1')
21
+ gem.add_dependency 'activerecord', '~> 3.2.11'
22
+ gem.add_development_dependency('sqlite3')
23
+ gem.add_development_dependency('rspec', '~> 2.12')
24
+ gem.add_development_dependency('factory_girl')
25
+ gem.add_development_dependency('database_cleaner')
26
+ gem.add_development_dependency('pry')
27
+ end
@@ -0,0 +1,85 @@
1
+ module DoubleDouble
2
+ # The Account class represents accounts in the system. Each account must be subclassed as one of the following types:
3
+ #
4
+ # TYPE | NORMAL BALANCE | DESCRIPTION
5
+ # --------------------------------------------------------------------------
6
+ # Asset | Debit | Resources owned by the Business Entity
7
+ # Liability | Credit | Debts owed to outsiders
8
+ # Equity | Credit | Owners rights to the Assets
9
+ # Revenue | Credit | Increases in owners equity
10
+ # Expense | Debit | Assets or services consumed in the generation of revenue
11
+ #
12
+ # Each account can also be marked as a "Contra Account". A contra account will have it's
13
+ # normal balance swapped. For example, to remove equity, a "Drawing" account may be created
14
+ # as a contra equity account as follows:
15
+ #
16
+ # DoubleDouble::Equity.create(:name => "Drawing", contra => true)
17
+ #
18
+ # At all times the balance of all accounts should conform to the "accounting equation"
19
+ # DoubleDouble::Assets = Liabilties + Owner's Equity
20
+ #
21
+ # Each sublclass account acts as it's own ledger. See the individual subclasses for a
22
+ # description.
23
+ #
24
+ # @abstract
25
+ # An account must be a subclass to be saved to the database. The Account class
26
+ # has a singleton method {trial_balance} to calculate the balance on all Accounts.
27
+ #
28
+ # @see http://en.wikipedia.org/wiki/Accounting_equation Accounting Equation
29
+ # @see http://en.wikipedia.org/wiki/Debits_and_credits Debits, Credits, and Contra Accounts
30
+ #
31
+ class Account < ActiveRecord::Base
32
+ self.table_name = 'double_double_accounts'
33
+
34
+ attr_accessible :name, :contra, :number
35
+
36
+ has_many :credit_amounts
37
+ has_many :debit_amounts
38
+ has_many :credit_transactions, :through => :credit_amounts, :source => :transaction
39
+ has_many :debit_transactions, :through => :debit_amounts, :source => :transaction
40
+
41
+ validates_presence_of :type, :name, :number
42
+ validates_uniqueness_of :name, :number
43
+
44
+ def side_balance(is_debit, hash)
45
+ a = is_debit ? DoubleDouble::DebitAmount.scoped : DoubleDouble::CreditAmount.scoped
46
+ a = a.where(account_id: self.id)
47
+ a = a.by_project_id( hash[:project_id] ) if hash.has_key?(:project_id)
48
+ a = a.by_approving_user_id( hash[:approving_user_id] ) if hash.has_key?(:approving_user_id)
49
+ a = a.by_targeted_user_id( hash[:targeted_user_id] ) if hash.has_key?(:targeted_user_id)
50
+ Money.new(a.sum(:amount_cents))
51
+ end
52
+
53
+ def credits_balance(hash = {})
54
+ side_balance(false, hash)
55
+ end
56
+
57
+ def debits_balance(hash = {})
58
+ side_balance(true, hash)
59
+ end
60
+
61
+ # The trial balance of all accounts in the system. This should always equal zero,
62
+ # otherwise there is an error in the system.
63
+ #
64
+ # @return [Money] The value balance of all accounts
65
+ def self.trial_balance
66
+ raise(NoMethodError, "undefined method 'trial_balance'") unless self.new.class == DoubleDouble::Account
67
+ Asset.balance - (Liability.balance + Equity.balance + Revenue.balance - Expense.balance)
68
+ end
69
+
70
+ def self.balance
71
+ raise(NoMethodError, "undefined method 'balance'") if self.new.class == DoubleDouble::Account
72
+ accounts_balance = Money.new(0)
73
+ accounts = self.all
74
+ accounts.each do |acct|
75
+ if acct.contra
76
+ accounts_balance -= acct.balance
77
+ else
78
+ accounts_balance += acct.balance
79
+ end
80
+ end
81
+ accounts_balance
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,33 @@
1
+ module DoubleDouble
2
+ # The Amount class represents debit and credit amounts in the system.
3
+ #
4
+ # @abstract
5
+ # An amount must be a subclass as either a debit or a credit to be saved to the database.
6
+ #
7
+ class Amount < ActiveRecord::Base
8
+ self.table_name = 'double_double_amounts'
9
+
10
+ attr_accessible :account, :amount, :transaction, :project_id, :approving_user_id, :targeted_user_id
11
+
12
+ belongs_to :transaction
13
+ belongs_to :account
14
+ belongs_to :project
15
+ belongs_to :approving_user, :class_name => "User"
16
+ belongs_to :targeted_user, :class_name => "User"
17
+
18
+ scope :by_project_id, lambda{|p_id| where(:project_id => p_id)}
19
+ scope :by_approving_user_id, lambda{|au_id| where(:approving_user_id => au_id)}
20
+ scope :by_targeted_user_id, lambda{|tu_id| where(:targeted_user_id => tu_id)}
21
+
22
+ scope :by_transaction_type_number, lambda{|tt_num| where( transaction: {transaction_type: {number: tt_num}})}
23
+
24
+ validates_presence_of :type, :amount_cents, :transaction, :account
25
+
26
+ composed_of :amount,
27
+ :class_name => "Money",
28
+ :mapping => [%w(amount_cents cents), %w(currency currency_as_string)],
29
+ :constructor => Proc.new { |cents, currency| Money.new(cents || 0, currency || Money.default_currency) },
30
+ :converter => Proc.new { |value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError, "Can't convert #{value.class} to Money") }
31
+
32
+ end
33
+ end
@@ -0,0 +1,25 @@
1
+ module DoubleDouble
2
+ # The Asset class is an account type used to represents resources owned by the business entity.
3
+ #
4
+ # === Normal Balance
5
+ # The normal balance on Asset accounts is a *Debit*.
6
+ #
7
+ # @see http://en.wikipedia.org/wiki/Asset Assets
8
+ #
9
+ class Asset < Account
10
+
11
+ # The balance of the account.
12
+ #
13
+ # Assets have normal debit balances, so the credits are subtracted from the debits
14
+ # unless this is a contra account, in which debits are subtracted from credits
15
+ #
16
+ # @return [Money] The value balance
17
+ def balance(hash = {})
18
+ if contra
19
+ credits_balance(hash) - debits_balance(hash)
20
+ else
21
+ debits_balance(hash) - credits_balance(hash)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ module DoubleDouble
2
+ # The CreditAmount class represents credit entries in the transaction journal.
3
+ #
4
+ # @example
5
+ # credit_amount = DoubleDouble::CreditAmount.new(:account => revenue, :amount => 1000)
6
+ #
7
+ class CreditAmount < Amount
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module DoubleDouble
2
+ # The DebitAmount class represents debit entries in the transaction journal.
3
+ #
4
+ # @example
5
+ # debit_amount = DoubleDouble::DebitAmount.new(:account => cash, :amount => 1000)
6
+ #
7
+ class DebitAmount < Amount
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ module DoubleDouble
2
+ # The Equity class is an account type used to represents owners rights to the assets.
3
+ #
4
+ # === Normal Balance
5
+ # The normal balance on Equity accounts is a *Credit*.
6
+ #
7
+ # @see http://en.wikipedia.org/wiki/Equity_(finance) Equity
8
+ #
9
+ # @author Michael Bulat
10
+ class Equity < Account
11
+
12
+ # The balance of the account.
13
+ #
14
+ # Equity accounts have normal credit balances, so the debits are subtracted from the credits
15
+ # unless this is a contra account, in which credits are subtracted from debits
16
+ #
17
+ # @return [Money] The value balance
18
+ def balance(hash = {})
19
+ if contra
20
+ debits_balance(hash) - credits_balance(hash)
21
+ else
22
+ credits_balance(hash) - debits_balance(hash)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module DoubleDouble
2
+ # The Expense class is an account type used to represents assets or services consumed in the generation of revenue.
3
+ #
4
+ # === Normal Balance
5
+ # The normal balance on Expense accounts is a *Debit*.
6
+ #
7
+ # @see http://en.wikipedia.org/wiki/Expense Expenses
8
+ #
9
+ # @author Michael Bulat
10
+ class Expense < Account
11
+
12
+ # The balance of the account.
13
+ #
14
+ # Expenses have normal debit balances, so the credits are subtracted from the debits
15
+ # unless this is a contra account, in which debits are subtracted from credits
16
+ #
17
+ # @return [Money] The value balance
18
+ def balance(hash = {})
19
+ if contra
20
+ credits_balance(hash) - debits_balance(hash)
21
+ else
22
+ debits_balance(hash) - credits_balance(hash)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,25 @@
1
+ module DoubleDouble
2
+ # The Liability class is an account type used to represents debts owed to outsiders.
3
+ #
4
+ # === Normal Balance
5
+ # The normal balance on Liability accounts is a *Credit*.
6
+ #
7
+ # @see http://en.wikipedia.org/wiki/Liability_(financial_accounting) Liability
8
+ #
9
+ class Liability < Account
10
+
11
+ # The balance of the account.
12
+ #
13
+ # Liability accounts have normal credit balances, so the debits are subtracted from the credits
14
+ # unless this is a contra account, in which credits are subtracted from debits
15
+ #
16
+ # @return [Money] The value balance
17
+ def balance(hash = {})
18
+ if contra
19
+ debits_balance(hash) - credits_balance(hash)
20
+ else
21
+ credits_balance(hash) - debits_balance(hash)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ module DoubleDouble
2
+ # The Revenue class is an account type used to represents increases in owners equity.
3
+ #
4
+ # === Normal Balance
5
+ # The normal balance on Revenue accounts is a *Credit*.
6
+ #
7
+ # @see http://en.wikipedia.org/wiki/Revenue Revenue
8
+ #
9
+ # @author Michael Bulat
10
+ class Revenue < Account
11
+
12
+ # The balance of the account.
13
+ #
14
+ # Revenue accounts have normal credit balances, so the debits are subtracted from the credits
15
+ # unless this is a contra account, in which credits are subtracted from debits
16
+ #
17
+ # @return [Money] The value balance
18
+ def balance(hash = {})
19
+ if contra
20
+ debits_balance(hash) - credits_balance(hash)
21
+ else
22
+ credits_balance(hash) - debits_balance(hash)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,88 @@
1
+ module DoubleDouble
2
+ # Transactions are the recording of debits and credits to various accounts.
3
+ # This table can be thought of as a traditional accounting Journal.
4
+ #
5
+ # Posting to a Ledger can be considered to happen automatically, since
6
+ # Accounts have the reverse 'has_many' relationship to either it's credit or
7
+ # debit transactions
8
+ #
9
+ # @example
10
+ # cash = DoubleDouble::Asset.find_by_name('Cash')
11
+ # accounts_receivable = DoubleDouble::Asset.find_by_name('Accounts Receivable')
12
+ #
13
+ # debit_amount = DoubleDouble::DebitAmount.new(:account => cash, :amount => 1000)
14
+ # credit_amount = DoubleDouble::CreditAmount.new(:account => accounts_receivable, :amount => 1000)
15
+ #
16
+ # transaction = DoubleDouble::Transaction.new(:description => "Receiving payment on an invoice")
17
+ # transaction.debit_amounts << debit_amount
18
+ # transaction.credit_amounts << credit_amount
19
+ # transaction.save
20
+ #
21
+ # @see http://en.wikipedia.org/wiki/Journal_entry Journal Entry
22
+ #
23
+ class Transaction < ActiveRecord::Base
24
+ self.table_name = 'double_double_transactions'
25
+
26
+ attr_accessible :description, :commercial_document
27
+
28
+ belongs_to :transaction_type
29
+ belongs_to :commercial_document, :polymorphic => true
30
+
31
+ has_many :credit_amounts
32
+ has_many :debit_amounts
33
+ has_many :credit_accounts, :through => :credit_amounts, :source => :account
34
+ has_many :debit_accounts, :through => :debit_amounts, :source => :account
35
+
36
+ validates_presence_of :description
37
+ validate :has_credit_amounts?
38
+ validate :has_debit_amounts?
39
+ validate :amounts_cancel?
40
+
41
+ scope :by_transaction_type_number, lambda{|tt_num| where(transaction_type: {number: tt_num})}
42
+
43
+ # Simple API for building a transaction and associated debit and credit amounts
44
+ #
45
+ # @example
46
+ # transaction = DoubleDouble::Transaction.build(
47
+ # description: "Sold some widgets",
48
+ # debits: [
49
+ # {account: "Accounts Receivable", amount: 50}],
50
+ # credits: [
51
+ # {account: "Sales Revenue", amount: 45},
52
+ # {account: "Sales Tax Payable", amount: 5}])
53
+ #
54
+ # @return [DoubleDouble::Transaction] A Transaction with built credit and debit objects ready for saving
55
+ def self.build(hash)
56
+ transaction = Transaction.new(description: hash[:description], commercial_document: hash[:commercial_document])
57
+ hash[:debits].each do |debit|
58
+ a = Account.find_by_name(debit[:account])
59
+ transaction.debit_amounts << DebitAmount.new(account: a, amount: debit[:amount], transaction: transaction, project_id: debit[:project_id], approving_user_id: debit[:approving_user_id], targeted_user_id: debit[:targeted_user_id])
60
+ end
61
+ hash[:credits].each do |credit|
62
+ a = Account.find_by_name(credit[:account])
63
+ transaction.credit_amounts << CreditAmount.new(account: a, amount: credit[:amount], transaction: transaction, project_id: credit[:project_id], approving_user_id: credit[:approving_user_id], targeted_user_id: credit[:targeted_user_id])
64
+ end
65
+ transaction.transaction_type = hash[:transaction_type] if hash.has_key?(:transaction_type)
66
+ transaction
67
+ end
68
+
69
+ private
70
+ def has_credit_amounts?
71
+ errors[:base] << "Transaction must have at least one credit amount" if self.credit_amounts.blank?
72
+ end
73
+
74
+ def has_debit_amounts?
75
+ errors[:base] << "Transaction must have at least one debit amount" if self.debit_amounts.blank?
76
+ end
77
+
78
+ def amounts_cancel?
79
+ errors[:base] << "The credit and debit amounts are not equal" if difference_of_amounts != 0
80
+ end
81
+
82
+ def difference_of_amounts
83
+ credit_amount_total = credit_amounts.inject(Money.new(0)) {|sum, credit_amount| sum + credit_amount.amount}
84
+ debit_amount_total = debit_amounts.inject(Money.new(0)) {|sum, debit_amount| sum + debit_amount.amount}
85
+ credit_amount_total - debit_amount_total
86
+ end
87
+ end
88
+ end