Files
baya-monorepo/dev/contracts/domains/bookings-evv.md
T
2026-07-06 19:23:44 +03:30

9.1 KiB
Raw Blame History

Contract — Bookings, Sessions, EVV & Cancellation (backend phase b9)

One-line: the post-payment engagement — convert a paid request into a booking + N sessions, the two-stage care-instructions boundary, per-session EVV check-in/out, dispute-window gating, and cancellation. Assumes ../conventions/api-conventions.md + ../conventions/money-and-types.md. Machine schema: ../openapi/swagger.v1.json.

Status: live as of backend-phase-b9 · Frontend consumer: frontend-phase-f8-b9

All money is IRR Rials, integer, on the wire as a string of digits ("15000000"). The three booking amounts always satisfy gross_price_irr = balinyaar_commission_irr + nurse_payout_amount. Timestamps are UTC ISO-8601. Enums cross as their stable string codes.

Enums used

  • BookingStatus: pending_payment | confirmed | in_progress | completed | disputed | closed | cancelled.
  • BookingSessionStatus: scheduled | in_progress | completed | missed | cancelled.
  • VisitVerificationStatus: pending | checked_in | completed.
  • CancellationActor (applies_to / cancelled_by): customer | nurse | admin.

Endpoints

POST api/v1/bookings/convert

  • Purpose: The (mock) payment-capture conversion — creates the booking 1:1 from an accepted_awaiting_payment request. In b10 the real card capture calls this path directly.
  • Auth: authenticated (owning customer or admin) · Rate-limited: yes (sensitive) · Idempotent: yes (replay returns the existing booking).
  • Request body: { "bookingRequestId": 123 }
  • Success 200 (data): the Booking shape (below), status = confirmed.
  • Failure: 400 bad id, 401 unauth, 404 request not found / not the caller's, 409 request not awaiting payment / no longer convertible, 400 capture failed (no booking created).
  • Notes: computes the three amounts (commission = round(gross × platform_fee_rate), rate snapshotted), writes variant_snapshot_json + encrypted address_snapshot_json, generates ≥ 1 session with Σ visit_payout_amount = nurse_payout_amount, flips the request → converted, notifies both parties.

GET api/v1/bookings/get/{id}

  • Purpose: Booking header + money summary + sessions + timeline. Auth: authenticated, tenancy-scoped (customer own / nurse assigned / admin all). The nurse view omits addressSnapshotJson. Never includes care-instruction clinical fields. Failure: 401, 404 (not found / not a party → no leak).

GET api/v1/bookings/list?role=customer|nurse|all&status=&page=&page_size=

  • Purpose: role-scoped "My bookings" (paginated, projected). role=all is admin-only (403 otherwise). Success: PagedResult<BookingListItem>.

POST api/v1/bookings/transition/{id}

  • Purpose: admin/dispute status move. Auth: admin (403 otherwise). Body: { "targetStatus": "disputed", "reason": "…" }. Failure: 409 (illegal edge, or contradicts EVV — e.g. in_progress with no session checked in, completed with a live session), 400 (use cancel for cancellation). Completing here also opens the dispute window.

POST api/v1/bookings/cancel/{id}

  • Purpose: cancel a whole booking. Auth: owning customer / assigned nurse / admin · Rate-limited: yes. Body: { "reason": "…" }. Success: CancellationResult. Resolves + snapshots the policy (code + refund_percentage) onto the booking, cancels only un-started (scheduled) sessions, computes the refundable amount. No refund ledger is posted (b11). Failure: 400 no reason, 409 not cancellable.

POST api/v1/bookings/submit_care_instructions/{id}

  • Purpose: write/update the encrypted stage-2 booking_care_instructions. Auth: owning customer or admin, booking must be confirmed+. Body: CareInstructions (all optional strings). Failure: 409 not confirmed, 404 not found.

GET api/v1/bookings/care_instructions/{id}

  • Purpose: the gated stage-2 read. Auth: assigned nurse or admin only, post-confirmation. Any other caller (customer, unassigned nurse, pre-confirmation) → 404 (never leaks). Returns decrypted CareInstructions. This is the two-stage disclosure boundary.

