# `Bookk.Operation`
[🔗](https://github.com/rwillians/bookk/blob/v1.0.0/lib/bookk/operation.ex#L1)

An operation describe a change in balance on a single account
(`Bookk.Account`). An operation is analogous to a git diff on
a file, they represent a diff on an account's balance.

## Related

- `Bookk.JournalEntry`;
- `Bookk.AccountHead`;
- `Bookk.AccountClass`.

# `t`

```elixir
@type t() :: %Bookk.Operation{
  account_head: Bookk.AccountHead.t(),
  amount: Decimal.t(),
  direction: :credit | :debit
}
```

The struct representing an operation.

## Fields

- `direction` (either `:debit` or `:credit`): by itself it means
  nothing -- it's just a lable -- but, once combined with the
  account's natural balance (`account_head.class.natural_balance`),
  then we're able to tell if the operation will result in an
  addition or a subtraction of balance;
- `account_head`: a `Bookk.AccountHead` struct used to either
  update or create the affected account in the ledger where the
  operation was posted;
- `amount`: the [positive] amount by which the account's balance
  will be changed. Whether the change will be an addition or a
  subtraction, that depends on `direction` and the account class'
  natural balance.

# `credit`

```elixir
@spec credit(Bookk.AccountHead.t(), amount) :: t()
when amount: Decimal.t() | integer() | float()
```

Creates a credit operation from a `Bookk.AccountHead` and an amount.

## Examples

Crediting a positive amount produces a credit operation:

    iex> head = fixture_account_head(:cash)
    iex> Bookk.Operation.credit(head, Decimal.new(25))
    %Bookk.Operation{
      direction: :credit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.new(25)
    }

Crediting a negative amount produces a debit operation:

    iex> head = fixture_account_head(:cash)
    iex> Bookk.Operation.credit(head, Decimal.new(-25))
    %Bookk.Operation{
      direction: :debit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.new(25)
    }

If the given amount is an `integer` or a `float`, it will be converted
to `Decimal`:

    iex> head = fixture_account_head(:cash)
    iex> Bookk.Operation.credit(head, 10)
    %Bookk.Operation{
      direction: :credit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.new(10)
    }

    iex> head = fixture_account_head(:cash)
    iex> Bookk.Operation.credit(head, 1000.00)
    %Bookk.Operation{
      direction: :credit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.from_float(1000.00)
    }

# `debit`

```elixir
@spec debit(Bookk.AccountHead.t(), amount :: integer()) :: t()
```

Creates a debit operation given a `Bookk.AccountHead` and an amount.

## Examples

Debiting a positive amount produces a debit operation:

    iex> head = fixture_account_head(:cash)
    iex> Bookk.Operation.debit(head, Decimal.new(25))
    %Bookk.Operation{
      direction: :debit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.new(25)
    }

Debiting a negative amount produces a credit operation:

    iex> head = fixture_account_head(:cash)
    iex> Bookk.Operation.debit(head, Decimal.new(-25))
    %Bookk.Operation{
      direction: :credit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.new(25)
    }

If the given amount is an `integer` or a `float`, it will be converted
to `Decimal`:

    iex> head = fixture_account_head(:cash)
    iex> Bookk.Operation.debit(head, 10)
    %Bookk.Operation{
      direction: :debit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.new(10)
    }

    iex> head = fixture_account_head(:cash)
    iex> Bookk.Operation.debit(head, 1000.00)
    %Bookk.Operation{
      direction: :debit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.from_float(1000.00)
    }

# `empty?`

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

Checks whether an operation is empty. It is considered empty when
its amount is zero, meaning no changes to the account's balance.

## Examples

Is empty when amount is zero:

    iex> Bookk.Operation.empty?(%Bookk.Operation{amount: Decimal.new(0)})
    true

Is not empty when amount is different than zero:

    iex> Bookk.Operation.empty?(%Bookk.Operation{amount: Decimal.new(1)})
    false

# `merge`

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

Same as {Bookk.Operation.merge/2} but it takes a non-empty list of
operations, all to the same account (same `account_head`).

## Examples

    iex> head = fixture_account_head(:cash)
    iex>
    iex> a = debit(head, Decimal.new(100))
    iex> b = debit(head, Decimal.new(200))
    iex> c = debit(head, Decimal.new(300))
    iex>
    iex> Bookk.Operation.merge([a, b, c])
    %Bookk.Operation{
      direction: :debit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.new(600)
    }

If you try to merge an empty list of operations, an error will be
raised.

# `merge`

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

Combines two operation against the same account into one operation.

## Examples

When the two operations have the same direction, the resulting
operation has the same direction and the sum of the two amounts as
its amount:

    iex> head = fixture_account_head(:cash)
    iex>
    iex> a = debit(head, Decimal.new(70))
    iex> b = debit(head, Decimal.new(30))
    iex>
    iex> Bookk.Operation.merge(a, b)
    %Bookk.Operation{
      direction: :debit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.new(100)
    }

When the two operations have different direction, the account's
natural balance will define the resulting direction. As for the
resulting amount, the operation that matches the natural balance
direction will have its amount subtracted by the other operation's
amount:

    iex> head = fixture_account_head(:cash)
    iex>
    iex> a = debit(head, Decimal.new(70))
    iex> b = credit(head, Decimal.new(30))
    iex>
    iex> Bookk.Operation.merge(a, b)
    %Bookk.Operation{
      direction: :debit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.new(40)
    }

If the resulting balance is a negative number, then the resulting
direction will be switched and the amount will be transformed into a
positive number:

    iex> head = fixture_account_head(:cash)
    iex>
    iex> a = credit(head, Decimal.new(70))
    iex> b = debit(head, Decimal.new(30))
    iex>
    iex> Bookk.Operation.merge(a, b)
    %Bookk.Operation{
      direction: :credit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.new(40)
    }

If the operations' account heads aren't the same in both operations,
then an error will be raised:

    iex> a = debit(fixture_account_head(:cash), Decimal.new(10))
    iex> b = credit(fixture_account_head(:deposits), Decimal.new(10))
    iex>
    iex> Bookk.Operation.merge(a, b)
    ** (FunctionClauseError) no function clause matching in Bookk.Operation.merge/2

# `new`

```elixir
@spec new(direction :: :credit | :debit, Bookk.AccountHead.t(), amount) :: t()
when amount: Decimal.t() | integer() | float()
```

Creates a new operation. Same as `credit/2` and `debit/2` but the
operation's direction is provided as an atom argument.

## Examples

By default, the amount should be a `Decimal`:

    iex> Bookk.Operation.new(:debit, fixture_account_head(:cash), Decimal.new(10))
    %Bookk.Operation{
      direction: :debit,
      account_head: fixture_account_head(:cash),
      amount: Decimal.new(10)
    }

# `reverse`

```elixir
@spec reverse(t()) :: t()
```

Produces a opposite operation from the given operation. The opposite
operation is capable of reverting the effect of the given operation
when posted to an account.

## Examples

A credit operation becomes a debit operation:

    iex> entry = %Bookk.Operation{direction: :credit, amount: Decimal.new(10)}
    iex> Bookk.Operation.reverse(entry)
    %Bookk.Operation{direction: :debit, amount: Decimal.new(10)}

A debit operation becomes a credit operation:

    iex> entry = %Bookk.Operation{direction: :debit, amount: Decimal.new(10)}
    iex> Bookk.Operation.reverse(entry)
    %Bookk.Operation{direction: :credit, amount: Decimal.new(10)}

# `to_delta_amount`

```elixir
@spec to_delta_amount(t()) :: integer()
```

Returns the operation's delta amount, which is the real number
(positive or negative integer) by which the account's balance will
be changed.

A negative integer is return in case the account should be
subtracted, making this value safe to always be used with an
addition operation against the account's balance.

## Examples

Debiting an account which has a debit natural balance produces a
positive number:

    iex> account_head = fixture_account_head(:cash)
    iex>
    iex> Bookk.Operation.debit(account_head, Decimal.new(100))
    iex> |> Bookk.Operation.to_delta_amount()
    Decimal.new(100)

Debiting an account which has a credit natural balance produces a
negative number:

    iex> account_head = fixture_account_head(:cash)
    iex>
    iex> Bookk.Operation.credit(account_head, Decimal.new(100))
    iex> |> Bookk.Operation.to_delta_amount()
    Decimal.new(-100)

Crediting an account which has a credit natural balance produces a
positive number:

    iex> account_head = fixture_account_head(:deposits)
    iex>
    iex> Bookk.Operation.credit(account_head, Decimal.new(100))
    iex> |> Bookk.Operation.to_delta_amount()
    Decimal.new(100)

Debiting an account which has a credit natural balance produces a
negative number:

    iex> account_head = fixture_account_head(:deposits)
    iex>
    iex> Bookk.Operation.debit(account_head, Decimal.new(100))
    iex> |> Bookk.Operation.to_delta_amount()
    Decimal.new(-100)

# `uniq`

```elixir
@spec uniq([t()]) :: [t()]
```

Takes a set of operations and returns a set of uniq operations per
account. The operations that affect the same account will be merged.

See `merge/1` and `merge/2` for more information on merging
operations.

## Examples

When there's more than one operation touching the same account,
those operations are merged together so that the resulting list
contains one a single operation thouching each account:

    iex> a = Bookk.Operation.debit(fixture_account_head(:cash), Decimal.new(40))
    iex> b = Bookk.Operation.debit(fixture_account_head(:cash), Decimal.new(60))
    iex> c = Bookk.Operation.credit(fixture_account_head(:deposits), Decimal.new(100))
    iex>
    iex> Bookk.Operation.uniq([a, b, c])
    [
      debit(fixture_account_head(:cash), Decimal.new(100)),
      credit(fixture_account_head(:deposits), Decimal.new(100))
    ]

When all operations are unique, the list is returned as is:

    iex> a = Bookk.Operation.debit(fixture_account_head(:cash), Decimal.new(100))
    iex> b = Bookk.Operation.credit(fixture_account_head(:deposits), Decimal.new(100))
    iex>
    iex> Bookk.Operation.uniq([a, b])
    [
      debit(fixture_account_head(:cash), Decimal.new(100)),
      credit(fixture_account_head(:deposits), Decimal.new(100))
    ]

When an empty list is given, the result will also be an empty list:

    iex> Bookk.Operation.uniq([])
    []

---

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