Skip to main content

EP-003 — Reservation Management RTM

EPIC: EP-003 Reservation Management Reconciled: 2026-05-24 Pilot artifact — see ./PLAYBOOK.md for 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.md until 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​

StatusMeaning
BuiltCode exists + tests exist + tests pass
Built-UntestedCode exists, no automated test coverage (or test exists but is a stub)
PartialCode exists for some acceptance criteria, gaps for others
StubCode is a placeholder / TODO / returns mock data
Not StartedNo code

Summary​

MetricCount
Total stories22
Catalog claims "Built"13
Verified-Built13 (US-003-001, -002, -003, -006, -007, -008, -009, -010, -011, -012, -015, -017, -020)
Built-Untested0
Partial7 (US-003-004, -005, -013, -014, -016, -018, -022)
Stub1 (US-003-019 — auto-charge calc not implemented; agent specifies manually)
Not Started1 (US-003-021 bulk group check-in)
Drift findings12 (D-001..D-012; 4 P0 / 5 P1 / 3 P2)
[SME-PROPOSED] refinements34

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; honors Idempotency-Key header — 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 reconciliation expectedTotalAmount guard, GST via GstService.getRoomGstRate(), transactional create + history + Kafka emit
  • hmp-reservation-service/src/reservations/dto/reservation.dto.ts — CreateReservationDto, RoomReservationDto with isTaxInclusive, allowOverOccupancy, expectedTotalAmount
  • hmp-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 page
  • hmp-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 assertions
  • hmp-reservation-service/src/reservations/reservations.enhanced.spec.ts — extended create paths
  • hmp-reservation-service/src/reservations/reservations.controller.spec.ts — controller wiring
  • hmp-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 doc
  • hmp-infrastructure/testing/flow-tests/src/flows/flow-4-reservation-review.spec.ts :: "reservation create/lookup/cancel" — TBD per 13-flow doc
  • hmp-infrastructure/testing/flow-tests/src/flows/idempotency-replay.flow.spec.ts :: "POST /reservations replay" — exercises AP-T03-001 Idempotency-Key path
  • hmp-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 flow
  • hmp-web-dashboard/e2e/tests/reservation-flow.spec.ts — granular reservation flow
  • hmp-web-dashboard/e2e/tests/reservations-create.spec.ts — create form
  • hmp-web-dashboard/e2e/tests/15-crud-reservations.spec.ts — CRUD coverage
  • hmp-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 via GstService.getRoomGstRate() (slab 0%/12%/18% — gotcha 12 in service CLAUDE.md), promo code processing was added per AP-T11-014 (PromotionServiceClient.applyPromotions in preview()), 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> per generateConfirmationNumber(). Story spec example GPH20260415001 aligns. The prefix comes from safeguard config (default RES) — 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 reconciliation to 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() iterates roomReservations[] to insert N RoomReservation rows under one Reservation
  • hmp-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-line checkInDate/checkOutDate/mealPlan (EP/CP/MAP/AP)

