4.9 KiB
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_paymentrequest → abookingsrow once payment is captured (b10), then set the request toconvertedthrough theBookingRequestTransitionsguard (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 tobooking_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 b5IVariantSnapshotSerializeris still the tool for the booking snapshot at conversion time. - Reuse the forward-only status-machine pattern for the
bookingsstate machine (see CONVENTIONS §6 — "Forward-only status machine"). Same shape: const codes + a staticCanTransitionedge 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; 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 inboxGET api/v1/booking_requests/list?role=customer. Show two countdowns: tonurseResponseDeadlineAt(pending) and topaymentDeadlineAt(accepted — the 30-min window). Customer canPOST …/cancel/{id}while pending or accepted-awaiting-payment. - Nurse incoming-requests inbox →
GET api/v1/booking_requests/list?role=nurse(+status=filter) withPOST …/accept/{id}andPOST …/reject/{id}(reason required). The nurse row showscounterpartyName= patient name +customerNotesonly, plus the response countdown — never a full address or any clinical field beyondcustomerNotes.
Rules the UI must respect
- Same-gender is first-class and required. Send
requiredCaregiverGenderexplicitly (never default it);male/femalemust match the nurse's gender or the create is a400.anymatches either. - Deadlines are server-frozen absolute UTC timestamps. Render countdowns from them; never recompute a
deadline client-side.
paymentDeadlineAtis 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 returns409. get/{id}is party-scoped. A non-party caller gets404(existence not leaked).
Contracts
- New:
dev/contracts/domains/booking-requests.md. swagger.v1.jsonrefreshed (addsbooking_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.