DoubleEntryLedger.Balance (double_entry_ledger v0.1.0)

View Source

Represents account balance components in the Double Entry Ledger system.

This module provides an embedded schema for tracking account balances with separate debit and credit components, along with the calculated net amount. It enables proper double-entry accounting operations by maintaining both sides of the ledger.

Structure

Balance contains three key fields:

  • amount: The net balance
  • debit: The cumulative debit entries
  • credit: The cumulative credit entries

Usage

Balance structs are typically embedded within Account records to track both posted (finalized) and pending balances separately. They're updated through transactions following double-entry accounting principles where:

  • For accounts with normal debit balance (assets, expenses):

    • Debits increase the account balance
    • Credits decrease the account balance
  • For accounts with normal credit balance (liabilities, equity, revenue):

    • Credits increase the account balance
    • Debits decrease the account balance

Key Functions

Summary

Types

t()

Represents the balance components of an account.

Functions

Builds and returns a changeset for the balance struct.

Creates a new balance struct with default values of zero.

Reverses a pending amount and applies a new amount in a single operation.

Reverses the effect of a pending entry on a balance.

Updates a balance based on an entry and account type.

Types

t()

@type t() :: %DoubleEntryLedger.Balance{
  amount: integer(),
  credit: integer(),
  debit: integer()
}

Represents the balance components of an account.

This structure maintains both the separate debit and credit sides of an account's balance, as well as the calculated net amount following accounting standards.

Fields

  • amount: The net balance
  • debit: The cumulative sum of debit entries
  • credit: The cumulative sum of credit entries

This structure is used for both posted (finalized) and pending balances.

Functions

changeset(balance, attrs)

@spec changeset(t(), map()) :: Ecto.Changeset.t()

Builds and returns a changeset for the balance struct.

Creates an Ecto changeset to validate and prepare balance data for database operations. This is typically used when embedding balance data within an account record.

Parameters

  • balance - The balance struct to modify
  • attrs - Map of attributes to apply to the balance

Returns

  • An Ecto.Changeset for the balance

Examples

iex> balance = DoubleEntryLedger.Balance.new()
iex> %Ecto.Changeset{valid?: true, changes: changes} = DoubleEntryLedger.Balance.changeset(balance, %{amount: 100, debit: 100})
iex> changes
%{amount: 100, debit: 100}

new()

@spec new() :: t()

Creates a new balance struct with default values of zero.

This function initializes a Balance struct with all fields set to zero, suitable for new accounts or for resetting balances.

Returns

  • A new Balance struct with zeroed fields

Examples

iex> DoubleEntryLedger.Balance.new()
%DoubleEntryLedger.Balance{amount: 0, credit: 0, debit: 0}

reverse_and_update_pending(balance, amt, new_amount, e_type, a_type)

@spec reverse_and_update_pending(t(), integer(), integer(), atom(), atom()) ::
  Ecto.Changeset.t()

Reverses a pending amount and applies a new amount in a single operation.

This function is used when updating pending entries to a different amount, such as when modifying a hold or authorization. It first reverses the original amount and then applies the new amount.

Parameters

  • balance - The balance struct to update
  • amount_to_reverse - The original amount to reverse
  • new_amount - The new amount to apply
  • e_type - The type of entry (:debit or :credit)
  • a_type - The normal balance type of the account (:debit or :credit)

Returns

  • An Ecto.Changeset with updated balance values

Error Handling

Returns an invalid changeset if attempting to reverse more than the current balance of the specific type (debit/credit).

Examples

iex> balance = %Balance{amount: -50, debit: 50, credit: 0}
iex> %Ecto.Changeset{valid?: true, changes: changes} = Balance.reverse_and_update_pending(balance, 50, 75, :debit, :credit)
iex> changes
%{amount: -75, debit: 75}

iex> balance = %Balance{amount: 50, credit: 50, debit: 0}
iex> %Ecto.Changeset{valid?: true, changes: changes} = Balance.reverse_and_update_pending(balance, 50, 75, :credit, :credit)
iex> changes
%{amount: 75, credit: 75}

iex> balance = %Balance{amount: -40, debit: 40, credit: 0}
iex> %Ecto.Changeset{valid?: false, errors: errors} = Balance.reverse_and_update_pending(balance, 50, 75, :debit, :credit)
iex> errors
[debit: {"Cannot reverse more than the current debit balance", []}]

iex> balance = %Balance{amount: 40, credit: 40, debit: 0}
iex> %Ecto.Changeset{valid?: false, errors: errors} = Balance.reverse_and_update_pending(balance, 50, 75, :credit, :credit)
iex> errors
[credit: {"Cannot reverse more than the current credit balance", []}]

reverse_pending(balance, amt, e_type, a_type)

@spec reverse_pending(t(), integer(), atom(), atom()) :: Ecto.Changeset.t()

Reverses the effect of a pending entry on a balance.

This function is used when canceling or removing pending entries from an account's balance. It performs the opposite operation of update_balance/4.

Parameters

  • balance - The balance struct to update
  • amount - The amount to reverse from the balance
  • e_type - The type of entry being reversed (:debit or :credit)
  • a_type - The normal balance type of the account (:debit or :credit)

Returns

  • An Ecto.Changeset with updated balance values

Error Handling

Returns an invalid changeset if attempting to reverse more than the current balance of the specific type (debit/credit).

Examples

iex> balance = %Balance{amount: 50, debit: 50, credit: 0}
iex> %Ecto.Changeset{valid?: true, changes: changes} = Balance.reverse_pending(balance, 25, :debit, :debit)
iex> changes
%{amount: 25, debit: 25}

iex> balance = %Balance{amount: -50, debit: 0, credit: 50}
iex> %Ecto.Changeset{valid?: true, changes: changes} = Balance.reverse_pending(balance, 25, :credit, :debit)
iex> changes
%{amount: -25, credit: 25}

iex> balance = Balance.new()
iex> %Ecto.Changeset{valid?: false, errors: errors} = Balance.reverse_pending(balance, 50, :debit, :debit)
iex> errors
[debit: {"Cannot reverse more than the current debit balance", []}]

iex> balance = Balance.new()
iex> %Ecto.Changeset{valid?: false, errors: errors} = Balance.reverse_pending(balance, 50, :credit, :debit)
iex> errors
[credit: {"Cannot reverse more than the current credit balance", []}]

update_balance(balance, amount, e_type, a_type)

@spec update_balance(t(), integer(), atom(), atom()) :: Ecto.Changeset.t()

Updates a balance based on an entry and account type.

This function handles the core accounting logic of how entries affect balances differently based on the entry type (debit/credit) and the account type.

Parameters

  • balance - The balance struct to update
  • amount - The amount to apply to the balance
  • e_type - The type of entry (:debit or :credit)
  • a_type - The normal balance type of the account (:debit or :credit)

Returns

  • An Ecto.Changeset with updated balance values

Accounting Logic

  • When entry type matches account's normal balance type:

    • The amount increases the balance
    • The corresponding debit/credit side increases
  • When entry type is opposite account's normal balance type:

    • The amount decreases the balance
    • The corresponding debit/credit side still increases

Examples

iex> balance = DoubleEntryLedger.Balance.new()
iex> %Ecto.Changeset{valid?: true, changes: changes} = DoubleEntryLedger.Balance.update_balance(balance, 50, :debit, :debit)
iex> changes
%{amount: 50, debit: 50}

iex> balance = DoubleEntryLedger.Balance.new()
iex> %Ecto.Changeset{valid?: true, changes: changes} = DoubleEntryLedger.Balance.update_balance(balance, 50, :debit, :credit)
iex> changes
%{amount: -50, debit: 50}