Unit + integration tests:

  • hmp-reservation-service/src/reservations/multi-room.service.spec.ts — dedicated multi-room suite
  • hmp-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" — TBD
  • hmp-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-RoomReservation checkInDate/checkOutDate, but no spec asserts the parent reservation's arrivalDate/departureDate are 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 on RoomReservation, not as a separate charge type — the spec doesn't say where this lives. Today it's bundled into rateAmount at 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.arrivalDate SHALL equal MIN(roomReservations[].checkInDate) and Reservation.departureDate SHALL equal MAX(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 RoomReservationExtra row (or a typed JSON column with audit visibility), not bundled into rateAmount opaquely. 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: guestName filters via local Guest read model — case-insensitive OR on firstName/lastName, no cross-service HTTP)
  • hmp-reservation-service/src/reservations/reservations.service.ts — search() with filters: propertyId, status[], guestName, confirmationNumber, fromDate, toDate, source[], roomTypeId, companyId
  • hmp-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 — SearchReservationsDto
  • hmp-reservation-service/src/reservations/search-q-param.spec.ts — ?q= quick-search parameter
  • hmp-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 coverage
  • hmp-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" — TBD
  • hmp-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 + filter
  • hmp-web-dashboard/e2e/tests/reservation-flow.spec.ts — search path
  • hmp-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 Guest filter eliminates a per-search property-service round-trip — useful for big-search latency. The story body does not mention this implementation choice.
  • confirmationNumber lookup is documented as "indexed" but no spec asserts the < 200 ms SLA. 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/:confirmationNumber SHALL respond < 200 ms p95 with 10K reservation rows in the property. Asserted via a k6 / flow-test scenario in performance.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 (parses If-Match ETag via parseIfMatchVersion)
  • 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 edit
  • hmp-reservation-service/src/reservations/version-optimistic-concurrency.spec.ts — version + If-Match handling
  • hmp-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 guard
  • hmp-reservation-service/src/reservations/reservation-extras.service.spec.ts — extras
  • hmp-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 dialog
  • hmp-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 totalAmount at create, so extending departure left billing under-counted. Now ratePerNight = frozenTotal / oldNights × newNights.
  • AP-T05-002 (extending a CHECKED_IN reservation) emits FOLIO_CHARGE_POSTED per added night — frontdesk's PosFolioChargeConsumer writes 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/:id and PATCH /reservations/:id/rooms/:roomReservationId SHALL return 409 with errorCode: '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, emits reservation.cancelled Kafka event
  • hmp-reservation-service/src/reservations/cancellation-policy.spec.ts — cancellation policy interaction spec
  • hmp-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 model
  • hmp-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 resolution
  • hmp-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 expectedRefundAmount and expectedRefundDate derived from Cancel reason × CancellationPolicy.rules + refundSlaHours (default 168 = 7 days, max 720 per DPDPA reasonable-period). Payment-service emits refund.processed within SLA window or refund.sla.breached for 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: "ChannelBookingCancelledConsumer SHALL pass through event.cancellationCharge (when set by OTA) into cancel() and the local policy SHALL NOT add on top. If both the OTA and property policy disagree by > ₹1, an OTA_CANCEL_CHARGE_MISMATCH warning 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 + bufferRooms per 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 path
  • hmp-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 buffer
  • hmp-reservation-service/src/availability/availability.controller.spec.ts — controller wiring
  • hmp-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" — TBD
  • hmp-infrastructure/testing/flow-tests/src/flows/tape-chart.flow.spec.ts :: "tape chart availability" — TBD
  • hmp-infrastructure/testing/flow-tests/src/flows/booking-engine.flow.spec.ts :: "BE availability search" — TBD
  • hmp-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 flow
  • hmp-web-dashboard/e2e/ari-grid.spec.ts — ARI grid availability
  • hmp-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 in effectiveCapacity — see gotcha 10. Story does not enumerate this.
  • roomBlocks array 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 = 3 excludes 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.ts against 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/:holdId
  • hmp-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" — TBD
  • hmp-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.medium instance type; a Redis outage releases all active holds. A future Postgres-backed durable-hold model is tracked under a new story US-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() writes RoomReservation.roomId, emits room.assigned AND reservation.modified with changeType: '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 emission
  • hmp-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" — TBD
  • hmp-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.yaml
  • docs/platform-registry/_data/use-cases/uc-tape-chart-no-show.yaml
  • docs/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.roomId index).
  • Indian-hospitality SME: "auto-suggest based on preferences" (EPIC body Scope line) is partial — guest preferences (high floor, non-smoking, accessible) live on Guest profile, 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.preferences JSON) alongside the available-rooms list, with rooms matching preferences sorted first. Preference-matching scoring documented in room-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-house
  • hmp-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 PASS
  • hmp-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-in
  • hmp-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-through
  • hmp-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: Folio is 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 /arrivals endpoints SHALL return the same row set for the same (propertyId, date). A contract test (api-contract-test skill) SHALL verify byte-equivalence of reservation-service /arrivals and frontdesk-service /arrivals against 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 synced Folio read 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, records noShowAt, emits reservation.no_show Kafka event
  • hmp-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" — TBD
  • hmp-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 a NO_SHOW_CHARGE folio transaction equal to (first-night room rate × number of rooms) by emitting FOLIO_CHARGE_POSTED with transactionCode='NO_SHOW_CHARGE'. The charge respects the policy's noShowCharge rule when set; defaults to 100 % first night. Tested in noshow-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 emit channel.noshow.reported so 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) in create()
  • hmp-reservation-service/src/reservations/reservations.controller.ts — GET /reservations/confirmation/:confirmationNumber
  • (uniqueness enforced via Reservation.confirmationNumber @unique in Prisma schema)