POST api/v1/booking_sessions/check_in/{id}

  • Purpose: assigned nurse clocks in. Auth: assigned nurse. Body: { "latitude": 35.6892, "longitude": 51.389 } (both nullable — GPS-denied still checks in, flagged). Moves the session + booking to in_progress; computes the advisory address match against evv_location_tolerance_meters. A mismatch raises a location_mismatch support alert + notifies without blocking. Success: VisitVerification. Failure: 401, 403 (not a nurse), 404 (not the nurse's session), 409 (not startable).

POST api/v1/booking_sessions/check_out/{id}

  • Purpose: assigned nurse clocks out — must follow an open check-in. Completes the session's EVV, sets its payout_eligible_at, and — when all sessions are settled — completes the booking + sets dispute_window_ends_at. Failure: 400 no open check-in, 409 not checkout-able.

GET api/v1/booking_sessions/today?date=&page=&page_size=

  • Purpose: the nurse's sessions for a day (default all), with check-in/out CTA state. Auth: nurse, tenancy-scoped. Success: PagedResult<BookingSessionListItem>.

GET api/v1/booking_sessions/evv/{id}

  • Purpose: per-session EVV detail. Auth: owning nurse + admin only (raw GPS gated); others → 404.

POST api/v1/booking_sessions/cancel/{id}

  • Purpose: cancel a single un-started session · Rate-limited: yes. Snapshots the policy + computes the session's refundable share. Failure: 409 if the session already started.

GET api/v1/admin_evv/list?type=mismatch|no_show&page=&page_size=

  • Purpose: admin EVV-review queue. Auth: admin policy · Rate-limited: yes. Success: PagedResult<AdminEvvItem>.

POST api/v1/admin_evv/detect_no_shows

  • Purpose: the manual no-show sweep trigger (the recurring cron is DEFERRED). Auth: admin. Marks overdue scheduled sessions missed, raises no_show alerts + notifies. Success: { "missed": N }.

POST api/v1/admin_cancellation_policies/upsert · GET api/v1/admin_cancellation_policies/list

  • Purpose: admin CRUD of cancellation tiers (keyed by unique code). Auth: admin policy. Editing a policy never mutates an already-snapshotted cancellation. Failure: 400 (percentage not 0100, bad actor, min ≥ max).

Shared shapes

  • Booking (bookings/get, convert, transition): id, bookingRequestId, status (BookingStatus), nurseId, nurseName, patientId, patientName, variantId, variantSnapshotJson (string), customerAddressId, addressSnapshotJson (string, null for the nurse view), grossPriceIrr, balinyaarCommissionIrr, nursePayoutAmount, pspFeeAmount (money strings; psp nullable), platformFeeRate (decimal), sessionCount (int), scheduledDate/scheduledTimeStart/scheduledTimeEnd, confirmedAt/completedAt/cancelledAt (nullable), cancelledBy/cancellationReason/ cancellationPolicyCode (nullable), cancellationRefundPercentage (nullable decimal), refundableAmountIrr (nullable money string), disputeWindowEndsAt (nullable), createdAt, sessions[].
  • BookingSessionSummary (embedded): id, sessionIndex, schedule, status (BookingSessionStatus), visitPayoutAmount (money string), payoutEligibleAt (nullable), evvStatus (VisitVerificationStatus), checkInAt/checkOutAt (nullable), checkInAddressMatch (nullable bool).
  • BookingListItem: id, status, counterpartyName, scheduledDate, sessionCount, amountIrr (gross for customer / payout for nurse), disputeWindowEndsAt, createdAt.
  • BookingSessionListItem: sessionId, bookingId, sessionIndex, patientName, schedule, status, evvStatus.
  • CareInstructions: bookingId + currentConditions/medications/allergies/specialInstructions/ emergencyContactName/emergencyContactPhone (all nullable). Encrypted at rest; gated read.
  • VisitVerification: id, bookingSessionId, status, checkInAt/checkInLat/checkInLng, checkOutAt/checkOutLat/checkOutLng, checkInAddressMatch, checkInDistanceMeters (raw GPS gated).
  • AdminEvvItem: sessionId, bookingId, nurseId, sessionStatus, scheduledDate, scheduledTimeStart, checkInAt, checkInAddressMatch, checkInDistanceMeters.
  • CancellationResult: bookingId, sessionId (nullable), bookingStatus, policyCode, refundPercentage, refundableAmountIrr (money string).
  • CancellationPolicy: id, code, appliesTo, hoursBeforeStartMin/Max (nullable), refundPercentage, feeAmountIrr (money string), feeRate (nullable), isActive.

Changelog

  • b9 — initial contract (bookings + sessions + care instructions + EVV + cancellation; capture mocked via IPaymentCaptureSimulator, real trigger arrives with b10).