EP-003 — Reservation Management RTM
EPIC: EP-003 Reservation Management Reconciled: 2026-05-24 Pilot artifact — see
./PLAYBOOK.mdfor the reconciliation pattern, drift criteria, and 5-state vocabulary. Source of truth for "% passing":hmp-docs/docs/platform-registry/2026-05-16-13-flow-demo-readiness.mduntil the next re-measure. Numbering convention: this RTM follows the EPIC-body US-003-001..US-003-022 numbering (22 stories). The catalog (01-epic-catalog.md) carries a different title-to-ID mapping for several rows — see D-001 below.
Status legend
| Status | Meaning |
|---|---|
Built | Code exists + tests exist + tests pass |
Built-Untested | Code exists, no automated test coverage (or test exists but is a stub) |
Partial | Code exists for some acceptance criteria, gaps for others |
Stub | Code is a placeholder / TODO / returns mock data |
Not Started | No code |
Summary
| Metric | Count |
|---|---|
| Total stories | 22 |
| Catalog claims "Built" | 13 |
| Verified-Built | 13 (US-003-001, -002, -003, -006, -007, -008, -009, -010, -011, -012, -015, -017, -020) |
| Built-Untested | 0 |
| Partial | 7 (US-003-004, -005, -013, -014, -016, -018, -022) |
| Stub | 1 (US-003-019 — auto-charge calc not implemented; agent specifies manually) |
| Not Started | 1 (US-003-021 bulk group check-in) |
| Drift findings | 12 (D-001..D-012; 4 P0 / 5 P1 / 3 P2) |
| [SME-PROPOSED] refinements | 34 |
Reservation count tally note: 22 stories. Sum = 13 Built + 7 Partial + 1 Stub + 1 Not Started = 22. US-003-005 sits in Partial despite catalog "Built" because auto-charge calc is deferred to US-003-019. US-003-022 is Partial (backend inventory-calendar API ships; dashboard UI gap). US-003-010 (No-Show Processing) is Verified-Built since the no-show transition has spec coverage in
reservations.service.spec.ts; D-010 captures the missing folio-charge emission as a story-vs-code drift, NOT a coverage gap.
US-003-001 — Create Individual Reservation
Catalog status: Built (P0, 5 points) — catalog title "Create New Reservation (Single & Multi-Room)" conflates this story with US-003-002. See D-001. Verified status: Built
Code locations:
hmp-reservation-service/src/reservations/reservations.controller.ts:65-110— POST/reservations(Roles: SUPER_ADMIN, ADMIN, MANAGER, RESERVATIONS, FRONT_DESK;@Audit; honorsIdempotency-Keyheader — AP-T03-001)hmp-reservation-service/src/reservations/reservations.service.ts—create()orchestrates: inline-guest creation, date/occupancy validation, rate auto-fill, safeguards (RES_PAST_DATE_BOOKING / RES_RATE_BELOW_MINIMUM / RES_RATE_ABOVE_MAXIMUM / RES_DUPLICATE_DETECTED / RES_WALKIN_SAME_DAY_DUPLICATE), tax-inclusive reverse-derivation (AP-T03-004), OTA total reconciliationexpectedTotalAmountguard, GST viaGstService.getRoomGstRate(), transactional create + history + Kafka emithmp-reservation-service/src/reservations/dto/reservation.dto.ts—CreateReservationDto,RoomReservationDtowithisTaxInclusive,allowOverOccupancy,expectedTotalAmounthmp-reservation-service/src/common/decorators/idempotency-key.decorator.ts— Idempotency-Key extraction (AP-T03-001)hmp-web-dashboard/src/app/(dashboard)/reservations/new/page.tsx— Reservation create pagehmp-web-dashboard/src/components/modules/reservations/reservation-form.tsx[CITED-BUT-NOT-FOUND]— form component referenced in old review
Unit + integration tests:
hmp-reservation-service/src/reservations/reservations.service.spec.ts— happy-path + FU-55 analytics emission + AP-T03-004 OTA tax-inclusive + tenant-ownership-error mapping (P0 5-spec set 404/403/401/503/ECONNREFUSED per gotcha 27)hmp-reservation-service/src/reservations/reservations.sprint2.spec.ts— 13 BookingSource enum coverage + sourceReference + Kafka event payload assertionshmp-reservation-service/src/reservations/reservations.enhanced.spec.ts— extended create pathshmp-reservation-service/src/reservations/reservations.controller.spec.ts— controller wiringhmp-reservation-service/src/reservations/reservations-xprisma-guest-removal.spec.ts— AP-T04-001 grep-guard for xPrisma.guest pattern
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-3-booking-engine.spec.ts:: "booking-engine create reservation" — TBD per 13-flow dochmp-infrastructure/testing/flow-tests/src/flows/flow-4-reservation-review.spec.ts:: "reservation create/lookup/cancel" — TBD per 13-flow dochmp-infrastructure/testing/flow-tests/src/flows/idempotency-replay.flow.spec.ts:: "POST /reservations replay" — exercises AP-T03-001 Idempotency-Key pathhmp-infrastructure/testing/flow-tests/src/flows/walk-in-saga-recovery.flow.spec.ts:: "walk-in same-day duplicate" — exercises RES_WALKIN_SAME_DAY_DUPLICATE safeguard
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/smoke-reservation-create.real.spec.ts— reservation create flow with seeded property
E2E (Playwright):
hmp-web-dashboard/e2e/reservation-flow.spec.ts— top-level reservation create flowhmp-web-dashboard/e2e/tests/reservation-flow.spec.ts— granular reservation flowhmp-web-dashboard/e2e/tests/reservations-create.spec.ts— create formhmp-web-dashboard/e2e/tests/15-crud-reservations.spec.ts— CRUD coveragehmp-web-dashboard/e2e/tests/03-reservation-lifecycle.spec.ts— lifecycle
Use cases referencing this story:
docs/platform-registry/_data/use-cases/uc-booking-idempotency.yaml(AP-T03-001 — Idempotency-Key replay)docs/platform-registry/_data/use-cases/uc-reservation-modified-event.yaml(AP-T05-007 — reservation.modified emission semantics)docs/platform-registry/_data/use-cases/uc-channel-canonical-ota-events.yaml(OTA-sourced creation)docs/platform-registry/_data/use-cases/uc-tax-configuration-inclusive.yaml(AP-T03-004 — tax-inclusive reverse-derivation)
SME notes:
- AC-06 / AC-07 / AC-08 in the legacy review file (
reviews/EP-003/US-003-001-review.md, dated 2026-03-07) flag GST + promo code + departure-validation as FAIL. Reality 2026-05-24: GST is computed viaGstService.getRoomGstRate()(slab 0%/12%/18% — gotcha 12 in service CLAUDE.md), promo code processing was added per AP-T11-014 (PromotionServiceClient.applyPromotionsinpreview()), and date validation is enforced at service-level. Review file is ~12 weeks stale. - AP-T03-004 OTA tax-inclusive flag is the most subtle real-world fix: the prior code stacked GST on top of OTA gross, producing a silent 12/18 % double-charge that surfaced as an artificial outstanding balance at check-in.
- The story body describes "one guest, one room" but the same controller endpoint handles multi-room via
roomReservations[]. Stories US-003-001 and US-003-002 share a single backend write path. - Indian-hospitality SME: confirmation-number format is
<prefix><YYYYMMDD><SEQ>pergenerateConfirmationNumber(). Story spec exampleGPH20260415001aligns. The prefix comes from safeguard config (defaultRES) — not from the property code; story should make this explicit.
Drift findings:
D-001 — [P0] [Catalog-vs-EPIC-body] The live catalog (01-epic-catalog.md rows 73-94) and the EPIC body (epics/EP-003-reservation-management.md lines 64-85) carry different title-to-ID mappings for several rows. Catalog US-003-001 = "Create New Reservation (Single & Multi-Room)" merges the EPIC's US-003-001 + US-003-002. Catalog US-003-002 = "Reservation Search & List View" corresponds to EPIC body US-003-003. Catalog US-003-003 = "Reservation Calendar View" corresponds to EPIC body US-003-022. Catalog US-003-016 = "No-Show Processing" but EPIC body US-003-016 = "Payment Link Generation". Numbering disagrees on >6 IDs. This is the same drift class as EP-002 D-007 and is a P0 pilot-blocking issue: two referent docs giving different answers to "what does US-003-006 mean".
- [SME-PROPOSED] Adopt the EPIC body numbering as canonical (22 stories, ID = order in EPIC body), rewrite catalog rows 73-94 to align, and add an alias line
> Previously catalogued as "X" prior to 2026-05-24 reconciliationto each catalog row so historic references stay traceable. EP-002 D-007 should be resolved in the same sweep.
US-003-002 — Multi-Room Reservation
Catalog status: Built (P0, 5 points) — catalog conflates with US-003-001. See D-001. Verified status: Built
Code locations:
hmp-reservation-service/src/reservations/reservations.service.ts—create()iteratesroomReservations[]to insert NRoomReservationrows under oneReservationhmp-reservation-service/src/reservations/multi-room.service.spec.ts— multi-room edge cases (per-room dates, mixed rate plans, totalAmount accumulation)hmp-reservation-service/src/reservations/dto/reservation.dto.ts—RoomReservationDto[]with per-linecheckInDate/checkOutDate/mealPlan(EP/CP/MAP/AP)
Unit + integration tests:
hmp-reservation-service/src/reservations/multi-room.service.spec.ts— dedicated multi-room suitehmp-reservation-service/src/reservations/reservations.service.spec.ts— multi-room paths in create + totals recompute
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/group-bookings.flow.spec.ts:: "multi-room block" — TBDhmp-infrastructure/testing/flow-tests/src/flows/reservation-amend.flow.spec.ts:: "multi-room amend" — TBD
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/smoke-reservation-create.real.spec.ts— covers single-room path; multi-room not explicitly asserted
E2E (Playwright):
- (covered via
reservation-flow.spec.ts+15-crud-reservations.spec.ts; no dedicated multi-room test file)
Use cases referencing this story:
- none
SME notes:
- Mixed-date multi-room (Room A: Apr 10-13, Room B: Apr 11-13) is supported at the DB level via per-
RoomReservationcheckInDate/checkOutDate, but no spec asserts the parent reservation'sarrivalDate/departureDateare derived as the min/max across the lines. Story spec says "reservation arrival = earliest, departure = latest" — currently caller-supplied, no derivation guard. - The OTA multi-RoomStay ingestion path (gotcha 23, 2026-05-05) added
event.rooms[]consumer support — the same DB write path serves both dashboard multi-room AND OTA multi-room. Story body does not mention OTA path. - Indian-hospitality SME: extra-child charges (story example
INR 600/night) are typically modeled as per-night line additions onRoomReservation, not as a separate charge type — the spec doesn't say where this lives. Today it's bundled intorateAmountat the form level; no audit visibility.
Drift findings:
D-002 — [P1] [Test-vs-code + integration-risk] No spec asserts the multi-room parent-reservation arrivalDate/departureDate are min/max across the room lines (per story AC). Today the caller supplies both. A bug where the form derives them incorrectly would never be caught.
- [SME-PROPOSED] Add AC: "When a multi-room reservation is created, the parent
Reservation.arrivalDateSHALL equalMIN(roomReservations[].checkInDate)andReservation.departureDateSHALL equalMAX(roomReservations[].checkOutDate). Service-level derivation; reject caller-supplied parent dates that don't match." - [SME-PROPOSED] Add AC: "Extra-occupancy charges (extra adult, extra child, infant) SHALL be modeled as a dedicated
RoomReservationExtrarow (or a typed JSON column with audit visibility), not bundled intorateAmountopaquely. Folio breakdown depends on this for GST line-itemization."
US-003-003 — Reservation Search & Filters
Catalog status: Built (P0, 5 points) — catalog row 75 says "Reservation Calendar View" — see D-001. Verified status: Built
Code locations:
hmp-reservation-service/src/reservations/reservations.controller.ts— POST/reservations/search(AP-T04-003:guestNamefilters via local Guest read model — case-insensitiveORonfirstName/lastName, no cross-service HTTP)hmp-reservation-service/src/reservations/reservations.service.ts—search()with filters: propertyId, status[], guestName, confirmationNumber, fromDate, toDate, source[], roomTypeId, companyIdhmp-reservation-service/src/reservations/reservations.controller.ts— GET/reservations/confirmation/:confirmationNumber(indexed lookup; story spec requires < 200 ms)hmp-reservation-service/src/reservations/dto/reservation.dto.ts—SearchReservationsDtohmp-reservation-service/src/reservations/search-q-param.spec.ts—?q=quick-search parameterhmp-web-dashboard/src/app/(dashboard)/reservations/page.tsx— Reservations list page with filters
Unit + integration tests:
hmp-reservation-service/src/reservations/search-q-param.spec.ts— q-param coveragehmp-reservation-service/src/reservations/reservations.service.spec.ts— search filter combinations
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-4-reservation-review.spec.ts:: "search by guest name + confirmation number" — TBDhmp-infrastructure/testing/flow-tests/src/flows/tape-chart.flow.spec.ts:: "filter reservations" — TBD
Real-backend smokes:
- none specific (search exercised via reservations-list page tests)
E2E (Playwright):
hmp-web-dashboard/e2e/tests/reservations-list.spec.ts— list + filterhmp-web-dashboard/e2e/tests/reservation-flow.spec.ts— search pathhmp-web-dashboard/e2e/tests/15-crud-reservations.spec.ts— search assertions
Use cases referencing this story:
- none
SME notes:
- The AP-T04-003 migration from cross-service HTTP to local-read-model
Guestfilter eliminates a per-search property-service round-trip — useful for big-search latency. The story body does not mention this implementation choice. confirmationNumberlookup is documented as "indexed" but no spec asserts the< 200 msSLA. P95 latency claims need a load test, not a unit test — orphaned AC.- Indian-hospitality SME: a common search miss is "find all OTA arrivals today regardless of which OTA" — the source filter accepts an array but the dashboard filter widget defaults to single-select. Verify UX.
Drift findings:
D-003 — [P1] [Story-incomplete + missing-SLA-test] Story AC requires < 200 ms p95 lookup latency on /reservations/confirmation/:num. No load test in hmp-infrastructure/testing/flow-tests/src/flows/performance.flow.spec.ts asserts this specifically.
- [SME-PROPOSED] Add AC: "
GET /reservations/confirmation/:confirmationNumberSHALL respond < 200 ms p95 with 10K reservation rows in the property. Asserted via a k6 / flow-test scenario inperformance.flow.spec.ts."
US-003-004 — Reservation Modification
Catalog status: Built (P0, 5 points) Verified status: Partial — backend covers dates/room/rate (AP-T05-001/-002/-007 shipped); per-room PATCH endpoint shipped (P0-4); but version-optimistic-concurrency is partial and frontend modification-history timeline (US-003-017) is incomplete
Code locations:
hmp-reservation-service/src/reservations/reservations.controller.ts— PUT/reservations/:id(parsesIf-MatchETag viaparseIfMatchVersion)hmp-reservation-service/src/reservations/reservations.controller.ts— PATCH/reservations/:id/rooms/:roomReservationId(P0-4 per-room edits)hmp-reservation-service/src/reservations/reservations.service.ts—update()recomputes totals when departureDate changes (AP-T05-001), emits FOLIO_CHARGE_POSTED per added night (AP-T05-002), emits reservation.modified with analytics fields (FU-55)hmp-reservation-service/src/reservations/reservation-extras.service.ts— extras-line edithmp-reservation-service/src/reservations/version-optimistic-concurrency.spec.ts— version + If-Match handlinghmp-web-dashboard/src/app/(dashboard)/reservations/[id]/page.tsx— Detail/Amend page
Unit + integration tests:
hmp-reservation-service/src/reservations/reservations.service.spec.ts:993— AP-T05-001 extend-stay rate recompute (Σ ratePerNight × newNights)hmp-reservation-service/src/reservations/version-optimistic-concurrency.spec.ts— If-Match concurrency guardhmp-reservation-service/src/reservations/reservation-extras.service.spec.ts— extrashmp-reservation-service/src/reservations/multi-room.service.spec.ts— per-room PATCH semantics
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/reservation-amend.flow.spec.ts:: "extend stay + room move + rate change" — TBD per 13-flow doc
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/smoke-reservation-amend.real.spec.ts— amend flow
E2E (Playwright):
hmp-web-dashboard/e2e/tests/reservations-amend.spec.ts— amend dialoghmp-web-dashboard/e2e/tests/reservations-detail.spec.ts— detail page
Use cases referencing this story:
docs/platform-registry/_data/use-cases/uc-reservation-extend-folio.yaml(AP-T05-002 — FOLIO_CHARGE_POSTED emission per added night)docs/platform-registry/_data/use-cases/uc-reservation-extend-rate.yaml(AP-T05-001 — totalAmount recompute on departure extend)docs/platform-registry/_data/use-cases/uc-reservation-modified-event.yaml(AP-T05-007 — ROOM_MOVE reservation.modified emission)
SME notes:
- AP-T05-001 closed a P0 silent under-billing bug: prior code froze
totalAmountat create, so extending departure left billing under-counted. NowratePerNight = frozenTotal / oldNights × newNights. - AP-T05-002 (extending a CHECKED_IN reservation) emits FOLIO_CHARGE_POSTED per added night — frontdesk's
PosFolioChargeConsumerwrites the FolioTransaction CHARGE row, so the bill reflects the increase before night audit. Out-of-DB-transaction fire-and-forget — tracked as item T4-1 for transactional outbox. - AP-T05-007 (room-move emits reservation.modified with
changeType: 'ROOM_MOVE') — analytics ADR/occupancy MVs depend on this. - Story AC scenario 3 ("Reject modification that exceeds availability") — the availability re-check on room upgrade is in the service but no integration test asserts the 409 path explicitly.
Drift findings:
D-004 — [P1] [Cross-service-handoff + missing-spec] AP-T05-002 emits FOLIO_CHARGE_POSTED outside the DB transaction (fire-and-forget Kafka publish). Tracked as T4-1 for transactional outbox. Today: a publisher crash between DB commit and event emit silently loses an extension charge — guest under-bills at checkout.
- [SME-PROPOSED] Add AC: "Reservation extend on CHECKED_IN status SHALL use a transactional outbox: the FOLIO_CHARGE_POSTED event row is written in the same DB transaction as the RoomReservation update; a background dispatcher publishes to Kafka with at-least-once delivery. Asserted by a flow test that kills the process between commit and dispatch."
D-005 — [P2] [Test-gap] No integration spec asserts the 409 path when modification exceeds availability (story AC scenario 3). The availability re-check exists in service but the rejection contract isn't pinned.
- [SME-PROPOSED] Add AC: "
PUT /reservations/:idandPATCH /reservations/:id/rooms/:roomReservationIdSHALL return 409 witherrorCode: 'AVAILABILITY_EXHAUSTED'when the modification would exceed effective capacity (including overbooking buffer). Reservation row is unchanged."
US-003-005 — Reservation Cancellation with Policy Enforcement
Catalog status: Partial (P0, 5 points) — catalog has corrected to Partial; EPIC body still says "Built" Verified status: Partial — cancel + reason recording + cache invalidation shipped; auto-charge calc from linked CancellationPolicy is deferred to US-003-019; no DPDPA refund-SLA contract
Code locations:
hmp-reservation-service/src/reservations/reservations.controller.ts— DELETE/reservations/:id(Roles: SUPER_ADMIN, ADMIN, MANAGER, RESERVATIONS;@Audit)hmp-reservation-service/src/reservations/reservations.service.ts—cancel()sets status=CANCELLED, records cancelledAt/cancelledBy/cancellationReason, invalidates availability cache, emitsreservation.cancelledKafka eventhmp-reservation-service/src/reservations/cancellation-policy.spec.ts— cancellation policy interaction spechmp-reservation-service/src/reservations/dto/reservation.dto.ts—CancelReservationDto(reason field)hmp-property-service/src/cancellation-policies/cancellation-policies.service.ts— owns the policy; reservation-service reads via cross-service read modelhmp-web-dashboard/src/app/(dashboard)/reservations/[id]/page.tsx— cancel button + reason dialog
Unit + integration tests:
hmp-reservation-service/src/reservations/cancellation-policy.spec.ts— policy resolutionhmp-reservation-service/src/reservations/reservations.service.spec.ts— cancel happy path
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/reservation-amend.flow.spec.ts:: "cancel with reason" — TBD
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/smoke-cancellation-policies.real.spec.ts— policy CRUD; cancel UX uses this
E2E (Playwright):
hmp-web-dashboard/e2e/tests/reservations-cancel.spec.ts— cancel dialog
Use cases referencing this story:
docs/platform-registry/_data/use-cases/uc-cancellation-rules-validation.yaml(AP-T02-006 — Zod typed cancellation rules)
SME notes:
- Cancellation charge calc is NOT automated today — the agent supplies the cancellation amount manually. Story AC scenarios 2 and 3 are aspirational, not asserted by code. This is documented in the EPIC body's gap-analysis ("Cancellation Charge Automation") and is the scope of US-003-019 (Not Started).
- Indian-hospitality SME: DPDPA art. 8(3) requires "reasonable period" refunds — industry interpretation 7 business days. Today the cancel API does not return any refund commitment or SLA timestamp. See EP-002 D-010 for the same pattern.
- Indian-hospitality SME: A common cancellation case is "guest cancels OTA booking via OTA portal; OTA pushes cancel event; PMS auto-applies the OTA's policy, NOT the property's". The
ChannelBookingCancelledConsumer(gotcha 19, schemaVersion 1.0) consumes the event but does not currently bridge to the OTA-stated charge — and the OTA policy may differ from the property's. Risk: double-application of cancellation fees.
Drift findings:
D-006 — [P0] [Story-vs-code + India-hospitality-critical] Story AC scenario 2 ("Late cancellation incurs first-night charge") and 3 ("Cancel non-refundable reservation … full charge retained") describe automated charge enforcement that is NOT implemented. Agent supplies the cancellation charge manually. This is a P0 because tenants relying on the listed AC believe the platform enforces their policy — it does not.
- [SME-PROPOSED] Demote catalog status of US-003-005 from
Built→Partial(catalog has already corrected; ensure EPIC body matches), and link explicitly to US-003-019 as the implementation track. Update story body AC to read "Cancellation reason captured + reservation marked CANCELLED; auto-charge calculation tracked under US-003-019." - [SME-PROPOSED] Add AC: "On cancel, the response SHALL include
expectedRefundAmountandexpectedRefundDatederived fromCancel reason × CancellationPolicy.rules+refundSlaHours(default 168 = 7 days, max 720 per DPDPA reasonable-period). Payment-service emitsrefund.processedwithin SLA window orrefund.sla.breachedfor ops alert." (Shared cause with EP-002 D-010.)
D-007 — [P1] [Cross-service-handoff + risk-of-double-charge] OTA cancellation flow consumes OtaBookingCancelledEvent and invokes ReservationsService.cancel() (gotcha 19, AP-T11-023). The platform's cancellation policy is applied but the OTA-stated charge is ignored — for OTAs that already debited the guest's card per their own policy, the local policy can double-apply.
- [SME-PROPOSED] Add AC: "
ChannelBookingCancelledConsumerSHALL pass throughevent.cancellationCharge(when set by OTA) intocancel()and the local policy SHALL NOT add on top. If both the OTA and property policy disagree by > ₹1, anOTA_CANCEL_CHARGE_MISMATCHwarning event is emitted for ops review (manual reconciliation) instead of silently using one over the other."
US-003-006 — Real-Time Availability Engine
Catalog status: Built (P0, 8 points) Verified status: Built
Code locations:
hmp-reservation-service/src/availability/availability.controller.ts— POST/availability/check+ GET/availability(both@Public()), GET/inventory/:propertyId/:roomTypeId(returns{ inventory, roomBlocks }per gotcha 19/20)hmp-reservation-service/src/availability/availability.service.ts—checkAvailability()(Redis-cached; effective capacity =totalRooms + bufferRoomsper OverbookingConfig — P0-2)hmp-reservation-service/src/availability/federated-availability.controller.ts— GET/availability/search(multi-property federated)hmp-reservation-service/src/availability/federated-availability.service.ts— sister-property overflow pathhmp-property-service/src/availability/— property-service-side inventory model (cross-service)hmp-booking-engine/src/— public guest-facing search consumes/booking-engine/availability
Unit + integration tests:
hmp-reservation-service/src/availability/availability.service.spec.ts— availability calculations + cache + overbooking bufferhmp-reservation-service/src/availability/availability.controller.spec.ts— controller wiringhmp-reservation-service/src/availability/federated-availability.service.spec.ts— federated search
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/ari-grid.flow.spec.ts:: "availability grid" — TBDhmp-infrastructure/testing/flow-tests/src/flows/tape-chart.flow.spec.ts:: "tape chart availability" — TBDhmp-infrastructure/testing/flow-tests/src/flows/booking-engine.flow.spec.ts:: "BE availability search" — TBDhmp-infrastructure/testing/flow-tests/src/flows/performance.flow.spec.ts:: "500 concurrent availability checks" — TBD (EPIC KPI: < 500 ms p95)
Real-backend smokes:
- (covered transitively via booking-engine smoke + reservation-create smoke)
E2E (Playwright):
hmp-web-dashboard/e2e/tests/13-booking-engine.spec.ts— BE availability flowhmp-web-dashboard/e2e/ari-grid.spec.ts— ARI grid availabilityhmp-web-dashboard/e2e/tape-chart-phase2.spec.ts+tape-chart-phase3.spec.ts+tape-chart-v2.spec.ts— tape chart
Use cases referencing this story:
- none direct (consumed by booking-idempotency UC)
SME notes:
- Both POST and GET availability endpoints exist with identical semantics — story says "guest searches" but the two-endpoint shape supports OTA channel-manager pull (GET with query) AND modern BE (POST with body). EPIC body should note both shapes.
- P0-2 overbooking buffer (
OverbookingConfig.bufferRooms) is honored ineffectiveCapacity— see gotcha 10. Story does not enumerate this. roomBlocksarray returned alongside inventory (gotcha 19) is required for the tape-chart UI to render maintenance markers. Story does not enumerate the response shape change.- Indian-hospitality SME: "minLOS" (min length-of-stay) restrictions are a common Diwali-week control (rate plan with
minLOS = 3excludes 1-night searches). Story AC scenario 2 references this but the response-shape contract for which restrictions trigger exclusion is not pinned.
Drift findings:
D-008 — [P1] [Story-incomplete] EPIC KPI says "Availability check p95 latency: < 500 ms" but no flow test in performance.flow.spec.ts asserts this under the EPIC's stated load (500 concurrent checks). The Redis cache makes this trivially achievable in clean-cache conditions; the SLA is only meaningful under cache-miss + concurrent-load.
- [SME-PROPOSED] Add AC: "Availability check SHALL respond < 500 ms p95 with 500 concurrent calls against a 30-day date range, with 50 % cache-miss ratio. Asserted in
performance.flow.spec.tsagainst the staging EKS cluster (not local docker)."
US-003-007 — Inventory Hold (Temporary Lock)
Catalog status: Partial (P1, 3 points) — catalog row 79 maps US-003-007 to "Overbooking Controls" — see D-001. The EPIC body's hold story is what's actually shipped. Verified status: Built
Code locations:
hmp-reservation-service/src/availability/availability.controller.ts— POST/availability/hold, GET/availability/hold/:holdId, DELETE/availability/hold/:holdIdhmp-reservation-service/src/availability/availability.service.ts—createHold()/getHold()/releaseHold()using Redis with TTL (default 15 min per gotcha 7)hmp-reservation-service/src/reservations/hold-release.scheduler.ts— scheduled job to release expired holds
Unit + integration tests:
hmp-reservation-service/src/availability/availability.service.spec.ts— hold create/get/release + TTL expiry
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/booking-engine.flow.spec.ts:: "hold during checkout" — TBDhmp-infrastructure/testing/flow-tests/src/flows/booking-engine-selfservice.flow.spec.ts:: "hold expiry" — TBD
Real-backend smokes:
- none specific (exercised via booking-engine smoke)
Use cases referencing this story:
- none
SME notes:
- Holds are Redis-only — they survive nothing. A Redis flush during peak booking releases every active hold and causes immediate over-sell windows. No Postgres fallback / persistence layer. Story does not document the durability trade-off.
- Story AC scenario 2 ("Hold expires automatically after TTL") relies on Redis EXPIRE — verify the scheduler doesn't double-count (Redis auto-expires the key; scheduler's job is just to surface a "released" event, not to do the freeing).
Drift findings: (none net new)
- [SME-PROPOSED] Add AC: "Hold persistence SHALL be documented as Redis-only with
cache.t3.mediuminstance type; a Redis outage releases all active holds. A future Postgres-backed durable-hold model is tracked under a new storyUS-003-NEW-DURABLE-HOLDS."
US-003-008 — Room Assignment
Catalog status: Built (P0, 5 points) — catalog row 80 says "Confirmation Number Generation & Email" — see D-001. Verified status: Built
Code locations:
hmp-reservation-service/src/reservations/reservations.controller.ts— POST/reservations/assign-room(Roles: SUPER_ADMIN, ADMIN, MANAGER, RESERVATIONS, FRONT_DESK;@Audit)hmp-reservation-service/src/reservations/reservations.service.ts—assignRoom()writesRoomReservation.roomId, emitsroom.assignedANDreservation.modifiedwithchangeType: 'ROOM_MOVE'(AP-T05-007)hmp-reservation-service/src/reservations/dto/reservation.dto.ts—AssignRoomDto
Unit + integration tests:
hmp-reservation-service/src/reservations/reservations.service.spec.ts— assign-room happy path + ROOM_MOVE event emissionhmp-reservation-service/src/reservations/__tests__/— covers double-assignment guard
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/tape-chart.flow.spec.ts:: "drag-drop assign room" — TBDhmp-infrastructure/testing/flow-tests/src/flows/flow-5-fd-hk-updates.spec.ts:: "room move" — 11/12 PASS
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/smoke-tape-chart.real.spec.ts— tape chart room-assign
E2E (Playwright):
hmp-web-dashboard/e2e/tape-chart-phase2.spec.ts+tape-chart-v2.spec.ts— tape-chart room assignment
Use cases referencing this story:
docs/platform-registry/_data/use-cases/uc-tape-chart-filters.yamldocs/platform-registry/_data/use-cases/uc-tape-chart-no-show.yamldocs/platform-registry/_data/use-cases/uc-reservation-modified-event.yaml(AP-T05-007 — ROOM_MOVE event)
SME notes:
- AP-T05-007 (room-move emits
reservation.modified) — analytics ADR/occupancy materialized views depend on this. Story AC does not describe the event emission semantics. - Double-assignment guard: story AC scenario 2 ("Prevent double-assignment of a room") — verify this is enforced server-side (today: room availability check via existing
RoomReservation.roomIdindex). - Indian-hospitality SME: "auto-suggest based on preferences" (EPIC body Scope line) is partial — guest preferences (high floor, non-smoking, accessible) live on
Guestprofile, but the auto-suggest UI is not in the room-assignment dialog. Story body is silent on whether this is in scope for US-003-008 or deferred.
Drift findings: (none net new)
- [SME-PROPOSED] Add AC: "Room assignment UI SHALL show guest preferences (from
Guest.preferencesJSON) alongside the available-rooms list, with rooms matching preferences sorted first. Preference-matching scoring documented inroom-assignment-suggestion.spec.ts."
US-003-009 — Arrivals / Departures / In-House Boards
Catalog status: Partial (P0, 3 points) — catalog row 81 says "Walk-In Reservation (Quick Create)" — see D-001. Verified status: Built
Code locations:
hmp-reservation-service/src/reservations/reservations.controller.ts— GET/properties/:propertyId/arrivals?date=, GET/properties/:propertyId/departures?date=, GET/properties/:propertyId/in-househmp-reservation-service/src/reservations/reservations.service.ts—getArrivals()/getDepartures()/getInHouse()hmp-frontdesk-service/src/checkin/— frontdesk-side mirror of arrivals (used during check-in flow)hmp-web-dashboard/src/app/(dashboard)/front-desk/[CITED-BUT-NOT-FOUND]— Front Desk module with Arrivals / Departures / In-House tabs
Unit + integration tests:
hmp-reservation-service/src/reservations/reservations.service.spec.ts— arrivals/departures/in-house list shape
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-6-check-in.spec.ts:: "arrivals board → check-in" — 6/13 PASS (Aadhaar/C-Form pending)hmp-infrastructure/testing/flow-tests/src/flows/flow-8-check-out.spec.ts:: "departures board → check-out" — 0/15 PASShmp-infrastructure/testing/flow-tests/src/flows/front-desk-checkin.flow.spec.ts:: "front desk arrivals" — TBD
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/front-desk-checkin.real.spec.ts— FD arrivals → check-inhmp-web-dashboard/e2e/real-backend/smoke-checkin-checkout.real.spec.ts— checkin/checkout boards
E2E (Playwright):
hmp-web-dashboard/e2e/check-in-flow.spec.ts— arrivals board click-throughhmp-web-dashboard/e2e/tests/16-checkin-checkout.spec.ts— full flow
Use cases referencing this story:
- none direct (consumed by BF-003 check-in flow)
SME notes:
- Both reservation-service AND frontdesk-service expose
/arrivals— this is a known duplication. The dashboard reads from reservation-service; frontdesk-service's copy is used during the check-in cascade. Story does not document the duplication. - Story AC scenario 2 ("Departures with outstanding balance flagged in red") requires reading the live folio balance from frontdesk-service. Today:
Foliois a synced read model in reservation-service — the balance may be stale. Live-vs-cached visibility is undocumented.
Drift findings:
D-009 — [P1] [Cross-service-handoff] /arrivals exists in BOTH reservation-service and frontdesk-service. They are NOT guaranteed to return the same row set (e.g. a NO_SHOW row may be filtered differently). The dashboard reads from reservation-service; frontdesk's internal check-in flow reads its own. Risk: a row visible on the dashboard arrivals board but invisible to the check-in flow (or vice-versa).
- [SME-PROPOSED] Add AC: "Both
/arrivalsendpoints SHALL return the same row set for the same(propertyId, date). A contract test (api-contract-testskill) SHALL verify byte-equivalence ofreservation-service /arrivalsandfrontdesk-service /arrivalsagainst the same seed." - [SME-PROPOSED] Add AC: "Departures board SHALL fetch live folio balance via
FolioServiceClient.getOpenFolio(reservationId)(5-min cache acceptable) rather than the local syncedFolioread model, to avoid stale-balance flagging."
US-003-010 — No-Show Processing
Catalog status: Built (P0, 2 points) — catalog row 82 = "Booking Source Tracking" — see D-001. Verified status: Built
Code locations:
hmp-reservation-service/src/reservations/reservations.controller.ts— POST/reservations/:id/no-show(Roles: SUPER_ADMIN, ADMIN, MANAGER, FRONT_DESK;@Audit)hmp-reservation-service/src/reservations/reservations.service.ts—processNoShow()sets status=NO_SHOW, recordsnoShowAt, emitsreservation.no_showKafka eventhmp-frontdesk-service/src/night-audit/[CITED-BUT-NOT-FOUND]— night audit batch typically invokes per-property no-show sweep
Unit + integration tests:
hmp-reservation-service/src/reservations/reservations.service.spec.ts— no-show happy path + status-guard (cannot no-show a CHECKED_IN row)
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/noshow-late-checkout.flow.spec.ts:: "no-show transition" — TBDhmp-infrastructure/testing/flow-tests/src/flows/flow-9-night-audit.spec.ts:: "night audit no-show sweep" — TBD
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/smoke-night-audit.real.spec.ts— night audit including no-show
E2E (Playwright):
- (covered via
tape-chart/reservation-flow; no dedicated no-show E2E)
Use cases referencing this story:
docs/platform-registry/_data/use-cases/uc-tape-chart-no-show.yaml
SME notes:
- Story AC scenario 1 says "a no-show charge (typically first night) is applied to the folio" — but
processNoShow()does NOT post a folio charge today. The charge application is a no-op. Story AC overpromises. - Story AC scenario 2 ("Cannot mark already checked-in as no-show") IS enforced via status guard.
- Indian-hospitality SME: For OTA bookings, the no-show charge typically routes back to the OTA's virtual credit card (VCC) — the OTA pre-paid the property, but the property must signal "guest didn't arrive" to trigger VCC settlement. Today: no VCC-aware no-show flow.
Drift findings:
D-010 — [P1] [Story-vs-code + revenue-loss] Story AC says "no-show charge (typically first night) is applied to the folio" but processNoShow() doesn't post a folio charge. This is a silent revenue loss: a no-show with the AC's expected behaviour leaves the room unbooked AND no charge posted.
- [SME-PROPOSED] Add AC: "On
POST /reservations/:id/no-show, the service SHALL post aNO_SHOW_CHARGEfolio transaction equal to (first-night room rate × number of rooms) by emittingFOLIO_CHARGE_POSTEDwithtransactionCode='NO_SHOW_CHARGE'. The charge respects the policy'snoShowChargerule when set; defaults to 100 % first night. Tested innoshow-late-checkout.flow.spec.ts." - [SME-PROPOSED] Add AC: "For OTA-sourced reservations (
source IN (BOOKING_COM, EXPEDIA, MAKEMYTRIP, AGODA, GOIBIBO, AIRBNB, GDS)),processNoShow()SHALL additionally emitchannel.noshow.reportedso channel-service can push a no-show confirmation back to the partner (required by Booking.com / Expedia for VCC settlement)."
US-003-011 — Confirmation Number Generation & Lookup
Catalog status: Partial (P1, 8 points) — catalog row 83 = "Group Booking Creation" — see D-001. Verified status: Built
Code locations:
hmp-reservation-service/src/reservations/reservations.service.ts—generateConfirmationNumber(prefix)increate()hmp-reservation-service/src/reservations/reservations.controller.ts— GET/reservations/confirmation/:confirmationNumber- (uniqueness enforced via
Reservation.confirmationNumber @uniquein Prisma schema)
Unit + integration tests:
hmp-reservation-service/src/reservations/reservations.sprint2.spec.ts— confirmation number formathmp-reservation-service/src/reservations/search-q-param.spec.ts— q-param lookup paths
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-4-reservation-review.spec.ts:: "confirmation lookup" — TBD
Real-backend smokes:
- none
Use cases referencing this story:
- none
SME notes:
- Story AC says format is
<PROP-CODE><YYYYMMDD><SEQ>(exampleGPH20260415001) — actual code uses<prefix>...where prefix defaults toRES, NOT the property code. Story example will not match real output. - Uniqueness is enforced; collision retry logic depends on
generateConfirmationNumber(verify the SEQ portion is monotonic per-day-per-property, not platform-global). - Story does NOT mention email sending (catalog title "& Email" implies it) — confirmation email is a notification-service concern, not reservation-service. Cross-EPIC handoff.
Drift findings:
D-011 — [P2] [Story-example-misleading] Story AC example confirmation number GPH20260415001 implies property-code prefix. Actual code defaults to RES (per safeguard config). A tenant configuring "GPH" expecting per-property prefix will find all reservations start with RES.
- [SME-PROPOSED] Add AC: "Confirmation number prefix SHALL default to the property's 3-letter code (
Property.codefield) if set; falls back toRESonly whenProperty.codeis null. The format<PROP-CODE><YYYYMMDD><SEQ>is the canonical shape; SEQ is per-(property, date) monotonic 3-digit, padded with leading zeros, rolling over to 4 digits beyond 999 reservations/day."
US-003-012 — Group Booking CRUD
Catalog status: Partial (P1, 5 points) — catalog row 84 = "Allotment Block Management" — see D-001. Verified status: Built
Code locations:
hmp-reservation-service/src/groups/groups.controller.ts— POST/GET/PUT/DELETE/groups+ POST/groups/:id/confirm+ DELETE/groups/:id(cancel with reason) + analytics/cutoff/folio sub-routeshmp-reservation-service/src/groups/groups.service.ts— Group lifecycle (TENTATIVE → CONFIRMED → CANCELLED), cutoff schedulerhmp-reservation-service/src/groups/group-cutoff.scheduler.ts— auto-release unpicked rooms past cutoffhmp-reservation-service/src/groups/dto/— DTOshmp-web-dashboard/src/app/(dashboard)/reservations/groups/page.tsx— Groups listhmp-web-dashboard/src/app/(dashboard)/reservations/groups/new/page.tsx— Group createhmp-web-dashboard/src/app/(dashboard)/reservations/groups/[id]/— Group detail
Unit + integration tests:
hmp-reservation-service/src/groups/groups.service.spec.ts— Group lifecyclehmp-reservation-service/src/groups/groups-phase-b.service.spec.ts— phase-B allocation/rooming-list features
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/group-bookings.flow.spec.ts:: "group lifecycle" — TBD
Real-backend smokes:
- none specific (groups exercised via dashboard tests)
E2E (Playwright):
hmp-web-dashboard/e2e/tests/group-bookings.spec.ts— group CRUD + confirm
Use cases referencing this story:
- none
SME notes:
- Group lifecycle is well-modeled (TENTATIVE → CONFIRMED → CANCELLED) and the cutoff scheduler auto-releases unpicked rooms. Story AC matches code.
- Indian-hospitality SME: wedding-season group bookings (Nov-Feb) frequently see
roomsBlockedadjustments after the cutoff via manager override — verify the cutoff scheduler emits a discoverable event (e.g.group.cutoff.released) so the sales team gets notified. - Group folio (
/groups/:id/folio) and analytics (/groups/:id/analytics) endpoints exist beyond the story body — orphaned-code candidates? They're useful but story is silent.
Drift findings: (none net new)
US-003-013 — Group Allotment & Rooming List
Catalog status: Partial (P1, 3 points) — catalog row 85 = "Rooming List" only — see D-001. EPIC body US-003-013 is broader: allotment + rooming. Verified status: Partial — backend allocation API + rooming-list upload shipped; no allotment-grid dashboard UI; no Excel-upload UX for rooming list
Code locations:
hmp-reservation-service/src/groups/groups.controller.ts— POST/groups/:id/allocations+ POST/groups/:id/allocate-rooms+ POST/groups/:id/rooming-list+ GET/groups/:id/roominghmp-reservation-service/src/groups/groups.service.ts—GroupRoomAllocationmodel (per-roomType + ratePlan negotiated rate)hmp-reservation-service/src/groups/groups-phase-b.service.spec.ts— phase-B allocation specshmp-web-dashboard/src/app/(dashboard)/reservations/groups/[id]/— Group detail (rooming list rendered; no upload widget yet)
Unit + integration tests:
hmp-reservation-service/src/groups/groups-phase-b.service.spec.ts— allotment + rooming listhmp-reservation-service/src/groups/groups.service.spec.ts— link/delink reservation
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/group-bookings.flow.spec.ts:: "rooming list pickup" — TBD
Real-backend smokes:
- none
Use cases referencing this story:
- none
SME notes:
- Backend has
POST /groups/:id/rooming-listfor Excel/JSON upload — front-end widget for drag-drop Excel isn't shipped. EPIC gap-analysis flagged this as "rooming list upload from Excel". - Allotment-grid UI (date × room-type showing blocked/picked-up/available) is gap-analysed in EPIC and remains undone.
- Indian-hospitality SME: MICE-segment hotels (Meetings, Incentives, Conferences, Events) need a "block release" workflow distinct from cutoff: a salesperson can release N rooms back to general inventory before cutoff if the group only filled M. The
POST /groups/:id/release-roomsendpoint exists for this — story AC doesn't describe it.
Drift findings: (none net new — feeds catalog status correction)
- [SME-PROPOSED] Add AC: "
POST /groups/:id/rooming-listSHALL accept multipart/form-data with an .xlsx/.csv file. Columns:guestName,email,phone,roomType,checkIn,checkOut,extraAdults,mealPlan. Validation: row-by-row with per-row error report; partial success allowed; transaction-per-row. Tested ingroups-phase-b.service.spec.tswith a fixture spreadsheet." - [SME-PROPOSED] Author dashboard story
US-003-NEW-ALLOTMENT-GRIDfor the date × room-type visual editor (gap-analysis item).
US-003-014 — Waitlist Management
Catalog status: Partial (P2, 5 points) — catalog row 86 = "Waitlist Management" — matches. Verified status: Partial — backend WaitlistController with 5 statuses shipped; no auto-offer-on-cancellation logic; no dashboard UI; no prioritization rules
Code locations:
hmp-reservation-service/src/waitlist/waitlist.controller.ts— POST/waitlist+ GET/waitlist+ DELETE/waitlist/:id+ POST/waitlist/:id/accepthmp-reservation-service/src/waitlist/waitlist.service.ts— Waitlist status: WAITING / OFFERED / ACCEPTED / EXPIRED / CANCELLEDhmp-reservation-service/src/waitlist/waitlist.module.ts
Unit + integration tests:
hmp-reservation-service/src/waitlist/waitlist.service.spec.ts— happy paths
Flow tests: NONE FOUND (drift candidate)
Real-backend smokes: NONE FOUND
E2E (Playwright): NONE FOUND (no waitlist UI in dashboard)
Use cases referencing this story:
- none
SME notes:
- The auto-offer-on-cancellation pipeline does not exist: cancelling a reservation does NOT scan the waitlist and transition the first WAITING entry to OFFERED. EPIC gap-analysis flags this.
- No prioritization rules (VIP first / FCFS) — today it's FCFS by
createdAtonly. - No dashboard UI — agents must call the API directly. The EPIC gap-analysis flags "dashboard lacks dedicated waitlist view".
Drift findings:
D-012 — [P1] [Story-vs-code + missing-pipeline] Story AC scenario 2 ("Accept a waitlist offer") describes a workflow where a cancellation auto-transitions the first waitlist entry to OFFERED. Today the WAITING → OFFERED transition has to be triggered manually via the API; there is no consumer of reservation.cancelled that scans waitlist rows.
- [SME-PROPOSED] Add AC: "A
WaitlistOfferConsumerSHALL subscribe toreservation.cancelledandavailability.openedevents. On consumption, it SHALL find the longest-WAITING entry whose(propertyId, roomTypeId, arrivalDate)matches the freed inventory and transition it to OFFERED with a 24 h TTL. A notification is sent (email + SMS) to the guest. On TTL expiry, status flips to EXPIRED and the next entry gets offered." - [SME-PROPOSED] Add AC: "Waitlist prioritization SHALL support pluggable rules (default FCFS, optional VIP-first ordering by
Guest.vipLevel). Configured per property via aWaitlistPolicy.priorityRulefield." - [SME-PROPOSED] Author dashboard story
US-003-NEW-WAITLIST-UIfor the waitlist board (gap-analysis item).
US-003-015 — Payment Initiation & Verification (Razorpay)
Catalog status: Built (P1, 3 points) — catalog row 87 = "Reservation Modification Audit Trail" — see D-001. Verified status: Built
Code locations:
hmp-reservation-service/src/payments/payments.controller.ts— POST/payments/initiate, POST/payments/verify, POST/payments/webhook(public,x-razorpay-signatureverification), POST/payments/refund, GET/payments/:id/statushmp-reservation-service/src/payments/payments.service.ts— Razorpay adapter via@hmp/paymentshmp-payment-service/src/payments/— modular payments service (port 3014; see platform CLAUDE.md "Modular Payments Platform Workstream A")hmp-booking-engine/src/— guest payment viaPOST /api/booking-engine/payments/initiate-v2→PaymentServiceClient.initiatePaymenthmp-web-dashboard/src/app/(dashboard)/settings/— payment provider settings
Unit + integration tests:
hmp-reservation-service/src/payments/payments.service.spec.ts— Razorpay flowshmp-payment-service/src/payments/*.spec.ts— modular payments service tests
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/payments.flow.spec.ts:: "Razorpay end-to-end" — TBDhmp-infrastructure/testing/flow-tests/src/flows/payment-links.flow.spec.ts:: "payment-link create + webhook" — TBDhmp-infrastructure/testing/flow-tests/src/flows/payment-subscription.flow.spec.ts:: "subscription / merchant-account flow" — TBDhmp-infrastructure/testing/flow-tests/src/flows/terminal-folio-settlement.flow.spec.ts:: "terminal charge → folio" — TBDhmp-infrastructure/testing/flow-tests/src/flows/upi-qr.flow.spec.ts:: "UPI QR" — TBDhmp-infrastructure/testing/flow-tests/src/flows/advance-deposit-split.flow.spec.ts:: "advance + split payment" — TBD
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/smoke-payment-link.real.spec.ts— payment linkhmp-web-dashboard/e2e/real-backend/smoke-merchant-account.real.spec.ts— merchant onboarding
E2E (Playwright):
hmp-web-dashboard/e2e/tests/payment-links-create.spec.tshmp-web-dashboard/e2e/tests/payment-links-page.spec.tshmp-web-dashboard/e2e/tests/payment-link-mode-gate.spec.tshmp-web-dashboard/e2e/tests/settings-payment-providers.spec.tshmp-web-dashboard/e2e/tests/advance-deposit-flow.spec.ts
Use cases referencing this story:
docs/platform-registry/_data/use-cases/uc-payment-merchant-account.yamldocs/platform-registry/_data/use-cases/uc-tenant-payment-onboarding.yaml
SME notes:
- Two payment paths exist: legacy
reservation-service/src/payments/AND modularhmp-payment-service(port 3014). The booking-engine path goes through payment-service via PaymentServiceClient; staff/dashboard initiations may still use reservation-service/payments/initiate. The split is documented in platform CLAUDE.md "Modular Payments Platform Workstream A" but the EPIC body says only "Razorpay integration" — single mention obscures the dual surface. - The 2026-05-19 reconciliation pipeline (memory note "Folio Payment Reconciliation 7-phase plan") added
payment.capturedemission +PaymentCapturedConsumerin frontdesk to dedupe and auto-post to folio. Story does not narrate this. - Razorpay amounts in paise (gotcha 8): watch
amount * 100conversion. No spec asserts the conversion direction is consistent across initiate/verify/webhook/refund paths.
Drift findings: (none net new — captured by platform-wide BF-008 future flow)
- [SME-PROPOSED] Add AC: "All Razorpay amount conversions (INR ↔ paise) SHALL go through a single
toPaise(inr)/fromPaise(paise)helper in@hmp/payments. Unit test asserts round-trip equivalence on edge cases (₹0.50, ₹99,999.99, ₹1,00,000)."
US-003-016 — Payment Link Generation & Delivery
Catalog status: Partial (P0, 3 points) — catalog row 88 = "No-Show Processing" — see D-001.
Verified status: Partial — payment-link generation shipped via Razorpay; payment-service emits payment.captured; notification-service exists but NOT wired for SMS/WhatsApp link delivery from reservation context
Code locations:
hmp-payment-service/src/payments/— Razorpay payment-link creationhmp-reservation-service/src/payments/payments.controller.ts— initiation surfacehmp-notification-service/src/whatsapp/[CITED-BUT-NOT-FOUND]— WhatsApp webhook controller exists per EPIC bodyhmp-web-dashboard/src/app/(dashboard)/reservations/[id]/page.tsx— "Send Payment Link" button (CreatePaymentLinkSheet — see memory note 2026-05-19)
Unit + integration tests:
hmp-payment-service/src/payments/*.spec.ts— payment-link pathhmp-reservation-service/src/payments/payments.service.spec.ts— initiation
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/payment-links.flow.spec.ts:: "payment-link create" — TBD
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/smoke-payment-link.real.spec.ts— payment link page
E2E (Playwright):
hmp-web-dashboard/e2e/tests/payment-links-create.spec.tshmp-web-dashboard/e2e/tests/payment-links-page.spec.tshmp-web-dashboard/e2e/tests/payment-link-mode-gate.spec.ts
Use cases referencing this story:
- (consumed indirectly via payment merchant-account UC)
SME notes:
- Story AC scenario 1 ("Send payment link via WhatsApp") is partly shipped: payment link generation exists, but the WhatsApp/SMS delivery via notification-service is NOT wired into the reservation context. Memory note 2026-05-19 confirms
CreatePaymentLinkSheetwas already shipped but the SMS/WhatsApp delivery side is the gap. - Story AC scenario 2 ("Payment link expires after 72 hours") — verify Razorpay default link expiry matches the AC. Razorpay supports
expire_byparameter but no spec asserts the default. - Indian-hospitality SME: WhatsApp Business API has strict template approval — link delivery template should already be pre-approved with Meta. TRAI DND rules don't apply to transactional WhatsApp, but do apply to SMS — a
payment.link.sentSMS must use a registered DLT template.
Drift findings:
(no new D-NNN; captured under D-005 of EP-005C and the EPIC body's gap-analysis)
- [SME-PROPOSED] Add AC: "Dashboard 'Send Payment Link' SHALL invoke
NotificationServiceClient.sendPaymentLink(reservationId, channel)where channel ∈ {EMAIL, SMS, WHATSAPP}. SMS path uses pre-registered DLT template. WhatsApp uses pre-approved Meta template. The notification-service emitsnotification.sentand the dashboard polls / subscribes for delivery status." - [SME-PROPOSED] Add AC: "Payment link
expire_bySHALL default to 72 hours from creation; tenant-configurable viaProperty.settings.paymentLinkExpiryHours(range 1-168)."
US-003-017 — Reservation Modification History
Catalog status: Built (P2, 5 points) — catalog row 89 = "Payment Link Generation for Guarantee" — see D-001.
Verified status: Built — backend ReservationHistory model + @Audit decorator populating it on every mutation; UI timeline missing per EPIC gap-analysis but data is exposed via reservation detail
Code locations:
hmp-reservation-service/prisma/schema.prisma[CITED-BUT-NOT-FOUND]—ReservationHistorymodel:(reservationId, action, changes JSON, performedBy, performedAt)hmp-reservation-service/src/reservations/reservations.service.ts—create()/update()/cancel()/assignRoom()/processNoShow()all write history rows@hmp/securityAuditdecorator — populatesAuditLogrows independently of ReservationHistoryhmp-web-dashboard/src/app/(dashboard)/reservations/[id]/page.tsx— Detail page (history exposed in payload; UI timeline view not yet implemented per EPIC gap)
Unit + integration tests:
hmp-reservation-service/src/reservations/reservations.service.spec.ts— history row writes verified per action
Flow tests:
- (history exercised transitively via reservation-amend flow-test)
Real-backend smokes:
- (none specific)
Use cases referencing this story:
- none
SME notes:
- ReservationHistory + AuditLog are TWO independent audit trails. ReservationHistory is per-row + entity-typed JSON changes; AuditLog is per-action + cross-entity. Story body doesn't differentiate.
- No dedicated API endpoint for
/reservations/:id/history— history is bundled in the reservation detail response. EPIC gap-analysis flags this. - Indian-hospitality SME: dispute resolution (the story's stated reason for the audit trail) commonly references the OTA's "modification reason code" — today changes JSON does NOT carry the OTA's reason; only the local user's change. OTA-driven modifications by
ChannelBookingReceivedConsumer.applyModification(gotcha 19) need to recordperformedBy='channel-service'+ the original OTA event id.
Drift findings: (none net new)
- [SME-PROPOSED] Add AC: "ReservationHistory rows for OTA-driven modifications SHALL set
performedBy='channel-service:<otaPartnerCode>'and include the originalotaEventId+notificationTypein thechangesJSON. Tested inchannel-booking-received.consumer.spec.ts." - [SME-PROPOSED] Add AC: "
GET /reservations/:id/historySHALL be a first-class endpoint returning a chronologically-sorted list with action / changes / performedBy / performedAt + computed before/after diff. Powers the dashboard timeline widget (US-003-NEW-HISTORY-TIMELINE)."
US-003-018 — Overbooking Control Configuration
Catalog status: Partial (P1, 3 points) — catalog row 90 = "Reservation Routing Rules" — see D-001. Verified status: Partial — backend OverbookingConfig model + per-(propertyId, roomTypeId, date) buffer respected in availability (P0-2); cross-service controller in analytics-service exists; no dashboard configuration UI; no walk-list management; no historical overbook-vs-walk analysis
Code locations:
hmp-reservation-service/prisma/schema.prisma[CITED-BUT-NOT-FOUND]—OverbookingConfigmodel(roomTypeId, date, bufferRooms, source)hmp-reservation-service/src/availability/availability.service.ts—effectiveCapacity = totalRooms + bufferRooms(P0-2)hmp-reservation-service/src/revenue-actions/— RevenueActionsModule async consumers for overbookinghmp-analytics-service/src/overbooking/[CITED-BUT-NOT-FOUND]— OverbookingController per EPIC body- (no dashboard UI for per-room-type buffer config)
Unit + integration tests:
hmp-reservation-service/src/availability/availability.service.spec.ts— overbook buffer in effectiveCapacityhmp-reservation-service/src/reservations/reservations.service.spec.ts—OVERBOOKEDerrorCode path
Flow tests:
- NONE FOUND specific to overbooking config (consumed implicitly by
ari-grid.flow.spec.ts)
Real-backend smokes:
- none
Use cases referencing this story:
- none
SME notes:
- The OverbookingConfig is a per-
(propertyId, roomTypeId, date)row — that's granular enough but cumbersome to populate. Story scenario "Set overbooking to 10% for Deluxe" implies a percentage; today the model stores absolutebufferRooms, not a percentage. - Walk-list (rooms-needed-to-be-walked-to-sister-property when overbook bites) is not implemented. Indian-hospitality SME: city-hotels with 90%+ occupancy commonly walk 1-2 guests per night and need a tracked workflow with cost reimbursement.
- Historical overbook-vs-walk analysis (was the overbooking justified?) — no MV / report.
Drift findings: (captured under EPIC gap-analysis)
- [SME-PROPOSED] Add AC: "OverbookingConfig SHALL support BOTH absolute
bufferRoomsAND percentagebufferPercent— UI lets RM choose. Effective buffer =MAX(bufferRooms, ⌈totalRooms × bufferPercent / 100⌉)per-(propertyId, roomTypeId, date)." - [SME-PROPOSED] Author new story
US-003-NEW-WALK-LIST"Walk-list management with sister-property routing + cost reimbursement" — covers the operational consequence of overbooking that today has no surface.
US-003-019 — Cancellation Charge Auto-Calculation
Catalog status: Partial (P1, 3 points) — catalog row 91 = "Corporate Rate & Travel Agent" — see D-001.
Verified status: Stub — CancellationPolicy rules model exists (hmp-property-service/src/cancellation-policies/) but ReservationsService.cancel() does NOT auto-calc charge; agent supplies manually
Code locations:
hmp-reservation-service/src/reservations/reservations.service.ts—cancel()recordscancellationReasonbut does NOT compute chargehmp-property-service/src/cancellation-policies/cancellation-policies.service.ts— owns CancellationPolicyhmp-property-service/src/cancellation-policies/dto/cancellation-rules-validator.ts— Zod typed rules ladder (AP-T02-006)
Unit + integration tests:
hmp-reservation-service/src/reservations/cancellation-policy.spec.ts— policy resolution (but does not assert auto-charge calc)hmp-property-service/src/cancellation-policies/cancellation-policy-rules.spec.ts— Zod rule shape (14 tests)
Flow tests:
- NONE FOUND for auto-charge calc (gap)
Use cases referencing this story:
docs/platform-registry/_data/use-cases/uc-cancellation-rules-validation.yaml
SME notes:
- This story is genuinely Stub: the data model + validation exist; the runtime application doesn't.
- The fix is straightforward: at
cancel()time, look up the policy from the linked rate plan, computedaysBeforeArrival = (arrivalDate − now) / 86400000, walk the rules ladder to find the matching tier, derivechargeAmount = rules[i].chargeType=='PERCENT' ? totalAmount × rules[i].value/100 : rules[i].flatValue. - Indian-hospitality SME: charge must respect non-refundable rate plans (full retention) AND DPDPA refund SLA (see EP-002 D-010). The auto-calc must also output
refundAmount = totalAmount − chargeAmountand anexpectedRefundDate.
Drift findings: (US-003-005 D-006 covers the same surface)
- [SME-PROPOSED] Add AC: "On
DELETE /reservations/:id, the service SHALL auto-calculatecancellationChargefromCancellationPolicy.rules+daysBeforeArrival+ reservation totalAmount, write it to a new columnReservation.cancellationCharge, and respond with{ cancellationCharge, refundAmount, expectedRefundDate, policySnapshot }. Manager-override path requiresforce=truequery param + Manager role. Asserted by 8 specs incancellation-policy.spec.tscovering: flexible-free / late-within-3-day / non-refundable / mid-tier / over-policy-window / manager-override / OTA-vcc-route / DPDPA-SLA."
US-003-020 — Charge Routing Rules
Catalog status: Not Started (P2, 3 points) — catalog row 92 = "Multi-Date Reservation Split" — see D-001. Verified status: Built
Code locations:
hmp-reservation-service/src/routing/routing-rules.controller.ts— POST/GET/PUT/DELETE/routing-ruleshmp-reservation-service/src/routing/routing-rules.service.ts— rule evaluatorhmp-reservation-service/src/routing/routing-rules.ts— typed rule structureshmp-frontdesk-service/src/folios/[CITED-BUT-NOT-FOUND]— folio posting consumes routing rules
Unit + integration tests:
hmp-reservation-service/src/routing/routing-rules.service.spec.ts— rule evaluation
Flow tests:
- NONE FOUND specific to charge routing
Use cases referencing this story:
- none
SME notes:
- Story AC scenario "Route room charges to corporate folio and F&B to guest folio" is a common B2B pattern. Verify the rule shape supports
{ chargeCategory: ['ROOM', 'TAX'] → folioId: 'corporate', chargeCategory: ['FB', 'MINIBAR'] → folioId: 'personal' }. - Indian-hospitality SME: GST treatment differs by folio (B2B corporate folio routes via GSTIN; personal folio is B2C). The router needs to be GST-aware: routing
TAXseparately fromROOMwithout preserving the GST tie-back will produce malformed e-invoices.
Drift findings: (none net new)
- [SME-PROPOSED] Add AC: "Charge routing rules SHALL preserve GST linkage: when ROOM goes to corporate folio, its associated CGST+SGST+IGST line items SHALL route to the same folio (not split). Cross-folio tax splits return 400 with
errorCode: 'GST_TAX_SPLIT_FORBIDDEN."
US-003-021 — Bulk Group Check-In & Room Assignment
Catalog status: Built (P1, 2 points) — catalog row 93 = "Reservation Notes & Alerts" — see D-001. Verified status: Not Started
Code locations: NONE FOUND (drift candidate)
Per EPIC body: "Backend: Not implemented. Individual check-in exists but no bulk operation."
Unit + integration tests: NONE FOUND
Flow tests: NONE FOUND
Real-backend smokes: NONE FOUND
E2E (Playwright): NONE FOUND
Use cases referencing this story:
- none
SME notes:
- "Not Started" matches reality. Bulk check-in is high-value for MICE / wedding-segment hotels: a 25-room group check-in queue blocks the lobby for 30+ minutes today.
- Indian-hospitality SME: bulk check-in must also handle bulk C-Form generation for international wedding guests (the FRRO online form requires per-guest data — needs an Excel-import path).
Drift findings: (story-vs-reality matches)
- [SME-PROPOSED] Add AC: "Bulk group check-in SHALL be transactional: either all 25 reservations transition CONFIRMED → CHECKED_IN OR none do. Partial-failure pre-flight: rooms blocked / out-of-order are surfaced as a precondition error with the offending room list; operator clears or reassigns; retry."
US-003-022 — Availability Calendar Visualization
Catalog status: Built (P0, 5 points, Sprint 2) — catalog row 75 (US-003-003 "Reservation Calendar View") corresponds to this — see D-001. Catalog row 94 = "Batch Reservation Import" is the catalog's actual US-003-022.
Verified status: Partial — backend GET /inventory/:propertyId/:roomTypeId?startDate=&endDate= ships (returns inventory + roomBlocks per gotcha 19); no calendar UI; no heat-map; no multi-room-type comparison
Code locations:
hmp-reservation-service/src/availability/availability.controller.ts— GET/inventory/:propertyId/:roomTypeId(returns{ inventory, roomBlocks })hmp-reservation-service/src/availability/availability.service.ts—getInventoryCalendar()hmp-web-dashboard/src/app/(dashboard)/...— calendar UI is NOT YET BUILT for the per-room-type heat-map; the existingari-grid+tape-chartcover adjacent UX but not the room-type × date occupancy heatmap
Unit + integration tests:
hmp-reservation-service/src/availability/availability.service.spec.ts— inventory calendar shape
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/ari-grid.flow.spec.ts:: "ARI grid" — TBD (adjacent UX, not the heat-map)
Real-backend smokes:
- (none specific)
E2E (Playwright):
hmp-web-dashboard/e2e/ari-grid.spec.ts+tape-chart-*.spec.ts— adjacent calendar UX
Use cases referencing this story:
- none
SME notes:
- Backend API is well-shaped; UI is the gap. EPIC gap-analysis says "No dashboard calendar UI. No color-coded heat map. No multi-room-type comparison view."
- Indian-hospitality SME: the RM use case is "where are my low-demand mid-week days for tactical pricing" — the heatmap must surface DOW patterns explicitly, not just per-day occupancy.
Drift findings: (Partial — UI gap matches)
- [SME-PROPOSED] Add AC: "Calendar heat-map SHALL render rows = room types, columns = dates (configurable 7/14/30/60/90 days), cells coloured by occupancy: green
< 70 %/ yellow70-90 %/ red> 90 %/ blacksold out. DOW pattern hint shown above the grid:Mon/Tue/Wed avg 45 % | Thu/Fri/Sat avg 78 %for the visible range. Click-through to rate calendar for the cell's room-type + date."