DoubleEntryLedger.Command.AccountCommandMap (double_entry_ledger v0.1.0)
View SourceCommandMap implementation for account-related operations in the Double Entry Ledger system.
This module provides validation and structure for account creation commands. It extends
the base CommandMap functionality with account-specific payload validation using the
AccountData schema.
Purpose
The AccountCommandMap is responsible for:
- Validating account creation command data before persistence
- Ensuring proper structure and required fields for account operations
- Providing type safety for account-specific payloads
- Converting account data to serializable map format
Supported Actions
Currently supports:
:create_account- Creates a new account in the ledger instance
Usage
# Create a valid account command
{:ok, command_map} = AccountCommandMap.create(%{
action: :create_account,
instance_id: "550e8400-e29b-41d4-a716-446655440000",
source: "accounting_system",
source_idempk: "acc_12345",
payload: %{
name: "Cash Account",
type: :asset,
currency: "USD"
}
})
# Convert to map for serialization
map_data = AccountCommandMap.to_map(command_map)Validation
The module validates:
- All base CommandMap fields (action, instance_id, source, source_idempk)
- Action must be
:create_account - Payload must conform to
AccountDataschema requirements - Required payload fields based on account type
Error Handling
# Invalid action
{:error, changeset} = AccountCommandMap.create(%{
action: :invalid_action,
# ... other fields
})
# Missing required fields
{:error, changeset} = AccountCommandMap.create(%{
action: :create_account,
# missing required fields
})Type Safety
The module provides compile-time type checking through:
@type t :: CommandMap.t(AccountData.t())This ensures that the payload is always of type AccountData.t().
Summary
Functions
Creates a changeset for AccountCommandMap validation.
Creates and validates an AccountCommandMap from the given attributes.
Types
@type t() :: %DoubleEntryLedger.Command.AccountCommandMap{ account_address: String.t() | nil, action: :create_account | :update_account, instance_address: String.t(), payload: DoubleEntryLedger.Command.AccountData.t(), source: String.t() }
Type definition for AccountCommandMap struct.
Represents an CommandMap specifically for account operations with an AccountData
payload. This provides type safety and clear documentation for functions that
work with account commands.
Usage in Function Signatures
@spec process_account_command(AccountCommandMap.t()) :: {:ok, Account.t()} | {:error, term()}
def process_account_command(%AccountCommandMap{} = command_map) do
# Implementation with type-safe access to AccountData payload
endPattern Matching
def handle_command(%AccountCommandMap{action: :create_account, payload: payload}) do
# payload is guaranteed to be AccountData.t()
end
Functions
@spec changeset(t() | map(), map()) :: Ecto.Changeset.t(t())
Creates a changeset for AccountCommandMap validation.
This function handles action-specific validation logic. It validates the base CommandMap fields and then applies account-specific payload validation based on the action type.
Parameters
command_map- The AccountCommandMap struct to validate (can be empty for new records)attrs- Map of attributes to validate and apply
Returns
Ecto.Changeset.t()- Changeset with validation results
Validation Logic
The function switches on the action to determine validation:
:create_account- Validates base fields + requires valid AccountData payload:update_account- Validates base fields + requires valid AccountData payload for updates- Other actions - Adds error indicating invalid action for account context
Examples
iex> attrs = %{
...> action: :create_account,
...> instance_address: "Test:Ledger",
...> source: "test",
...> payload: %{name: "Test", address: "account:main", type: :asset, currency: "USD"}
...> }
iex> changeset = DoubleEntryLedger.Command.AccountCommandMap.changeset(%DoubleEntryLedger.Command.AccountCommandMap{}, attrs)
iex> changeset.valid?
true
iex> update_attrs = %{
...> action: :update_account,
...> instance_address: "Test:Ledger",
...> source: "test",
...> account_address: "account:test",
...> payload: %{description: "Updated Test Account"}
...> }
iex> changeset = DoubleEntryLedger.Command.AccountCommandMap.changeset(%DoubleEntryLedger.Command.AccountCommandMap{}, update_attrs)
iex> changeset.valid?
true
iex> invalid_attrs = %{action: :delete_account, source: "test"}
iex> changeset = DoubleEntryLedger.Command.AccountCommandMap.changeset(%DoubleEntryLedger.Command.AccountCommandMap{}, invalid_attrs)
iex> changeset.valid?
false
iex> Keyword.has_key?(changeset.errors, :action)
true
@spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t(t())}
Creates and validates an AccountCommandMap from the given attributes.
This is the primary entry point for creating account commands. It performs full validation including payload validation and returns either a valid CommandMap struct or validation errors.
Parameters
attrs- Map containing the command attributes including payload data
Returns
{:ok, AccountCommandMap.t()}- Successfully created and validated command map{:error, Ecto.Changeset.t()}- Validation errors
Required Attributes
action- Must be:create_accountor"create_account"instance_id- UUID string of the ledger instancesource- String identifier of the external systemsource_idempk- String identifier for idempotencypayload- Map containing account data (seeAccountDatafor requirements)
Optional Attributes
source_data- Additional metadata from the source systemupdate_idempk- For update operations (not used for account creation)
Examples
iex> attrs = %{
...> action: :create_account,
...> instance_address: "Test:Ledger",
...> source: "web_app",
...> payload: %{
...> name: "Test Account",
...> address: "account:main",
...> type: :asset,
...> currency: "USD"
...> }
...> }
iex> {:ok, command_map} = DoubleEntryLedger.Command.AccountCommandMap.create(attrs)
iex> command_map.action
:create_account
iex> command_map.payload.name
"Test Account"Error Examples
# Invalid action
iex> attrs = %{action: :invalid_action, source: "test"}
iex> {:error, changeset} = DoubleEntryLedger.Command.AccountCommandMap.create(attrs)
iex> changeset.valid?
false
iex> changeset.errors[:action]
{"invalid in this context", [{:value, "invalid_action"}]}