Bookk.NaiveState (bookk v1.0.0)

Copy Markdown View Source

A state struct that holds multiple ledgers. It's considered "naive" because it doesn't hold any information regarding the events that led to the current state.

Summary

Types

t()

The struct representing a naive state.

Functions

Checks whether the state is balanced.

Calculates a Bookk.InterledgerEntry represending the diff between two Bookk.NaiveState where, if such interledger entry were to be posted to state "a", it would become equal to state "b".

Checks wether the given state is empty (no ledgers with balance).

Get's a ledger from the state by its id. If the ledger doesn't exist in the state yet, then a new empty ledger will be returned.

Merges a set of states into one.

Merges two states into one.

Produces a new state struct from a set of ledgers.

Posts a Bookk.InterledgerEntry to the state, appling changes in balance to multiple accounts accross multiple ledgers.

Types

t()

@type t() :: %Bookk.NaiveState{
  ledgers_by_id: %{required(id :: String.t()) => Bookk.Ledger.t()}
}

The struct representing a naive state.

Fields

  • ledgers_by_id: the ledgers known by the state, grouped by their id.

Functions

balanced?(state)

@spec balanced?(t()) :: boolean()

Checks whether the state is balanced.

A state is considered balance when the sum of balance from its debit accounts is equal the sum of balance from its credit accounts. You know if an account is a "debit account" or a "credit account" by the natural balance of its class.

See Bookk.AccountClass for more information on natural balance.

Examples

Balanced:

iex> state = Bookk.NaiveState.new([
iex>   Bookk.Ledger.new("acme", [
iex>     Bookk.Account.new(fixture_account_head(:cash), Decimal.new(25)),
iex>     Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(25))
iex>   ])
iex> ])
iex>
iex> Bookk.NaiveState.balanced?(state)
true

Unbalanced:

iex> state = Bookk.NaiveState.new([
iex>   Bookk.Ledger.new("acme", [
iex>     Bookk.Account.new(fixture_account_head(:cash), Decimal.new(25)),
iex>     Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(10))
iex>   ])
iex> ])
iex>
iex> Bookk.NaiveState.balanced?(state)
false

diff(a, b)

@spec diff(a :: t(), b :: t()) :: Bookk.InterledgerEntry.t()

Calculates a Bookk.InterledgerEntry represending the diff between two Bookk.NaiveState where, if such interledger entry were to be posted to state "a", it would become equal to state "b".

Examples

iex> a = Bookk.NaiveState.new([
iex>   Bookk.Ledger.new("acme", [
iex>     Bookk.Account.new(fixture_account_head(:cash), Decimal.new(25)),
iex>     Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(25))
iex>   ])
iex> ])
iex>
iex> b = Bookk.NaiveState.new([
iex>   Bookk.Ledger.new("acme", [
iex>     Bookk.Account.new(fixture_account_head(:cash), Decimal.new(100)),
iex>     Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(25)),
iex>     Bookk.Account.new(fixture_account_head({:unspent_cash, {:user, "12345"}}), Decimal.new(75))
iex>   ]),
iex>   Bookk.Ledger.new("foo", [
iex>     Bookk.Account.new(fixture_account_head(:cash), Decimal.new(75)),
iex>     Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(75))
iex>   ])
iex> ])
iex>
iex> Bookk.NaiveState.diff(a, b)
Bookk.InterledgerEntry.new([
  {"acme", Bookk.JournalEntry.new([
    debit(fixture_account_head(:cash), Decimal.new(75)),
    credit(fixture_account_head({:unspent_cash, {:user, "12345"}}), Decimal.new(75))
  ])},
  {"foo", Bookk.JournalEntry.new([
    debit(fixture_account_head(:cash), Decimal.new(75)),
    credit(fixture_account_head(:deposits), Decimal.new(75))
  ])}
])

empty?(state)

@spec empty?(t()) :: boolean()

Checks wether the given state is empty (no ledgers with balance).

Examples

iex> Bookk.NaiveState.new()
iex> |> Bookk.NaiveState.empty?()
true

iex> state = Bookk.NaiveState.new([
iex>   Bookk.Ledger.new("acme", [
iex>     Bookk.Account.new(fixture_account_head(:cash), Decimal.new(0)),
iex>     Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(0))
iex>   ])
iex> ])
iex>
iex> Bookk.NaiveState.empty?(state)
true

iex> state = Bookk.NaiveState.new([
iex>   Bookk.Ledger.new("acme", [
iex>     Bookk.Account.new(fixture_account_head(:cash), Decimal.new(10)),
iex>     Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(10))
iex>   ])
iex> ])
iex>
iex> Bookk.NaiveState.empty?(state)
false

get_ledger(naive_state, arg)

@spec get_ledger(t(), String.t()) :: Bookk.Ledger.t()

Get's a ledger from the state by its id. If the ledger doesn't exist in the state yet, then a new empty ledger will be returned.

Examples

Returns an empty ledger when requested ledger doesn't exist in state:

iex> Bookk.NaiveState.get_ledger(%Bookk.NaiveState{}, "acme")
%Bookk.Ledger{id: "acme"}

Returns the ledger when it exists in state:

