Skip to content

P-04b · Staff: the Validation Work page (stage-first layout) ​

SOP: Payroll_Processing.md §6 / Step 3.0–4.0 (validation and internal review). This page describes how staff work Gate 1 on the console; the SOP is unchanged. The rules themselves are in P-04. Actors: Payroll Executive, Payroll Lead (and Admin / Platform admin). Pre-state: a payroll month in SUBMITTED, ACTION_REQUIRED or READY_FOR_INTERNAL_REVIEW, staff-opened or agent-run. Post-state: the review is confirmed; the month is CLIENT_REVIEW with the client approval drafted, and today's page takes over.

Since 31 Oct 2026 (branch feat/work-page-stage-first) a month at Validation opens on the same stage-first frame as Collecting inputs (P-02b). Every later stage still opens today's page. Add ?layout=classic to the URL to see today's page for any month.

What changed in the system (not only the page) ​

Validation runs again after every staff change (decision V2). Before, a decision at Validation changed nothing until someone pressed Re-check, so the page said "stops the sign-off" about findings that would clear on the next run. Now every route that approves, rejects, corrects, reopens, fills in or undoes a change, resolves or overrides a warning, confirms or takes back an increment or records its amount, accepts a pay movement, records an IRAS clearance, remittance or separate payment, authorises a bank account, or applies an employee list, writes a validation-run outbox row in its own transaction (apps/api/src/lib/payroll-revalidate.ts). The worker's run then re-evaluates every rule and moves the month between Action required and Ready for review by itself. Overridden and acknowledged findings survive the run; a Lead's movement acceptance holds for the figures as they are.

Only on a month no agent run governs and only at ACTION_REQUIRED / READY_FOR_INTERNAL_REVIEW (a SUBMITTED month already has its first run queued; past Validation the drift check applies instead). An agent-run month keeps Re-check on the Validation line.

Each run records why it ran (result_summary_json.trigger: "approved a change", "overrode a warning", "when intake was confirmed", "when Run validation now was pressed") and who. The compliance payload's validationChecks gained lastRun (when, why, who, which rules' results changed since the run before) and pendingRun (an unprocessed outbox row exists); the page polls while one is pending. The runless checklist is now computed at all three Validation statuses, not only Ready for review.

What the page shows, top to bottom ​

  1. Header. Company · month, the "Validation" pill, assignee, pay-by date, when the month reached Validation, how long it has waited; the opened-late banner when it applies.
  2. This stage's job. "Make sure the month's pay is right before the client sees it." One line on what ends it.
  3. Open now. Tiles: people who stop Confirm review (red), people needing a Lead, waiting on the client, to accept at sign-off, month-wide items. Each person is in one tile: someone whose stop is a Lead's call sits in the Lead tile, whose hint says "N of them stop Confirm review until a Lead acts". Each jumps to the list and tab. While the first run is still out (SUBMITTED) a Validation is running card shows instead, with Run validation now for a run that never came back.
  4. New since you last opened. As on the inputs page.
  5. Validation line. When the rules last ran and why ("last ran 1m ago, after Sarah accepted a pay movement"), four counts of rules ("9 rules passed · 1 rule stops it · 3 rules to accept"; the tiles count people), See all N rules (a table: rule, result, who it is about, note; rows whose result the last run changed are marked), Ask the client about N open items (one numbered message), and Run validation now / Re-check. While a run is queued it says running now.
  6. Employee list. The inputs rows plus everyone a rule finding names. Each row shows last month → this month, the difference, and one line: stops Confirm review · decide the change, your call as a Lead, accept at sign-off, with a reason, waiting on the client · moves to next month at sign-off, fine. Tabs: To decide, Waiting on client, Needs a Lead, Accept at sign-off (this stage only), Done, All. Opening a row shows the pay reason, standing (in this month / moves to next month / final pay held) and the CPF and net estimate; then every finding on the person with its action: approve or reject a change, Accept this movement… (Lead, with a reason) or Escalate to Lead… (Executive: passes the approved change that moved the pay to a Lead with a note), Ask client, Resolve… / Override…, the leaver's withheld final pay with Record clearance, Confirm IRAS remittance, Record separate payment, a confirmed increment with Take back…. A Pay result switch in the heading shows the whole roster as a table: last month, this month, difference, why, and the total (which leaves out held final pay and a held joiner, and says so). The "In this month?" column appears only when someone is out; otherwise the total row says everyone is in. Two identical parts read "2 × Claims reimbursement 61.30".
  7. Needs attention. Only month-wide things: one About the whole month card holding the rule findings about the whole month (total pay moved over its limit, CPF table not verified, CPF ceiling not checked) and the issues about it (the sign-off aging warnings, client messages), then the employee-list and attendance cards from inputs. Hidden when empty.
  8. Documents, folded: "N received · nothing needs a look". It opens itself when a document needs a look or a new one arrived. Add a document for the client lives inside.
  9. Background, folded: attendance once every row is decided ("Attendance · Oct 2026 · 7 approved · reopen a row here if one was approved by mistake", the same table with Reopen… on each approved row; also on the inputs page), closed warnings with Reopen, Cancel this month, the full cycle page.
  10. Bottom bar, fixed: "N things stop Confirm review · Show me" (the list on hover), or "Nothing stops it. N people have warnings to accept with a reason when you sign." Confirm review… opens today's sign-off dialog (warnings accepted with a reason each, movements a Lead accepts, changes moving to next month) plus What happens next (the draft's total; nothing is sent). On an agent-run month the review's Request changes / Reject sit beside it. The button says why it is disabled: a run pending, things that stop it, a role that cannot sign, a Lead-only flag.

