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
@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"}).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"
@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 flippeddebit_credit,memo: "reversal", andreversal_of_postingin 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"