# `Logistiki.Accounting.Journal`
[🔗](https://github.com/thanos/logistiki/blob/v0.1.0/lib/logistiki/accounting/journal.ex#L1)

A journal is an internal accounting artifact created by the runtime from a
business event. Journals are not the main application-facing API; applications
publish business events.

## Statuses

  * `draft` — built but not yet posted
  * `validated` — passed invariant validation
  * `posted` — immutable, persisted, executed by the ledger backend
  * `reversed` — fully reversed by a later reversal journal
  * `rejected` — preserved for audit

## Fields

  * `id` — `integer()` — primary key
  * `event_id` — `String.t() | nil` — the originating business event id
  * `external_id` — `String.t() | nil`
  * `source_system` / `source_type` / `source_id` — `String.t() | nil` — provenance
  * `selected_policy` — `String.t() | nil` — e.g. `"cash_deposit"`
  * `selected_template` — `String.t() | nil`
  * `description` — `String.t() | nil`
  * `status` — `String.t()` — one of `statuses/0` (default `"draft"`)
  * `effective_date` — `Date.t() | nil`
  * `posted_at` — `DateTime.t() | nil` — set when posted
  * `reversed_at` — `DateTime.t() | nil` — set when reversed
  * `reversal_of_id` — `integer() | nil` — self-reference for reversals
  * `idempotency_key` — `String.t() | nil` — unique; prevents duplicate posting
  * `explanation` — `map() | nil` — full knowledge-layer explanation
  * `metadata` — `map()`
  * `postings` — `[Posting.t()]` — has_many association
  * `inserted_at` / `updated_at` — `DateTime.t()`

## Rules

  * posted journals are immutable
  * rejected journals are preserved for audit
  * reversals create a new journal that exactly negates the original postings
  * the idempotency key prevents duplicate posting

## Example

    %Logistiki.Accounting.Journal{
      id: 1,
      event_id: "evt_001",
      selected_policy: "cash_deposit",
      status: "posted",
      effective_date: ~D[2026-07-07],
      posted_at: ~U[2026-07-07 12:00:00Z],
      idempotency_key: "evt:evt_001:policy:cash_deposit"
    }

# `t`

```elixir
@type t() :: %Logistiki.Accounting.Journal{
  __meta__: term(),
  description: String.t() | nil,
  effective_date: Date.t() | nil,
  event_id: String.t() | nil,
  explanation: map() | nil,
  external_id: String.t() | nil,
  id: integer() | nil,
  idempotency_key: String.t() | nil,
  inserted_at: DateTime.t() | nil,
  metadata: map() | nil,
  posted_at: DateTime.t() | nil,
  postings: [Logistiki.Accounting.Posting.t()] | Ecto.Association.NotLoaded.t(),
  reversal_of: term(),
  reversal_of_id: integer() | nil,
  reversed_at: DateTime.t() | nil,
  selected_policy: String.t() | nil,
  selected_template: String.t() | nil,
  source_id: String.t() | nil,
  source_system: String.t() | nil,
  source_type: String.t() | nil,
  status: String.t() | nil,
  updated_at: DateTime.t() | nil
}
```

The `Journal` struct type — an internal accounting artifact created by the
runtime from a business event.

## Fields

  * `id` — `integer() | nil` — primary key (e.g. `1`)
  * `event_id` — `String.t() | nil` — originating event id (e.g. `"evt_001"`)
  * `external_id` — `String.t() | nil` — external reference
  * `source_system` — `String.t() | nil` — e.g. `"bank_core"`
  * `source_type` — `String.t() | nil` — e.g. `"deposit_received"`
  * `source_id` — `String.t() | nil` — e.g. `"wire_123"`
  * `selected_policy` — `String.t() | nil` — e.g. `"cash_deposit"`
  * `selected_template` — `String.t() | nil` — e.g. `"cash_deposit"`
  * `description` — `String.t() | nil` — e.g. `"deposit_received via cash_deposit"`
  * `status` — `String.t() | nil` — e.g. `"draft"`, `"posted"`, `"reversed"`
  * `effective_date` — `Date.t() | nil` — e.g. `~D[2026-07-07]`
  * `posted_at` — `DateTime.t() | nil` — e.g. `~U[2026-07-07 12:00:00Z]`
  * `reversed_at` — `DateTime.t() | nil`
  * `reversal_of_id` — `integer() | nil` — self-reference for reversals
  * `idempotency_key` — `String.t() | nil` — unique duplicate-prevention key
  * `explanation` — `map() | nil` — full pipeline explanation
  * `metadata` — `map() | nil` — extensible metadata
  * `postings` — `[Posting.t()] | Ecto.Association.NotLoaded.t()` — postings
  * `inserted_at` — `DateTime.t() | nil`
  * `updated_at` — `DateTime.t() | nil`

## Example

    %Logistiki.Accounting.Journal{
      id: 1, event_id: "evt_001", selected_policy: "cash_deposit",
      status: "posted", effective_date: ~D[2026-07-07]
    }

# `posted?`
*since 0.1.0* 

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

True when the journal is posted (and therefore immutable).

## Arguments

  * `journal` — `%__MODULE__{}` or any term.

## Returns

  * `boolean()` — `true` only when `status == "posted"`.

## Examples

    iex> Logistiki.Accounting.Journal.posted?(%Logistiki.Accounting.Journal{status: "posted"})
    true
    iex> Logistiki.Accounting.Journal.posted?(%Logistiki.Accounting.Journal{status: "draft"})
    false

# `reversal?`
*since 0.1.0* 

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

True when the journal is a reversal of another journal.

## Arguments

  * `journal` — `%__MODULE__{}` or any term.

## Returns

  * `boolean()` — `true` when `reversal_of_id` is not nil.

## Examples

    iex> Logistiki.Accounting.Journal.reversal?(%Logistiki.Accounting.Journal{reversal_of_id: 5})
    true
    iex> Logistiki.Accounting.Journal.reversal?(%Logistiki.Accounting.Journal{reversal_of_id: nil})
    false

# `statuses`
*since 0.1.0* 

```elixir
@spec statuses() :: [atom(), ...]
```

Returns the list of allowed journal statuses as atoms.

## Returns

  * `[atom()]` — `[:draft, :validated, :posted, :reversed, :rejected]`

## Examples

    iex> :posted in Logistiki.Accounting.Journal.statuses()
    true

---

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