The Ledger gem is a comprehensive implementation of a double-entry bookkeeping system, following the principles of Double-Entry Bookkeeping. Key features include:
- Requires source documents
- Creates transfers and double-entry lines (which may exceed two) in the database
- Maintains current balances for each account
Note: This gem does not enforce accounting rules beyond optionally ensuring positive balances and an allowlist of approved transfers. It utilizes the Money gem for currency operations.
- Provides a base table for documents, connectable to business models via a polymorphic association
- Includes a chart of accounts table where the
id
represents the account number (e.g.,ledger_accounts.id = 331
)
GEM IS STILL IN ALPHA MODE, NOT DEPLOYED YET
bundle add double_entry_ledger
orgem install double_entry_ledger
if you are not in rails and also check out additional stepsrails g ledger::install
- In the migration, update
tenant_table_name
anduser_accounts_table_name
to your tables, for all the required FKs.
tenant_table_name = "tenants"
user_accounts_table_name = "accounts"
- In the created ``
- Add the following code to your
Tenant
andPerson
models:
# models\tenant.rb
class Tenant < ApplicationRecord
has_many :transfers, class_name: "Ledger::Transfer", foreign_key: "tenant_id"
end
# models\person.rb # This is an example of a name.
class Person < ApplicationRecord
has_many :account_balances, class_name: "Ledger::AccountBalance", foreign_key: "person_id"
has_many :entries, class_name: "Ledger::Entry", foreign_key: "person_id", inverse_of: :person
has_many :transfers, through: :entries, source: :ledger_transfer
has_many :documents, through: :transfers, source: :ledger_document
end
Replace UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
with your gem name after releasing it to RubyGems.org. If not releasing to RubyGems.org, replace this section with installation instructions from git.
To install and add the gem to your application's Gemfile, run:
$ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
If not using Bundler, install the gem with:
$ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
In order to use this gem you need to have tenant
and person
models in your app already.
When running
rails g ledger::install
If not using Rails, initialize with:
MoneyRails::Hooks.init
erDiagram
ledger_documents {
bigint ledger_document_type_id PK
date date
string number
string description
text comments
text internal_comments
string documentable_type
bigint documentable_id
string external_id
}
ledger_document_types {
string name PK
string description
}
ledger_entries {
bigint ledger_transfer_id
bigint ledger_account_id
bigint account_id
boolean is_debit
integer amount_cents
string amount_currency
}
ledger_transfers {
date date
bigint ledger_document_id
string description
}
ledger_account_balances {
bigint account_id
integer balance_cents
string balance_currency
bigint ledger_account_id
date date
}
ledger_documents ||--o| ledger_document_types : "has"
ledger_transfers ||--o| ledger_documents : "references"
ledger_entries ||--o| ledger_accounts : "belongs to"
ledger_entries ||--o| ledger_transfers : "references"
ledger_entries ||--o| ledger_people : "optional belongs to"
ledger_account_balances ||--o| ledger_people : "belongs to"
ledger_account_balances ||--o| ledger_accounts : "belongs to"
ledger_account_balances ||--o| ledger_people : "date balance"
First of all you must setup
- chart of accounts
- person
- use or opt-out of tenancy
- use or opt out of accounting periods
Then you can go to the actual double entry api, transfers.
erDiagram
ledger_accounts {
bigint id
string name
integer type
string official_code
}
The main accounts are stored in the ledger_accounts
table, accessible via Ledger::Accounts
. The id
column represents the account number and should be provided by the user (it is not auto-incremented). Key attributes include:
id
: Account number (positive integer)name
: Name of the accounttype
: Type of account (:passive
,:active
,:mixed
)official_code
: Optional code for the account
# Example account creation
Ledger::Accounts.new(id: 1200, name: "Accounts Receivable", type: :active)
The transfers between specific person
s is implemented, however, if you would need to add additional sub-accounts, like for example a location to an account, and a person
is not enough, you must extend the person
table to handle that.
erDiagram
ledger_people {
string personable_type
bigint personable_id
}
Details on managing balances will be added here.
Tenancy support is optional.
Accounting periods support is optional.
Transfer money from one account to another with the following method, with a person account on either side, or none at all.
The Ledger.transfer
is the main entry point. It accepts a not saved transfer object, that is required to have a document
, date
and other fields filled out.
A Ledger.transfer
may process a single transaction or a set of transactions at the same time, by passing a :transactions
hash key with an array of transaction hashes you
execute multiple transactions.
When passing a person
, it will find_or_create
a account_balances entry for that person
, account
.
And date
(read accounting period), and tenant
if these options are included.
transfer
[Ledger::Transfer::Instance]: A prepared transfer object (not saved in the db).options
[Hash]: Options including::amount
[Money]: Amount to transfer.:debit
[Ledger::Account::Instance, Integer]: Debit side.:credit
[Ledger::Account::Instance, Integer]: Credit side.:person_debit
[Ledger::Person::Instance]: Optional person on the debit side.:person_credit
[Ledger::Person::Instance]: Optional person on the credit side.
:transactions
[Array]: Array of transaction hashes with the listed under options above for each element.
transfer = Transfer.new(document: document, date: Date.today, description: 'Transfer description')
Ledger.transfer(
transfer: transfer,
amount: Money.new(20_00, 'USD'),
debit: Ledger::Account.find_by(111),
credit: 222
)
Money is being transfered
transfer = Transfer.new(document: document, date: Date.today, description: 'Transfer description')
Ledger.transfer(
transfer: transfer,
amount: Money.new(20_00, 'USD'),
debit: ledger_account_a,
credit: ledger_account_b,
person_debit: person_a
)
transfer = Transfer.new(document: document, date: Date.today, description: 'Transfer description')
Ledger.transfer(
transfer: transfer,
amount: Money.new(20_00, 'USD'),
debit: ledger_account_a,
credit: ledger_account_b,
person_debit: person_a,
person_credit: person_b
)
transfer = Transfer.new(document: document, date: Date.today, description: 'Transfer description')
Ledger.transfer(
transfer: transfer,
transactions: [
{amount: Money.new(20_00, 'USD'), debit: account_a, credit: account_b},
{amount: Money.new(20_00, 'USD'), debit: account_a, credit: account_c, person_debit: person_a}
]
)
Ledger::TransferIsNegative
: Raised if the amount is less than zero.Ledger::TransferAlreadyExists
: Raised if the transfer instance is already recorded.Ledger::InsufficientMoney
: Raised if the account has insufficient funds.Ledger::TransferNotAllowed
: Raised if the transfer is not permitted.
This gem uses development containers, with vs code. If you already have VS Code and Docker installed, you can click the badge above or here to get started. Clicking these links will cause VS Code to automatically install the Dev Containers extension if needed, clone the source code into a container volume, and spin up a dev container for use.
Then, run rake spec
to run the tests.
You can also run bin/console
for an interactive prompt that will allow you to experiment.
TODO: check this
To install this gem onto your local machine, run bundle exec rake install
. To release a new version, update the version number in version.rb
, and then run bundle exec rake release
, which will create a git tag for the version, push git commits and the created tag, and push the .gem
file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ledger. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the Ledger project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
Inspired by double_entry
gem by Envato, parts of the code have been copied over or adapted.
And plutus gem