DoubleEntryLedger.Command.AccountCommandMap (double_entry_ledger v0.1.0)

View Source

CommandMap 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 AccountData schema 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

Types

t()

Type definition for AccountCommandMap struct.

Functions

Creates a changeset for AccountCommandMap validation.

Creates and validates an AccountCommandMap from the given attributes.

Types

t()

@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
end

Pattern Matching

def handle_command(%AccountCommandMap{action: :create_account, payload: payload}) do
  # payload is guaranteed to be AccountData.t()
end

Functions

actions()

base_changeset(struct, attrs)

changeset(command_map, attrs)

@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

create(attrs)

@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_account or "create_account"
  • instance_id - UUID string of the ledger instance
  • source - String identifier of the external system
  • source_idempk - String identifier for idempotency
  • payload - Map containing account data (see AccountData for requirements)

Optional Attributes

  • source_data - Additional metadata from the source system
  • update_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"}]}

to_map(command_map)

@spec to_map(struct()) :: map()

update_changeset(struct, attrs)