Files
baya-monorepo/product/data-model/05-booking-and-scheduling.md
T
2026-06-24 01:32:46 +03:30

8.3 KiB
Raw Blame History

Domain 5 — Booking & Scheduling

← Database Model

Related: business requirements — Booking & scheduling.

Two distinct phases: the request phase (pre-payment intent) and the booking phase (post-payment commitment). The previous model's biggest domain gap — single-visit-only bookings — is fixed here with booking_sessions, because elder care is dominantly multi-day / live-in.

booking_requests [CORE]

Role: A customer's pre-payment intent for a nurse, service, date, and time. No money involved. Why separate from bookings: a request may be rejected, expire, or have its payment window lapse without a booking ever existing — merging would mean many nullable fields and tangled status logic. The deadlines are computed once and stored so they're immune to later config changes.

Field Type Notes
id, customer_id, nurse_id, patient_id, variant_id, customer_address_id Baseline FKs. Tenancy invariant: patient & address belong to customer_id; variant belongs to nurse_id.
required_caregiver_gender NVARCHAR(10) NULL NEWmale/female/any. Same-gender care is decisive in Iranian bodily-care; surfaced as a first-class filter and matched against nurse gender.
requested_date, requested_time_start, requested_time_end For multi-day engagements these are the engagement start; sessions carry the per-visit schedule.
customer_notes NVARCHAR(1000) NULL Unencrypted, request-stage only — the only clinical context the nurse sees before accepting (Principle 6).
status NVARCHAR(50) pending_nurse_responseaccepted_awaiting_paymentconverted / rejected_by_nurse / expired_no_response / payment_deadline_expired / cancelled_by_customer
nurse_response_deadline_at DATETIME2 From config at creation; auto-expire after.
payment_deadline_at DATETIME2 NULL Set on accept; 30-min window.
nurse_rejection_reason NVARCHAR(500) NULL
timestamps

Relations: N:1 → customer_profiles, nurse_profiles, patients, nurse_service_variants, customer_addresses; 1:1 → bookings (on conversion).

bookings [CORE]

Role: The confirmed engagement — exists only when the nurse accepted and payment was captured. Source of truth for the service event and its money split. Why snapshots: variant_snapshot_json and address_snapshot_json freeze the service and address at booking time, so later edits/deletes can't corrupt history or disputes.

Field Type Notes
id BIGINT PK
booking_request_id BIGINT FK UNIQUE 1:1 — the request that created it
customer_id, nurse_id, patient_id, variant_id, customer_address_id Denormalized for query performance
variant_snapshot_json NVARCHAR(MAX) Variant + option labels at booking time
address_snapshot_json NVARCHAR(MAX) (enc) Full address at booking time
partner_center_id BIGINT FK → partner_centers NULL NEW — the licensed center legally covering this visit / merchant-of-record
gross_price_irr BIGINT CHANGED (renamed) — total charged to customer
balinyaar_commission_irr BIGINT CHANGED (renamed from platform_fee_amount) — platform's own cut
platform_fee_rate DECIMAL(5,4) Rate snapshot for audit
nurse_payout_amount BIGINT = gross_price_irr balinyaar_commission_irr. CHECK enforced.
psp_fee_amount BIGINT NULL NEW — gateway cost on this payment, for true margin/reconciliation
session_count SMALLINT NOT NULL DEFAULT 1 NEW — 1 = single visit; >1 = multi-session engagement
scheduled_date, scheduled_time_start, scheduled_time_end Engagement-level; per-visit lives in booking_sessions
status NVARCHAR(30) pending_paymentconfirmedin_progresscompleted → (disputedclosed) / cancelled. Now guarded by an allowed-transition table/CHECK so it can't contradict EVV.
confirmed_at, cancelled_at, cancellation_reason, cancelled_by, completed_at
dispute_window_ends_at DATETIME2 NULL NEWcompleted_at + config(dispute_window_hours, default 72). Payout eligible only after this passes with no open dispute.
payout_released CUT — second source of truth for "paid"; now derived from a nurse_payout_booking_links row + ledger.
timestamps

CHECK: gross_price_irr = balinyaar_commission_irr + nurse_payout_amount; all amounts ≥ 0. Relations: 1:1 ← booking_requests; 1:N → booking_sessions, payment_transactions, ledger_entries; 1:1 → booking_care_instructions, visit_verifications (see note), reviews, invoices; referenced by nurse_payout_booking_links, refunds, nurse_clawbacks.

booking_sessions [MVP] — NEW

Role: One row per visit within a booking. A 10-day post-op package or a month of nightly شبانه‌روزی care is one booking with N sessions, each with its own schedule, EVV, and payout eligibility. Why: the single-visit model literally cannot represent the dominant elder-care engagement; and money for a long engagement must release per completed session, not as one whole-month escrow held for weeks. Mid-engagement cancellation then cleanly refunds only the un-started sessions.

Field Type Notes
id BIGINT PK
booking_id BIGINT FK → bookings
session_index INT 1-based ordinal
scheduled_date, scheduled_time_start, scheduled_time_end Per-visit
visit_payout_amount BIGINT Portion of nurse_payout_amount for this session
status NVARCHAR(20) scheduled / in_progress / completed / missed / cancelled
payout_eligible_at DATETIME2 NULL Per-session dispute-window close
cancellation_event_id BIGINT NULL If this session was cancelled
timestamps

Relations: N:1 → bookings; 1:1 → visit_verifications. Note: for a single-visit booking, exactly one session is created so the EVV/payout path is uniform.

booking_care_instructions [CORE]

Role: Encrypted clinical/logistical context provided at booking time, visible only post-confirmation to the assigned nurse and admin. Why separate + encrypted: keeps the financial/scheduling table clean and enforces the two-stage disclosure boundary (Principle 6) with stricter access controls. Fields unchanged (current conditions, medications, allergies, special instructions, emergency contact — all enc). Relations: 1:1 → bookings.

visit_verifications [CORE]

Role: Electronic Visit Verification — the authoritative record that a visit happened and for how long; required for payout. Why: in-home care is unobserved; GPS + timestamped check-in/out is the proof that safely releases escrow. check_in_address_match is advisory (a mismatch triggers admin review, not auto-cancel). CHANGED: the FK moves to booking_session_id so each visit in a multi-session engagement is verified independently; the mapping between visit_verifications.status and the parent bookings.status is documented so the two state machines cannot silently diverge. Fields otherwise unchanged. Relations: 1:1 → booking_sessions.

cancellation_policies [MVP] — NEW

Role: Config-driven, snapshot-able cancellation/refund tiers by lead time and initiating actor. Why: "default 100% refund" is naive — a free cancel 24h ahead, a 50% charge inside 24h, and a nurse-no-show full refund + nurse penalty are different money flows, and the applicable rule must be frozen onto the booking at cancel time (the same snapshot discipline used for platform_fee_rate).

Field Type Notes
id BIGINT PK
code NVARCHAR(50) UNIQUE e.g. standard_24h
applies_to NVARCHAR(20) customer / nurse / admin
hours_before_start_min, hours_before_start_max INT NULL Tier bounds
refund_percentage DECIMAL(5,2) 0100
fee_amount_or_rate Cancellation fee / nurse penalty
is_active, timestamps

Relations: referenced (snapshot) by refunds and the cancellation_event recorded on a session/booking.