Files
baya-monorepo/dev/shared-working-context/reports/frontend-phase-12-report.md
T
2026-07-10 14:48:15 +03:30

8.3 KiB
Raw Blame History

Frontend Phase 12 — Nurse earnings & payout history — report

Date: 2026-07-10 · Track: frontend · Depends on: f8 (nurse booking detail) + the b13 payouts contract · Status: complete (mock-primary) · Gate: npm run check green · npm run test:ci green (242 tests, +3 suites) · npm run build green with NEXT_PUBLIC_API_URL set.

This is the last money-path frontend phase — it closes the loop on the nurse side ("I did the work, where is my money?") as a strictly read-only surface.

What was built

services/payouts domain (nurse read; the auth-service shape)

  • types.ts — string-literal unions + shapes derived from the b13 contract:
    • EarningsState = pending | eligible | paid | clawback_applied — a client display model (there is no single wire enum; it is derived server-side from bookings.status + dispute_window_ends_at + the payout link + clawbacks). PayoutStatus = the contract's pending | submitted | paid | failed (NB submitted, not "processing"). PayoutBatchStatus = draft | processing | partially_failed | completed | failed.
    • NurseEarningsSummary (four buckets + a signed netPayableBalanceIrr that may be negative), NurseEarningsItem (per-booking, all four states), NursePayoutHistoryItem, NursePayoutBookingLink, NursePayoutBatchContext, NursePayoutDetail, EarningsListParams, and the PayoutsApi seam (all reads, no mutations).
  • keys.tspayoutKeys with the state filter + page baked into the key (earningsList(state, page), history(page), detail(id), earningsSummary()) so each tab/page caches independently.
  • constants.tsUSE_PAYOUTS_MOCK = true (mock-primary), a MOCK_SCENARIO toggle (standard | clawback_heavy) for the negative-balance demo, generous per-read staleTimes (earnings move weekly), PAYOUTS_PAGE_SIZE.
  • apis/clientApi.ts — real HTTP impl: getNursePayoutHistory → the live GET api/v1/nurse_payouts/ history; getNurseEarningsBalance / getNurseEarnings / getNursePayoutDetail target the proposed REQ-025 slugs. apis/mockApi.ts — the primary impl (self-contained, money-correct fixtures — see below). apis/index.ts — one-line seam selector.
  • hooks/useNurseEarningsBalance / useNurseEarnings(state, page) / useNursePayoutHistory(page) / useNursePayoutDetail(id) — all read-only useQuery, keepPreviousData on the paged lists, detail disabled on an invalid id. index.ts re-exports hooks only.

Three shared, tested composites (src/components/)

  • EarningsBalanceHeader — the net payable balance + four buckets (pending/eligible/paid-lifetime/clawback off --bal-{warning,info,success,error}). A negative net renders an explicit "owed back" state (error-toned card, the magnitude only — never a bare minus).
  • EarningsRow — the three-amount breakdown framed for the nurse (gross commission = your payout, reconciled by PriceBreakdown), one of four visually-distinct state chips, and the state affordance: pending → a display-only dispute-window countdown (reuses CountdownTimer); eligible → awaiting-batch; paid → paid_at + transferReference + a payout-detail link; clawback_applied → the original clawback = net explanation. Every row deep-links to the f8 booking detail.
  • PayoutHistoryRow — net transferred + payout-status chip + period + masked IBAN (last-4, dir="ltr") + transfer ref; a failed payout shows its reason as a read-only banner (no nurse retry).

Three nurse screens (nurse shell)

  • /nurse/earnings — balance header + collapsible explainer + a Tabs state segment (All + the four states) → the earnings list (skeleton / empty / error-with-retry) + a prev/next pager.
  • /nurse/earnings/payouts — the payout history list (same state treatments) → payout detail.
  • /nurse/earnings/payouts/[id] — the reconciliation detail: batch window (Shamsi) + status chips, the money decomposition (gross_earnings clawback_applied = net_amount, plus the amount transferred), masked IBAN + transfer ref, a failure banner, and the list of covered bookings (each deep-linking to /nurse/visits/[id]).

