A ledger is a book that holds accounts. Traditionally, ledgers would also hold the journal entries that changed the accounts but, in this library, persisting those journal entries is considered off scope. You may persist state the way the best fits your needs.
Related
Summary
Functions
Checks whether the ledger is balanced.
Calculates a Bookk.JournalEntry represending the diff between two
ledgers where, if such journal entry were to be applied to ledger "a",
its state would become equal to ledger "b".
Checks whether the given ledger is empty (no accounts with balance).
Get an account from the ledger by its Bookk.AccountHead. If the
account doesn't exist yet, then an account will be returned with
empty state.
Merges a non-empty set of Ledgers into one (ledger id MUST be the same).
Merges two ledgers into one (ledger id MUST be the same).
Creates a new Bookk.Ledger from its id and, optionally, a list
of Bookk.Account.
Posts a Bookk.JournalEntry to a ledger. This means that the
balance change described in each operation of the journal entry will
be applied to their respective accounts of the ledger. If there's a
change to an account that doesn't exist yet, then the account is
first created.
Types
@type t() :: %Bookk.Ledger{ accounts_by_name: %{required(name :: String.t()) => Bookk.Account.t()}, id: String.t() }
The struct that represents a ledger.
Fields
id: the id of the ledger;accounts_by_name: a map of the accounts known by the ledger, grouped by their name.
Functions
Checks whether the ledger is balanced.
A ledger 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
Is balanced when the ledger is empty:
iex> Bookk.Ledger.new("acme")
iex> |> Bookk.Ledger.balanced?()
trueIs balanced when the sum of debit accounts balances is equal the sum of credit accounts balances:
iex> ledger = Bookk.Ledger.new("acme")
iex>
iex> journal_entry = %Bookk.JournalEntry{
iex> operations: [
iex> debit(fixture_account_head(:cash), Decimal.new(50)),
iex> credit(fixture_account_head(:deposits), Decimal.new(50))
iex> ]
iex> }
iex>
iex> Bookk.Ledger.post(ledger, journal_entry)
iex> |> Bookk.Ledger.balanced?()
trueIs unbalanced when the sum of debit accounts balances isn't equal the sum of credit accounts balances:
iex> ledger = Bookk.Ledger.new("acme")
iex>
iex> journal_entry = %Bookk.JournalEntry{
iex> operations: [
iex> debit(fixture_account_head(:cash), Decimal.new(50))
iex> ]
iex> }
iex>
iex> Bookk.Ledger.post(ledger, journal_entry)
iex> |> Bookk.Ledger.balanced?()
false
@spec diff(a :: t(), b :: t()) :: Bookk.JournalEntry.t()
Calculates a Bookk.JournalEntry represending the diff between two
ledgers where, if such journal entry were to be applied to ledger "a",
its state would become equal to ledger "b".
Examples
iex> a = 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> b = Bookk.Ledger.new("acme", [
iex> Bookk.Account.new(fixture_account_head(:cash), Decimal.new(50)),
iex> Bookk.Account.new(fixture_account_head({:unspent_cash, {:user, "1234"}}), Decimal.new(50))
iex> ])
iex>
iex> Bookk.Ledger.diff(a, b)
Bookk.JournalEntry.new([
Bookk.Operation.debit(fixture_account_head(:cash), Decimal.new(40)),
Bookk.Operation.debit(fixture_account_head(:deposits), Decimal.new(10)),
Bookk.Operation.credit(fixture_account_head({:unspent_cash, {:user, "1234"}}), Decimal.new(50)),
])
Checks whether the given ledger is empty (no accounts with balance).
Examples
iex> Bookk.Ledger.new("acme")
iex> |> Bookk.Ledger.empty?()
true
iex> ledger = 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> Bookk.Ledger.empty?(ledger)
true
iex> ledger = Bookk.Ledger.new("acme", [
iex> Bookk.Account.new(fixture_account_head(:cash), Decimal.new(10))
iex> ])
iex>
iex> Bookk.Ledger.empty?(ledger)
false
@spec get_account(t(), Bookk.AccountHead.t()) :: Bookk.Account.t()
Get an account from the ledger by its Bookk.AccountHead. If the
account doesn't exist yet, then an account will be returned with
empty state.
Examples
Returns the account when it exists in the ledger:
iex> ledger = Bookk.Ledger.new("acme", [
iex> %Bookk.Account{
iex> head: fixture_account_head(:cash),
iex> balance: Decimal.new(25)
iex> }
iex> ])
iex>
iex> Bookk.Ledger.get_account(ledger, fixture_account_head(:cash))
%Bookk.Account{
head: fixture_account_head(:cash),
balance: Decimal.new(25)
}Returns an empty account when the it doesn't exist in the ledger:
iex> Bookk.Ledger.new("acme")
iex> |> Bookk.Ledger.get_account(fixture_account_head(:cash))
%Bookk.Account{
head: fixture_account_head(:cash),
balance: Decimal.new(0)
}
Merges a non-empty set of Ledgers into one (ledger id MUST be the same).
Examples
iex> a = Bookk.Ledger.new("acme", [
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> b = Bookk.Ledger.new("acme", [
iex> Bookk.Account.new(fixture_account_head(:cash), Decimal.new(15)),
iex> Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(15)),
iex> ])
iex>
iex> c = Bookk.Ledger.new("acme", [
iex> Bookk.Account.new(fixture_account_head(:cash), Decimal.new(30)),
iex> Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(30)),
iex> ])
iex>
iex> Bookk.Ledger.merge([a, b, c])
Bookk.Ledger.new("acme", [
Bookk.Account.new(fixture_account_head(:cash), Decimal.new(50)),
Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(50))
])If you try to merge an empty list of ledgers, an error will be raised.
Merges two ledgers into one (ledger id MUST be the same).
Examples
iex> a = Bookk.Ledger.new("acme", [
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> b = Bookk.Ledger.new("acme", [
iex> Bookk.Account.new(fixture_account_head(:cash), Decimal.new(15)),
iex> Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(15)),
iex> ])
iex>
iex> Bookk.Ledger.merge(a, b)
Bookk.Ledger.new("acme", [
Bookk.Account.new(fixture_account_head(:cash), Decimal.new(20)),
Bookk.Account.new(fixture_account_head(:deposits), Decimal.new(20))
])It will raise if ledgers have different ids:
iex> a = Bookk.Ledger.new("acme")
iex> b = Bookk.Ledger.new("foo")
iex> Bookk.Ledger.merge(a, b)
** (FunctionClauseError) no function clause matching in Bookk.Ledger.merge/2
@spec new(id :: String.t(), [Bookk.Account.t()]) :: t()
Creates a new Bookk.Ledger from its id and, optionally, a list
of Bookk.Account.
@spec post(t(), Bookk.JournalEntry.t()) :: t()
Posts a Bookk.JournalEntry to a ledger. This means that the
balance change described in each operation of the journal entry will
be applied to their respective accounts of the ledger. If there's a
change to an account that doesn't exist yet, then the account is
first created.
Examples
When account doesn't exist then it gets created:
iex> ledger = Bookk.Ledger.new("acme")
iex>
iex> journal_entry = %Bookk.JournalEntry{
iex> operations: [
iex> debit(fixture_account_head(:cash), Decimal.new(50)),
iex> credit(fixture_account_head(:deposits), Decimal.new(50))
iex> ]
iex> }
iex>
iex> updated_ledger = Bookk.Ledger.post(ledger, journal_entry)
iex>
iex> [
iex> Bookk.Ledger.get_account(updated_ledger, fixture_account_head(:cash)),
iex> Bookk.Ledger.get_account(updated_ledger, fixture_account_head(:deposits))
iex> ]
[
%Bookk.Account{head: fixture_account_head(:cash), balance: Decimal.new(50)},
%Bookk.Account{head: fixture_account_head(:deposits), balance: Decimal.new(50)}
]When account exists then it gets updated:
iex> ledger = Bookk.Ledger.new("acme")
iex>
iex> journal_entry = %Bookk.JournalEntry{
iex> operations: [
iex> debit(fixture_account_head(:cash), Decimal.new(50)),
iex> credit(fixture_account_head(:deposits), Decimal.new(50))
iex> ]
iex> }
iex>
iex> updated_ledger =
iex> ledger
iex> |> Bookk.Ledger.post(journal_entry)
iex> |> Bookk.Ledger.post(journal_entry) # post twice
iex>
iex> [
iex> Bookk.Ledger.get_account(updated_ledger, fixture_account_head(:cash)),
iex> Bookk.Ledger.get_account(updated_ledger, fixture_account_head(:deposits))
iex> ]
[
%Bookk.Account{head: fixture_account_head(:cash), balance: Decimal.new(100)},
%Bookk.Account{head: fixture_account_head(:deposits), balance: Decimal.new(100)}
]