Module: Twobook

Defined in:
lib/twobook/entry.rb,
lib/twobook/event.rb,
lib/twobook/account.rb,
lib/twobook/handler.rb,
lib/twobook/helpers.rb,
lib/twobook/version.rb,
lib/twobook/agreement.rb,
lib/twobook/utilities.rb,
lib/twobook/corrections.rb,
lib/twobook/account_query.rb,
lib/twobook/configuration.rb,
lib/twobook/serialization.rb,
lib/twobook/number_handling.rb,
lib/twobook/event_processing.rb,
lib/twobook/handler/query_helpers.rb,
lib/twobook/handler/booking_helpers.rb

Defined Under Namespace

Modules: Corrections, Helpers, Serialization, Utilities Classes: Account, AccountQuery, Agreement, Configuration, Entry, Event, Handler

Constant Summary collapse

VERSION =
'0.1.4'
PRECISION =

significant figures

15
SCALE =

decimals after the point

6

Class Method Summary collapse

Class Method Details

.configurationObject



6
7
8
# File 'lib/twobook/configuration.rb', line 6

def self.configuration
  @configuration
end

.configure {|@configuration| ... } ⇒ Object

Yields:



10
11
12
13
# File 'lib/twobook/configuration.rb', line 10

def self.configure
  @configuration ||= Configuration.new
  yield(@configuration)
end

.ensure_transaction!(accounts, event) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/twobook/event_processing.rb', line 39

def self.ensure_transaction!(accounts, event)
  assets = (:assets, accounts, event)
  liabilities = (:liabilities, accounts, event)
  revenue = (:revenue, accounts, event)
  expenses = (:expenses, accounts, event)

  sum = assets - liabilities - revenue + expenses

  if sum.nonzero?
    report = accounts.map do |a|
      "#{a.name}: #{a.entries.select { |e| e.event == event }.map(&:amount).sum}"
    end.join("\n")

    raise "Invalid transaction: must sum to zero, but summed to #{sum}. \n#{report}"
  end
end

.simulate(event, input_accounts = []) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/twobook/event_processing.rb', line 2

def self.simulate(event, input_accounts = [])
  return simulate_chain(event, input_accounts) if event.is_a?(Array)
  raise 'Cannot simulate: event has no agreements' unless event.agreements.any?

  handlers = event.agreements.reduce([]) do |memo, agreement|
    memo + agreement.handlers_for(event)
  end

  new_accounts = handlers.reduce(Set.new(input_accounts)) do |processing_accounts, handler|
    handler.run(processing_accounts)
  end

  # All entries added while processesing that event should satisfy the accounting equation.
  ensure_transaction! new_accounts, event

  new_accounts
end

.simulate_chain(events, input_accounts = []) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/twobook/event_processing.rb', line 20

def self.simulate_chain(events, input_accounts = [])
  sorted_groups = events.group_by(&:happened_at)
  sorted = []
  sorted_groups.keys.sort.each do |time|
    group = sorted_groups[time]
    # Assign order to groups of events with the same timestamp according to the order passed in
    sorted_group = if group.any? { |event| event.partial_order.nil? }
                     group.map.with_index { |event, i| event.update_partial_order(i) }
                   else
                     group.sort_by(&:partial_order)
                   end
    sorted_group.each { |event| sorted << event }
  end

  sorted.reduce(input_accounts) do |accounts, event|
    simulate(event, accounts)
  end
end

.sum_entries_under_account_type(type, accounts, event) ⇒ Object



56
57
58
59
60
# File 'lib/twobook/event_processing.rb', line 56

def self.(type, accounts, event)
  accounts.select { |a| a.class. == type }.map do |a|
    a.entries.select { |e| e.event == event }.map(&:amount).sum
  end.sum
end

.where(**conditions) ⇒ Object



2
3
4
# File 'lib/twobook/helpers.rb', line 2

def self.where(**conditions)
  AccountQuery.where(**conditions)
end

.wrap_number(n) ⇒ Object



19
20
21
# File 'lib/twobook/number_handling.rb', line 19

def self.wrap_number(n)
  ::BigDecimal.new(n, PRECISION).round(SCALE)
end