Wiring

  • earnings nav item + a PaidOutlined icon; NURSE_EARNINGS / NURSE_EARNINGS_PAYOUTS routes + nursePayoutDetailPath / nurseBookingDetailPath helpers; the three components in the @/components barrel; a payouts i18n namespace (82 keys, en.json + fa.json in sync) + nav.earnings.

What is now testable and exactly how (phase §7)

Prereq: npm run dev (mock is primary, USE_PAYOUTS_MOCK=true), sign in as a nurse, open درآمدها.

  1. Pending — booking 5001 shows under pending / "in escrow · dispute window open" with a live countdown off disputeWindowEndsAt; it counts into the pending bucket, never as paid.
  2. Eligible → paid — booking 5002 shows eligible / "awaiting the weekly batch"; booking 5003 shows paid with paid_at (Shamsi) + a transferReference, links to the payout detail, and appears in payout history; the detail lists the exact booking(s) covered.
  3. Clawback nets the total — booking 5004 shows clawback_applied with the original clawback = net explanation (1,700,000 1,700,000 = 0). To see the negative net balance ("owed back"), set MOCK_SCENARIO = 'clawback_heavy' in services/payouts/constants.ts → the header renders the explicit owed-back state (magnitude only).
  4. Failed payout — payout 9003 (in history + at /nurse/earnings/payouts/9003) shows failure_reason invalid_sheba as a read-only banner; no retry control exists for the nurse.
  5. Money correctness — every row satisfies gross commission = your payout; Toman = IRR ÷ 10; no BNPL provider commission appears; the amount is identical for a card- vs BNPL-funded booking of the same gross.
  6. i18n / RTL / cachingfaen translates + mirrors; switching state tabs / paging shows separate cache entries (React Query Devtools) and no refetch of already-loaded data.
  7. Gatenpm run check + npm run test:ci pass.

What is mocked, and how it swaps

services/payouts is mock-primary (payoutsMockApi) because b13 serves the nurse only GET api/v1/nurse_payouts/history. The four-bucket earnings summary, the per-booking earnings list + money-state, the nurse-readable payout detail (batch context + booking links), and failureReason on the history DTO are contract gaps filed as REQ-025. payoutsClientApi already maps the live history route 1:1 and targets the proposed slugs for the rest — when REQ-025 lands, the swap is a single USE_PAYOUTS_MOCK=false flip with no hook/component change. Recorded in reports/mocks-registry.md (PayoutsApi row).

The mock fixtures are engineered to be money-correct (gross = commission + payout; net = gross clawback; Σ booking-link amounts = grossEarnings; the signed net balance computed with BigInt, not clamped) and to exercise every UI state (all four earnings states, all four payout statuses incl. a failed one, a negative net balance). Booking ids 50015004 align with the f8 bookings-store seeds so "view booking" deep-links land on real mock detail screens.

Contracts consumed

  • dev/contracts/domains/payouts.md (b13) — GET api/v1/nurse_payouts/history (live) + the enum codes (PayoutStatus, PayoutBatchStatus) and the PayoutDto/PayoutBookingLinkDto/PayoutBatchDto shapes the nurse-read analogues mirror. api-conventions.md (page/pageSize, envelope) + money-and-types.md (IRR integer digit-strings, Toman display-only, UTC → Shamsi).

Follow-ups (deferred, not built here)

  • REQ-025 (this phase's filing) — the three nurse-read endpoints + failureReason on the history DTO.
  • Admin payout console (create/process/retry batch, eligible-earnings preview, clawback write-off queue) → DEFERRED to f15.
  • Nurse bank-account add/verify (استعلام شبا) UI → already the nurse onboarding/profile phase (f2//nurse/ bank). This phase only displays the masked iban_snapshot; it never edits bank accounts.
  • On-demand / instant withdrawal, per-nurse payout-frequency settings → DEFERRED product-side (MVP is weekly).