Giter Site home page Giter Site logo

double_entry's Introduction

DoubleEntry

License MIT Gem Version Build Status Code Climate

Show me the Money

Keep track of all the monies!

DoubleEntry is an accounting system based on the principles of a Double-entry Bookkeeping system. While this gem acts like a double-entry bookkeeping system, as it creates two entries in the database for each transfer, it does not enforce accounting rules.

DoubleEntry uses the Money gem to encapsulate operations on currency values.

Compatibility

DoubleEntry is tested against:

Ruby

  • 2.3.x
  • 2.4.x
  • 2.5.x

Rails

  • 4.2.x
  • 5.0.x
  • 5.1.x
  • 5.2.x

Databases

  • MySQL
  • PostgreSQL
  • SQLite

Installation

In your application's Gemfile, add:

gem 'double_entry'

Download and install the gem with Bundler:

bundle

Generate Rails schema migrations for the required tables:

rails generate double_entry:install

Update the local database:

rake db:migrate

Interface

The entire API for recording financial transactions is available through a few methods in the DoubleEntry module. For full details on what the API provides, please view the documentation on these methods.

A configuration file should be used to define a set of accounts, and potential transfers between those accounts. See the Configuration section for more details.

Accounts

Money is kept in Accounts.

Each Account has a scope, which is used to subdivide the account into smaller accounts. For example, an account can be scoped by user to ensure that each user has their own individual account.

Scoping accounts is recommended. Unscoped accounts may perform more slowly than scoped accounts due to lock contention.

To get a particular account:

account = DoubleEntry.account(:spending, :scope => user)

(This actually returns an Account::Instance object.)

See DoubleEntry::Account for more info.

Balances

Calling:

account.balance

will return the current balance for an account as a Money object.

Transfers

To transfer money between accounts:

DoubleEntry.transfer(
  Money.new(20_00),
  :from => one_account,
  :to   => another_account,
  :code => :a_business_code_for_this_type_of_transfer,
)

The possible transfers, and their codes, should be defined in the configuration.

See DoubleEntry::Transfer for more info.

Metadata

You may associate arbitrary metadata with transfers, for example:

DoubleEntry.transfer(
  Money.new(20_00),
  :from => one_account,
  :to   => another_account,
  :code => :a_business_code_for_this_type_of_transfer,
  :metadata => {:key1 => ['value 1', 'value 2'], :key2 => 'value 3'},
)

Locking

If you're doing more than one transfer in a single financial transaction, or you're doing other database operations along with the transfer, you'll need to manually lock the accounts you're using:

DoubleEntry.lock_accounts(account_a, account_b) do
  # Perhaps transfer some money
  DoubleEntry.transfer(Money.new(20_00), :from => account_a, :to => account_b, :code => :purchase)
  # Perform other tasks that should be commited atomically with the transfer of funds...
end

The lock_accounts call generates a database transaction, which must be the outermost transaction.

See DoubleEntry::Locking for more info.

Implementation

All transfers and balances are stored in the lines table. As this is a double-entry accounting system, each transfer generates two lines table entries: one for the source account, and one for the destination.

Lines table entries also store the running balance for the account. To retrieve the current balance for an account, we find the most recent lines table entry for it.

See DoubleEntry::Line for more info.

AccountBalance records cache the current balance for each Account, and are used to perform database level locking.

Transfer metadata is stored as key/value pairs associated with both the source and destination lines of the transfer. See DoubleEntry::LineMetadata for more info.

Configuration

A configuration file should be used to define a set of accounts, optional scopes on the accounts, and permitted transfers between those accounts.

The configuration file should be kept in your application's load path. For example, config/initializers/double_entry.rb

For example, the following specifies two accounts, savings and checking. Each account is scoped by User (where User is an object with an ID), meaning each user can have their own account of each type.

This configuration also specifies that money can be transferred between the two accounts.

require 'double_entry'

