Files
baya-monorepo/mvp/blocker-phases/11-nurse-payouts.md
T
2026-08-02 20:37:14 +03:30

4.5 KiB

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 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:13USE_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}/processExecutePayoutBatchCommand.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.)

PayoutRepository.cs:279, inside DeriveEarningsState:

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.