All Projects β†’ envato β†’ Double_entry

envato / Double_entry

Licence: mit
A double-entry accounting system for Ruby applications.

Programming Languages

ruby
36898 projects - #4 most used programming language

Projects that are alternatives of or similar to Double entry

Transity
Keep track of your πŸ’΅, πŸ•˜, πŸ–, πŸ„, 🍻 on your command line
Stars: ✭ 528 (+91.3%)
Mutual labels:  accounting, finance
Gooderp addons
ε―θƒ½ζ˜―δΈ­ε›½η”¨ζˆ·ζ•°ζœ€ε€šηš„εΌ€ζΊERP
Stars: ✭ 1,315 (+376.45%)
Mutual labels:  accounting, finance
Akaunting
Free and Online Accounting Software
Stars: ✭ 4,599 (+1566.3%)
Mutual labels:  accounting, finance
Moneygo
An accounting web application to track personal finances written in Go and React/Bootstrap
Stars: ✭ 138 (-50%)
Mutual labels:  accounting, finance
Django Ledger
A bookkeeping & financial analysis engine for the Django Framework. UNDER ACTIVE DEVELOPMENT & NOT STABLE YET.
Stars: ✭ 253 (-8.33%)
Mutual labels:  accounting, finance
python api client
A Python client for Calcbench's API.
Stars: ✭ 16 (-94.2%)
Mutual labels:  finance, accounting
Budget
Get a grip on your finances.
Stars: ✭ 609 (+120.65%)
Mutual labels:  accounting, finance
Learnpythonforresearch
This repository provides everything you need to get started with Python for (social science) research.
Stars: ✭ 163 (-40.94%)
Mutual labels:  accounting, finance
finac
Finac - financial accounting for humans
Stars: ✭ 27 (-90.22%)
Mutual labels:  finance, accounting
knut
knut is an efficient plain text accounting tool with support for multiple currencies and valuation.
Stars: ✭ 40 (-85.51%)
Mutual labels:  finance, accounting
awesome-beancount
Awesome Beancount Resources
Stars: ✭ 114 (-58.7%)
Mutual labels:  finance, accounting
budget-tracker
A full-featured budget and saving tracking application in go
Stars: ✭ 26 (-90.58%)
Mutual labels:  finance
jekyll-commonmark
CommonMark generator for Jekyll
Stars: ✭ 28 (-89.86%)
Mutual labels:  gem
dude
A daily assistant in the hard work of a programmer
Stars: ✭ 19 (-93.12%)
Mutual labels:  gem
ofxgo
Golang library for querying and parsing OFX
Stars: ✭ 96 (-65.22%)
Mutual labels:  finance
Tabs
The expense sharing app.
Stars: ✭ 259 (-6.16%)
Mutual labels:  finance
prometheus
A flask web app that analyzes your stock portfolio performance, optimizes your asset allocation, and provides performance enhancement alerts.
Stars: ✭ 26 (-90.58%)
Mutual labels:  finance
technical-indicators
Finance package written in Golang, mainly with TA indicators.
Stars: ✭ 56 (-79.71%)
Mutual labels:  finance
Order-Book-Matching-Engine
Order Book Matching Engine for Stock Exchanges (1us latency for matching)
Stars: ✭ 112 (-59.42%)
Mutual labels:  finance
FinancialDerivatives.jl
Financial derivatives modeling and pricing in Julia.
Stars: ✭ 37 (-86.59%)
Mutual labels:  finance

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
  • 2.6.x
  • 2.7.x

Rails

  • 4.2.x
  • 5.0.x
  • 5.1.x
  • 5.2.x
  • 6.0.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:

The default behavior is to store metadata in a json(b) column rather than a separate double_entry_line_metadata table. If you would like the old (1.x) behavior, you can add --no-json-metadata.

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.

Account Checker/Fixer

Although it's unlikely, DoubleEntry has tools for checking accounts to make sure they tie out, and optionally fixing them if there is a discrepancy. It's recommended to run this in a scheduled job, somewhere on the order of hourly to daily, depending on transaction volume. Keep in mind that this process locks accounts as it inspects their balances, so it will have an prevent new transactions from being written for a short time.

Here are examples that could go in your scheduled job, depending on your needs:

# Check all accounts & write the results to the double_entry_line_checks table
DoubleEntry::Validation::LineCheck.perform!

# Check & fix accounts (results will also be written to the table)
DoubleEntry::Validation::LineCheck.perform!(fixer: DoubleEntry::Validation::AccountFixer.new)

See DoubleEntry::Validation 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 in a json(b) column on both the source and destination lines of the transfer.

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. By default, this file will be created when you run the installer, but you will need to fill out your accounts.

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|
  # Use json(b) column in double_entry_lines table to store metadata instead of separate metadata table
  config.json_metadata = true

  config.define_accounts do |accounts|
    user_scope = ->(user) do
      raise 'not a User' unless user.class.name == 'User'
      user.id
    end
    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

We're using Docker to provide a convenient and consistent environment for executing tests during development. This allows engineers to quickly set up a productive development environment.

Note: Most development files are mounted in the Docker container. This enables engineers to edit files in their favourite editor (on the host OS) and have the changes immediately available in the Docker container to be exercised.

One exception to this is the RSpec configuration. Changes to these files will require a rebuild of the Docker image (step 2).

Prerequisites:

  • Docker
  • Docker Compose
  • Git
  1. Clone this repo.

    git clone [email protected]:envato/double_entry.git && cd double_entry
    
  2. Build the Docker image we'll use to run tests

    docker-compose build --pull double_entry
    
  3. Startup a container and attach a terminal. This will also start up a MySQL and Postgres database.

    docker-compose run --rm double_entry ash
    
  4. Run the tests

    DB=mysql bundle exec rspec
    DB=postgres bundle exec rspec
    DB=sqlite bundle exec rspec
    
  5. When finished, exit the container terminal and shut down the databases.

    exit
    docker-compose down
    

Contributors

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

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].