Skip to main content

BF-011 — Spa Booking (Treatment → Therapist + Room → Folio)

Business flow (cross-EPIC user journey). Reconciled 2026-05-25 as the Wave-2 RTM Scale-Out artefact for EP-016 Spa & Wellness Management (PLAYBOOK.md order 11). Drift IDs (D-NNN) below cross-reference the per-EPIC EP-016-rtm.md.

Per the PLAYBOOK.md, each step names (a) the user story that owns the requirement, (b) the primary code path that runs it, (c) the closest automated test that exercises it, and (d) pre/post-conditions and notes. Where the catalog contains no matching story, the step is tagged [SME-PROPOSED] as a NEW story for the SME's intake.

Personas​

PersonaRoleSource
GuestBooks treatment via booking engine or via receptionhmp-booking-engine/src/components/spa/SpaBookingForm.tsx
Spa ReceptionistBooks on guest's behalf, takes payment if walk-in, marks arrivalhmp-web-dashboard/src/app/(dashboard)/spa/appointments/page.tsx
Spa TherapistMarks IN_PROGRESS, delivers treatment, marks COMPLETEDhmp-web-dashboard/src/app/(dashboard)/spa/appointments/page.tsx (no dedicated therapist view yet — D-016-013 follow-up)
Spa Manager (SPA_MGR)Configures treatments, therapists, packages; pulls reportshmp-web-dashboard/src/app/(dashboard)/spa/*
Front-Desk AgentConfirms folio charge appears on guest foliohmp-web-dashboard/src/app/(dashboard)/front-desk/folios/page.tsx

Entry points​

EntryURL / SurfaceBacked by
Guest-facing browse + bookhttps://book.<env>.keynova.ai/{subdomain}/spahmp-booking-engine/src/components/spa/SpaTreatmentBrowser.tsx → SpaAvailabilityCalendar.tsx → SpaBookingForm.tsx
Staff dashboardhttps://dashboard.<env>.keynova.ai/spa/appointmentshmp-web-dashboard/src/app/(dashboard)/spa/appointments/page.tsx
API direct (OTA / partner)POST /api/spa/appointmentshmp-spa-service/src/appointments/appointments.controller.ts

The 11-step journey​

Step 1: Guest browses treatment menu​

  • Story: US-016-001 — Treatment Catalog Management. Catalog conflates this with Packages (US-016-006 in EPIC body); see EP-016 D-001.
  • Code: hmp-spa-service/src/treatments/treatments.controller.ts — GET /spa/treatments (filterable by category, isActive, propertyId). Guest surface: hmp-booking-engine/src/components/spa/SpaTreatmentBrowser.tsx + hmp-booking-engine/src/lib/api/spaApi.ts (with mockSpaApi.ts fallback in dev). Each row carries id, name, category (MASSAGE/FACIAL/BODY_WRAP/HAIR/NAIL/AYURVEDA/WELLNESS/OTHER), duration (minutes), price (₹), taxRate (default 18 for SAC 999722 per prisma/schema.prisma).
  • Test: hmp-spa-service/src/treatments/treatments.service.spec.ts :: 8 specs (findAll with filters, findOne, create, update, soft-delete) — PASS. Flow: spa-booking.flow.spec.ts :: "Scenario 1 — a spa treatment exists (list or create)" — TBD pass count (not yet in 13-flow-demo-readiness doc; BF-011 is order-11 per PLAYBOOK).
  • Pre-conditions: Property has at least one active SpaTreatment row. Booking engine resolves the property via host mapping (per booking-engine-theme skill).
  • Post-conditions: Guest sees treatment list with duration + price + GST-inclusive total. Selection persists in browser state until Step 2.
  • Notes: GAP-1 from US-016-001 review — no text search. Staff browsing 50+ treatments cannot filter by keyword today; only category dropdown. SpaTreatment.taxRate=18 is the right India default for SAC 999722 (Beauty & Physical Well-Being Services) but no sacCode column exists on the row (drift EP-016 D-002 — required for downstream e-invoice IRN per NIC schema 1.1).

Step 2: Time slot picked (therapist availability + room availability)​

  • Story: US-016-003 — Spa Appointment Booking (availability check half).
  • Code: hmp-spa-service/src/appointments/appointments.controller.ts — GET /spa/appointments/availability?propertyId=&treatmentId=&date=&therapistId? (returns 15-min slot grid). Delegates to hmp-spa-service/src/scheduling/scheduling.service.ts::getAvailableSlots() which iterates configured opening hours, filters booked slots with 15-min buffer, excludes therapist off-days. Guest surface: hmp-booking-engine/src/components/spa/SpaAvailabilityCalendar.tsx.
  • Test: hmp-spa-service/src/scheduling/scheduling.service.spec.ts :: 16 specs (8 for getAvailableSlots, 8 for checkConflict) — PASS. This is the cleanest spec coverage in the spa module.
  • Pre-conditions: Step 1 completed; treatmentId selected; at least one SpaTherapist row exists with non-null availability JSON; at least one SpaRoom exists.
  • Post-conditions: Browser holds {treatmentId, therapistId?, startTime ISO} ready to POST.
  • Notes: GAP-1/GAP-2 from US-016-003 review — auto-assignment is NOT implemented (operator must pick therapist) and operating-hours check uses hard-coded defaults instead of propertySettings.spaOpeningHours. Split-shift therapists (typical in Kerala Ayurveda resorts) are REJECTED by the validator (see EP-016 US-016-002 SME notes — operators will hit this day-1). Gender-pairing preference is missing entirely (D-016-004): female guests routinely request female therapists for body massage in Indian context; today the system has no way to honor this and receptionists work around with paper lists.

Step 3: Appointment created (status=BOOKED)​

  • Story: US-016-003 — Spa Appointment Booking (create half).
  • Code: hmp-spa-service/src/appointments/appointments.service.ts::create() — validates treatmentId + therapistId (when supplied) belong to caller's tenant via prisma.spaTreatment.findFirst({where:{id, tenantId}}) (Gold-Standard cross-tenant guard, Phase 3 rollout 2026-05-24); auto-assigns appointmentNumber (sequential per propertyId); calls scheduling.checkConflict() (15-min buffer) before insert; status defaults BOOKED; publishes SPA_APPOINTMENT_CREATED Kafka event with {appointmentId, tenantId, propertyId, treatmentId, therapistId, startTime, guestName, guestEmail, reservationId?}.
  • Test: hmp-spa-service/src/appointments/__tests__/appointment-cross-tenant-denial.spec.ts :: 10 specs (Gold-Standard Pick D template — covers findOne/findAll/create/getHistory; forged-propertyId guard; parent-tenant rejection on treatment + therapist). Flow: spa-booking.flow.spec.ts :: "Scenario 2 — book an appointment — status=BOOKED, appointmentNumber assigned" — TBD.
  • Pre-conditions: Step 2 completed; guest contact details collected (guestName + guestEmail or guestPhone); optional reservationId linkage if guest is checked-in.
  • Post-conditions: SpaAppointment row created with status=BOOKED, unique appointmentNumber, totalAmount = treatment.price + tax. SPA_APPOINTMENT_CREATED Kafka event published.
  • Notes: [CITED-BUT-NOT-FOUND] — canonical appointments.service.spec.ts does NOT exist; only the cross-tenant-denial spec covers behaviour, leaving status-transition and happy-path-create as defense-only coverage. EP-016 D-005 [P0 security] — findOne uses findUnique({id}) + post-fetch ForbiddenException, leaking row-existence across tenants; the orphaned spec PINS this deviation rather than guards against it.

Step 4: Confirmation email (with reminder T-24h)​

  • Story: [SME-PROPOSED] new story — US-016-NEW-CONFIRMATION-COMMS "Spa appointment confirmation + T-24h reminder + T-2h SMS". No catalog story owns the comms surface; the EPIC body doesn't enumerate it. Cross-EPIC: depends on @hmp/notification-contracts v1 events + notification-service.
  • Code: [CITED-BUT-NOT-FOUND] — there is no SpaAppointmentCreatedConsumer in hmp-notification-service/src/. The SPA_APPOINTMENT_CREATED Kafka event is published (Step 3) but no listener consumes it for guest comms today. Operator workaround: spa receptionist manually emails the confirmation.
  • Test: none.
  • Pre-conditions: Step 3 completed; SES_FROM_EMAIL configured (OP-2 / FU-30 IRSA role per hmp-infrastructure/CLAUDE.md SES module).
  • Post-conditions: (intended) Guest receives confirmation email immediately + reminder T-24h before startTime + (optional, TRAI DND-respecting) SMS T-2h before. Today: nothing fires automatically.
  • Notes: Drift EP-016 D-012 (NEW finding from BF-011 reconciliation) — high-visibility gap. Without confirmation comms, guests who book via the booking engine receive no record beyond the on-screen success message. TRAI DND check (@hmp/integrations checks guest's phone against DND registry) MUST gate the T-2h SMS for non-transactional reminders; transactional confirmation is exempt per TRAI 2018 amendment.

Step 5: (Optional) Health form pre-arrival​

  • Story: US-016-009 — Guest Health & Consent Form (Digital). EPIC body says "Not Started"; verified Not Started.
  • Code: NONE FOUND — no SpaHealthForm, Consent, or Prakriti entities in spa Prisma schema. Operator workaround: paper form at reception.
  • Test: none.
  • Pre-conditions: Guest opted into health form pre-arrival link.
  • Post-conditions: (intended) SpaHealthForm row with allergies[], medicalConditions[], pregnancyStatus, bloodPressureCondition, prakritiAssessment Json?, consentSignedAt, consentVersion. GuestErasedConsumer MUST extend to null formData + prakritiAssessment on guest erasure (DPDPA art. 8(7)).
  • Notes: DPDPA art. 6 + 8 mandate explicit consent for processing health data (sensitive personal data category). Skipping this opens the property to consumer-protection liability AND breaks DPDPA compliance. Ayurveda-specific: Prakriti (body-constitution) assessment is a regulated practice; CTAA + Ayush Ministry require licensed practitioners to record Prakriti before any Panchakarma treatment. The current appointments flow allows BOOKED → COMPLETED with no health-form gate — first-arrival booking for an Ayurveda treatment is currently a malpractice-liability surface. Drift EP-016 US-016-009 SME-PROPOSED.

Step 6: Guest arrives at spa reception​

  • Story: [SME-PROPOSED] new story — US-016-NEW-GUEST-ARRIVAL "Spa arrival check-in (verify identity + ID for first visit + collect signed health form)". No catalog row. Status-transition is via PATCH /spa/appointments/:id/status {status:'CONFIRMED'}.
  • Code: hmp-spa-service/src/appointments/appointments.service.ts::updateStatus() — STATUS_TRANSITIONS map enforces BOOKED → CONFIRMED only. Receptionist UI: hmp-web-dashboard/src/app/(dashboard)/spa/appointments/page.tsx (status pill click).
  • Test: Flow: spa-booking.flow.spec.ts :: "Scenario 3 — status machine BOOKED → CONFIRMED → IN_PROGRESS → COMPLETED" — TBD. No unit spec for status-transition matrix (canonical appointments.service.spec.ts missing).
  • Pre-conditions: Step 3 completed; appointment in BOOKED status; current time within startTime ± 30min (no spec'd grace window — D-016-013).
  • Post-conditions: SpaAppointment.status = CONFIRMED. SPA_APPOINTMENT_CONFIRMED Kafka event published.
  • Notes: No identity-verification step on the appointment row; spa receptionists check guest ID on paper. First-visit health-form validation (Step 5) SHOULD gate this transition (drift EP-016 US-016-009 SME-PROPOSED — return 412 code=SPA_HEALTH_FORM_REQUIRED on BOOKED → CONFIRMED if no form exists).

Step 7: Therapist marks IN_PROGRESS​

  • Story: US-016-003 — Spa Appointment Booking (status machine).
  • Code: hmp-spa-service/src/appointments/appointments.service.ts::updateStatus() — CONFIRMED → IN_PROGRESS transition; publishes SPA_APPOINTMENT_STARTED. Controller PATCH /spa/appointments/:id/status role-gated to SUPER_ADMIN | ADMIN | MANAGER | SPA_STAFF | FRONT_DESK_AGENT.
  • Test: Flow: spa-booking.flow.spec.ts :: "Scenario 3 — status machine" — TBD. No allergy-acknowledgement spec (gated by Step 5 absence).
  • Pre-conditions: Step 6 completed; therapist in attendance; treatment room assigned (no dedicated assignment endpoint — D-016-014 follow-up).
  • Post-conditions: SpaAppointment.status = IN_PROGRESS. Treatment timer starts (no real timer entity; receptionist tracks on paper).
  • Notes: Drift EP-016 US-016-009 SME-PROPOSED — therapist UI SHALL surface allergies + medical conditions as a prominent banner before status can transition to IN_PROGRESS, requiring explicit acknowledgement timestamped + audit-logged. Not implemented today. No SPA_STAFF mobile app — therapists work from receptionist's terminal (operationally fragile).

Step 8: Treatment delivered​

  • Story: (operational — not a separate story; spans the time between Step 7 and Step 9).
  • Code: no platform code — physical treatment delivery.
  • Test: n/a.
  • Pre-conditions: Step 7 completed.
  • Post-conditions: Treatment delivered per spec (massage duration + technique per treatment.category + product upsells captured separately if any).
  • Notes: Upsell capture is NOT a platform feature today (drift EP-016 follow-up): if a therapist sells an oil + body scrub during a massage, those line items must be added to the folio out-of-band (POS-service path) — there's no SpaAppointment.upsells[] field. Material consumption tracking (an oil bottle has N uses) is also missing — operators reconcile inventory weekly on paper.

Step 9: Therapist marks COMPLETED​

  • Story: US-016-003 — Spa Appointment Booking (status machine).
  • Code: hmp-spa-service/src/appointments/appointments.service.ts::updateStatus() — IN_PROGRESS → COMPLETED transition; publishes SPA_APPOINTMENT_COMPLETED. Idempotent — re-issuing the same transition returns 200 without side effects (STATUS_TRANSITIONS map enforces).
  • Test: Flow: spa-booking.flow.spec.ts :: "Scenario 3 — status machine BOOKED → CONFIRMED → IN_PROGRESS → COMPLETED" — TBD (asserts the full chain).
  • Pre-conditions: Step 7 completed; treatment finished.
  • Post-conditions: SpaAppointment.status = COMPLETED. SPA_APPOINTMENT_COMPLETED Kafka event published. Appointment now eligible for folio post (Step 10).
  • Notes: No therapist-feedback capture on this transition (no therapistNotes or nextRecommendedTreatment field). For wellness packages / Panchakarma series, this loses the cross-session continuity Indian Ayurveda treatments depend on (drift EP-016 US-016-006 SME-PROPOSED for package tracking).

Step 10: Charge posted to room folio (or walk-in receipt)​

  • Story: US-016-005 — Charge Posting to Guest Folio. Catalog ID drift D-016-001 (catalog calls this US-016-004 "Spa Charge Posting"; EPIC body has it as US-016-005).
  • Code: hmp-spa-service/src/billing/billing.service.ts::postToFolio(tenantId, appointmentId) — 92 LOC; validates appointment exists + tenant matches + status === 'COMPLETED' + paidAmount === 0 + reservationId set; computes cgst = sgst = taxAmount / 2 (50/50 split); publishes SPA_CHARGE_CREATED Kafka event with {appointmentId, tenantId, propertyId, reservationId, amount, cgst, sgst, igst, description, treatmentName}; updates paidAmount = totalAmount. Controller route: POST /spa/appointments/:id/folio at appointments.controller.ts:127-137. Receiver: hmp-frontdesk-service/src/folios/consumers/spa-folio-charge.consumer.ts creates the FolioTransaction row with transactionCode = 'SPA'.
  • Test: hmp-spa-service/src/billing/billing.service.spec.ts :: 6 specs (Kafka event, paidAmount update, appointment-not-found, no-reservation walk-in guard, tax calc, description). hmp-frontdesk-service/src/folios/consumers/spa-folio-charge.consumer.spec.ts — receiver spec. Flow: spa-booking.flow.spec.ts :: "Scenario 4 — post charges to folio — rejects without reservation linkage (guard)" — TBD. The flow test asserts ONLY the GUARD path (400 on missing reservationId); does NOT assert end-to-end that a folio row is created. Drift EP-016 D-007 [P0 cross-service-handoff].
  • Pre-conditions: Step 9 completed; appointment.status === 'COMPLETED'; appointment.reservationId populated (set at Step 3 when guest is checked-in).
  • Post-conditions: SpaAppointment.paidAmount === totalAmount. SPA_CHARGE_CREATED event consumed by frontdesk-service → FolioTransaction row appended to guest folio with transactionCode='SPA', amount, cgst, sgst. Guest folio total reflects the new charge.
  • Notes: Three P0 / P1 drifts on this step: (a) EP-016 D-007 — no end-to-end test asserts the folio row appears; (b) EP-016 D-008 [India-compliance-critical] — CGST/SGST hardcoded 50/50, no IGST resolution for inter-state guests (~30% of resort bookings), no sacCode in payload → e-invoice IRN generation breaks for B2B invoices > ₹500 per NIC schema 1.1; (c) EP-016 D-009 — no reversal endpoint (POST /spa/appointments/:id/folio/reverse missing). Walk-in path: when appointment.reservationId is NULL (guest not staying at hotel), postToFolio returns 400. There is no direct-payment walk-in path in the platform today — the receptionist must run a POS-service order or accept cash off-system. Drift EP-016 US-016-005 SME-PROPOSED for walk-in support.

Step 11: Receipt + feedback survey​

  • Story: [SME-PROPOSED] new story — US-016-NEW-POST-VISIT-COMMS "Spa post-visit thank-you + CRM-driven feedback survey (NPS) + repeat-treatment recommendation". No catalog row. Cross-EPIC: depends on CRM-service campaign trigger.
  • Code: [CITED-BUT-NOT-FOUND] — no SpaAppointmentCompletedConsumer in hmp-crm-service/src/ wires SPA_APPOINTMENT_COMPLETED to a campaign or NPS survey. Receipt printing via @hmp/common/printing generatePosReceipt() is available but not wired to spa charges.
  • Test: none.
  • Pre-conditions: Step 10 completed (folio charge posted) OR walk-in receipt requested (Step 10 NOT-applicable).
  • Post-conditions: (intended) Guest receives thank-you email immediately; NPS survey link T+24h; repeat-treatment recommendation T+14d (or per treatment.recommendedFrequency, which is also not a column today).
  • Notes: Drift EP-016 D-013 (NEW finding from BF-011 reconciliation). Indian spa context — repeat-visit rate for Ayurveda treatments is highest in the platform (Panchakarma is by design a 7/14/21-day course); losing the upsell loop loses the most defensible revenue.

Failure modes worth testing​

#ScenarioExpected behaviourCoverage todayOwning story
F1Therapist no-showReceptionist re-assigns to another available therapist OR cancels with notice; guest auto-notifiedNO CODE — manual workaround[SME-PROPOSED] US-016-NEW-THERAPIST-REASSIGN
F2Treatment room double-bookedScheduling MUST reject second BOOKED appointment for same room+time-window with code=SPA_ROOM_CONFLICTscheduling.service.spec.ts covers therapist conflict (15-min buffer); ROOM conflict NOT spec'd — no SpaRoom.bookedSlots queryUS-016-003 (gap)
F3Cancellation inside refund windowPer-treatment cancellation policy: free if T-24h; 50% if T-2h; 100% no-show. Today: no cancellation entity existsNO CODE — no CancelAppointmentDto, no refund-calc, no SPA_APPOINTMENT_CANCELLED event[SME-PROPOSED] US-016-NEW-CANCELLATION-POLICY
F4Contraindication missed (no health form)BOOKED → IN_PROGRESS SHOULD be blocked with code=SPA_HEALTH_FORM_REQUIRED for first-visit guests on Ayurveda/Body treatmentsNO CODE — no health-form entity at all (US-016-009 Not Started)US-016-009
F5Folio post fails (no active reservation — walk-in)POST /spa/appointments/:id/folio returns 400 with code=NO_RESERVATION_LINKED — guard worksbilling.service.spec.ts no-reservation walk-in guard spec — PASS. Flow spa-booking.flow.spec.ts Scenario 4 asserts 400 — PASSUS-016-005
F6Gender-pairing mismatchWhen requestedTherapistGender = FEMALE and only MALE therapist available, scheduling SHOULD return code=NO_GENDER_MATCH instead of silently assigningNO CODE — SpaTherapist.gender field missing, SpaAppointment.requestedTherapistGender missing (D-016-004)[SME-PROPOSED] US-016-NEW-GENDER-PAIRING
F7Inter-state guest GST mis-classificationWhen reservation.guest.billingStateCode !== property.gstStateCode, charge SHOULD post IGST (full taxAmount) not CGST+SGST 50/50NO CODE — billing.service.ts:55-57 hardcoded taxAmount / 2 regardless of guest state (D-016-008)US-016-005
F8Cross-tenant appointment fetchTenant A requests /spa/appointments/{id} for an ID belonging to tenant B → 403 ForbiddenException (leaks row-existence; SHOULD be 404 NotFound)appointment-cross-tenant-denial.spec.ts 10 specs — PASS (asserts current leaky behaviour, NOT canonical)US-016-003 (D-016-005)
F9Status-machine illegal transition (BOOKED → COMPLETED skipping CONFIRMED+IN_PROGRESS)updateStatus() STATUS_TRANSITIONS map rejects with 422 code=ILLEGAL_STATUS_TRANSITIONimplied by STATUS_TRANSITIONS map; no dedicated specUS-016-003

Indian-SME context (always-in-scope)​

  • GST: SAC 999722 (Beauty & Physical Well-Being Services), 18% default per SpaTreatment.taxRate=18 schema default. CGST/SGST 50/50 for intra-state (property + guest in same state); IGST full-amount for inter-state. D-016-008 P0: current code hardcodes CGST/SGST split, breaking GSTR-1 return filing for inter-state guests (~30% of resort bookings).
  • HSN/SAC: every line item on a B2B invoice > ₹500 needs HSN/SAC per NIC IRP schema 1.1. D-016-002 P1: no sacCode column on SpaTreatment, no sacCode in SPA_CHARGE_CREATED payload, blocking e-invoice IRN.
  • Gender-pairing: Indian cultural norm — female guests routinely request female therapists for body massage / facial / Ayurveda Abhyanga. D-016-004 P1: no SpaTherapist.gender, no SpaAppointment.requestedTherapistGender — receptionists maintain a paper list.
  • Ayurveda Prakriti: CTAA + Ayush Ministry require licensed practitioners to record Prakriti (body-constitution) before any Panchakarma treatment. US-016-009 Not Started — opens malpractice-liability surface.
  • DPDPA art. 6 + 8: health data is "sensitive personal data"; explicit consent required for processing; deletion on guest erasure. GuestErasedConsumer in spa-service only erases guestName/guestEmail/guestPhone today — does NOT handle health-form fields because they don't exist (US-016-009 cascade).
  • TRAI DND: T-2h SMS reminder MUST gate on DND registry check for non-transactional messages; the T-24h confirmation is exempt as transactional.
  • Seasonality patterns operators MUST be able to filter on (D-016-011 SME-PROPOSED):
    • Festival windows: Diwali week (Oct/Nov), Holi week (Mar), Pongal week (Jan, Tamil Nadu) — spa volume spikes 40-80% in Kerala/Goa/Rajasthan resorts.
    • Monsoon (Jun-Sep): Ayurveda demand peaks per traditional Panchakarma calendar — Kerala resorts run 14-day packages at near-100% spa utilization.
    • Wedding season (Nov-Feb): Pre-wedding bridal packages drive 30% of revenue at city luxury hotels; couple-room bookings spike (US-016-007 Not Started caps the opportunity).
  • Prepaid wellness packages (US-016-006 Not Started): ₹40K-₹120K Panchakarma packages are the highest-margin spa product; FSSAI requires validity-period disclosure in the sale contract; auto-expiry tracking is a consumer-protection helper. Today: paper coupons.

Drift summary​

ID (EP-016)TypeSeverityDescriptionSurfaced by step
D-001Catalog-vs-EPIC-bodyP0Catalog 01-epic-catalog.md §EP-016 and EPIC body disagree on which title goes with which story ID from US-016-004 onward. Fix-shape: adopt EPIC-body numbering as canonical, rewrite catalog.Step 1
D-002Story-incomplete + India-complianceP1SpaTreatment missing sacCode field (SAC 999722 default). SPA_CHARGE_CREATED payload also missing it. Breaks e-invoice IRN for B2B > ₹500.Steps 1, 10
D-003Test-vs-codeP0Zero unit tests for TherapistsService despite "Built" status. Availability JSON validator (the only custom class-validator in the module) has no co-located spec.Step 2
D-004Story-incomplete + India-culturalP1No SpaTherapist.gender, no SpaAppointment.requestedTherapistGender. Female guests cannot specify same-gender therapist.Steps 2, F6
D-005Security + tests-pin-deviationP0AppointmentsService.findOne + updateStatus + BillingService.postToFolio use findUnique({id}) + ForbiddenException pattern → row-existence leak across tenants. Orphaned cross-tenant-denial spec asserts this AS current behaviour (pinning the gap, not guarding).Steps 3, 6-7, 10, F8
D-006Review-stale + test-vs-codeP1reviews/EP-016/US-016-003-review.md (2026-03-08) lists 16 gaps; several closed since (BillingService.postToFolio fully wired; date filters present). Re-run review against current code.Step 10
D-007Cross-service-handoff + no-e2e-assertionP0Spa charge posting (spa-service → Kafka → frontdesk-service → folio row) is the most complex cross-service event in the module. Flow test asserts ONLY the GUARD path (no reservation → 400); no positive end-to-end assertion. A typo in topic name / payload-shape drift would not fail any test.Step 10
D-008India-compliance-critical + GST-splitP0CGST/SGST hardcoded 50/50 in billing.service.ts:55-57. No IGST path. Inter-state guests (~30% of bookings) silently mis-classified. Breaks GSTR-1 return filing.Step 10, F7
D-009Story-incomplete + reversal-gapP1No POST /spa/appointments/:id/folio/reverse endpoint, no SPA_CHARGE_REVERSED event. Disputed charges must be hand-edited frontdesk-side.Step 10
D-010Story-incomplete + entity-model-missingP1EPIC body promises per-guest package tracking (US-016-006 Not Started, P1 priority); no PackagePurchase / PackageSessionUsage Prisma models exist. High-margin Panchakarma packages tracked on paper.Steps 1, 11
D-011Cross-service-boundary + story-vagueP2Reports (US-016-008) straddle spa-service (sales-detail feed shipped) and analytics-service (RevPATH aggregation Not Started). Story doesn't delineate the boundary.(Day-2 — reporting)
D-012 (NEW from BF-011)Cross-EPIC-orphan + comms-gapP1No SpaAppointmentCreatedConsumer in notification-service. SPA_APPOINTMENT_CREATED event published but no confirmation email / T-24h reminder / T-2h SMS fires automatically. Operator workaround: manual email.Step 4
D-013 (NEW from BF-011)Cross-EPIC-orphan + comms-gapP1No SpaAppointmentCompletedConsumer in crm-service. No post-visit thank-you / NPS survey / repeat-treatment recommendation. Loses highest-margin repeat-visit upsell loop for Ayurveda.Step 11
D-014 (NEW from BF-011)Story-incomplete + walk-in-gapP1BillingService.postToFolio requires reservationId (correct guard) but the platform has no walk-in direct-payment path for spa. Receptionists must run POS-service order or accept cash off-system.Step 10

Day-2 spa-ops (NOT in BF-011 scope)​

Items the spa typically tackles in the first 30-90 days post go-live. Not in BF-011 scope but flagged so future BFs / Day-2 RTM sweeps know where to pick up:

  • Spa Calendar grid view — US-016-004 Partial; backend has all the data, gap is 100% frontend (FullCalendar / dnd-kit not in package.json).
  • Prepaid Package & Session Tracking — US-016-006 Not Started, P1 priority. Highest-margin product (Panchakarma ₹40K-₹120K).
  • Couple & Group Appointment Booking — US-016-007 Not Started. SpaRoom.type=COUPLE exists, appointment-pairing layer missing. ~40% mismatch rate today on paper-booked honeymoon packages.
  • Spa Revenue & Therapist Productivity Reports — US-016-008 Partial; sales-detail feed shipped, RevPATH / utilization / treatment-popularity aggregation deferred to analytics-service.
  • Guest Health & Consent Form (Digital + Prakriti) — US-016-009 Not Started. DPDPA + CTAA mandate.
  • Therapist leave-management + deactivation guard — US-016-002 review GAP-3 HIGH (deactivating a therapist with future appointments today silently corrupts the schedule).
  • Spa-service mobile app for therapists — no SPA_STAFF mobile workflow; therapists work from receptionist terminal.
  • Spa upsell capture (oil, scrub, take-home product) — no SpaAppointment.upsells[] field; revenue leaks to POS-service or off-system.
  • Material consumption tracking — paper-based weekly reconciliation today.
  • Festival / monsoon / wedding-season demand reports (D-016-011 SME-PROPOSED — extend US-016-008 report filters).

Cross-EPIC narrative summary​

StepSpansWhy cross-EPIC
1 → 3EP-016 (spa)Catalog browse + slot pick + booking — all internal to spa-service.
4EP-016 ↔ EP-013 (notifications)Confirmation comms — orphan-gap D-016-012. Spa publishes; notification-service doesn't consume.
5EP-016 ↔ EP-014 (compliance/DPDPA)Health form storage + consent — story Not Started; DPDPA art. 8 mandate.
6 → 9EP-016 (spa)Status-machine walk — internal to spa-service.
10EP-016 ↔ EP-005B (frontdesk/folio) ↔ EP-012C (e-invoice)Charge posting — Kafka handoff, GST split, e-invoice IRN. Three drift findings (D-007/D-008/D-009).
11EP-016 ↔ EP-010 (CRM)Post-visit comms — orphan-gap D-016-013. Spa publishes; crm-service doesn't consume.

The two orphan-comms gaps (Step 4, Step 11) and the GST-mis-classification + no-e2e-assertion gaps (Step 10) are the primary BF-011 findings the SME must act on, alongside the catalog-vs-EPIC-body numbering drift (D-001) and the security row-existence leak (D-005) carried forward from EP-016-rtm.md.