# Phase 11 — Nurse pay / payouts **Blocker:** blockers.md § "Nurse pay," and closes §B.6 (real bank-transfer rail) as a side effect — see the note at the end. **Depends on:** nothing to start; benefits from [01-admin-rbac.md](01-admin-rbac.md) to actually exercise the admin-side actions with the seeded `finance` account. --- Three distinct sub-issues, all independently real. ## 11a. The mock hides four already-real endpoints `client/src/services/payouts/constants.ts:13` — `USE_PAYOUTS_MOCK = true`, with a stale comment claiming 3 of 4 nurse reads are server gaps. All four are real (`NursePayoutsController.cs:27-48`, fully implemented handlers, and `clientApi.ts:162-271` already correctly calls them). The mock/comment simply predate the server catching up (server phase merged after the client wrote the mock). **Fix:** flip `USE_PAYOUTS_MOCK` to `false`. Two secondary defects surface immediately and should land in the same change: `clientApi.ts:60` hardcodes `failureReason: null` though the wire carries it; and `previewPayoutBatch` (`clientApi.ts:205-227`) computes money client-side (sums, fabricates a processing date) — violates the client's own hard rule 18 ("the client never computes money"). The real fix needs a server-side preview endpoint (already tracked as REQ-036); until that lands, this is a documented gap, not something to silently patch client-side. ## 11b. No client entry point for the one irreversible action The real, correct implementation already exists: `AdminPayoutsController.cs:49-52`, `POST admin_payouts/batches/{id}/process` → `ExecutePayoutBatchCommand.Handler.cs` (submits to `IBankTransferProvider`, posts the ledger, nets clawbacks, idempotent). **`PayoutsApi` has no method for it at all** — grepped the whole client, nothing calls `/process`. The admin UI's scary-looking "Run" button with a typed confirmation (`admin/payouts/page.tsx:220-235`, `useRunPayoutBatch.ts`) actually calls **Generate** (`POST admin_payouts/batches`, draft-only, moves no money) — the UX implies it sends money; it doesn't. A generated batch sits in `draft` forever with no way to advance it. **Fix:** add `processPayoutBatch(batchId)` to `PayoutsApi` (both `clientApi.ts`/`mockApi.ts`), a `useProcessPayoutBatch` hook, and a distinct, clearly-separate "Process" action on `admin/payouts/[batchId]/page.tsx` (gated on `caps.canPayout`). Relabel the existing "Run"/"payout_run" copy so it's honestly "Generate a draft batch," not "send money." Bundle in the same change: `POST admin_payouts/{id}/mark_failed` also exists server-side with no client op — add the console action to record a manually-reconciled bank rejection (this is also the forgotten-features.md item "no way to confirm a payout succeeded or failed"). *(This is also where blockers.md §B.6's "mock the bank-transfer payment pages for now" mostly resolves itself: `IBankTransferProvider`/`MockBankTransferProvider` already exist server-side — the "mock" is already built and DI-registered. What's actually missing is exactly 11a/11b/11c — the client UI in front of that existing mock. No new bank-transfer mock needs to be invented; de-mocking + adding the process/mark-failed actions above is the whole of it.)* ## 11c. "Paid" is set at link-time, not bank-confirmation-time `PayoutRepository.cs:279`, inside `DeriveEarningsState`: ```csharp if (payout is not null) return NurseEarningsState.Paid; ``` `payout` is non-null as soon as `GeneratePayoutBatchCommand` links the booking to a batch (`GeneratePayoutBatchCommand.Handler.cs:107-112`) — while `NursePayout.Status` still defaults to `Pending`. The struct carries `Status` already; it's fetched and never read here. **Fix:** gate on `payout.Status == PayoutStatus.Paid` instead — the definition the codebase already treats as authoritative elsewhere (`NursePayoutLinkStatusService.cs:22-33`). For the mocked bank rail this'll be set at `process` time; for a real async rail it should only flip via the reconciliation webhook (`WebhooksPayoutsController.cs:33-43`). **Flag:** once fixed, a linked-but-unpaid booking falls into **no** bucket at all in the 4-state summary model (`pending`/`eligible`/`paid`/`clawback_applied` — none currently represents "batched, awaiting settlement"). Needs a product decision: add a state, or fold it into `eligible`. Separately flagged (needs its own look, not necessarily the same root cause): the earnings-balance buckets and the raw ledger net don't reconcile on a live probe — which one is authoritative isn't documented; don't pick one silently.