Who may do what ​

  • Any payroll operator decides changes, resolves staff tasks, confirms increments, records a clearance.
  • A Payroll Lead overrides a rule finding, accepts a pay movement, authorises a bank account in another name, and clears Lead-only warnings. For an Executive these show as stops Confirm review · a Lead and the sign-off waits.
  • The sign-off itself: Executive or Lead on a staff-opened month; on an agent-run month PAY_APPROVE_VALIDATION_ROLES (the server refuses anyone else and the page says so first).

Where each action went (from today's page) ​

Today's pageStage-first page
Validation summary + checks recordValidation line (+ rules table)
Pay this monthPay result view of the Employee list; each row's figure line
Needs clarificationOn the person's row; month-wide ones in Needs attention
Proposed changes tableThe person's row
Statutory changesThe person's row (increment confirm / amount / take back); drift findings on the person they name
Pay withheld for IR21The leaver's row, folded
Movement acceptance (Lead)Accept this movement… on the person's row
Re-check buttonGone on a staff-opened month (runs by itself); Run validation now on the line; Re-check stays on an agent-run month
Confirm reviewThe bar

Added on 1 Nov 2026 (gap review) ​

  • Each blocking month-wide warning is its own reason in the bar ("2 things stop Confirm review"), named on hover, with a red tile "month-wide items stop Confirm review" that Show me lands on. The server's "N open blocking issues" sentence is left out only when the page already lists that many.
  • Confirming intake closes the day-7 "No inputs yet" warnings (staff's and the Lead's), recorded as INTAKE_CONFIRMED; they no longer block this page.
  • An escalated rule warning stays escalated across validation runs while the rule still fails: same row, Lead-only, note kept. Before, the next run dropped all three.
  • Warnings accepted at the sign-off show under Closed warnings as "Accepted at sign-off" and can be reopened (a figure check only after the month is sent back, as for an override).
  • Override shows only on validation findings; on a staff task it did exactly what Mark resolved does.
  • The sign-off dialog lists the people the page calls "accept at sign-off" that are not warnings on the list ("Also accepted with this sign-off"), and then asks for the reason.
  • Run validation now says, on hover, that pressing it counts as preparing the month.
  • Dates line, deadline-at-risk tile and the preparers note as on the inputs page.

Not on this page by design ​

  • A first-month "acknowledge the roster against the client's list" step (V15): there is no server record for it yet.
  • The baseline month (client silent, standing instruction) card: shown through its existing issue, not a special card.
  • Rohan's 80% rule: still not built.

Manual test (seeded stage companies, built app) ​

Run the built api, web and worker (the re-run needs the worker's outbox poller, about once a minute); sign in as executive@spade.local / lead@spade.local.

  1. Open Dev Exception 03 Raise Above Limit, October. As Executive: one person stops Confirm review (salary over the limit, a Lead); the bar's button is disabled and says why. As Lead: open the row, Accept this movement…, give a reason. The bar confirms; the Validation line says running now; within about a minute it says last ran … after ‹you› accepted a pay movement, and the stop tile is gone.
  2. Confirm review…: the dialog lists the two aging warnings to accept with a reason and What happens next. Confirm. The month moves to Client approval and today's page takes over.
  3. Open Dev Exception 26 Duplicate Receipts (Action required). As Lead: open the row, the duplicate-receipt finding, Override… with a reason. Within a minute the month is Ready for review by itself; the line says The last run changed: Duplicate receipts.
  4. Switch the Employee list to Pay result: the whole roster, with the total and what it leaves out.

Internal use only — BreezyCorp