Logistiki.Accounting.PostingBuilder (logistiki v0.1.0)

Copy Markdown View Source

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.

Summary

Functions

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

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

Functions

build(template_postings, account_roles, amount, currency, journal_id)

(since 0.1.0)
@spec build(
  [map()],
  %{required(atom()) => 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"}).
  • amountDecimal.t() — the event amount (e.g. Decimal.new("1000.00")).
  • currencyString.t() — the event currency (e.g. "USD").
  • _journal_idterm() — 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(postings, reversal_journal_id)

(since 0.1.0)
@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_idterm() — 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"