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.
Summary
Functions
Builds a draft journal struct (with postings attached) from a knowledge result and normalized event.
Builds a reversal journal struct that exactly negates journal's postings.
Functions
@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
@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()ormap()of options::description—String.t— defaults to"Reversal of journal <id>":effective_date—Date.t— defaults toDate.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"