Unit + integration tests:

  • hmp-reservation-service/src/reservations/reservations.sprint2.spec.ts — confirmation number format
  • hmp-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> (example GPH20260415001) — actual code uses <prefix>... where prefix defaults to RES, 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.code field) if set; falls back to RES only when Property.code is 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-routes
  • hmp-reservation-service/src/groups/groups.service.ts — Group lifecycle (TENTATIVE → CONFIRMED → CANCELLED), cutoff scheduler
  • hmp-reservation-service/src/groups/group-cutoff.scheduler.ts — auto-release unpicked rooms past cutoff
  • hmp-reservation-service/src/groups/dto/ — DTOs
  • hmp-web-dashboard/src/app/(dashboard)/reservations/groups/page.tsx — Groups list
  • hmp-web-dashboard/src/app/(dashboard)/reservations/groups/new/page.tsx — Group create
  • hmp-web-dashboard/src/app/(dashboard)/reservations/groups/[id]/ — Group detail

Unit + integration tests:

  • hmp-reservation-service/src/groups/groups.service.spec.ts — Group lifecycle
  • hmp-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 roomsBlocked adjustments 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/rooming
  • hmp-reservation-service/src/groups/groups.service.ts — GroupRoomAllocation model (per-roomType + ratePlan negotiated rate)
  • hmp-reservation-service/src/groups/groups-phase-b.service.spec.ts — phase-B allocation specs
  • hmp-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 list
  • hmp-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-list for 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-rooms endpoint 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-list SHALL 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 in groups-phase-b.service.spec.ts with a fixture spreadsheet."
  • [SME-PROPOSED] Author dashboard story US-003-NEW-ALLOTMENT-GRID for 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/accept
  • hmp-reservation-service/src/waitlist/waitlist.service.ts — Waitlist status: WAITING / OFFERED / ACCEPTED / EXPIRED / CANCELLED
  • hmp-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 createdAt only.
  • 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 WaitlistOfferConsumer SHALL subscribe to reservation.cancelled and availability.opened events. 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 a WaitlistPolicy.priorityRule field."
  • [SME-PROPOSED] Author dashboard story US-003-NEW-WAITLIST-UI for 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-signature verification), POST /payments/refund, GET /payments/:id/status
  • hmp-reservation-service/src/payments/payments.service.ts — Razorpay adapter via @hmp/payments
  • hmp-payment-service/src/payments/ — modular payments service (port 3014; see platform CLAUDE.md "Modular Payments Platform Workstream A")
  • hmp-booking-engine/src/ — guest payment via POST /api/booking-engine/payments/initiate-v2 → PaymentServiceClient.initiatePayment
  • hmp-web-dashboard/src/app/(dashboard)/settings/ — payment provider settings

Unit + integration tests:

  • hmp-reservation-service/src/payments/payments.service.spec.ts — Razorpay flows
  • hmp-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" — TBD
  • hmp-infrastructure/testing/flow-tests/src/flows/payment-links.flow.spec.ts :: "payment-link create + webhook" — TBD
  • hmp-infrastructure/testing/flow-tests/src/flows/payment-subscription.flow.spec.ts :: "subscription / merchant-account flow" — TBD
  • hmp-infrastructure/testing/flow-tests/src/flows/terminal-folio-settlement.flow.spec.ts :: "terminal charge → folio" — TBD
  • hmp-infrastructure/testing/flow-tests/src/flows/upi-qr.flow.spec.ts :: "UPI QR" — TBD
  • hmp-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 link
  • hmp-web-dashboard/e2e/real-backend/smoke-merchant-account.real.spec.ts — merchant onboarding

