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