Repository is archived
No commit activity in last 3 years
No release in over 3 years
The Event-Sourced Accounting plugin provides an event-sourced double entry accounting system for use in any Ruby on Rails application.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies
 Project Readme

Gem Version Build Status Code Climate Code Climate

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