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.mdorder 11). Drift IDs (D-NNN) below cross-reference the per-EPICEP-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
| Persona | Role | Source |
|---|---|---|
| Guest | Books treatment via booking engine or via reception | hmp-booking-engine/src/components/spa/SpaBookingForm.tsx |
| Spa Receptionist | Books on guest's behalf, takes payment if walk-in, marks arrival | hmp-web-dashboard/src/app/(dashboard)/spa/appointments/page.tsx |
| Spa Therapist | Marks IN_PROGRESS, delivers treatment, marks COMPLETED | hmp-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 reports | hmp-web-dashboard/src/app/(dashboard)/spa/* |
| Front-Desk Agent | Confirms folio charge appears on guest folio | hmp-web-dashboard/src/app/(dashboard)/front-desk/folios/page.tsx |
Entry points
| Entry | URL / Surface | Backed by |
|---|---|---|
| Guest-facing browse + book | https://book.<env>.keynova.ai/{subdomain}/spa | hmp-booking-engine/src/components/spa/SpaTreatmentBrowser.tsx → SpaAvailabilityCalendar.tsx → SpaBookingForm.tsx |
| Staff dashboard | https://dashboard.<env>.keynova.ai/spa/appointments | hmp-web-dashboard/src/app/(dashboard)/spa/appointments/page.tsx |
| API direct (OTA / partner) | POST /api/spa/appointments | hmp-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 bycategory,isActive,propertyId). Guest surface:hmp-booking-engine/src/components/spa/SpaTreatmentBrowser.tsx+hmp-booking-engine/src/lib/api/spaApi.ts(withmockSpaApi.tsfallback in dev). Each row carriesid,name,category(MASSAGE/FACIAL/BODY_WRAP/HAIR/NAIL/AYURVEDA/WELLNESS/OTHER),duration(minutes),price(₹),taxRate(default18for SAC 999722 perprisma/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
SpaTreatmentrow. Booking engine resolves the property via host mapping (perbooking-engine-themeskill). - 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=18is the right India default for SAC 999722 (Beauty & Physical Well-Being Services) but nosacCodecolumn 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 tohmp-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
SpaTherapistrow exists with non-nullavailabilityJSON; at least oneSpaRoomexists. - 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()— validatestreatmentId+therapistId(when supplied) belong to caller's tenant viaprisma.spaTreatment.findFirst({where:{id, tenantId}})(Gold-Standard cross-tenant guard, Phase 3 rollout 2026-05-24); auto-assignsappointmentNumber(sequential per propertyId); callsscheduling.checkConflict()(15-min buffer) before insert; status defaultsBOOKED; publishesSPA_APPOINTMENT_CREATEDKafka 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
reservationIdlinkage if guest is checked-in. - Post-conditions:
SpaAppointmentrow created withstatus=BOOKED, uniqueappointmentNumber,totalAmount= treatment.price + tax.SPA_APPOINTMENT_CREATEDKafka event published. - Notes:
[CITED-BUT-NOT-FOUND]— canonicalappointments.service.spec.tsdoes 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] —findOneusesfindUnique({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-contractsv1 events + notification-service. - Code:
[CITED-BUT-NOT-FOUND]— there is noSpaAppointmentCreatedConsumerinhmp-notification-service/src/. TheSPA_APPOINTMENT_CREATEDKafka 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_EMAILconfigured (OP-2 / FU-30 IRSA role perhmp-infrastructure/CLAUDE.mdSES 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/integrationschecks 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, orPrakritientities 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)
SpaHealthFormrow withallergies[],medicalConditions[],pregnancyStatus,bloodPressureCondition,prakritiAssessment Json?,consentSignedAt,consentVersion.GuestErasedConsumerMUST extend to nullformData+prakritiAssessmenton 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
appointmentsflow 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 viaPATCH /spa/appointments/:id/status {status:'CONFIRMED'}. - Code:
hmp-spa-service/src/appointments/appointments.service.ts::updateStatus()— STATUS_TRANSITIONS map enforcesBOOKED → CONFIRMEDonly. 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 (canonicalappointments.service.spec.tsmissing). - Pre-conditions: Step 3 completed; appointment in
BOOKEDstatus; current time withinstartTime ± 30min(no spec'd grace window — D-016-013). - Post-conditions:
SpaAppointment.status = CONFIRMED.SPA_APPOINTMENT_CONFIRMEDKafka 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_REQUIREDonBOOKED → CONFIRMEDif 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_PROGRESStransition; publishesSPA_APPOINTMENT_STARTED. ControllerPATCH /spa/appointments/:id/statusrole-gated toSUPER_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 → COMPLETEDtransition; publishesSPA_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_COMPLETEDKafka event published. Appointment now eligible for folio post (Step 10). - Notes: No therapist-feedback capture on this transition (no
therapistNotesornextRecommendedTreatmentfield). 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+reservationIdset; computescgst = sgst = taxAmount / 2(50/50 split); publishesSPA_CHARGE_CREATEDKafka event with{appointmentId, tenantId, propertyId, reservationId, amount, cgst, sgst, igst, description, treatmentName}; updatespaidAmount = totalAmount. Controller route:POST /spa/appointments/:id/folioatappointments.controller.ts:127-137. Receiver:hmp-frontdesk-service/src/folios/consumers/spa-folio-charge.consumer.tscreates theFolioTransactionrow withtransactionCode = '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.reservationIdpopulated (set at Step 3 when guest is checked-in). - Post-conditions:
SpaAppointment.paidAmount === totalAmount.SPA_CHARGE_CREATEDevent consumed by frontdesk-service →FolioTransactionrow appended to guest folio withtransactionCode='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
sacCodein 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/reversemissing). Walk-in path: whenappointment.reservationIdis NULL (guest not staying at hotel),postToFolioreturns 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]— noSpaAppointmentCompletedConsumerinhmp-crm-service/src/wiresSPA_APPOINTMENT_COMPLETEDto a campaign or NPS survey. Receipt printing via@hmp/common/printinggeneratePosReceipt()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
| # | Scenario | Expected behaviour | Coverage today | Owning story |
|---|---|---|---|---|
| F1 | Therapist no-show | Receptionist re-assigns to another available therapist OR cancels with notice; guest auto-notified | NO CODE — manual workaround | [SME-PROPOSED] US-016-NEW-THERAPIST-REASSIGN |
| F2 | Treatment room double-booked | Scheduling MUST reject second BOOKED appointment for same room+time-window with code=SPA_ROOM_CONFLICT | scheduling.service.spec.ts covers therapist conflict (15-min buffer); ROOM conflict NOT spec'd — no SpaRoom.bookedSlots query | US-016-003 (gap) |
| F3 | Cancellation inside refund window | Per-treatment cancellation policy: free if T-24h; 50% if T-2h; 100% no-show. Today: no cancellation entity exists | NO CODE — no CancelAppointmentDto, no refund-calc, no SPA_APPOINTMENT_CANCELLED event | [SME-PROPOSED] US-016-NEW-CANCELLATION-POLICY |
| F4 | Contraindication missed (no health form) | BOOKED → IN_PROGRESS SHOULD be blocked with code=SPA_HEALTH_FORM_REQUIRED for first-visit guests on Ayurveda/Body treatments | NO CODE — no health-form entity at all (US-016-009 Not Started) | US-016-009 |
| F5 | Folio post fails (no active reservation — walk-in) | POST /spa/appointments/:id/folio returns 400 with code=NO_RESERVATION_LINKED — guard works | billing.service.spec.ts no-reservation walk-in guard spec — PASS. Flow spa-booking.flow.spec.ts Scenario 4 asserts 400 — PASS | US-016-005 |
| F6 | Gender-pairing mismatch | When requestedTherapistGender = FEMALE and only MALE therapist available, scheduling SHOULD return code=NO_GENDER_MATCH instead of silently assigning | NO CODE — SpaTherapist.gender field missing, SpaAppointment.requestedTherapistGender missing (D-016-004) | [SME-PROPOSED] US-016-NEW-GENDER-PAIRING |
| F7 | Inter-state guest GST mis-classification | When reservation.guest.billingStateCode !== property.gstStateCode, charge SHOULD post IGST (full taxAmount) not CGST+SGST 50/50 | NO CODE — billing.service.ts:55-57 hardcoded taxAmount / 2 regardless of guest state (D-016-008) | US-016-005 |
| F8 | Cross-tenant appointment fetch | Tenant 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) |
| F9 | Status-machine illegal transition (BOOKED → COMPLETED skipping CONFIRMED+IN_PROGRESS) | updateStatus() STATUS_TRANSITIONS map rejects with 422 code=ILLEGAL_STATUS_TRANSITION | implied by STATUS_TRANSITIONS map; no dedicated spec | US-016-003 |
Indian-SME context (always-in-scope)
- GST: SAC 999722 (Beauty & Physical Well-Being Services), 18% default per
SpaTreatment.taxRate=18schema 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
sacCodecolumn onSpaTreatment, nosacCodeinSPA_CHARGE_CREATEDpayload, 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, noSpaAppointment.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/guestPhonetoday — 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) | Type | Severity | Description | Surfaced by step |
|---|---|---|---|---|
| D-001 | Catalog-vs-EPIC-body | P0 | Catalog 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-002 | Story-incomplete + India-compliance | P1 | SpaTreatment missing sacCode field (SAC 999722 default). SPA_CHARGE_CREATED payload also missing it. Breaks e-invoice IRN for B2B > ₹500. | Steps 1, 10 |
| D-003 | Test-vs-code | P0 | Zero 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-004 | Story-incomplete + India-cultural | P1 | No SpaTherapist.gender, no SpaAppointment.requestedTherapistGender. Female guests cannot specify same-gender therapist. | Steps 2, F6 |
| D-005 | Security + tests-pin-deviation | P0 | AppointmentsService.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-006 | Review-stale + test-vs-code | P1 | reviews/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-007 | Cross-service-handoff + no-e2e-assertion | P0 | Spa 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-008 | India-compliance-critical + GST-split | P0 | CGST/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-009 | Story-incomplete + reversal-gap | P1 | No POST /spa/appointments/:id/folio/reverse endpoint, no SPA_CHARGE_REVERSED event. Disputed charges must be hand-edited frontdesk-side. | Step 10 |
| D-010 | Story-incomplete + entity-model-missing | P1 | EPIC 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-011 | Cross-service-boundary + story-vague | P2 | Reports (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-gap | P1 | No 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-gap | P1 | No 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-gap | P1 | BillingService.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=COUPLEexists, 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
| Step | Spans | Why cross-EPIC |
|---|---|---|
| 1 → 3 | EP-016 (spa) | Catalog browse + slot pick + booking — all internal to spa-service. |
| 4 | EP-016 ↔ EP-013 (notifications) | Confirmation comms — orphan-gap D-016-012. Spa publishes; notification-service doesn't consume. |
| 5 | EP-016 ↔ EP-014 (compliance/DPDPA) | Health form storage + consent — story Not Started; DPDPA art. 8 mandate. |
| 6 → 9 | EP-016 (spa) | Status-machine walk — internal to spa-service. |
| 10 | EP-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). |
| 11 | EP-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.