Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

cashflow package

Overview

Package cashflow derives a DIRECT-METHOD statement of cash flows from a double-entry book: how cash moved over one window, split into operating, investing and financing activity, with an explicit refusal where the split is not determinate.

It is the third primary financial statement. p/statements derives the other two from a chart at an instant, and p/periods derives one window's movement; all three of those packages carry a note saying they do not do this one, and all three say the INDIRECT method needs a cash-vs-accrual classification journal.Posting does not carry. That is true. The direct method needs something different and smaller: a declaration of which chart codes hold cash. A declaration is something a caller can supply, which is what makes this package possible without changing what a posting is.

The sign convention, and why cash must be an asset

A debit to a cash account is an INFLOW and a credit is an OUTFLOW. That follows from cash being debit-normal, so Policy requires every declared cash code to be a journal.Asset and refuses anything else (ErrCashAccountNotAsset). Declaring a credit-normal account as cash would invert every figure in the report and nothing downstream could tell. The cost of the rule is real and is stated rather than hidden: a bank overdraft carried as a liability cannot be declared as cash, and a caller holding one must carry it as a contra-asset or leave it out.

Attribution is per ENTRY, never per leg

This is the package's central correctness decision, and the natural design is wrong in a way that produces numbers which balance. Take one ordinary part-paid purchase:

Example
1debit  Inventory          100
2credit Cash                60
3credit AccountsPayable     40

Classifying each non-cash leg and attributing its own amount reports 100 of inventory-related outflow against the 60 of cash that actually moved. It invents a cash flow of 40 that did not happen, and the statement still adds up. So the unit of attribution is the entry:

  • An entry with NO cash legs is skipped. It is accrual-only and moved no cash. This is the direct method's whole point.
  • An entry whose net cash movement is ZERO is named in [Report.NetZero] and not classified. A transfer between two declared cash accounts is the common case; there is nothing to attribute, so determinacy is not demanded of it.
  • Otherwise the entry's net cash movement is computed from its CASH legs alone — so the figure is what actually moved — and that whole figure is attributed to one category.

The determinacy rule, stated as a refusal

An entry touching cash is determinate iff it has at least one non-cash leg and all of its non-cash legs classify to the SAME category, in which case the entry's whole net cash delta goes there. Otherwise the entry is named in [Report.Unresolved] with the reason, and its cash delta is carried in [Report.Unattributed].

The "at least one" clause is not pedantry. An entry with nonzero net cash and no non-cash leg at all cannot come out of a balanced book, and it gets its own ReasonUnbalanced rather than being reported as a conflict with nothing to conflict — see that constant.

Under that rule the purchase above is determinate when Inventory and AccountsPayable both classify operating: 60 of operating outflow, which is right. An entry that settles a loan and buys equipment at once — one leg financing, one investing — is NOT determinate, and no apportionment is invented for it. Splitting it proportionally would be a guess wearing a number's clothes.

Why classification is per CODE and not per journal.Kind

A mapping from journal.Kind to a category would need no caller declaration at all, and it is the first design anyone reaches for. It is wrong on the most common accounts in any book: receivables and inventory are assets whose movement is OPERATING, not investing, and a kind-based mapping puts the entire working-capital cycle in the wrong section. So there is no kind-based default and no default bucket of any sort. A non-cash account with no declared category makes its entry unresolved (ReasonUndeclared) — silence is not assent.

What this package does not do

  • **It never writes, and never sees a Book.** Derive returns figures; the caller owns persistence and idempotence.
  • **It cannot verify the policy.** Which codes hold cash and what each other code's movement means are caller declarations. A chart that declares a receivable as cash produces a plausible, wrong statement. The declaration is the trust boundary.
  • **It cannot verify the opening balance.** A window-filtered entry slice carries period movement, not a cumulative position, so [Report.Opening] is echoed back exactly as supplied and [Report.Closing] is only as good as it. This is p/periods' limitation inherited, not a new one.
  • **No reversal handling.** A reversing entry that touches cash moved cash, and is classified like any other entry. That differs from p/periods, where an unresolved reversal is a census item, and the difference is deliberate: a cash-flow statement reports movement, and a reversal's movement is movement.
  • **No paging and no merge.** MaxEntries is the hard bound. Merging reports across pages would make the unresolved set an artifact of the paging rather than of the data.
  • **No audit.** Every figure is derived from what the caller supplied. A chart from one book and entries from another produce a plausible report and nothing here detects it.
  • **Reconciling against p/periods proves nothing about the split.** Comparing [Report.NetChange] with the cash accounts' movement in a periods report is an arithmetic self-check that both packages added the same columns the same way. A statement that files every flow under the wrong category reconciles perfectly.

p/journal: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/journal p/periods: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/periods p/statements: /p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/statements

Function

Derive

func Derive(chart []journal.Account, entries []journal.Entry, w periods.Window, p Policy, opening int64) (Report, error)
Ask AI Claude ChatGPT

Derive builds the statement for one window.

Preconditions, all refused rather than assumed: w must be valid; chart must be strictly ascending by Code, no longer than MaxAccounts, with every Kind one of journal's five; entries must be strictly ascending by ID and no longer than MaxEntries, with at most journal.MaxPostingsPerEntry postings each; and p must satisfy the rules on Policy.

Entries outside w are skipped, not refused — a caller passing the whole book is the expected case — and their postings are therefore not content-checked, because no figure in the report depends on them. ErrInvalidPosting and ErrUnknownAccount are scoped to the window for that reason; the postings-per-entry cap is not, since it is what makes MaxEntries's cost ceiling a bound rather than a hope.

Params

Command

gnokey query vm/qeval -remote "https://rpc.onyx.testnets.gno.land" -data "gno.land/p/g1ut6uspuh73e02yauxpmyt8g3wwddaq8utagvm3/cashflow.Derive(,,,,)"

Result