# `Bookk.Ledger`
[🔗](https://github.com/rwillians/bookk/blob/v1.0.0/lib/bookk/ledger.ex#L5)

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

- `Bookk.Account`;
- `Bookk.JournalEntry`.

# `t`

```elixir
@type t() :: %Bookk.Ledger{
  accounts_by_name: %{required(name :: String.t()) =&gt; 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.

# `balanced?`

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

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?()
    true

Is 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?()
    true

Is 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

# `diff`

```elixir
@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)),
    ])

# `empty?`

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

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

# `get_account`

```elixir
@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)
    }

# `merge`

```elixir
@spec merge([t(), ...]) :: t()
```

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.

# `merge`

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

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

# `new`

```elixir
@spec new(id :: String.t(), [Bookk.Account.t()]) :: t()
```

Creates a new `Bookk.Ledger` from its id and, optionally, a list
of `Bookk.Account`.

# `post`

```elixir
@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)}
    ]

---

*Consult [api-reference.md](api-reference.md) for complete listing*
