Cash Flow Forecast
What it is
A projection of money in and money out over the next 30, 60, 90 or 180 days, for the entity selected in the header.
- Inflows are open invoices from the
invoice_balancesview, bucketed on their due date. - Outflows are open purchase orders, assumed to be paid 30 days after the PO date, plus unpaid expenses on their expense date.
Who can open it
Needs the finance.pricing feature grant while FEATURE_GRANULAR_RBAC is on; the owner
bypasses it. With the flag off, any signed-in account can reach it by URL.
Before you start
Pick an entity first. With none selected the screen says so and shows nothing — this forecast is always per-entity.
fx_rates is empty in production, so anything not already in your chosen currency is excludedRows in another currency are converted using the closest-dated rate in fx_rates. That table
currently holds zero rows, so today every cross-currency invoice, PO and expense is left out of
the forecast entirely.
The screen tells you when this happens — a banner reads "Excluded N row(s) — no FX rate found for …". Read it. A forecast with that banner showing is a forecast of part of your business.
Nothing is guessed: a missing rate means the row is dropped, never converted at an invented rate.
The outflow dates are an assumption, not a schedule. A purchase order is assumed to be paid 30 days after it was raised. Nothing records an actual agreed payment date, so a supplier on different terms will be in the wrong bucket. The page states the assumption it used at the foot.
Only unpaid expenses count as outflows. An expense recorded as paid is history, not a projection —
and Expenses always saves with status: 'paid', so expenses
contribute nothing to this forecast in practice.
Every control on the page
| Control | What it does |
|---|---|
| Currency | USD, INR, AED or LKR. Everything is converted to this, and rows that cannot be are excluded |
| 30d / 60d / 90d / 180d | The horizon. Changing it refetches |
The entity comes from the header switcher; there is no entity control on the page itself.
What it shows:
- Summary cards — total inflow, total outflow, and net, in the chosen currency.
- A chart of the net position across the horizon.
- Expected Inflows — the invoices making up the money in.
- Expected Outflows — the purchase orders and expenses making up the money out.
- Warnings at the foot — the excluded-row count when there is one, and the outflow date assumption, always.
The header shows when the figures were computed ("Refreshed …"). Data is fetched with
cache: 'no-store', so it is current each time the page loads.
What it writes
Nothing. Every part of this screen is a read through GET /api/analytics/cashflow. There is no
control here that changes a record.
The API checks your entity access through get_my_entity_ids and returns 403 for an entity you
are not assigned to, so the entity switcher cannot be used to see another company's forecast.
When it goes wrong
"Select an entity from the top bar…" No entity chosen.
"Excluded N row(s) — no FX rate found for …" Those rows are in a currency with no rate on or
before their date. The forecast is incomplete by that many rows. Enter the auditor's rates in
fx_rates, or switch Currency to the one those documents are already in.
"Request failed (403)" You are not assigned to the selected entity.
"Request failed (…)" for anything else — nothing was computed; the number is the HTTP status.
Outflows look far too small. Two reasons, both above: expenses are only counted while unpaid and the expense screen always saves them as paid; and purchase orders are dated by an assumption rather than by real terms.
The net line disagrees with the SOA. The SOA is what is owed today; this is what is expected to land by a date. They are different questions, and the SOA is not currency-converted while this is.
Related screens
- SOA — what is owed now, per client, unconverted.
- Invoices — the inflow side.
- Purchase Orders — the outflow side.
- Expenses — the other outflow source.