# Backend phase 9 report — Bookings, sessions, care instructions & EVV ## What was built - **Domain (`Baya.Domain/Entities/Booking/`):** `Booking`, `BookingSession`, `BookingCareInstruction`, `VisitVerification`, `CancellationPolicy` (+ `CancellationPolicyCode` seed codes), the `BookingStatus` / `BookingSessionStatus` / `VisitVerificationStatus` / `CancellationActor` code sets, the `BookingTransitions` / `BookingSessionTransitions` guards, and `BookingAmounts` (the pure integer-money split/reconciliation). - **Persistence:** one migration `BookingsSessionsEvvCancellation` — the five `booking`-schema tables with the `gross = commission + payout` (all ≥ 0) **DB CHECK**, the `booking_request_id` / `booking_id` (care) / `booking_session_id` (EVV) UNIQUE 1:1 indexes, encrypted `address_snapshot_json` + care columns, seeded cancellation tiers + 2 new `platform_configs` rows. `BookingConfig/` configs; `BookingRepository` + `CancellationPolicyRepository` on `IUnitOfWork`; `IBookingRequestRepository` gained `GetTrackedByIdAsync` + `GetConversionSourceAsync`. - **Application (`Features/Bookings/`):** `ConvertRequestToBooking`, `SubmitCareInstructions`, `CheckInVisit`, `CheckOutVisit`, `TransitionBookingStatus`, `CancelBooking`, `CancelSession`, `DetectNoShowSessions`, `UpsertCancellationPolicy` (commands) + `GetBookingDetail`, `ListBookings`, `ListSessionsForNurse`, `GetCareInstructions`, `GetVisitVerification`, `ListAdminEvv`, `ListCancellationPolicies` (queries), plus `BookingMapper`, `CancellationHelper`, `GeoDistance`. - **Seam:** `IPaymentCaptureSimulator` (Application `Contracts/Common`) + `MockPaymentCaptureSimulator` (CrossCutting), registered in `AddCrossCuttingSeams`, config `Seams:PaymentCapture`. - **API:** `BookingsController`, `BookingSessionsController`, `AdminEvvController`, `AdminCancellationPoliciesController` (convert/cancel + admin EVV/policies are rate-limited). ## What is now testable, and exactly how (per §7 of the phase) 1. **Convert** — `POST bookings/convert` (as the owning customer) with an `accepted_awaiting_payment` request → a `confirmed` booking whose three amounts sum, snapshots are populated (address encrypted at rest), N sessions reconcile (`Σ visit_payout = nurse_payout`), request → `converted`. Re-convert → same booking. 2. **Single-visit** — a `session_count=1` request → exactly one session. 3. **Care disclosure** — submit as customer, then `GET bookings/care_instructions/{id}`: assigned nurse + admin get the decrypted fields; customer / unassigned nurse / pre-confirmation → 404. Never in list/detail. 4. **EVV** — `check_in` (in-range GPS) → session + booking `in_progress`; `check_out` → session `completed`, EVV `completed`. 5. **Mismatch** — `check_in` out-of-range → still succeeds, `check_in_address_match=false`, a `evv_location_mismatch` support alert + notification; visible in `admin_evv/list?type=mismatch`. 6. **Completion** — last `check_out` → booking `completed`, `dispute_window_ends_at = completed_at + 72h` (config), each completed session's `payout_eligible_at` set; not payout-eligible before that. 7. **Cancellation** — `bookings/cancel/{id}` resolves the tier by lead-time + actor, snapshots `code` + `refund_percentage`, refunds only un-started sessions; a later policy edit leaves the snapshot unchanged. 8. **Transition guard** — an illegal/EVV-contradicting transition → `OperationResult` failure, no state change. 9. **No-show** — `admin_evv/detect_no_shows` for an overdue scheduled session → `missed` + `no_show` alert + family notification. Automated coverage: 42 booking foundation tests (SQLite host over the real EF model + real handlers) — the three-amount split + session reconciliation, the transition guards, the two-stage disclosure gate, the advisory-mismatch-raises-alert-without-blocking path, `SetDisputeWindow` on completion, and the policy-snapshot immutability — plus one WebApplicationFactory integration test per controller (happy path / 401 / 400 / disclosure not-found). Full suite green (269 tests). `dotnet build` zero new code warnings. ## What is mocked, and how to make it real - **`IPaymentCaptureSimulator`** — see `reports/mocks-registry.md`. In b10 the real card capture calls `ConvertRequestToBooking` directly on a `payment_transactions.succeeded`; remove the seam + mock. A config switch forces a failed capture today so the "no booking on failure" path is covered. ## Decisions recorded (not in the product docs before) - The `visit_payout_amount` split places the remainder of integer division on the **last** session so `Σ = nurse_payout_amount` exactly. - The EVV-state ↔ booking-state mapping (`checked_in` ↔ session `in_progress` ↔ booking `in_progress`; all sessions settled ↔ booking `completed`). - Seeded cancellation tiers: `standard_24h` (customer ≥24h → 100%), `standard_inside_24h` (customer <24h → 50%, open lower bound so an already-started cancel still resolves), `nurse_no_show` (nurse → 100% + a modelled penalty whose posting is deferred to b13), `admin_cancellation` (admin → 100%). - `no_show_threshold_minutes` default **60**; `no_show_scan_cadence_hours` default **1**. - The cancellation snapshot (`cancellation_policy_code` / `cancellation_refund_percentage` / `refundable_amount_irr`) lives on the `bookings` row for MVP — the typed per-event/refund record lands in b11; `booking_sessions.cancellation_event_id` is a nullable column left unset until then. ## Contracts produced / consumed - Produced: `dev/contracts/domains/bookings-evv.md` + refreshed `swagger.v1.json`. Consumes b8's `booking_requests`, b5's `IVariantSnapshotSerializer`, b4's `IGeocoder` + address coords, b1's config / `support_alerts` / `INotificationDispatcher`, b0's `IFieldEncryptor` / `ICurrentUser` / `OperationResult`. ## Follow-ups - **b10** — real card capture (`payment_transactions`, ledger) → replaces the `IPaymentCaptureSimulator` trigger. - **b11** — refund execution consumes the frozen policy snapshot + `refundable_amount_irr`; adds the cancellation-event/refund records that `booking_sessions.cancellation_event_id` will reference. - **b13** — payout batching consumes `dispute_window_ends_at` / `payout_eligible_at`; posts the nurse penalty. - **b14** — reviews on a completed booking. **b15** — `partner_centers` wires `partner_center_id`. - The **no-show cron** and a **recurring dispute-window/close sweep** remain DEFERRED (hosted-scheduler pattern).