iex> state = Bookk.NaiveState.new([
iex>   Bookk.Ledger.new("foo", [
iex>     Bookk.Account.new(fixture_account_head(:cash))
iex>   ])
iex> ])
iex>
iex> Bookk.NaiveState.get_ledger(state, "foo")
Bookk.Ledger.new("foo", [
  Bookk.Account.new(fixture_account_head(:cash))
])

merge(list)

@spec merge([t()]) :: t()

Merges a set of states into one.

Examples

iex> a = Bookk.NaiveState.new([
iex>   Bookk.Ledger.new("acme", [
iex>     Bookk.Account.new(fixture_account_head(:cash), Decimal.new(5))
iex>   ])
iex> ])
iex>
iex> b = Bookk.NaiveState.new([
iex>   Bookk.Ledger.new("acme", [
iex>     Bookk.Account.new(fixture_account_head({:unspent_cash, {:user, "12345"}}), Decimal.new(5))
iex>   ]),
iex>   Bookk.Ledger.new("user(12345)", [
iex>     Bookk.Account.new(fixture_account_head(:cash), Decimal.new(5)),
iex>     Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(5)),
iex>   ])
iex> ])
iex>
iex> c = Bookk.NaiveState.new([
iex>   Bookk.Ledger.new("foo", [
iex>     Bookk.Account.new(fixture_account_head(:cash), Decimal.new(25)),
iex>     Bookk.Account.new(fixture_account_head({:unspent_cash, {:user, "12345"}}), Decimal.new(25))
iex>   ])
iex> ])
iex>
iex> Bookk.NaiveState.merge([a, b, c])
Bookk.NaiveState.new([
  Bookk.Ledger.new("acme", [
    Bookk.Account.new(fixture_account_head(:cash), Decimal.new(5)),
    Bookk.Account.new(fixture_account_head({:unspent_cash, {:user, "12345"}}), Decimal.new(5))
  ]),
  Bookk.Ledger.new("foo", [
    Bookk.Account.new(fixture_account_head(:cash), Decimal.new(25)),
    Bookk.Account.new(fixture_account_head({:unspent_cash, {:user, "12345"}}), Decimal.new(25))
  ]),
  Bookk.Ledger.new("user(12345)", [
    Bookk.Account.new(fixture_account_head(:cash), Decimal.new(5)),
    Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(5)),
  ])
])

merge(a, b)

@spec merge(t(), t()) :: t()

Merges two states into one.

Examples

iex> a = Bookk.NaiveState.new([
iex>   Bookk.Ledger.new("acme", [
iex>     Bookk.Account.new(fixture_account_head(:cash), Decimal.new(5))
iex>   ])
iex> ])
iex>
iex> b = Bookk.NaiveState.new([
iex>   Bookk.Ledger.new("acme", [
iex>     Bookk.Account.new(fixture_account_head({:unspent_cash, {:user, "12345"}}), Decimal.new(5))
iex>   ]),
iex>   Bookk.Ledger.new("user(12345)", [
iex>     Bookk.Account.new(fixture_account_head(:cash), Decimal.new(5)),
iex>     Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(5)),
iex>   ])
iex> ])
iex>
iex> Bookk.NaiveState.merge(a, b)
Bookk.NaiveState.new([
  Bookk.Ledger.new("acme", [
    Bookk.Account.new(fixture_account_head(:cash), Decimal.new(5)),
    Bookk.Account.new(fixture_account_head({:unspent_cash, {:user, "12345"}}), Decimal.new(5))
  ]),
  Bookk.Ledger.new("user(12345)", [
    Bookk.Account.new(fixture_account_head(:cash), Decimal.new(5)),
    Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(5)),
  ])
])

new(ledgers \\ [])

@spec new([Bookk.Ledger.t()]) :: t()

Produces a new state struct from a set of ledgers.

post(state, entry)

@spec post(t(), Bookk.InterledgerEntry.t()) :: t()

Posts a Bookk.InterledgerEntry to the state, appling changes in balance to multiple accounts accross multiple ledgers.

Examples

iex> use Bookk.Notation
iex>
iex> user_id = "123"
iex> deposited_amount = Decimal.new(500)
iex>
iex> journal_entry =
iex>   journalize using: DummyChartOfAccounts do
iex>     on ledger(:acme) do
iex>       debit account(:cash), deposited_amount
iex>       credit account({:unspent_cash, {:user, user_id}}), deposited_amount
iex>     end
iex>
iex>     on ledger({:user, user_id}) do
iex>       debit account(:cash), deposited_amount
iex>       credit account(:deposits), deposited_amount
iex>     end
iex>   end
iex>
iex> Bookk.NaiveState.new()
iex> |> Bookk.NaiveState.post(journal_entry)
Bookk.NaiveState.new([
  Bookk.Ledger.new("acme", [
    Bookk.Account.new(fixture_account_head(:cash), Decimal.new(500)),
    Bookk.Account.new(fixture_account_head({:unspent_cash, {:user, "123"}}), Decimal.new(500))
  ]),
  Bookk.Ledger.new("user(123)", [
    Bookk.Account.new(fixture_account_head(:cash), Decimal.new(500)),
    Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(500))
  ])
])