E2E (Playwright):

  • hmp-web-dashboard/e2e/tests/payment-links-create.spec.ts
  • hmp-web-dashboard/e2e/tests/payment-links-page.spec.ts
  • hmp-web-dashboard/e2e/tests/payment-link-mode-gate.spec.ts
  • hmp-web-dashboard/e2e/tests/settings-payment-providers.spec.ts
  • hmp-web-dashboard/e2e/tests/advance-deposit-flow.spec.ts

Use cases referencing this story:

  • docs/platform-registry/_data/use-cases/uc-payment-merchant-account.yaml
  • docs/platform-registry/_data/use-cases/uc-tenant-payment-onboarding.yaml

SME notes:

  • Two payment paths exist: legacy reservation-service/src/payments/ AND modular hmp-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.captured emission + PaymentCapturedConsumer in frontdesk to dedupe and auto-post to folio. Story does not narrate this.
  • Razorpay amounts in paise (gotcha 8): watch amount * 100 conversion. 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)."

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 creation
  • hmp-reservation-service/src/payments/payments.controller.ts — initiation surface
  • hmp-notification-service/src/whatsapp/ [CITED-BUT-NOT-FOUND] — WhatsApp webhook controller exists per EPIC body
  • hmp-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 path
  • hmp-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.ts
  • hmp-web-dashboard/e2e/tests/payment-links-page.spec.ts
  • hmp-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 CreatePaymentLinkSheet was 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_by parameter 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.sent SMS 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 emits notification.sent and the dashboard polls / subscribes for delivery status."
  • [SME-PROPOSED] Add AC: "Payment link expire_by SHALL default to 72 hours from creation; tenant-configurable via Property.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] — ReservationHistory model: (reservationId, action, changes JSON, performedBy, performedAt)
  • hmp-reservation-service/src/reservations/reservations.service.ts — create() / update() / cancel() / assignRoom() / processNoShow() all write history rows
  • @hmp/security Audit decorator — populates AuditLog rows independently of ReservationHistory
  • hmp-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 record performedBy='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 original otaEventId + notificationType in the changes JSON. Tested in channel-booking-received.consumer.spec.ts."
  • [SME-PROPOSED] Add AC: "GET /reservations/:id/history SHALL 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] — OverbookingConfig model (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 overbooking
  • hmp-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 effectiveCapacity
  • hmp-reservation-service/src/reservations/reservations.service.spec.ts — OVERBOOKED errorCode 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 absolute bufferRooms, 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 bufferRooms AND percentage bufferPercent — 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() records cancellationReason but does NOT compute charge
  • hmp-property-service/src/cancellation-policies/cancellation-policies.service.ts — owns CancellationPolicy
  • hmp-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, compute daysBeforeArrival = (arrivalDate − now) / 86400000, walk the rules ladder to find the matching tier, derive chargeAmount = 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 − chargeAmount and an expectedRefundDate.

Drift findings: (US-003-005 D-006 covers the same surface)

  • [SME-PROPOSED] Add AC: "On DELETE /reservations/:id, the service SHALL auto-calculate cancellationCharge from CancellationPolicy.rules + daysBeforeArrival + reservation totalAmount, write it to a new column Reservation.cancellationCharge, and respond with { cancellationCharge, refundAmount, expectedRefundDate, policySnapshot }. Manager-override path requires force=true query param + Manager role. Asserted by 8 specs in cancellation-policy.spec.ts covering: 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-rules
  • hmp-reservation-service/src/routing/routing-rules.service.ts — rule evaluator
  • hmp-reservation-service/src/routing/routing-rules.ts — typed rule structures
  • hmp-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 TAX separately from ROOM without 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 existing ari-grid + tape-chart cover 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 % / yellow 70-90 % / red > 90 % / black sold 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."