Skip to content

feat(console): payroll run lifecycle onto real /orgs/:id/payroll + /payroll/:runId #66

Description

@hitakshiA

Epic: #58 · Step E (Payroll — flagship)

Rewire the console's Payroll screen off its demo-era model (contractors + rate cards + 4 explicit prove-* steps) onto the real backend's CSV run → start → live progress lifecycle. Builds on auth (#60), onboarding (#63), treasury (#65). This is the "encrypted payroll, live on our L1" demo.

Backend contract (source of truth — services/api/src/routes/payroll.ts)

POST /orgs/:id/payroll  { csv, token?:"usdc"|"eurc" }
     → 201 { runId, status:"ready"|"failed", token, tokenId,
             summary:{ total, valid, invalid, totalAmount, token, tokenId },
             items:[{ rowIndex, recipientInput, resolvedAddress, amount,
                      status:"pending"|"failed", error }] }
     csv rows = "recipient(@handle|0xaddr),amount(decimal ≤6dp)"; optional header; ≤10000 rows.
GET  /payroll/:runId    → JSON { run:PayrollRun, progress:PayrollProgressCounts, items:[…] }
     (Accept: text/event-stream) → SSE `event: progress` `data:{ runId, status, progress }` (immediate + every 2s), closes on complete|failed.
POST /payroll/:runId/start   → 202 { runId, status:"running", enqueued, totalPending, progress }
POST /payroll/:runId/pause   → 200 { runId, status:"paused", progress }
POST /payroll/:runId/resume  → 202 { runId, status:"running", enqueued, totalPending, progress }

PayrollRun = { id, orgId, status:"draft"|"validating"|"ready"|"running"|"paused"|"complete"|"failed", itemCount, totalAmount, token, tokenId, createdBy, error, createdAt, updatedAt }.
PayrollProgressCounts = { total, pending, proving, submitted, confirmed, failed, proved } (proved == confirmed). Proving is server-side + sequential — surfaced only via these counts. /start can 409 treasury_underfunded { availableAmount, requiredAmount, token, tokenId }.

Key constraint

There is no "list payroll runs" endpoint — only GET /payroll/:runId. So the screen is compose → validate → run → watch, not a run history. Persist the current/last run id in localStorage (per org) so a reload re-attaches to the in-flight run; optionally keep a short client-side list of recent run ids and hydrate each via GET /payroll/:runId.

Changes

  • lib/api.tscreatePayrollRun(orgId, { csv, token? })POST /orgs/:id/payroll; getPayrollRun(runId)GET /payroll/:runId (JSON); subscribePayrollProgress(runId, onProgress, onError) → SSE on /payroll/:runId with Accept: text/event-stream (credentialed EventSource; the URL is the same path — set the header via a fetch-based SSE if EventSource can't send Accept, else rely on the server's dual-mode + a 2s getPayrollRun polling fallback), closing on terminal status; startPayrollRun/pausePayrollRun/resumePayrollRun(runId). REMOVE the demo-era fns/paths: payrolls(GET), createPayroll(POST /payrolls), approvePayroll, proveFunded/proveApproval/proveComputation/provePolicy, payslips.
  • lib/store.tsx — remove the payrolls batch read (no list endpoint). The Payroll screen owns its run state.
  • screens/Payroll.tsx — rewrite to the real lifecycle: Compose (a CSV textarea and/or a small row editor "@handle or 0x…, amount", or CSV upload) → Validate (createPayrollRun) showing the summary + per-row resolved/failed items → Run (startPayrollRun) → live progress driven by the SSE progress counts (pending → proving → submitted → confirmed, with a bar + numbers) with Pause/Resume; handle treasury_underfunded with a clear message (amount short + link to Treasury). Replace the old 4-proof UX and the contractor/rate-card model entirely. Persist the active run id in localStorage.
  • packages/typesPayrollRun, PayrollProgressCounts, create-run response + item types matching the backend; drop the demo-era PayrollBatch/PayrollLine/proof types from the real path.
  • Demo — update demo/api.ts/demo/data.ts so VITE_DEMO_MODE=1 walks a full run: validate a seeded CSV → start → animate the progress counts to complete.

Out of scope

Hiding the no-backend nav (step B — next). Anything payments/invoices/grants.

Definition of done — run ALL, pass:

pnpm --filter @benzo/console typecheck · test · build · VITE_DEMO_MODE=1 … build · pnpm exec biome lint .

Operating rules

No git (can't commit). Match the codebase style. End with (1) a one-line conventional-commit message and (2) the exact git add <paths> list.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions