Freshness and history

How often the numbers update and how far back, what a month still in progress means, and why a closed month can never be rewritten underneath you.

Updated 6 min read

Finance → Profitability never computes anything while you wait. Every figure is read from stored monthly numbers, which is why a twenty-four-month view loads instantly — and why it is worth knowing exactly when those numbers were last updated.

The client profitability report

The current and previous month update within minutes#

When time is logged or edited, or an invoice, payment, expense or agreement changes, the month it belongs to is rebuilt a few minutes later. A burst of changes (a timesheet of twenty entries, a QuickBooks sync) costs one rebuild, a few minutes after the last change.

A scheduled job also runs every night at 04:00, after the day's QuickBooks sync and payment pulls have landed, and rebuilds the current month and the previous month for every organization. That catches anything the quick refresh cannot see, such as tickets closed or tasks completed.

Both cover the same two months, deliberately: those are the months late time entries, payments and QuickBooks rows actually land in. It has a consequence worth stating plainly: older months are frozen until someone rebuilds them. A correction dated three months back does not appear on this page by itself (see Rebuilding history yourself below).

The page header shows "Updated ", taken from the most recent write. On a working day it is usually minutes; if it says more than a day, the nightly update has not run.

Rebuilding is always in place: the month is deleted and recomputed from source records, so a stored month always equals a fresh query over its records and any correction is just a rebuild. Running it twice changes nothing.

Settings apply when you read, not when you rebuild#

Most Profitability settings don't queue a rebuild:

  • Count revenue from is applied to the stored numbers as the page is read.
  • The estimated team cost assumptions price the stored counts as the page is read — which is why changing the team cost rate restates every month at once, closed months included.
  • The signal limits are applied by the next scan.

Two settings are the exception, because they decide how money is shared when a month is built: Share agreement fees across projects and people and Where project revenue comes from. Changing either rebuilds the stored months, and project and team figures update within a few minutes.

So a settings change is visible immediately, everywhere, with no waiting.

Months still in progress#

A month is still in progress while it is still running — in practice, the current month. These months are marked with a * in the month-by-month charts, and the caption says so: "Months marked * are still in progress."

Read a month in progress with care, because its revenue and its cost do not arrive at the same speed:

  • A monthly retainer's invoice usually lands on day one to day five, so revenue arrives early.
  • Team cost builds every working day, so cost arrives evenly.
  • Expenses arrive whenever QuickBooks syncs, so client expenses arrive late (and they count only when Include client expenses in return on time is switched on in the Profitability settings).

Mid-month, that makes the return look flattering and, near month end, sometimes worse than it finishes. A dip at the right-hand end of a trend line is usually "the month isn't over", not a client going bad.

Signals never read a month in progress. At risk, Room to grow and Earning below your usual read the last completed month, and Takes more than it pays the last three completed months, for exactly this reason.

Closed months are frozen#

Each month's agreement population is snapshotted as it stood — status, effective dates, amount, billing cycle, whether it was paused, excluded or lapsed. The month's stored numbers read that snapshot, not today's agreements.

So renewing, completing, repricing or deleting an agreement today cannot rewrite what last quarter's expected revenue was. Rebuild an old month a year from now and you get the same numbers.

The mechanics:

  • Snapshots are captured nightly at 03:00, an hour before the update, so a rebuild always reads a fresh population.
  • The month is finalized on the 1st, within a 30-hour window after it ends: agreements that joined late are added, and what the month already observed is never dropped. After that window the month is frozen.
  • A rebuild of the current or previous month may re-capture. A rebuild of any older month never does.

When a month has no snapshot#

A month that was never snapshotted — months predating the feature, or a organization that has not backfilled — has no agreement population to read. Rebuilding such a month reports zero expected revenue rather than inventing one from today's agreements. Invoiced revenue, costs, return and work items are unaffected; only the expectation is missing, so those months read as if you were counting invoices only.

That is why finance:snapshot-backfill exists: it reconstructs the population for a historical range explicitly, so you can see that you did it. Backfill first, then rebuild, and the expectation comes back.

The rebuild command warns you when this happens, naming the affected months and the exact command to run: php artisan finance:snapshot-backfill --from=YYYY-MM --tenant=SLUG.

Work items are counted per rebuilt month#

Work-item counts exist only for months rebuilt since counting shipped. A month without them is treated as unknown, not zero — it is dropped from both the client's items and the work total of every share, and every surface says how many months it had to leave out:

  • The Clients tab: "N earlier months in this period were built before work was counted and are left out of every share below."
  • A Share of work cell with no counted work reads —, never 0%.
  • The Signals tab says the "takes more than it pays" check was not run rather than reporting "nothing found".

Rebuilding those months fills them in.

Rebuilding history yourself#

Two commands, both per organization:

  • finance:rollup --from=YYYY-MM walks every month from there to now. Use it after a backfill, after correcting historical time or invoices, or to fill in work items for older months.
  • finance:rollup --month=YYYY-MM rebuilds exactly one.

Both refuse to re-capture agreement snapshots for historical months, so neither can quietly rewrite history.

Worked example — the same month, twice#

Rivera Group, June 2026, with Include client expenses in return on time switched on, looked at on two different days.

Read on 14 June — still in progress. INV-1042 was issued on the 5th and settled on the 9th, so the whole retainer and its card fee are already in. Half the month's time is logged, and QuickBooks has not yet synced the stock-photo licence.

invoiced         $6,000.00
expected         15.0 h billable × $150.00 = $2,250.00
revenue          $8,250.00
team cost        68.0 h → $3,510.00
client expenses  $174.30   (card fee only)
return           $8,250.00 − $3,510.00 − $174.30 = $4,565.70
return %         55.3%

The row is marked *.

Read on 1 July — final. The month is complete: all 121.5 hours, all the T&M work in progress, and the licence.

invoiced         $6,000.00
expected         26.0 h billable × $150.00 = $3,900.00
revenue          $9,900.00
team cost        121.5 h → $6,437.00
client expenses  $594.30   (card fee + $420.00 licence)
return           $9,900.00 − $6,437.00 − $594.30 = $2,868.70
return %         29.0%

Nothing went wrong between the two readings. The mid-month figure was not an error and the final one is not a correction — the retainer arrived on day five and the cost of delivering it arrived over thirty days. That is the whole reason the * exists, and the reason no signal is ever raised from a month still in progress.

If a client's return looks alarming and today is the 10th, look at the * before you look at the client.

Bring your whole services business together

Stop stitching together five tools. Run sales, delivery, support, and billing from one organization — with an AI assistant on every page.