Event-Sourced Accounting
The Event-Sourced Accounting plugin provides an event-sourced double entry accounting system. It uses the data models of a Rails application as a data source and automatically generates accounting transactions based on defined accounting rules.
This plugin began life as a fork of the Plutus plugin with many added features and refactored compontents. As the aims of the ESA plug-in have completely changed compared to the original project, it warrants a release under its own name.
Installation
-
Add
gem "event_sourced_accounting"
to your Gemfile -
generate migration files with
rails g event_sourced_accounting
-
run migrations
rake db:migrate
Integration
First, configure the gem by creating config/initializers/accounting.rb
.
require 'esa'
ESA.configure do |config|
config.processor = ESA::BlockingProcessor # default
config.extension_namespace = 'Accounting' # default
config.register('BankTransaction')
...
end
Then add include ESA::Traits::Accountable
to the registered models.
class BankTransaction < ActiveRecord::Base
include ESA::Traits::Accountable
...
end
Implement the corresponding Event, Flag, Ruleset and Transaction classes for the registered models.
# app/models/accounting/events/bank_transaction_event.rb
module Accounting
module Events
class BankTransactionEvent < ESA::Event
enumerize :nature, in: [
:adjustment, # mandatory
:confirm, # example
:revoke, # example
]
end
end
end
# app/models/accounting/flags/bank_transaction_flag.rb
module Accounting
module Flags
class BankTransactionFlag < ESA::Flag
enumerize :nature, in: [
:complete, # example
]
end
end
end
# app/models/accounting/transactions/bank_transaction_transaction.rb
module Accounting
module Transactions
class BankTransactionTransaction < ESA::Transaction
# this relation definition is optional
has_one :bank_transaction, :through => :flag, :source => :accountable, :source_type => "BankTransaction"
end
end
end
# app/models/accounting/rulesets/bank_transaction_ruleset.rb
module Accounting
module Rulesets
class BankTransactionRuleset < ESA::Ruleset
# events that have happened according to the current state
def event_times(bank_transaction)
{
confirm: bank_transaction.confirm_time,
revoke: bank_transaction.revoke_time,
}
end
# flags to be changed when events occur
def event_nature_flags
{
confirm: {complete: true},
revoke: {complete: false},
}
end
# transaction for when the :complete flag is switched to true
def flag_complete_transactions(bank_transaction)
{
:description => 'BankTransaction completed',
:debits => [
{
:account => find_account('Asset', 'Bank'),
:amount => bank_transaction.transferred_amount
}
],
:credits => [
{
:account => find_account('Asset', 'Bank Transit'),
:amount => bank_transaction.transferred_amount
}
],
}
end
end
end
end
Usage
In order to create events and transactions, the accountable objects have to pass through a processor, which will register the necessary Events, Flags & Transactions in the database.
You can use the provided processor implementation, or inherit from the base implementation and provide your own class (e.g. to implement delayed or scheduled processing).
>> bank_transaction = BankTransaction.find(..)
>> bank_transaction.confirm_time = Time.now
>> bank_transaction.save
true
>> ESA.configuration.processor.enqueue(bank_transaction)
>> bank_transaction.esa_events.count
1
>> bank_transaction.esa_flags.count
1
>> bank_transaction.esa_transactions.count
1
Reporting
There are many different reporting and filtering implementations available. For a simple example, let's look at a report that only involves the transaction.
The following commands initialize the report and update the persisted values to the depth of 1, which includes the creation of sub-reports per each account involved in the transactions of that BankAccount.
>> report = ESA::Contexts::AccountableContext.create(chart: ESA::Chart.first, accountable: bank_transaction)
>> report.check_freshness(1)
Complex reports can be constructed automatically using the context provider functionality. Reports, filters and context providers are available for:
- account
- accountable object (e.g. a single BankTransaction)
- accountable type (e.g. all known BankTransactions)
- date periods (year, month, date, custom)
Please refer to the source code for examples.
Subreport structure and context providers need to be configured:
ESA.configure do |config|
...
config.context_providers['bank_account'] = Accounting::ContextProviders::BankAccountContextProvider
config.context_tree = {
'month' => {
'account' => {
'bank_account' => {},
'date' => {},
},
},
}
...
end
Development
Any comments and contributions are welcome. Will gladly accept patches sent via pull requests.
-
run rspec tests simply with
rake
-
update documentation with
yard