EP-008 — Banquet & Events Management RTM
EPIC: EP-008 Banquet & Events Management Reconciled: 2026-05-25 (Wave 1 fan-out — BF-010 Banquet Event flow precursor) Pattern: Follows the BF-001 pilot at
EP-002-rtm.md; seePLAYBOOK.md. Source of truth for "% passing":hmp-docs/docs/platform-registry/2026-05-16-13-flow-demo-readiness.md(the 13-flow doc doesn't enumeratebanquet-lifecycle.flow.spec.ts, so flow pass-rates below are tagged TBD).
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 | 13 |
| Catalog claims "Built" | 8 |
| Verified-Built | 2 (US-008-004 venues, US-008-009 floor-plan) |
| Built-Untested | 1 (US-008-008 — calendar wired to API but no Jest/Playwright coverage on the page) |
| Partial | 7 (US-008-001, -002, -003, -005, -006, -011, -013) |
| Stub | 2 (US-008-007 BEO/Proposal — endpoints exist on contracts.service.ts only; US-008-010 multi-venue — schema only) |
| Not Started | 1 (US-008-012 inquiry-to-quotation as a dedicated workflow — flow-test only) |
| Drift findings | 11 (D-001..D-011; 4 P0 / 5 P1 / 2 P2) |
| [SME-PROPOSED] refinements | 19 |
Numbering convention note — the EPIC body (
epics/EP-008-banquet-events.md) uses 2-digit story IDs (US-008-01..US-008-13) with one set of titles; the catalog (01-epic-catalog.md) uses 3-digit IDs (US-008-001..US-008-013) with a DIFFERENT title-to-ID mapping (e.g. catalogUS-008-007= "BEO / Proposal PDF Generation"; EPIC bodyUS-008-07= "Function Sheet Generation"). Thereviews/EP-008/US-008-NNN-review.mdfiles follow the catalog numbering. This RTM follows the catalog per playbook Task 8 convention. The mismatch is the most acute P0 drift in this EPIC — see D-001.
US-008-001 — Event Creation (7-Status Lifecycle)
Catalog status: Built (P1, 5 points) Verified status: Partial — CRUD + status machine + venue-conflict + EVT- numbering all shipped; cancellation-charge calc, contract-signed gate on TENTATIVE→CONFIRMED, capacity validation, and audit timeline are all gaps per the review
Code locations:
hmp-banquet-service/src/events/events.controller.ts:31-166—@Controller('events')with@Roles('SUPER_ADMIN','ADMIN','MANAGER')+@RequiresFeature('banquet'); routes GET/POST/, GET/PATCH/:id/status, GET/POST/PATCH/DELETE/:id/items, GET/PATCH/:id/group+/group/room-blockhmp-banquet-service/src/events/events.service.ts:22-30—STATUS_TRANSITIONS7-state machine (INQUIRY→QUOTATION_SENT→TENTATIVE→CONFIRMED→IN_PROGRESS→COMPLETED, plus →CANCELLED from any pre-COMPLETED)hmp-banquet-service/src/events/events.service.ts:46-52—generateEventNumber()returnsEVT-YYYYMM-{6-char base36}(note: review file saysEVT-XXXXXXsequential — code has since been hardened to date-prefix + random suffix; review is stale)hmp-banquet-service/src/events/events.service.ts:58-92—checkVenueConflict()with setup/teardown buffer overlap checkhmp-banquet-service/src/events/event-group.service.ts—createGroupForEvent()auto-createsGroupwithroomEstimate = ceil(expectedGuests/2)on CONFIRMEDhmp-banquet-service/src/events/dto/event.dto.ts—CreateEventDto,UpdateEventStatusDto,CreateEventItemDto,EventFilterDto,toEventResponseDtohmp-banquet-service/src/events/consumers/— domain consumers (per CLAUDE.md inventory:GuestErasedConsumerlives here)hmp-web-dashboard/src/app/(dashboard)/banquet/events/page.tsx— event list pagehmp-web-dashboard/src/app/(dashboard)/banquet/events/new/page.tsx— new-event placeholder (review file flagged it as text-only; not re-verified)hmp-web-dashboard/src/app/(dashboard)/banquet/events/[id]/page.tsx— event detail (overview, items, payments, client info, room block)
Unit + integration tests:
hmp-banquet-service/src/events/events.service.spec.ts— event-booking lifecycle coverage (create, conflict, status transitions; review confirms PASS on the 4 enumerated cases)hmp-banquet-service/src/events/event-group.service.spec.ts— room-block group creation/cancellationhmp-banquet-service/src/events/__tests__/event-cross-tenant-denial.spec.ts— 8 tests, Gold-Standard Pick D phase-3 port fromhmp-pos-service(shipped 2026-05-24). AssertsfindOneForbiddenException on cross-tenant,findAlltenantId-pinned scoping,createrejects cross-tenant venueId + cross-property-same-tenant venueId + leaks no tenant info via NotFoundException. Orphaned-test today: no story acceptance criterion references it (see D-005)hmp-web-dashboard/src/app/(dashboard)/banquet/events/__tests__/events-page.test.tsx— event list page test (existence verified; coverage TBD)
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/banquet-lifecycle.flow.spec.ts:: "Scenario 2 — create event in INQUIRY" + "Scenario 4 — walk status machine INQUIRY→TENTATIVE→CONFIRMED" — TBD (not in 13-flow doc; taggedfeatureSkipif banquet flag off for canary tenant) (2026-05-16-13-flow-demo-readiness.md)
Real-backend smokes: none specific found
E2E (Playwright): none specific found for banquet under hmp-web-dashboard/e2e/
Use cases referencing this story: none located
SME notes:
- The 7-state machine is encoded as a static
STATUS_TRANSITIONSmap. Precondition checks are NOT enforced: INQUIRY→QUOTATION_SENT does not requireitems.length > 0; TENTATIVE→CONFIRMED does not require a signed contract or 25%-of-total payment; CONFIRMED→IN_PROGRESS does not requireDate.now() >= event.startDateTime. Per the review, this is the highest-severity gap in this story. EVT-numbering: code uses date-prefix + 6-char random suffix (EVT-202604-AB12CD). EPIC body Scenario "Capture multi-day wedding inquiry" impliesEVT-2026-0042(date-year + sequential daily counter). The review's spec citationEVT-YYYYMMDD-NNNadds a day-component and daily-reset sequence which is again different. 3 different number formats across catalog, EPIC body, and review → pick one in spec refinement.- No
Reservation.outboxEvent-equivalent for banquet —event.status.changedis published fire-and-forget after the Prisma write. A status PATCH that succeeds in DB but fails to publish leaves the analytics MV silently behind. (Common pattern across banquet — see EP-008 D-008.) - The Indian-wedding multi-day reality (mehndi/sangeet/wedding/reception) is captured in the EPIC body's Scenario 3 (parent + sub-events) but the schema today has no
parentEventIdFK column to support it — see US-008-010 D-007.
Drift findings:
D-001 — [P0] [Catalog-vs-EPIC-body] EPIC body uses 2-digit story IDs (US-008-01..US-008-13) with one title-to-ID mapping; catalog uses 3-digit IDs (US-008-001..US-008-013) with a DIFFERENT mapping. Examples: catalog US-008-001 = "Event Creation (7-Status Lifecycle)" vs EPIC body US-008-01 = "Event Inquiry & Creation"; catalog US-008-007 = "BEO / Proposal PDF Generation" vs EPIC body US-008-07 = "Function Sheet Generation". Reviews follow catalog. A new engineer reading the EPIC body for "US-008-07 = function sheets" will look up reviews/EP-008/US-008-007-review.md and find a BEO/Proposal review — confusion guaranteed.
- [SME-PROPOSED] Adopt catalog numbering as canonical, rewrite
epics/EP-008-banquet-events.md§10 detailed-stories to match (3-digit IDs + catalog titles), and add an alias line> Previously titled "X" in EPIC body draft prior to 2026-05-25 reconciliationto each section. Mirror the EP-002 D-007 fix pattern.
D-002 — [P0] [Tests-assert-behavior-story-doesn't-describe] The event-cross-tenant-denial.spec.ts suite (8 tests, shipped 2026-05-24) asserts ForbiddenException on cross-tenant findOne, tenantId-pinned findAll, and venue-tenant + venue-property guards on create(). NONE of these defenses appear in the US-008-001 acceptance criteria, nor in the BNQ-010 venue-tenant guard story (which doesn't exist as a separate story today).
- [SME-PROPOSED] Add AC to US-008-001: "EventsService SHALL reject cross-tenant reads with
ForbiddenExceptionand cross-tenantvenueIdon create withForbiddenExceptionBEFORE any write. List endpoints SHALL pinwhere.tenantIdto the JWT-derived tenantId; client-suppliedpropertyIdfilter is a narrowing filter only, never a tenant-scope replacement." - [SME-PROPOSED] Author a new story
US-008-NEW-VENUE-TENANT-GUARD(BNQ-010) documenting the venue-tenant + venue-property cross-check explicitly, with the 8-test suite as its evidence file. Closes the orphaned-test gap.
US-008-002 — Venue Management (6 Setup Types)
Catalog status: Built (P1, 3 points)
Verified status: Partial — CRUD + availability shipped; duplicate-name check, deactivation, day-by-day calendar, capacity-vs-event validation, DELETE endpoint, and isActive/images/floorPlanUrl DTO fields all missing per review
Code locations:
hmp-banquet-service/src/venues/venues.controller.ts— GET/venues?propertyId=, POST/venues, GET/venues/:id, PATCH/venues/:id, GET/venues/:id/availability?startDate=&endDate=hmp-banquet-service/src/venues/venues.service.ts— Venue CRUD;checkAvailability()returns{isAvailable, conflicts}(multi-day overlap correctly handled)hmp-banquet-service/src/venues/dto/—CreateVenueDto(noreceptionCapacity/images/floorPlanUrl/isActiveper review)hmp-banquet-service/src/spaces/— hourly meeting-room overlay that re-uses theVenuemodel (filters byhourlyRate)hmp-web-dashboard/src/app/(dashboard)/banquet/venues/page.tsx— venue list (review flagged MOCK_VENUES; not re-verified)hmp-web-dashboard/src/app/(dashboard)/banquet/venues/[id]/— venue detail page
Unit + integration tests:
hmp-banquet-service/src/venues/venues.service.spec.ts— venue CRUD + availabilityhmp-web-dashboard/src/app/(dashboard)/banquet/venues/__tests__/—[CITED-BUT-NOT-FOUND](directory exists, contents not enumerated)
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/banquet-lifecycle.flow.spec.ts:: "Scenario 1 — a venue exists" — TBD (not in 13-flow doc) (2026-05-16-13-flow-demo-readiness.md)
Real-backend smokes: none
Use cases referencing this story: none
SME notes:
- Six setup-type capacity columns (
theaterCapacity,classroomCapacity,banquetCapacity,uShapeCapacity,boardroomCapacity, plusmaxCapacity) are on the Venue model per CLAUDE.md. Spec body Scenario "Create venue with multiple setup types" mentionsReceptionas a 7th capacity — code does NOT havereceptionCapacitycolumn. Indian receptions (especially wedding receptions for 600–1000 pax) are the highest-pax setup; missing this column will force operators to abusebanquetCapacityfor reception pax. - No DELETE endpoint — review flags. Real-world: a property closing a venue cannot remove it. Workaround is
isActive=falsewhich is also not in the DTO. Today a closed venue stays in dropdown lists. - Spaces module (hourly) and Venues module (full-day) both write to the same
venuestable but with different rate columns (hourlyRatevsbaseRate/halfDayRate). Per CLAUDE.md "Spaces vs Venues" — hourly bookings createEventBookingrecords with type MEETING. This dual-write semantics is undocumented in the story.
Drift findings:
D-003 — [P1] [Story-incomplete + India-hospitality-relevant] Venue model lacks receptionCapacity column despite EPIC body Scenario referencing a 600-pax Reception layout. Indian weddings are reception-heavy (600–2000 pax). Operators today abuse banquetCapacity for reception pax, breaking per-setup-type rate cards.
- [SME-PROPOSED] Add AC: "Venue model SHALL carry 7 capacity columns:
maxCapacity+ per-setup{theatre, classroom, uShape, boardroom, banquet, reception, cocktail}— all Int?. Frontend setup-type dropdown SHALL show only the configured layouts (capacity > 0)." - [SME-PROPOSED] Add AC: "Per-setup-type rental rate SHALL be configurable via a
VenueRatechild table{venueId, setupType, hourlyRate?, halfDayRate?, fullDayRate?, currency='INR', validFrom, validTo}. The flatbaseRate/halfDayRate/hourlyRatecolumns onVenueare the default fallback when noVenueRaterow matches."
US-008-003 — Function Sheet Management
Catalog status: Built (P1, 5 points) Verified status: Partial — CRUD + version increment + approval shipped; GET-returns-404 (vs template-from-event-data per spec), no edit-lock after approve, no version history, no status gate, no department-routing Kafka, no PDF — all gaps per review
Code locations:
hmp-banquet-service/src/function-sheets/function-sheets.controller.ts— GET/PUT/events/:eventId/function-sheet, POST/events/:eventId/function-sheet/approvehmp-banquet-service/src/function-sheets/function-sheets.service.ts— CRUD;updateFunctionSheet()incrementsversion+ clearsapprovedBy/approvedAthmp-banquet-service/prisma/schema.prisma(FunctionSheet) —eventId(1:1),setupInstructions,seatingArrangement,menuDetails JSON,beverageDetails JSON,avRequirements JSON,timeline JSON,staffAssignments JSON,version,approvedBy/Athmp-web-dashboard/src/app/(dashboard)/banquet/events/[id]/function-sheet/page.tsx— function-sheet page (existence verified; coverage TBD)
Unit + integration tests: [CITED-BUT-NOT-FOUND] — review enumerates no function-sheets.service.spec.ts; CLAUDE.md test inventory also omits one
Flow tests: none enumerated
Real-backend smokes: none
Use cases referencing this story: none
SME notes:
- Function sheets are CORE to Indian banquet operations — chef, AV, housekeeping, security, valet all coordinate from this single document. Missing PDF generation + missing department-routing Kafka means ops teams still print from JSON view or screenshot — every property reports this as the #1 friction.
- Once approved, sheet is supposed to lock for edits (spec criterion 3). Today
updateFunctionSheet()accepts edits regardless ofapprovedAt. Risk: silent overwrite of approved sheet on the day of event → chef receives a stale version. - All 6 JSON-blob columns (
menuDetails,beverageDetails,avRequirements,timeline,staffAssignments,seatingArrangement) are unstructured. There's no validator on the JSON shape — a malformed timeline JSON is accepted and renders empty. Indian-wedding muhurat ("auspicious time" — wedding ceremony slot) needs a structured timeline withISTtimezone +eventTypeenum to be useful.
Drift findings:
D-004 — [P1] [Story-incomplete + ops-critical] No edit-lock after approval + no PDF export + no department-routing Kafka. Each is independently a P2 but compounded they make the function sheet feature unusable in real banquet ops — chefs work from PDFs, not dashboard URLs.
- [SME-PROPOSED] Add AC: "Once
approvedAtis set, subsequent PUT to the function sheet SHALL return 409code=FUNCTION_SHEET_LOCKEDunless the caller passes?force=true&reason=<text>(manager-only role gate); forced unlocks SHALL incrementversion, clearapprovedAt/By, and write aFunctionSheetAuditLogrow." - [SME-PROPOSED] Add AC: "Function sheet PDF SHALL be generated via the existing Puppeteer pipeline in
contracts.service.ts(the same per-property branding pipeline). PDF is downloadable via GET/events/:eventId/function-sheet/pdf?department=kitchen|av|housekeeping|all—departmentparameter filters which JSON blocks are rendered." - [SME-PROPOSED] Add AC: "On
approveFunctionSheet, publish Kafkabanquet.function_sheet.approvedwith{eventId, version, approvedBy, approvedAt, departments[]}— consumed by notification-service to route per-department notifications (kitchen email, AV WhatsApp, housekeeping app push)."
US-008-004 — Event Items & Costing (Venue/Food/Bev/AV/Decor)
Catalog status: Built (P1, 5 points) Verified status: Partial — item CRUD + total recalc shipped; GST auto-calc, status-gate for COMPLETED/CANCELLED, costPrice population, bulk add, item-level Kafka — all NOT_IMPL per review
Code locations:
hmp-banquet-service/src/events/events.controller.ts:93-141— POST/GET/PATCH/DELETE/events/:id/itemshmp-banquet-service/src/events/events.service.ts(addItem,updateItem,removeItem,recalculateTotal) — item CRUD; total = SUM(lineTotal) across itemshmp-banquet-service/src/events/dto/event.dto.ts(CreateEventItemDto) —@IsEnumon 6 categories (VENUE/FOOD/BEVERAGE/AUDIO_VISUAL/DECORATION/OTHER per code; CLAUDE.md also lists AV_EQUIPMENT/DECOR/MISCELLANEOUS as the EPIC body taxonomy — drift)hmp-banquet-service/prisma/schema.prisma(EventItem) —quantity,unitPrice,total,menuItemId(P3-2 soft FK to pos-service MenuItem for ingredient-cost breakdown),costPrice(column EXISTS per review but NEVER populated; not in DTO)hmp-banquet-service/src/costing/(/events/:id/costing,/menu-items/:id/cost) — ingredient-cost breakdown via PosServiceClient whenmenuItemIdset
Unit + integration tests:
hmp-banquet-service/src/costing/costing.service.spec.ts— food cost calculation tests (per CLAUDE.md inventory)hmp-banquet-service/src/events/events.service.spec.ts— item CRUD (assumed; CLAUDE.md cites the file)
Flow tests:
banquet-lifecycle.flow.spec.ts:: "Scenario 3 — attach food + AV line items" + "Scenario 5 — list items" — TBD
Real-backend smokes: none
Use cases referencing this story: none
SME notes:
- No GST calculation — review's #1 gap. In Indian banquet ops, GST is split: 5% (food at in-house restaurant), 18% (venue rental + AV + decor + beverages — except alcohol which is state-VAT, not GST). The DTO accepts
unitPriceandtotalas flat numbers — there's nogstRate/gstAmount/totalExclGst/totalInclGstsplit. Folio postings downstream cannot reconstruct the correct GST breakdown for the GSTR-1 return. costPricecolumn exists but is never written — making P&L (US-008-006) impossible to compute from real cost data. The frontend instead uses hardcoded 35%/20% ratios (see US-008-006 D-006).- 6-category enum drift: code uses
VENUE/FOOD/BEVERAGE/AUDIO_VISUAL/DECORATION/OTHER; EPIC body Scenario usesVENUE/F&B/Bar/Decor/AV; review usesF&B / AV Equipment / Decor / Venue Rental / Staffing / Miscellaneous. Three different category taxonomies. TheSTAFFINGcategory is in the review but NOT in code — banquet staff costs (waiters, captains, security per pax) have no item-type today.
Drift findings:
D-005 — [P0] [Story-incomplete + India-hospitality-critical + GST-blocker] No GST split on event items. India GST-CGST/SGST/IGST split for banquet events: food = 5%, venue rental + AV + decor + beverages (non-alcoholic) = 18%, alcohol = state-VAT (state-dependent, not GST). Without per-item GST split, folio cannot produce a compliant GSTR-1 entry for the event. Revenue-leakage risk: if GST is computed at folio level using a default 18%, the 5% food slice over-pays GST by 13%.
- [SME-PROPOSED] Add AC: "
EventItemSHALL carrygstRate (Decimal),gstAmount (Decimal),totalExclGst (Decimal),totalInclGst (Decimal). DefaultgstRateSHALL be resolved fromcategory: FOOD=5, BEVERAGE_NON_ALCOHOLIC=18, BEVERAGE_ALCOHOLIC=null (operator enters state-VAT manually withvatRateoverride), VENUE=18, AV=18, DECOR=18, STAFFING=18, OTHER=18. Manual override allowed." - [SME-PROPOSED] Add AC: "Item category enum SHALL be unified to: VENUE | FOOD | BEVERAGE_NON_ALCOHOLIC | BEVERAGE_ALCOHOLIC | AV_EQUIPMENT | DECOR | STAFFING | MISCELLANEOUS. Migration from current 6 values: AUDIO_VISUAL→AV_EQUIPMENT, DECORATION→DECOR, OTHER→MISCELLANEOUS, BEVERAGE splits into the two new enum values (default existing rows to NON_ALCOHOLIC; manual review for alcohol-containing menus)."
- [SME-PROPOSED] Add AC: "
costPricefield SHALL be exposed inCreateEventItemDtoandUpdateEventItemDto(Decimal, nullable, role-gated toMANAGER+ — sales-coordinator role CANNOT see/set costPrice). Populated value flows to US-008-006 P&L."
US-008-005 — Payment Milestones (Advance/Milestone/Final)
Catalog status: Built (P1, 5 points) Verified status: Partial — flat payment recording shipped; NO milestone schedule, NO PENDING/PAID/OVERDUE status, NO receipt numbering, NO CRON overdue detection, NO refund logic, NO partial-payment-against-milestone — every milestone-flavoured criterion is NOT_IMPL per review
Code locations:
hmp-banquet-service/src/event-payments/event-payments.controller.ts— GET/POST/events/:eventId/paymentshmp-banquet-service/src/event-payments/event-payments.service.ts— create payment (flat record) + recalcevent.paidAmounthmp-banquet-service/prisma/schema.prisma(EventPayment) —eventId,paymentType (ADVANCE|MILESTONE|FINAL),amount,paymentMode,reference,receiptNumber,dueDate,paidDate- (no
PaymentMilestonemodel)
Unit + integration tests: [CITED-BUT-NOT-FOUND] — no event-payments.service.spec.ts enumerated in CLAUDE.md test inventory
Flow tests: none enumerated
Real-backend smokes: none
Use cases referencing this story: none
SME notes:
- The schema has
paymentTypeanddueDate/paidDatecolumns, so a single-payment-per-milestone model is half-built. The missing pieces are: (a) schedule generation on event CONFIRMED, (b) milestone-status tracking (PENDING/PAID/OVERDUE), (c) overdue-detection CRON, (d) refund. Without aPaymentMilestonemodel, partial-payments-against-a-milestone are impossible to model. - Indian-wedding payment realities: 30%/40%/30% (advance/pre-event/final) is one common split but cancellation-policy schedules (50% non-refundable advance, 100% within 14 days) are extremely common. Spec gives no flexibility — it hardcodes 25/25/40/10 per the review.
- Razorpay subscription/payment-link integration (per platform CLAUDE.md
hmp-payment-service) is the obvious downstream — banquet payments today have no Razorpay handoff path. Wedding clients want to pay via UPI link, not bank-NEFT-with-reference manually entered by sales-coordinator. - TRAI DND: payment-due reminder SMS need a transactional template (not promotional) AND a registered sender ID — today's "no SMS reminder" gap is also a downstream telephony-compliance problem when implemented.
Drift findings:
D-006 — [P0] [Story-incomplete + revenue-protection] No milestone schedule generation on event CONFIRMED. Real-world flow: client confirms wedding → sales coordinator MUST manually create payments with due dates → invariably forgets the pre-event milestone → wedding day arrives with unpaid balance → painful collection on the day.
- [SME-PROPOSED] Add AC: "On EventBooking transition to CONFIRMED, generate
PaymentMilestonerows from a per-propertyPaymentMilestoneTemplate(default template: Advance 25% on D-confirm, Pre-event 50% on D-event minus 30, Final 25% on D-event plus 7). Template editable per property underPATCH /properties/:id/banquet-payment-template. Each milestone gets PENDING status, calculateddueDate, calculatedamountfrom event total." - [SME-PROPOSED] Add AC: "Payment record now FKs to
PaymentMilestone(nullable for non-milestone direct payments). When a payment lands, update the milestone to PAID if amount >= milestone.amount; PARTIALLY_PAID otherwise. A daily CRON at 02:00 IST scans all PENDING milestones withdueDate < todayand flips to OVERDUE; publishbanquet.payment.overduefor notification-service." - [SME-PROPOSED] Add AC: "Receipt number SHALL be generated on every paid milestone in format
BNQ-RCP-YYYYMMDD-NNN(daily sequence reset, atomic viapg_advisory_xact_lock). Receipt PDF downloadable via GET/payments/:id/receipt.pdf— reuses Puppeteer pipeline."
US-008-006 — Event P&L Report (Estimated vs Actual)
Catalog status: Partial (P2, 3 points)
Verified status: Partial — front-end P&L page exists with HARDCODED 35%/20% cost ratios (no real cost data); backend has NO P&L endpoint; EventItem.costPrice column never populated; no estimated-vs-actual delta view
Code locations:
hmp-banquet-service/src/costing/—/events/:id/costingreturns ingredient-cost breakdown via PosServiceClient (per CLAUDE.md) — NOT a P&L endpoint, but the closest existing surfacehmp-web-dashboard/src/app/(dashboard)/banquet/events/[id]/pnl/page.tsx— P&L page (review confirms hardcoded ratios)hmp-web-dashboard/src/app/(dashboard)/banquet/events/[id]/pnl/floor-plan/— sub-route (floor-plan within P&L?? — directory exists; appears to be misplaced; verify)
Unit + integration tests:
hmp-banquet-service/src/costing/costing.service.spec.ts— food cost calc (per CLAUDE.md) — covers/events/:id/costing, NOT P&L- (no
pnl.service.spec.ts)
Flow tests: none
Real-backend smokes: none
Use cases referencing this story: none
SME notes:
- The hardcoded ratios (F&B 35% of revenue as cost, AV 20%, Staff ₹200/pax) in the dashboard are dangerously plausible — a Banquet Manager seeing a clean P&L page won't realize the numbers are not derived from real costs. This is a "looks right, is wrong" surface — higher severity than a blank page.
- The
/events/:id/costingendpoint already does ingredient-cost lookups via PosServiceClient. That's the right backend foundation — P&L should aggregatecosting+event-payments+event-items.costPriceinto a single response. - "Estimated vs Actual" comparison requires capturing the estimate at quotation-sent time (a snapshot) and comparing to final actuals. Today there's no snapshot mechanism — modifying an item rewrites the estimate, losing the original.
- Indian-banquet margin reality: weddings target 30–40% gross margin; corporate conferences 50%+; social events 25–35%. Benchmark comparison (spec criterion 6) is the highest-value future addition but blocked on the snapshot mechanism.
Drift findings:
D-007 — [P1] [Stub-shipped-as-Partial + dangerous-UX] Dashboard P&L page uses hardcoded ratios but presents as real data. A new banquet manager will trust the numbers. This is the only "Partial" in the catalog that should arguably be Stub because the visible output is plausible-but-fake.
- [SME-PROPOSED] Bump status from
Partial→Stubuntil backend P&L endpoint ships. - [SME-PROPOSED] Add AC: "Backend SHALL expose
GET /events/:id/pnlreturning{estimated: {revenue, cost, margin, marginPct}, actual: {revenue, cost, margin, marginPct}, variance: {revenuePct, costPct, marginPct}, breakdown: {byCategory: [...], byMilestone: [...]}}—estimatedfrom item.unitPrice * quantity + costPrice * quantity;actualfrom event-payments aggregated + post-event cost entries;breakdownjoinsEventItem.category+PaymentMilestone.name." - [SME-PROPOSED] Add AC: "Until backend P&L ships, the dashboard P&L page SHALL display a banner: 'Cost estimates use industry averages, not your property's actual costs. Enter cost prices on event items to enable real P&L.' — protects banquet managers from acting on fake margins."
US-008-007 — BEO / Proposal PDF Generation
Catalog status: Not Started (P2, 5 points)
Verified status: Stub — endpoints declared on contracts.service.ts per CLAUDE.md (POST /events/:id/contract/generate, GET /events/:id/contract, POST /events/:id/contract/accept) — but these are LEGAL CONTRACTS, NOT BEOs or Proposals; review verdict NOT_IMPL stands for BEO/Proposal specifically
Code locations:
hmp-banquet-service/src/contracts/contracts.controller.ts— POST/events/:id/contract/generate, GET/events/:id/contract, POST/events/:id/contract/accept(LEGAL contract — different purpose)hmp-banquet-service/src/contracts/contracts.service.ts— Puppeteer-based PDF generation, fallback to HTML when Puppeteer unavailable, accept/sign workflow (per CLAUDE.md Gotcha #1)hmp-banquet-service/src/contracts/contract-template.ts— inline HTML template (no property branding, no GST breakdown per review)- (no
beo.service.ts/beo.controller.ts/proposal.service.ts)
Unit + integration tests:
hmp-banquet-service/src/contracts/contracts.service.spec.ts— contract gen/accept tests (per CLAUDE.md)- (no BEO or Proposal tests)
Flow tests: none
Real-backend smokes: none
Use cases referencing this story: none
SME notes:
- The infrastructure is HALF-built: Puppeteer + Chromium are already in the Docker image (per CLAUDE.md Gotcha #1,
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser). Building BEO and Proposal templates is a template-authoring + content-aggregation task, not a tooling task. - BEO is operational (every department reads it); Proposal is sales-facing (client reads it). They share the event-data spine but diverge sharply on layout. Each should be its own template, not one shared "event PDF".
- Per CLAUDE.md Gotcha #1: when user-editable contract templates land (BNQ-027 referenced), move puppeteer to a dedicated rootless sidecar — this is the same architectural decision point for BEO/Proposal.
- The contract template runs WITHOUT property branding. Indian-hotel proposals are signature pieces — branded letterhead, GSTIN, signature blocks for both parties, terms in English + (sometimes) Hindi. Spec criterion 3 (property branding) is foundational; without it, sales coordinators will keep using Word/Excel as the review notes.
Drift findings:
D-008 — [P0] [Status-mislabel + revenue-blocker] Catalog shows Not Started and review verdict is NOT_IMPL, but the infrastructure (Puppeteer + Chromium image, contract template pattern) is HALF-built — the missing piece is template authoring + content aggregation, not new tooling. Sales teams are blocked on this; "Not Started" understates the readiness.
- [SME-PROPOSED] Reclassify as
Stub(endpoints declared viacontracts.*adjacent module; template authoring + branding system needed). - [SME-PROPOSED] Add AC: "Service SHALL expose
POST /events/:id/proposal/generate?version=NandPOST /events/:id/beo/generate?revision=R. Both reuse the contracts.service.ts Puppeteer pipeline. Proposals are version-tracked (ProposalVersiontable:{eventId, version, generatedAt, generatedBy, pdfS3Key, changesSinceLastVersion[]}); BEOs are revision-tracked (BeoRevisiontable: same shape withrevisionnumbering)." - [SME-PROPOSED] Add AC: "Both PDFs SHALL include property branding sourced from
PropertyTheme(per BF-001 D-005 / AP-T02-NEW-BRANDING) — logo (S3 presigned), Midnight Sovereign palette, GSTIN, registered address. Proposals additionally include GST breakdown table per the per-item GST rates from D-005, terms & conditions block (configurable per property), and a 2-party signature block."
US-008-008 — Event Calendar View
Catalog status: Built (P1, 3 points) Verified status: Built-Untested — calendar shipped per EPIC body §4.2 (basic monthly view, color-coded by status); drag-reschedule + week/day views + time-slot granularity all flagged Partial in EPIC body; no dedicated Jest/Playwright tests located
Code locations:
hmp-web-dashboard/src/app/(dashboard)/banquet/page.tsx— banquet landing (per directory listing); calendar may be a component within this —[CITED-BUT-NOT-FOUND]for the exact calendar component file path- (no dedicated calendar backend endpoint — calendar likely fetches
GET /eventswith date filter)
Unit + integration tests: [CITED-BUT-NOT-FOUND] — no calendar.test.tsx enumerated
Flow tests: none
Real-backend smokes: none
Use cases referencing this story: none
SME notes:
- EPIC body §4.2 explicitly flags calendar as "Partial" but catalog says "Built". The "Built" call seems to weigh frontend-page-exists more heavily than feature-completeness. The drag-to-reschedule + week/day views + time-slot granularity gaps are real.
- The same-day conflict-detection scenario (Spec Scenario 2 for US-008-09 in EPIC body) requires time-slot overlap math at the dashboard layer — today's
checkVenueConflict()on the backend uses startDateTime/endDateTime with setup/teardown buffers (good), but the dashboard calendar's same-day rendering is purely visual. - Indian wedding-season real reality: a single Saturday in Nov-Feb might have 4-6 simultaneous functions across 3+ venues — the calendar's color-coded monthly view is INADEQUATE; sales coordinators need a "Saturday tab" that flattens 4-6 events into a single time-track view.
Drift findings: (none unique — feeds the EPIC body's existing Partial flag; covered in summary as Built-Untested)
- [SME-PROPOSED] Add AC: "Calendar SHALL support 3 view modes: month (current), week, day. Week + day views SHALL render events with hour-level granularity and visualize setup/teardown buffer separately from the event slot. Backend SHALL accept
GET /events?from=X&to=Y&granularity=hourreturning the same payload — no new endpoint." - [SME-PROPOSED] Add AC: "Calendar page SHALL have a Vitest test covering: 1) renders events grouped by venue, 2) colour codes match status, 3) hover shows event tooltip, 4) clicking opens event detail. AND a Playwright test covering: 1) filter by venue, 2) navigate month-prev/next, 3) drag-to-reschedule (when shipped per criterion above)."
US-008-009 — Venue Floor Plan Editor (Konva)
Catalog status: Built (P2, 5 points) Verified status: Built — per EPIC body §4.2 Konva.js-based drag-drop editor shipped; supports tables/chairs/stage/dance floor/buffet/AV; floor plans saved as JSON + PNG export
Code locations:
hmp-web-dashboard/src/app/(dashboard)/banquet/events/[id]/pnl/floor-plan/— floor-plan sub-route under pnl (path looks unusual — likely should be under[id]/directly, not underpnl/; verify)- (Konva.js component file path not enumerated —
[CITED-BUT-NOT-FOUND]) hmp-banquet-service/prisma/schema.prisma(Venue.floorPlanper CLAUDE.md) — column exists for floor-plan storage
Unit + integration tests: [CITED-BUT-NOT-FOUND] — no floor-plan.test.tsx enumerated
Flow tests: none
Real-backend smokes: none
Use cases referencing this story: none
SME notes:
- Path
[id]/pnl/floor-plan/is suspicious — floor plans logically belong to the event detail, not under the P&L sub-tree. Likely a routing-tree oversight from the original page split. Worth verifying and moving in a future refactor. - Real-world floor plan editing is a SECONDARY workflow for most Indian properties (the operations team draws plans on paper or PowerPoint). The Konva editor is over-engineered for the typical 5-star property and under-tested for the chains that would actually use it.
- Floor plans saved as JSON: useful for template re-use across events (spec criterion 2 "reuse floor plan template"). Today's storage as JSON-blob on Venue is right; what's missing is the explicit
FloorPlanTemplateentity that's not tied to a specific event (mentioned in EPIC body Scenario 2).
Drift findings: (none net new — UI works; reusability template is the SME ask)
- [SME-PROPOSED] Add AC: "Floor plans SHALL be persistable as templates under
FloorPlanTemplate {id, venueId, name, setupType, layoutJson, thumbnailPngS3Key, createdBy, isDefault}— separate from per-event saved floor plans. Editor toolbar SHALL have 'Save as Template' (CTA-secondary) alongside 'Save for this Event' (CTA-primary)."
US-008-010 — Multi-Venue Event Support
Catalog status: Not Started (P3, 5 points) Verified status: Stub — schema supports it (per EPIC body §4.1 "Multi-Venue Events: Partial — Schema supports it, API logic for conflict check incomplete"); no API enforcement; no parent-child UI
Code locations:
hmp-banquet-service/prisma/schema.prisma(EventBooking) — supports parent-child relationship per EPIC body §4.1; column likelyparentEventId String?—[CITED-BUT-NOT-FOUND](CLAUDE.md model row doesn't enumerate)- (no
multi-venue.service.ts) - (no multi-venue UI in
banquet/events/)
Unit + integration tests: none
Flow tests: none
Real-backend smokes: none
Use cases referencing this story: none
SME notes:
- This is the CORE Indian-wedding feature — 3-day wedding (mehndi/sangeet/wedding/reception) needs to be ONE booking with sub-events at different venues. Treating it as 4 independent events loses the parent-aggregate view (total revenue, total payments, total guest count).
- The conflict-check gap (per EPIC body §5 gap #4 "Multi-venue conflict check incomplete — High, S, P1") is the highest-risk gap for the highest-revenue event class. Booking Grand Ballroom for Wedding 10 AM-4 PM AND Reception 7 PM-12 AM on same day → today no automatic gap-validation, can produce no-buffer schedule.
- Catalog priority P3 is WRONG — Indian-wedding revenue contribution (30-40% of total per EPIC §1) makes this P0/P1.
Drift findings:
D-009 — [P0] [Catalog-priority-mismatch + India-revenue-critical] Catalog marks this P3 ("low priority"). For an Indian banquet platform that targets weddings (30-40% of property revenue per EPIC §1), multi-venue/multi-day support is P0 — without it, the wedding-focused properties cannot use the platform for the majority of their revenue.
- [SME-PROPOSED] Bump priority P3 → P0 in catalog.
- [SME-PROPOSED] Add AC: "EventBooking SHALL carry
parentEventId String?(self-FK, nullable). Master event =parentEventId IS NULLwithexpectedGuestsandtotalAmountaggregated from children. Sub-event =parentEventId IS NOT NULLwith own venue/dates/menu. GET/events/:id?includeChildren=truereturns master + children. Payment milestones SHALL be created on the master only — children inherit financial roll-up." - [SME-PROPOSED] Add AC: "Multi-day events SHALL respect a configurable per-venue
setupTimeandteardownTime(default 60 min each).checkVenueConflict()SHALL enforce a minimum back-to-back gap ofpreviousEvent.teardownTime + nextEvent.setupTimebetween two same-venue events. Insufficient gap returns 409code=VENUE_GAP_INSUFFICIENTwith the offending pair."
US-008-011 — Event Staffing Assignment
Catalog status: Partial (P2, 3 points)
Verified status: Partial — schema has staffAssignments JSON on FunctionSheet per CLAUDE.md; no UI for assignment; no link to HR/staff roster per EPIC body §4.1
Code locations:
hmp-banquet-service/prisma/schema.prisma(FunctionSheet.staffAssignments JSON) — free-form JSON blob- (no
staffing.service.ts) - (no staff-roster cross-service client —
hmp-auth-serviceowns users but noStaffServiceClientexists)
Unit + integration tests: none
Flow tests: none
Real-backend smokes: none
Use cases referencing this story: none
SME notes:
- "Staffing assignment" with no HR/staff roster integration is just text-editing today. Real value would be
assignedTo: userIdwith auto-conflict-check (is this captain double-booked across two events at 8 PM?). - Indian banquet operations: typical staffing for a 500-pax wedding = 1 captain + 2 supervisors + 25 service staff + 4 chefs + 2 AV operators + 2 housekeeping + 4 security = 40+ people. Hand-typing this into a JSON blob is impossible to maintain.
- No labour-compliance gate today: shift duration (8 hr max per Karnataka Shops & Establishments Act), minimum-wage compliance per state, overtime tracking — none enforced.
Drift findings:
D-010 — [P1] [Story-incomplete + cross-service-integration] Staffing has no link to user identity (just free-form JSON), no conflict detection across events, no role/skill matching, no labour-compliance gate. A "Partial" rating overstates the readiness.
- [SME-PROPOSED] Demote to
Stubuntil cross-service integration shipped. - [SME-PROPOSED] Add AC: "Create
EventStaffAssignment {id, eventId, userId, role (CAPTAIN|SUPERVISOR|WAITER|CHEF|AV|HK|SECURITY), shiftStart, shiftEnd, isLead, hourlyRate?, totalHours}model. Conflict-check: refuse insert if the user has anotherEventStaffAssignmentoverlapping[shiftStart, shiftEnd]for a different event in same property.POST /events/:id/staffandGET /events/:id/staffendpoints." - [SME-PROPOSED] Add AC: "Per-shift duration MUST be ≤ 8h (configurable per
Property.maxShiftHours) — soft warning on insert if exceeded; hard reject ifProperty.enforceShiftLimits=true. Saves Indian-state Shops & Establishments Act compliance."
US-008-012 — Event Inquiry to Quotation Flow
Catalog status: Built (P1, 3 points) Verified status: Not Started as a DEDICATED workflow — the underlying status-machine transition INQUIRY→QUOTATION_SENT works (covered by US-008-001 events.service tests) but there is NO inquiry-capture form, NO quotation-builder UI, NO send-quotation-via-email path, NO public inquiry-receive endpoint for booking-engine integration
Code locations:
hmp-banquet-service/src/events/events.service.ts(updateStatus) — handles the INQUIRY→QUOTATION_SENT transition (with NO precondition check that items exist — flagged in US-008-001 review)- (no inquiry-intake form on
hmp-web-dashboard/src/app/(dashboard)/banquet/events/new/) - (no public
@Public()inquiry-receive endpoint for booking-engine handoff)
Unit + integration tests:
events.service.spec.ts— covers the status transition mechanically
Flow tests: none enumerated for the inquiry→quotation workflow specifically
Real-backend smokes: none
Use cases referencing this story: none
SME notes:
- "Built" in the catalog feels wrong — the status transition exists, but the WORKFLOW (capture inquiry → build quotation → send PDF → track response) is missing every step except the status change itself. Catalog conflates "transition exists" with "workflow exists".
- Real-world Indian-wedding inquiry funnel: 90% inquiries via WhatsApp Business / phone / walk-in, 8% via web form, 2% via OTA cross-sell. The booking engine + WhatsApp Business API integration is the obvious missing channel.
- The TENTATIVE state (between QUOTATION_SENT and CONFIRMED) is supposed to model "client has verbally committed but not paid advance". Today's flow has no "hold venue tentatively for 7 days" semantics — first-come-first-served at CONFIRMED level only.
Drift findings:
D-011 — [P1] [Status-mislabel + booking-engine-integration-gap] Catalog Built overstates readiness. The status transition mechanic exists; the workflow does not. New banquet PMs reading the catalog will conclude the inquiry funnel is solved.
- [SME-PROPOSED] Demote to
Stub. - [SME-PROPOSED] Add AC: "TENTATIVE state SHALL hold the venue for a configurable
tentativeHoldHours(default 168 = 7 days). A daily CRON at 03:00 IST scans TENTATIVE events with(now - statusChangedAt) > tentativeHoldHoursand auto-transitions to QUOTATION_SENT (releasing the venue), publishingbanquet.tentative.expired." - [SME-PROPOSED] Add AC: "Booking-engine SHALL expose a public
POST /api/booking-engine/banquet/inquiryendpoint (API-key gated@Public(), similar to reservation booking-engine endpoints) that creates an INQUIRY-status event for the property. Captcha required. Auto-acknowledges via email; emitsbanquet.inquiry.receivedfor sales-team notification."
US-008-013 — Event Cancellation with Charges
Catalog status: Partial (P1, 3 points)
Verified status: Partial — status transition →CANCELLED works from any pre-COMPLETED state per code; NO charge calculation per US-008-001 review; NO refund flow; NO cancellation-policy model on banquet (banquet does NOT re-use the property-service CancellationPolicy per US-002-006)
Code locations:
hmp-banquet-service/src/events/events.service.ts(updateStatus) — handles → CANCELLED transitionshmp-banquet-service/src/events/event-group.service.ts(cancelGroupForEvent) — releases the room block- (no
cancellation-policy.service.tsin banquet-service — property-service's policies are NOT consumed here) - (no refund handling — would need a
hmp-payment-servicehandoff per CLAUDE.md modular-payments-platform pattern)
Unit + integration tests:
event-group.service.spec.ts— covers room-block cancellation
Flow tests: none
Real-backend smokes: none
Use cases referencing this story: none
SME notes:
- Banquet cancellation policies are MORE complex than room cancellations: typically a sliding scale based on days-to-event (e.g. >90d=10% non-refundable, 60-90d=25%, 30-60d=50%, <30d=100%). Today there's no policy model, so cancellation is a free-text decision.
- DPDPA refund-SLA gap (per EP-002 D-010) applies here too: a cancellation must commit to a refund SLA. Today no SLA contract.
- Russian-doll problem: a multi-day wedding cancellation (D-009) should cascade to all sub-events with their own charge calculations — not one charge for the whole booking.
Drift findings: (feeds D-009 multi-venue + D-006 milestone-cancel-refund cascades; no fully new finding)
- [SME-PROPOSED] Add AC: "Banquet cancellation SHALL evaluate a
BanquetCancellationPolicyper event (configurable per property, default sliding scale 90d/60d/30d/event-day). Charge amount =event.totalAmount * policy.matchRule(daysUntilEvent).chargePct. Charge adjusts against advance paid viaPaymentMilestoneupdates; refund delta posted via PaymentServiceClient (Razorpay refund) withrefundSlaHours=168(D-010 parallel)." - [SME-PROPOSED] Add AC: "Cancellation of a master event (with
parentEventId IS NULL) SHALL cascade to all sub-events with the same policy; refund total aggregates across children. Cancellation of a sub-event SHALL only release that venue slot and trigger its proportional charge — master event remains active with reduced total."