DoubleEntryLedger.Instance (double_entry_ledger v0.1.0)
View SourceDefines and manages ledger instances in the Double Entry Ledger system.
A ledger instance acts as a container and isolation boundary for a set of accounts and their transactions. Each instance represents a complete, self-contained accounting system with its own configuration settings.
Key Concepts
- Instance Isolation: Each instance maintains separate accounts and transactions
- Balance Integrity: The system ensures that debits and credits remain balanced within each instance
- Configuration: Instances can have custom configurations to control behavior
- Validation: Methods exist to verify the accounting integrity of the ledger
Common Use Cases
- Multi-tenant Systems: Create separate ledger instances for each tenant
- Application Segmentation: Isolate different parts of an application's accounting
- Testing Environments: Maintain separate testing and production ledgers
Schema Structure
The instance schema contains basic metadata (address, description) along with configuration settings and associations to its accounts and transactions.
Summary
Types
Represents a ledger instance in the Double Entry Ledger system.
Represents the aggregate balance totals across all accounts in a ledger instance.
Functions
Creates a changeset for validating and creating/updating a ledger instance.
Creates a changeset for safely deleting a ledger instance.
Calculates the total value of all accounts in the ledger instance.
Validates that all accounts in the ledger instance maintain balanced debits and credits.
Types
@type t() :: %DoubleEntryLedger.Instance{ __meta__: term(), accounts: [DoubleEntryLedger.Account.t()] | Ecto.Association.NotLoaded.t(), address: String.t() | nil, config: map() | nil, description: String.t() | nil, id: binary() | nil, inserted_at: DateTime.t() | nil, transactions: [DoubleEntryLedger.Transaction.t()] | Ecto.Association.NotLoaded.t(), updated_at: DateTime.t() | nil }
Represents a ledger instance in the Double Entry Ledger system.
An instance acts as a container for a set of accounts and transactions, creating an isolation boundary for a complete accounting system.
Fields
id: UUID primary keyconfig: Map of configuration settings for this instancedescription: Optional text descriptionaddress: Human-readable instance address (required)accounts: List of accounts belonging to this instancetransactions: List of transactions belonging to this instanceinserted_at: Creation timestampupdated_at: Last update timestamp
@type validation_map() :: %{ posted_debit: integer(), posted_credit: integer(), pending_debit: integer(), pending_credit: integer() }
Represents the aggregate balance totals across all accounts in a ledger instance.
This map contains the summed debit and credit values for both posted and pending balances, used to validate the fundamental accounting equation (debits = credits) across the ledger.
Keys
posted_debit: Sum of all posted debit balances across all accountsposted_credit: Sum of all posted credit balances across all accountspending_debit: Sum of all pending debit balances across all accountspending_credit: Sum of all pending credit balances across all accounts
Used by validate_account_balances/1 to verify ledger balance integrity.
Functions
@spec changeset(t(), map()) :: Ecto.Changeset.t()
Creates a changeset for validating and creating/updating a ledger instance.
This function builds an Ecto changeset to validate the instance data according to the business rules.
Parameters
instance- The Instance struct to create a changeset forattrs- Map of attributes to apply to the instance
Returns
- An Ecto.Changeset with validations applied
Validations
- Required fields:
:address - Optional fields:
:description,:config
Examples
iex> instance = %Instance{}
iex> changeset = Instance.changeset(instance, %{address: "New:Ledger"})
iex> changeset.valid?
true
iex> instance = %Instance{}
iex> changeset = Instance.changeset(instance, %{})
iex> changeset.valid?
false
@spec delete_changeset(t()) :: Ecto.Changeset.t()
Creates a changeset for safely deleting a ledger instance.
This function builds a changeset that ensures a ledger instance can only be deleted if it has no associated accounts or transactions, maintaining data integrity.
Parameters
instance- The Instance struct to delete
Returns
- An Ecto.Changeset that will fail if the instance has accounts or transactions
Constraints
- No associated accounts allowed
- No associated transactions allowed
Examples
iex> {:ok, instance} = Repo.insert(%Instance{address: "Temporary Ledger"})
iex> changeset = Instance.delete_changeset(instance)
iex> {:ok, _} = Repo.delete(changeset)
iex> alias DoubleEntryLedger.Repo
iex> alias DoubleEntryLedger.Stores.AccountStore
iex> {:ok, instance} = Repo.insert(%Instance{address: "Temporary:Ledger"})
iex> AccountStore.create(instance.address, %{name: "Test Account", address: "account:main1", type: :asset, currency: :USD}, "unique_id_123")
iex> changeset = Instance.delete_changeset(instance)
iex> {:error, _} = Repo.delete(changeset)
@spec ledger_value(t()) :: validation_map()
Calculates the total value of all accounts in the ledger instance.
This function aggregates the debit and credit balances across all accounts in the instance, providing a summary of the entire ledger's financial state.
Parameters
instance- The Instance to calculate totals for (will be preloaded with accounts)
Returns
- A map containing the aggregated balances:
:posted_debit- Sum of all posted debit balances:posted_credit- Sum of all posted credit balances:pending_debit- Sum of all pending debit balances:pending_credit- Sum of all pending credit balances
Example
iex> {:ok, instance} = Repo.insert(%Instance{address: "Value Ledger"})
iex> instance = Repo.preload(instance, [:accounts])
iex> Instance.ledger_value(instance)
%{
posted_debit: 0,
posted_credit: 0,
pending_debit: 0,
pending_credit: 0
}
@spec validate_account_balances(t()) :: {:ok, validation_map()} | {:error, validation_map()}
Validates that all accounts in the ledger instance maintain balanced debits and credits.
This function checks for one of the fundamental principles of double-entry accounting: the sum of all debits must equal the sum of all credits. It verifies this separately for both posted and pending balances.
Parameters
instance- The Instance to validate (will be preloaded with accounts)
Returns
{:ok, map}- If balances are equal, with the total values{:error, reason}- If the accounts are not balanced
Example
iex> alias DoubleEntryLedger.{Account, Repo}
iex> alias DoubleEntryLedger.Stores.AccountStore
iex> alias DoubleEntryLedger.Apis.CommandApi
iex> {:ok, instance} = Repo.insert(%Instance{address: "Balanced Ledger"})
iex> {:ok, acc1} = AccountStore.create(instance.address, %{address: "account:main1", type: :asset, currency: :USD, posted: %{amount: 10, debit: 10, credit: 0}}, "unique_id_123")
iex> {:ok, acc2} = AccountStore.create(instance.address, %{address: "account:main2", type: :liability, currency: :USD, posted: %{amount: 10, debit: 0, credit: 10}}, "unique_id_456")
iex> {:ok, _, _} = CommandApi.process_from_params(%{"instance_address" => instance.address,
...> "source" => "s1", "source_idempk" => "1", "action" => "create_transaction",
...> "payload" => %{"status" => :posted, "entries" => [
...> %{"account_address" => acc1.address, "amount" => 10, "currency" => :USD},
...> %{"account_address" => acc2.address, "amount" => 10, "currency" => :USD},
...> ]}})
iex> instance = Repo.preload(instance, [:accounts])
iex> Instance.validate_account_balances(instance)
{:ok, %{
posted_debit: 10,
posted_credit: 10,
pending_debit: 0,
pending_credit: 0
}}
iex> alias DoubleEntryLedger.{Account, Repo}
iex> alias DoubleEntryLedger.Stores.AccountStore
iex> {:ok, instance} = Repo.insert(%Instance{address: "Balanced Ledger"})
iex> %Account{address: "account:main1", normal_balance: :debit, instance_id: instance.id, type: :asset, currency: :USD, posted: %{amount: 10, debit: 10, credit: 0}} |> Repo.insert()
iex> AccountStore.create(instance.address, %{name: "Test Account 2", address: "account:main2", type: :liability, currency: :USD}, "unique_id_456")
iex> instance = Repo.preload(instance, [:accounts])
iex> Instance.validate_account_balances(instance)
{:error, %{
posted_debit: 10,
posted_credit: 0,
pending_debit: 0,
pending_credit: 0
}}