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