DoubleEntry.configure do |config|
  config.define_accounts do |accounts|
    user_scope = accounts.active_record_scope_identifier(User)
    accounts.define(:identifier => :savings,  :scope_identifier => user_scope, :positive_only => true)
    accounts.define(:identifier => :checking, :scope_identifier => user_scope)
  end

  config.define_transfers do |transfers|
    transfers.define(:from => :checking, :to => :savings,  :code => :deposit)
    transfers.define(:from => :savings,  :to => :checking, :code => :withdraw)
  end
end

By default an account's currency is the same as Money.default_currency from the money gem.

You can also specify a currency on a per account basis. Transfers between accounts of different currencies are not allowed.

DoubleEntry.configure do |config|
  config.define_accounts do |accounts|
    accounts.define(:identifier => :savings,  :scope_identifier => user_scope, :currency => 'AUD')
  end
end

Jackhammer

Run a concurrency test on the code.

This spawns a bunch of processes, and does random transactions between a set of accounts, then validates that all the numbers add up at the end.

You can also tell it to flush out the account balances table at regular intervals, to validate that new account balances records get created with the correct balances from the lines table.

./script/jack_hammer -t 20
Cleaning out the database...
Setting up 5 accounts...
Spawning 20 processes...
Flushing balances
Process 1 running 1 transfers...
Process 0 running 1 transfers...
Process 3 running 1 transfers...
Process 2 running 1 transfers...
Process 4 running 1 transfers...
Process 5 running 1 transfers...
Process 6 running 1 transfers...
Process 7 running 1 transfers...
Process 8 running 1 transfers...
Process 9 running 1 transfers...
Process 10 running 1 transfers...
Process 11 running 1 transfers...
Process 12 running 1 transfers...
Process 13 running 1 transfers...
Process 14 running 1 transfers...
Process 16 running 1 transfers...
Process 15 running 1 transfers...
Process 17 running 1 transfers...
Process 19 running 1 transfers...
Process 18 running 1 transfers...
Reconciling...
All the Line records were written, FTW!
All accounts reconciled, FTW!
Done successfully :)

Future Direction

See the Github project issues.

Development Environment Setup

  1. Clone this repo.

    git clone [email protected]:envato/double_entry.git && cd double_entry
  2. Run the included setup script to install the gem dependencies.

    ./script/setup.sh
  3. Install MySQL, PostgreSQL and SQLite. We run tests against all three databases.

  4. Create a database in MySQL.

    mysql -u root -e 'create database double_entry_test;'
  5. Create a database in PostgreSQL.

    psql -c 'create database double_entry_test;' -U postgres
  6. Specify how the tests should connect to the database

    cp spec/support/{database.example.yml,database.yml}
    vim spec/support/database.yml
  7. Run the tests

    bundle exec rake

Contributors

Many thanks to those who have contributed to both this gem, and the library upon which it was based, over the years:

  • Anthony Sellitti - @asellitt
  • Clinton Forbes - @clinton
  • Eaden McKee - @eadz
  • Giancarlo Salamanca - @salamagd
  • Jiexin Huang - @jiexinhuang
  • Keith Pitt - @keithpitt
  • Kelsey Hannan - @KelseyDH
  • Mark Turnley - @rabidcarrot
  • Martin Jagusch - @MJIO
  • Martin Spickermann - @spickermann
  • Mary-Anne Cosgrove - @macosgrove
  • Orien Madgwick - @orien
  • Pete Yandall - @notahat
  • Rizal Muthi - @rizalmuthi
  • Ryan Allen - @ryan-allen
  • Samuel Cochran - @sj26
  • Stephanie Staub - @stephnacios
  • Trung LĂŞ - @joneslee85
  • Vahid Ta'eed - @vahid

double_entry's People

Contributors

doncote avatar eadz avatar jacobbednarz avatar jiexinhuang avatar kelseydh avatar orien avatar rabidcarrot avatar rizalmuthi avatar ryanzhou avatar salamagd avatar sj26 avatar spickermann avatar stavro avatar stephnacios avatar swrobel avatar

Stargazers

 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.