# Handoff — after backend-phase-8 (Booking requests · pre-payment intent) **The booking-request lifecycle is live end-to-end.** A customer can request a nurse; the nurse accepts (opening a config-driven 30-minute payment window) or rejects; both sides read their role-scoped inbox and a single request; unanswered/unpaid requests auto-expire on a recurring sweep (also an admin manual trigger). **No money and no `bookings` row exist yet** — that conversion is b9/b10. ## What b9 must consume (the next backend phase) - **Convert an `accepted_awaiting_payment` request → a `bookings` row** once payment is captured (b10), then set the request to `converted` **through the `BookingRequestTransitions` guard** (`request.MarkConverted()` already exists and is the only sanctioned path; b8 never writes this edge itself). The request↔booking link is 1:1 and b9-owned. - **b8 deliberately exposes only `customer_notes` (stage 1).** The full **encrypted** clinical/care instructions are b9's **stage 2** (`booking_care_instructions`, readable only post-confirmation by the assigned nurse + admin). Do **not** add an encrypted clinical field to `booking_requests`. - **The money split, snapshots, sessions, EVV, dispute window** are all b9/b10 — b8 persists **no** `variant_snapshot_json`/`address_snapshot_json`, no price, no ledger entry. The b5 `IVariantSnapshotSerializer` is still the tool for the booking snapshot at conversion time. - **Reuse the forward-only status-machine pattern** for the `bookings` state machine (see CONVENTIONS §6 — "Forward-only status machine"). Same shape: const codes + a static `CanTransition` edge table + entity-owned transitions + handler pre-check → 409. ## What f7-b8 can now build (frontend) All routes are **action-style** (`[controller]/[action]`, snake_case) with the standard `{ succeeded, statusCode, data }` envelope. Full shapes in [`dev/contracts/domains/booking-requests.md`](../../contracts/domains/booking-requests.md); types come from the refreshed `swagger.v1.json` — **do not guess**. - **Request form (C4)** → `POST api/v1/booking_requests/create` (customer). Body: `nurseId`, `variantId`, `patientId`, `customerAddressId`, `requestedDate` (`YYYY-MM-DD`), `requestedTimeStart`/`requestedTimeEnd` (`HH:mm:ss`), `requiredCaregiverGender` (`male`|`female`|`any`, **required**), `customerNotes` (≤ 1000, the **only** clinical text the nurse sees). Inputs come from search (nurse+variant), patients (b3), addresses (b4). - **Awaiting-acceptance / status tracker (C5)** → `GET api/v1/booking_requests/get/{id}` + the customer inbox `GET api/v1/booking_requests/list?role=customer`. Show two countdowns: to `nurseResponseDeadlineAt` (pending) and to `paymentDeadlineAt` (accepted — the 30-min window). Customer can `POST …/cancel/{id}` while pending or accepted-awaiting-payment. - **Nurse incoming-requests inbox** → `GET api/v1/booking_requests/list?role=nurse` (+ `status=` filter) with `POST …/accept/{id}` and `POST …/reject/{id}` (reason required). The nurse row shows `counterpartyName` = patient name + `customerNotes` only, plus the response countdown — **never** a full address or any clinical field beyond `customerNotes`. ## Rules the UI must respect - **Same-gender is first-class and required.** Send `requiredCaregiverGender` explicitly (never default it); `male`/`female` must match the nurse's gender or the create is a `400`. `any` matches either. - **Deadlines are server-frozen absolute UTC timestamps.** Render countdowns from them; never recompute a deadline client-side. `paymentDeadlineAt` is null until the nurse accepts. - **Two-stage disclosure.** The nurse detail view returns a **masked** address (city/district only, `addressLine`/`postalCode`/recipient are null); the customer/admin view returns the full address. Do not expect (or request) clinical instructions here — they don't exist until b9. - **Status drives the UI.** Terminal states (`converted`/`rejected_by_nurse`/`expired_no_response`/ `payment_deadline_expired`/`cancelled_by_customer`) allow no further actions; a stale action returns `409`. - **`get/{id}` is party-scoped.** A non-party caller gets `404` (existence not leaked). ## Contracts - New: [`dev/contracts/domains/booking-requests.md`](../../contracts/domains/booking-requests.md). - `swagger.v1.json` refreshed (adds `booking_requests/{create,accept,reject,cancel,list,get}` + `admin_booking_requests/expire` + `BookingRequestDto`/`BookingRequestListItemDto`/`ExpireBookingRequestsResult`). ## Nothing new is mocked b8 introduces **no** external seam and adds **no** new `mocks-registry.md` row. It reuses `IPlatformConfig`, `INotificationDispatcher`, `IJobScheduler`/`BackgroundService`, `IDateTimeProvider`, `ICurrentUser`. The expiry sweep is an internal hosted service (the registry's `IJobScheduler` row was updated to note the second job), not an external integration.