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

Builds concrete postings from a posting template and resolved account roles.

The journal builder calls this module with:

  * the template postings (from the knowledge layer)
  * the resolved account roles (role -> account code)
  * the event amount and currency
  * the journal id the postings belong to

Each template posting spec is turned into a `%Logistiki.Accounting.Posting`
struct. Symbolic roles are resolved to account codes; missing roles produce
an error.

# `build`
*since 0.1.0* 

```elixir
@spec build(
  [map()],
  %{required(atom()) =&gt; String.t()},
  Decimal.t(),
  String.t(),
  term()
) ::
  {:ok, [Logistiki.Accounting.Posting.t()]} | {:error, Logistiki.Error.t()}
```

Builds draft posting structs from `template_postings`, `account_roles`,
`amount`, and `currency`.

The postings carry `account_code`, `debit_credit`, `amount`, `currency`,
`sequence`, and role metadata but no `virtual_account_id` or `journal_id`
yet — the ledger backend resolves and sets those on persistence.

## Arguments

  * `template_postings` — `[map()]` — posting specs from the knowledge layer,
    each with `:sequence`, `:direction`, `:role`, `:amount_var`,
    `:currency_var`.
  * `account_roles` — `%{atom() => String.t()}` — resolved role-to-code map
    (e.g. `%{cash_account: "ASSETS:CASH:USD:NOSTRO"}`).
  * `amount` — `Decimal.t()` — the event amount (e.g.
    `Decimal.new("1000.00")`).
  * `currency` — `String.t()` — the event currency (e.g. `"USD"`).
  * `_journal_id` — `term()` — unused in v0.1.0 (postings are linked on
    persistence).

## Returns

  * `{:ok, [%Posting{}]}` — the built posting structs, sorted by sequence.
  * `{:error, %Error{code: :invalid_template}}` — amount or currency is
    missing/invalid.
  * `{:error, %Error{code: :account_not_found}}` — a role could not be
    resolved to an account code.

## Examples

    iex> {:ok, postings} = Logistiki.Accounting.PostingBuilder.build(
    ...>   [%{sequence: 1, direction: :debit, role: :cash_account, amount_var: :event_amount, currency_var: :event_currency},
    ...>    %{sequence: 2, direction: :credit, role: :client_liability_account, amount_var: :event_amount, currency_var: :event_currency}],
    ...>   %{cash_account: "ASSETS:CASH:USD:NOSTRO", client_liability_account: "LIABILITIES:CLIENT_DEPOSITS:USD:ACME:OPERATING"},
    ...>   Decimal.new("1000.00"), "USD", nil
    ...> )
    iex> length(postings)
    2
    iex> hd(postings).account_code
    "ASSETS:CASH:USD:NOSTRO"

# `build_reversals`
*since 0.1.0* 

```elixir
@spec build_reversals([Logistiki.Accounting.Posting.t()], term()) :: [
  Logistiki.Accounting.Posting.t()
]
```

Builds reversal postings that exactly negate `postings` (flip direction,
keep amount, currency, account, and sequence).

## Arguments

  * `postings` — `[Posting.t()]` — the original postings to reverse.
  * `_reversal_journal_id` — `term()` — unused in v0.1.0.

## Returns

  * `[Posting.t()]` — reversal posting structs with flipped `debit_credit`,
    `memo: "reversal"`, and `reversal_of_posting` in metadata.

## Examples

    iex> reversals = Logistiki.Accounting.PostingBuilder.build_reversals([%Posting{account_code: "A", debit_credit: "debit", amount: Decimal.new("100"), currency: "USD", sequence: 1}], nil)
    iex> hd(reversals).debit_credit
    "credit"
    iex> hd(reversals).memo
    "reversal"

---

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