Files
baya-monorepo/dev/shared-working-context/reports/backend-phase-9-report.md
T
2026-07-06 19:23:44 +03:30

6.4 KiB

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. ConvertPOST 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. EVVcheck_in (in-range GPS) → session + booking in_progress; check_out → session completed, EVV completed.
  5. Mismatchcheck_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. Cancellationbookings/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-showadmin_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. b15partner_centers wires partner_center_id.
  • The no-show cron and a recurring dispute-window/close sweep remain DEFERRED (hosted-scheduler pattern).