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

Builds a journal draft (and its posting changesets) from a knowledge result
and a normalized event.

The journal builder is the bridge between the knowledge layer (which decides
facts and relationships) and the accounting runtime (which materializes
Elixir structs). Datalog does not construct structs; this module does.

The draft journal is `status: "draft"` and carries the selected policy,
template, event id, idempotency key, and an explanation map. The postings are
built by `Logistiki.Accounting.PostingBuilder` and validated by
`Logistiki.Accounting.InvariantValidator` before posting.

# `build`
*since 0.1.0* 

```elixir
@spec build(Logistiki.Knowledge.Result.t(), Logistiki.Event.Normalized.t()) ::
  {:ok, Logistiki.Accounting.Journal.t() | nil, [Posting.t()], map()}
  | {:error, Logistiki.Error.t()}
```

Builds a draft journal struct (with postings attached) from a knowledge
result and normalized event.

When the knowledge result has no policy (e.g. an event with no accounting
impact), returns `{:ok, nil, [], explanation}`.

## Arguments

  * `result` — `%Logistiki.Knowledge.Result{}` — the knowledge layer output.
  * `event` — `%Logistiki.Event.Normalized{}` — the flattened event.

## Returns

  * `{:ok, %Journal{}, [Posting.t()], map()}` — the draft journal, its
    postings, and the explanation map.
  * `{:ok, nil, [], map()}` — no accounting impact (policy is nil).
  * `{:error, %Error{}}` — a posting could not be built (missing role).

## Examples

    iex> {:ok, journal, postings, explanation} = Logistiki.Accounting.JournalBuilder.build(knowledge_result, normalized_event)
    iex> journal.status
    "draft"
    iex> journal.selected_policy
    "cash_deposit"
    iex> length(postings)
    2

# `build_reversal`
*since 0.1.0* 

```elixir
@spec build_reversal(
  Logistiki.Accounting.Journal.t(),
  [Logistiki.Accounting.Posting.t()],
  keyword() | map()
) :: {:ok, Logistiki.Accounting.Journal.t(), [Logistiki.Accounting.Posting.t()]}
```

Builds a reversal journal struct that exactly negates `journal`'s postings.

## Arguments

  * `journal` — `%Journal{}` — the posted journal to reverse.
  * `postings` — `[Posting.t()]` — the original journal's postings.
  * `attrs` — `keyword()` or `map()` of options:
      * `:description` — `String.t` — defaults to `"Reversal of journal <id>"`
      * `:effective_date` — `Date.t` — defaults to `Date.utc_today/0`
      * `:idempotency_key` — `String.t` — defaults to `"reversal:<original_key>"`
      * `:reason` — `String.t` — the reason for the reversal
      * `:metadata` — `map()`

## Returns

  * `{:ok, %Journal{}, [Posting.t()]}` — the draft reversal journal and its
    reversal postings.

## Examples

    iex> {:ok, reversal, postings} = Logistiki.Accounting.JournalBuilder.build_reversal(journal, original_postings, reason: "mistaken fee")
    iex> reversal.reversal_of_id
    1
    iex> hd(postings).debit_credit
    "credit"

---

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