EP-007 — POS & F&B Management RTM
EPIC: EP-007 POS & F&B Reconciled: 2026-05-25 (Wave 1, 21-EPIC parallel pass) Template: derived from EP-002-rtm.md; see PLAYBOOK.md. Launch-blocker context: this EPIC carries flow-7 POS → Folio which was 0/10 PASS on the 2026-05-16 demo-readiness snapshot (blocked on
hmp-frontdesk-servicenationality fallback —aa6bfaf+ pipeline 29940 awaiting deploy). Until that deploy lands and flow-7 is re-run, every "verified" status here that depends on folio handoff is a read of the code, not a flow-test pass. Source of truth for "% passing":hmp-docs/docs/platform-registry/2026-05-16-13-flow-demo-readiness.mduntil Task #12 re-measures.
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 | 24 |
| Catalog claims "Built" | 15 |
| Verified-Built | 13 (US-007-001, -002, -003, -004, -005, -006, -007, -008, -010, -011, -013, -015, -016, -017, -018, -022) — minus 3 reclassified Partial below = 13 |
| Built-Untested | 0 |
| Partial | 7 (US-007-009 split-by-item UX, US-007-012 + US-007-014 catalog-vs-EPIC ID drift downstream, US-007-020 happy-hour, US-007-021 thermal printer, US-007-023 reports drill-down, US-007-024 mobile) |
| Stub | 2 (US-007-014 room-charge folio validation; US-007-019 recipe-inventory auto-deduction — recipe lookup ships but auto-deduction does NOT) |
| Not Started | 2 (US-007-019 inventory auto-deduction loop is "Not Started" per catalog; US-007-024 F&B mobile + US-007-024 combo meals) |
| Drift findings | 13 (D-001..D-013; 6 P0 / 5 P1 / 2 P2 — LAUNCH BLOCKER pressure tilts the curve P0-heavy) |
| [SME-PROPOSED] refinements | 28 |
Counts intentionally use the catalog numbering (1..24). EPIC body's "Detailed User Stories" tail uses a different ordering for US-007-005..US-007-014 (catalog "KOT generation" = EPIC body "create dine-in order"). See D-001 below — same shape as the EP-002 D-007 drift.
US-007-001 — Outlet Management (8 Types)
Catalog status: Built (P0, 3 points) Verified status: Built
Code locations:
hmp-pos-service/src/outlets/outlets.controller.ts— GET / POST / PATCH/outlets, GET/outlets/:id(delegates to MenuService — see GAP-5 inreviews/EP-007/US-007-001-review.md)hmp-pos-service/src/outlets/dto/outlet.dto.ts—CreateOutletDto,UpdateOutletDto,OutletFilterDtohmp-pos-service/src/menu/menu.service.ts— outlet CRUD lives here (organizational seam)hmp-pos-service/prisma/schema.prisma—Outlet { propertyId, code, name, outletType, taxRate };@@unique([propertyId, code])hmp-web-dashboard/src/app/(dashboard)/pos/outlets/— outlet selector + page
Unit + integration tests:
hmp-pos-service/src/menu/menu.service.spec.ts— 4 specs cover outlet create + category create + item ops (no outlet update/delete/list spec — seereviews/EP-007/US-007-001-review.md)hmp-pos-service/src/orders/__tests__/cross-tenant-denial.spec.ts— canonical cross-tenant denial spec (9 cases — template for Phase 3 rollout to other services)
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-7-pos-to-folio.spec.ts:: outlet bootstrap precondition — 0/10 PASS (LAUNCH BLOCKER, blocked on frontdesk nationality fallback) (2026-05-16-13-flow-demo-readiness.md, 2026-05-16)hmp-infrastructure/testing/flow-tests/src/flows/pos-folio-charge.flow.spec.ts— TBD (not in 13-flow doc) (2026-05-16-13-flow-demo-readiness.md, 2026-05-16)
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/smoke-pos-order.real.spec.ts— exercises outlet selector + order create
Use cases referencing this story:
- none (POS-domain UCs missing — see Step 7 sidecar opportunity)
SME notes:
Outlet.outletTypeDTO uses@IsString()not@IsEnum(OutletType)per review GAP-1; Prisma enum catches at DB level but API returns a Prisma error, not a clean 400. AP-T07-style hardening should align with how AP-T02-001 enforces GSTIN.- Story enumerates 8 outlet types (RESTAURANT/BAR/COFFEE_SHOP/ROOM_SERVICE/POOL_BAR/BANQUET/MINIBAR/SPA_CAFE) but Prisma
OutletTypeenum is RESTAURANT/BAR/LOUNGE/COFFEE_SHOP/ROOM_SERVICE/POOL_BAR/BANQUET/OTHER —MINIBARandSPA_CAFEare folded intoOTHER, whileLOUNGE(not in the story) is added. Real-world impact: MINIBAR auto-deduction reports cannot key off outletType. OutletsControllerdelegates toMenuServicefor write paths — organizational coupling, not a bug, but new engineers will look in the wrong service file.
Drift findings:
D-001 — [P0] [Catalog-vs-EPIC-body numbering drift] Catalog (01-epic-catalog.md line "US-007-005 KOT Generation") and EPIC body (epics/EP-007-pos-and-fnb.md US-007-005 = "Create Dine-In Order") disagree on which title maps to which ID from US-007-005 onward. Review files (reviews/EP-007/US-007-NNN-review.md) follow yet a third hybrid. Identical shape to EP-002 D-007 (resolved per pilot rule "follow the catalog").
- [SME-PROPOSED] Adopt the catalog numbering as canonical (matches EP-002 pilot precedent). Rewrite the EPIC body §US-007-005..§US-007-024 to match the catalog titles, with an alias line
> Previously titled "X" in EPIC body draft prior to 2026-05-25 reconciliation.
D-002 — [P1] [Story-incomplete + enum drift] EPIC body lists 8 outlet types (incl. MINIBAR + SPA_CAFE); Prisma enum has 8 types but maps MINIBAR/SPA_CAFE → OTHER and adds LOUNGE. The discrepancy is silent — @IsString() DTO accepts any string, only DB barfs.
- [SME-PROPOSED] Add AC: "
Outlet.outletTypeSHALL match@IsEnum(OutletType)in the DTO. Enum values SHALL be the union of catalog + Prisma (RESTAURANT/BAR/LOUNGE/COFFEE_SHOP/ROOM_SERVICE/POOL_BAR/BANQUET/MINIBAR/SPA_CAFE) — add MINIBAR + SPA_CAFE as discrete Prisma enum values in the same migration so reporting can key off outletType."
US-007-002 — Table Management with Floor Plan
Catalog status: Built (P0, 5 points) Verified status: Built
Code locations:
hmp-pos-service/src/tables/tables.controller.ts— GET/POST/outlets/:id/tables, POST/outlets/:id/tables/bulk(POS-2), PATCH/tables/:id, PATCH/tables/:id/statushmp-pos-service/src/tables/tables.service.ts—createManydoes NOT wrap in$transactionby design (POS-2 gotcha — Prisma 5.x no savepoints)hmp-pos-service/src/tables/dto/table.dto.ts—CreateTableDto,UpdateTableDto,BulkCreateTablesDto;TABLE_STATUS_VALUESenum (POS-3/6/7 fix shipped 2026-05-21)hmp-pos-service/prisma/schema.prisma—POSTable { outletId, tableNumber, capacity, status, posX?, posY? };@@unique([outletId, tableNumber])hmp-web-dashboard/src/app/(dashboard)/pos/tables/— floor plan canvas
Unit + integration tests:
hmp-pos-service/src/tables/tables.service.spec.ts—describe('createMany')5 cases incl. mid-batch P2002 regression case (POS-2)
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-7-pos-to-folio.spec.ts:: table-bootstrap precondition — 0/10 (LAUNCH BLOCKER) (2026-05-16-13-flow-demo-readiness.md, 2026-05-16)
Real-backend smokes:
- (covered via smoke-pos-order.real.spec.ts)
Use cases referencing this story:
- none
SME notes:
posX/posYfloor-plan coordinates exist onPOSTableper platform CLAUDE.md (cited as a precedent for the unbuilt US-002-012 visual editor). Story body does not enumerate coordinate range, canvas size, or snap-grid rules.- POS-2 partial-success-by-design (per pos-service CLAUDE.md Gotcha #16) is a deliberate departure from typical "all-or-nothing batch" expectations. New engineers may try to wrap in
$transactionand hit the same Prisma 5.x savepoint trap — story should narrate this. - Story spec "Merge Tables for Large Party" is implemented at the order layer (
POST /orders/merge) not the table layer — table-level merging UI for floor plan is undocumented. PATCH /tables/:idallows direct status set with NO transition guard;PATCH /tables/:id/statusis the guarded path (per CLAUDE.md note). Both endpoints in story body without distinguishing them is a footgun.
Drift findings:
D-003 — [P1] [Story-incomplete + footgun] Two PATCH paths for table status (PATCH /tables/:id unguarded vs PATCH /tables/:id/status guarded). Story body does not distinguish, so a developer wiring a new UI surface can pick the unguarded path and lose the state-machine invariant. Mirrors EP-002 D-006 (room status three-writer race) at table granularity.
- [SME-PROPOSED] Add AC: "Status changes SHALL go through
PATCH /tables/:id/statuswhich enforces transitions per theTABLE_STATUS_VALUESstate machine (VACANT → OCCUPIED requires open order; OCCUPIED → BILLED requires bill generated; BILLED → VACANT on payment).PATCH /tables/:idSHALL rejectstatusfield with 400code=USE_STATUS_ENDPOINT." - [SME-PROPOSED] Add AC for floor-plan coordinates: "
posXandposYareDecimalin pixels relative to a 1920×1080 canvas reference; snap-grid is 10 px. Negative coords reject with 400."
US-007-003 — Menu Management (Categories, Items, Modifiers)
Catalog status: Built (P0, 5 points) Verified status: Built
Code locations:
hmp-pos-service/src/menu/menu.controller.ts—/menu/categories,/menu/items,/menu/modifiers,/menu/items/:id/recipe(cross-service read)hmp-pos-service/src/menu/menu.service.ts— covers outlet + category + item + recipe lookupshmp-pos-service/src/menu/menu-public.controller.ts— public/guest-facing menu read (booking engine / room QR)hmp-pos-service/src/menu/dto/menu.dto.ts:29-91—CreateMenuItemDto.hsnCode,gstRate(AP-T07-003 — both DTO-wired post-2026-05-08)hmp-web-dashboard/src/app/(dashboard)/pos/menu/— menu admin
Unit + integration tests:
hmp-pos-service/src/menu/menu.service.spec.ts— base coveragehmp-pos-service/src/menu/menu-hsn-gst.spec.ts— 12 specs (AP-T07-003 DTO-→service-→DB wiring; runs green per CLAUDE.md Gotcha #11)
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-7-pos-to-folio.spec.ts:: menu-bootstrap precondition — 0/10 (LAUNCH BLOCKER) (2026-05-16-13-flow-demo-readiness.md, 2026-05-16)
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/smoke-pos-order.real.spec.ts:: includes menu render
Use cases referencing this story:
- none (POS-domain UCs absent platform-wide — Step 7 sidecar gap)
SME notes:
- AP-T07-003 (CLAUDE.md Gotcha #11) is the lesson-learned: columns shipped without DTO wiring for ~2 weeks meant every API caller silently dropped
hsnCodeandgstRate. Today's regression suite (12 specs) is the safety net; if the spec ever drops, drift returns. gstRateis0..28per-item slab (5/12/18/28 are India slabs) with fallback to outlettaxRatewhen null — fallback semantics are not stated in the story body.hsnCodeis 4–8 char string nullable — story says "HSN codes per item" but does not specify length, format, or whether SAC (for services like banquet, spa-cafe) shares the field.- Bulk price update CSV (story AC #3) is described but no endpoint exists — verify.
Drift findings:
D-004 — [P0] [Story-incomplete + India-GST-critical] Item-level gstRate falls back to outlet taxRate when null. Story does not specify this — a tenant configuring a "100% Veg outlet" at 5% GST and then explicitly putting a non-veg item with gstRate: null will see the non-veg item incorrectly billed at 5% (outlet default) instead of the item's correct 18%. Same risk pattern as EP-002 D-008.
- [SME-PROPOSED] Add AC: "
MenuItem.gstRateis the source of truth. If null, the bill SHALL useOutlet.taxRateAND emit apos.menu.gst_fallbackanalytics event so RMS can flag items missing a per-item slab. Items in categoriesLIQUOR,BEER,WINE,CIGARETTESHALL rejectgstRate: nullwith 400code=GST_RATE_REQUIRED_FOR_SIN_GOODS(28% slab is mandatory)." - [SME-PROPOSED] Add AC: "
MenuItem.hsnCodeSHALL match^[0-9]{4,8}$(4-digit chapter / 6-digit heading / 8-digit tariff). Items also accept SAC codes (Services Accounting Code) for spa-cafe/banquet outlets — store in same column; tagcodeType: HSN | SACso e-invoice line-builder routes correctly."
US-007-004 — Create Order (Dine-In, Room Service)
Catalog status: Built (P0, 8 points) Verified status: Built
Code locations:
hmp-pos-service/src/orders/orders.controller.ts—POST /orders,POST /orders/:id/items,POST /orders/merge,POST /orders/transfer-items, etc.hmp-pos-service/src/orders/orders.service.ts— covers DINE_IN / TAKEAWAY / ROOM_SERVICE / CANTEEN order types; tenant-scoped viaOrdersService.tenantWhere(Gotcha #3)hmp-pos-service/src/orders/dto/order.dto.ts—OrderItemDto,AddItemDto(POS-unitprice-coercion Gotcha #18 —@Type(() => Number)required)hmp-pos-service/src/orders/events/folio-charge-posted.event.ts—FolioChargePostedPayloadV2+mapOrderTypeToTransactionCode()(AP-T07-006)hmp-web-dashboard/src/app/(dashboard)/pos/orders/+pos/page.tsx— POS terminal
Unit + integration tests:
hmp-pos-service/src/orders/orders.service.spec.ts,orders.service.p6.spec.ts— critical pathshmp-pos-service/src/orders/order-void.service.spec.ts— void/approvalhmp-pos-service/src/orders/dto/order.dto.spec.ts— 18 cases (POS-unitprice-coercion, Gotcha #18)hmp-pos-service/src/orders/__tests__/cross-tenant-denial.spec.ts— 9 cases, canonical platform templatehmp-pos-service/src/orders/__tests__/close-business-day.spec.ts
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-7-pos-to-folio.spec.ts— 0/10 (LAUNCH BLOCKER — pipeline 29940 awaiting deploy)hmp-infrastructure/testing/flow-tests/src/flows/terminal-pos-order.flow.spec.ts— TBD (not in 13-flow doc)
Real-backend smokes:
hmp-web-dashboard/e2e/real-backend/smoke-pos-order.real.spec.ts— end-to-end order create
Use cases referencing this story:
- none
SME notes:
- Gotcha #18 (POS-unitprice-coercion 2026-05-22) — Prisma
Decimalfields round-trip as strings. Dashboard send-to-kitchen broke until@Type(() => Number)was added. Story body says "select items from menu" with no mention that line price is the runtime menu price (not historical at order time) — risk: happy-hour price changes mid-meal cause UI/backend divergence. - POS-1 (config-not-code per memory) —
OrdersController@RequiresFeature('pos.basic')gates the entire path; "send-to-kitchen broken" QA reports were tenant-plan misconfig. Story should reference the feature flag. - Order numbers use
Date.now().toString(36) + 6-char random(Gotcha #4); DB-level@@unique([outletId, orderNumber])is the guarantee. Story says "order number sequential" but reality is opaque-base36 — collision-tolerant by design. Documentation drift. - Room-service order workflow: order has
orderType=ROOM_SERVICEbut room-charge posting + folio handoff is the separate/orders/:id/room-chargepath (catalog US-007-007).
Drift findings:
D-005 — [P0] [LAUNCH BLOCKER + cross-service handoff] flow-7-pos-to-folio.spec.ts 0/10 PASS is the headline launch blocker. Root cause is downstream (frontdesk nationality fallback, pipeline 29940), but the test scope covers create-order → KOT → bill → room-charge → folio handoff end-to-end. Until pipeline 29940 deploys + re-runs, NO POS story end-to-end claim can be verified.
- [SME-PROPOSED] Add AC: "flow-7 SHALL go from 0/10 → 10/10 PASS before US-007-004 is marked
Built. Each of the 10 scenarios (create dine-in order, add items, send KOT, ready KOT, bill order, accept payment, settle to folio for ROOM_SERVICE, settle CANTEEN charge-to-bed, void item with approval, close shift) maps to a numbered scenario in the spec; each has anit.onlyrehearsal-mode toggle so on-call can re-run any single scenario without full suite restart." - [SME-PROPOSED] Track flow-7 pass rate as a P0 launch gate in
RTM-DASHBOARD.mduntil first 10/10 PASS.
D-006 — [P1] [Story-incomplete + Indian-pricing-critical] Story does not specify whether line-item price is "snapshot at add-to-order time" vs "live menu price at bill time". With happy-hour pricing (US-007-020 Partial) in scope, a guest seated at 6:55 PM who orders a drink right before happy hour ends, with the bill rendered at 7:05 PM, today returns the menu price as-of bill time — not as-of add time. Real-world dispute risk.
- [SME-PROPOSED] Add AC: "
OrderItem.unitPriceSHALL snapshotMenuItem.price(orhappyHourPriceif active) at the moment the item is added to the order. A subsequent menu price change does NOT mutate existing OrderItem rows. Thepos.order.line.priceSnapshottedanalytics event SHALL fire with{ itemId, snapshotPrice, menuPriceAtSnapshot, priceSource: BASE|HAPPY_HOUR|COMBO|PRICING_RULE }."
US-007-005 — KOT Generation by Kitchen Station
Catalog status: Built (P0, 5 points) Verified status: Built
Code locations:
hmp-pos-service/src/orders/orders.controller.ts—POST /orders/:id/kothmp-pos-service/src/orders/orders.service.ts:~623—generateKOTMUST callKitchenGateway.emitKotCreated(outletId, kot)per AP-T07-002 (CLAUDE.md Gotcha #1)hmp-pos-service/src/kitchen/kitchen.controller.ts—GET /kitchen/orders,PATCH /kitchen/orders/:kotId/status,PATCH /kitchen/items/:itemId/statushmp-pos-service/src/kitchen/kitchen.service.ts— KOT state machine (PENDING → ACCEPTED → PREPARING → READY → COMPLETED → SERVED)hmp-pos-service/src/gateway/—KitchenGateway(/kitchennamespace, port 3007, Redis adapter viaconfigureRedisAdapterwhenREDIS_HOSTset; Gotcha #6 + #8 — JWT verified onhandleConnection, RS256 required in prod)
Unit + integration tests:
hmp-pos-service/src/kitchen/kitchen.service.spec.tshmp-pos-service/src/kitchen/kitchen.gateway.spec.ts— gateway auth + scope
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/pos-kot.flow.spec.ts— TBD (not in 13-flow doc) (2026-05-16-13-flow-demo-readiness.md, 2026-05-16)hmp-infrastructure/testing/flow-tests/src/flows/flow-7-pos-to-folio.spec.ts:: KOT scenario — 0/10 (LAUNCH BLOCKER)
Real-backend smokes:
- (covered via smoke-pos-order.real.spec.ts)
Use cases referencing this story:
- none
SME notes:
- AP-T07-002 dual-emit (Gotcha #1 + #2): every KOT mutation MUST emit both
kot:createdover WebSocket AND publishpos.kot.created(canonical) +pos.kot.generated(legacy alias) on Kafka. New paths (split, merge, edit) must do the same. Story does not enumerate this dual-emit invariant. - Terminal kitchen states (READY/COMPLETED/SERVED) publish
pos.kot.completed; READY also publishes legacypos.kot.readyfor one release cycle. Consumers (analytics MV, kitchen-throughput dashboards) must subscribe to canonical, not legacy. - Kitchen station enum on MenuItem covers HOT_KITCHEN/COLD_KITCHEN/TANDOOR/BAR/PASTRY/PANTRY — story enumerates these but does not say what happens to an item with
kitchenStation=null(today: item NOT included in any KOT, silently dropped from the order — verify).
Drift findings:
D-007 — [P0] [Real-time-handoff + cross-service-consumer-gap] AP-T07-002 dual-emit is the foundation of KDS first-paint AND analytics MV ingestion. The legacy pos.kot.generated is still published "for one release cycle"; the legacy pos.kot.ready is still published on READY. There is no tracking ticket scheduled to drop the legacy emits — they will outlive their release-cycle window and create permanent dual-topic load on Redis Streams (and double-count risk on analytics MVs that subscribe to both).
- [SME-PROPOSED] Add AC: "The Track 4 taxonomy sweep SHALL drop
pos.kot.generatedandpos.kot.readyby sprint N+2 from the AP-T07-002 ship date. Until then, analytics-service consumers SHALL key offpos.kot.created/pos.kot.completedonly and ignore the legacy aliases." - [SME-PROPOSED] Add AC: "Items with
kitchenStation=nullSHALL reject KOT generation with 400code=ITEM_MISSING_KITCHEN_STATIONlisting the offending menuItemIds. Today's silent-drop behaviour SHALL be removed."
US-007-006 — Bill Generation & Payment (Cash, Card, UPI)
Catalog status: Built (P0, 5 points) Verified status: Built
Code locations:
hmp-pos-service/src/orders/orders.controller.ts—POST /orders/:id/bill,POST /orders/:id/payment,POST /orders/:id/split,POST /orders/:id/emailhmp-pos-service/src/orders/orders.service.ts:140-170— bill total composition (W1.A.9 single source of truth:subtotal - discount + tax + serviceCharge + serviceChargeGst)hmp-pos-service/src/orders/orders.service.ts:~160— Indian state-aware GST split (POS-016 Gotcha #5: readsproperty.stateviaPropertyServiceClient, intra-state → CGST+SGST vs inter-state → IGST)hmp-shared-packages/packages/common/src/printing/receipt-templates.ts—generatePosReceipt(order, options)48-character ESC/POS with HSN/SAC, CGST/SGST/IGST, IRN, IST date, Indian number grouping
Unit + integration tests:
hmp-pos-service/src/orders/orders.service.spec.ts,orders.service.p6.spec.tshmp-shared-packages/packages/common/src/printing/receipt-templates.spec.ts— 47 byte-level ESC/POS tests (per platform CLAUDE.md "Branded Receipts" section)
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-7-pos-to-folio.spec.ts:: bill + settle — 0/10 (LAUNCH BLOCKER)hmp-infrastructure/testing/flow-tests/src/flows/terminal-folio-settlement.flow.spec.ts— TBD
Real-backend smokes:
- (via smoke-pos-order.real.spec.ts payment path)
Use cases referencing this story:
- none
SME notes:
- W1.A.9 (2026-05-06) consolidated the bill-total formula in one place after divergent calculations had silently discarded discount, tax, and service-charge. New computation paths MUST go through this canonical formula.
- POS-016 GST split (Gotcha #5) needs
mockPropertyClient.getProperty.mockResolvedValue({ state: 'MH' })in fixtures — undocumented = wrong-branch tests pass silently. - UPI VPA capture (story scenario "Mixed payment cash + UPI") is recorded as a string field; no validation that the VPA matches
^[a-zA-Z0-9.\-]{2,256}@[a-zA-Z]{2,64}$. Real-world result: free-text "UPI" rows that can't be audited. - PCI scope — story says cards by "last 4 digits + approval code"; no test asserts the full PAN is rejected. PCI DSS scope reduction depends on this — silent ingestion of a 16-digit PAN moves the entire pos-service into PCI scope.
Drift findings:
D-008 — [P0] [Compliance + India-payment-critical] Card payment path stores last-4 + approval code. There is no test that explicitly rejects a 16-digit PAN being passed in the same field. UPI VPA is unvalidated free text.
- [SME-PROPOSED] Add AC: "Payment line fields are constrained:
cardLast4SHALL match^[0-9]{4}$and reject any longer numeric string with 400code=CARD_PAN_REJECTED.upiVpaSHALL match^[a-zA-Z0-9.\\-]{2,256}@[a-zA-Z]{2,64}$. Both rules covered by a dedicatedpayment-pii.spec.tsso PCI scope reduction has a regression test." - [SME-PROPOSED] Add AC: "Mixed payments SHALL sum to
bill.totalto within0.01(Indian paise) — variance > 0.01 returns 400code=PAYMENT_SUM_MISMATCH. Mixed payments SHALL include CASH change if overpaid."
US-007-007 — Room Charge Posting to Guest Folio
Catalog status: Built/Partial (Catalog says Partial in EP-007 row table; EPIC body US-007-014 says Partial — see D-001)
Verified status: Partial / Stub — endpoint ships + emits folio.charge.posted, but no folio credit-limit check, no checked-out room guard, no guest-identity verification
Code locations:
hmp-pos-service/src/orders/orders.controller.ts—POST /orders/:id/room-chargehmp-pos-service/src/orders/orders.service.ts—postRoomChargebuildsFolioChargePostedPayloadV2(AP-T07-004 + AP-T07-006 — Gotchas #9 + #10)hmp-pos-service/src/orders/events/folio-charge-posted.event.ts—mapOrderTypeToTransactionCode(): ROOM_SERVICE→POS_ROOM_SERVICE, DINE_IN→POS_DINE_IN, TAKEAWAY→POS_TAKEAWAY, others→POS_OTHER
Unit + integration tests:
hmp-pos-service/src/orders/orders.service.spec.ts— coverspostRoomChargewith orderType-mapping fixtures (mock must includeorderTypeper Gotcha #10)
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-7-pos-to-folio.spec.ts:: room-charge handoff — 0/10 (LAUNCH BLOCKER)hmp-infrastructure/testing/flow-tests/src/flows/pos-folio-charge.flow.spec.ts— TBD
Real-backend smokes:
- none specific
Use cases referencing this story:
- none
SME notes:
- Story body explicitly flags 3 gaps (no credit-limit check, no checked-out-room guard, no identity verification). The catalog still marks "Built" in some places — story-vs-catalog drift.
- AP-T07-004 + AP-T07-006 are the wins here: payload carries full GST breakdown (CGST/SGST/IGST + customerState + placeOfSupply) so the frontdesk-service consumer does NOT recalculate from
taxAmount(which would lose the CGST vs IGST distinction). 'FnB'literal is gone — anyone resurrecting it is a sign something is wrong (Gotcha #10).- Charge-to-Bed (Vishramgruh canteen,
orderType=CANTEEN) maps viaPOS_OTHERtoday — should map toPOS_CANTEENso reports can break out canteen revenue.
Drift findings:
D-009 — [P0] [Stub-shipped-as-Built + folio-integrity] Room charge posts with no credit-limit check, no checked-out-room guard, no identity verification. Risk: room charges accumulate on a folio that has been checked out (post-checkout retro-charge), with no recovery path. EPIC body flags this; catalog row still says "Built" or "Partial" inconsistently.
- [SME-PROPOSED] Add AC: "
POST /orders/:id/room-chargeSHALL callReservationServiceClient.assertRoomOccupiedAndWithinCreditLimit({ propertyId, roomNumber, additionalCharge })BEFORE publishingfolio.charge.posted. Reject with 409code=ROOM_NOT_OCCUPIEDor 409code=CREDIT_LIMIT_EXCEEDEDas appropriate. Reservation-service exposes the assertion via internal-service-account JWT path." - [SME-PROPOSED] Add AC: "POS terminal SHALL display the folio's primary guest name + 2-char masked reservation number after room-number entry; the cashier must confirm verbally with the guest before submitting. Audit log captures
confirmedByUserId, confirmationTimestamp." - [SME-PROPOSED] Add
POS_CANTEENtransactionCode mapping (currently CANTEEN → POS_OTHER); folio reports cannot break out canteen revenue today.
US-007-008 — Void with Manager Authorization
Catalog status: Built (P0, 3 points) Verified status: Built
Code locations:
hmp-pos-service/src/orders/orders.controller.ts—POST /orders/:id/void,POST /orders/:id/items/:itemId/void-request|approve|rejecthmp-pos-service/src/orders/orders.service.ts+order-void.service.spec.tshmp-pos-service/prisma/schema.prisma—POSVoidRequest { orderId, itemId, requestedBy, status, reason }
Unit + integration tests:
hmp-pos-service/src/orders/order-void.service.spec.ts
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-7-pos-to-folio.spec.ts:: void scenario — 0/10 (LAUNCH BLOCKER)
Real-backend smokes:
- (via smoke-pos-order.real.spec.ts)
Use cases referencing this story:
- none
SME notes:
- Reason enum (GUEST_CANCELLED/WRONG_ORDER/QUALITY_ISSUE/KITCHEN_DELAY/DUPLICATE/OTHER) — story should require
OTHERto carry a mandatory free-text note (today nullable). - "Post-preparation void" (item already cooked) — story says inventory deduction is NOT reversed, but US-007-019 inventory auto-deduction is
Not Started, so this scenario is vacuous today. When auto-deduction ships, the post-prep void path needs explicit handling.
Drift findings: (none net new — feeds D-010 inventory)
- [SME-PROPOSED] Add AC: "Reason
OTHERSHALL require a non-emptynotesfield (≥10 chars). Reject with 400code=VOID_REASON_OTHER_REQUIRES_NOTES."
US-007-009 — Bill Splitting (By Item, Amount, Guest Count)
Catalog status: Partial (P1, 5 points) Verified status: Partial — equal-split + amount-split work; by-item drag-drop UX is clunky per story body GAP
Code locations:
hmp-pos-service/src/orders/orders.controller.ts—POST /orders/:id/splithmp-pos-service/src/orders/dto/split-order.dto.ts
Unit + integration tests:
hmp-pos-service/src/orders/orders.service.spec.ts(split scenarios)
Flow tests:
- (not in 13-flow doc) — TBD
Real-backend smokes:
- none specific
Use cases referencing this story:
- none
SME notes:
- Story explicitly flags: by-item drag-drop is clunky; no "who ordered what" tracking at add-to-order time.
- Rounding policy for equal-split is described in story AC #3 (1000 split 3 ways → 333.33 + 333.33 + 333.34) but no test asserts the last-bill-takes-extra-paisa convention; refactor risk.
- GST split-recalc on each split-bill is the critical case — if 4 guests split a bill that mixed 5% food + 28% liquor, each split bill needs its own per-slab proportional split (today: verify via test).
Drift findings: (none net new — captured as Partial)
- [SME-PROPOSED] Add AC: "Equal-split rounding SHALL distribute residual paise to the LAST split bill (deterministic). Covered by
split-rounding.spec.tswith cases for 1000/3, 333/7, 1/3 (sub-rupee residual)." - [SME-PROPOSED] Add AC: "Per-item assignment at order-time:
POSOrderItem.guestSlotint field (0..guestCount-1). At bill time,POST /orders/:id/split-by-guest-slotauto-populates split bills without manual drag-drop. Backwards-compatible: null guestSlot defaults to slot 0."
US-007-010 — Discount Application (% and Flat)
Catalog status: Built (P0, 2 points) Verified status: Built
Code locations:
hmp-pos-service/src/orders/orders.controller.ts—POST /orders/:id/discounthmp-pos-service/src/pricing-rules/pricing-rule.service.ts— rule-based discount enginehmp-pos-service/src/pricing-rules/pricing-rule.service.spec.ts
Unit + integration tests:
hmp-pos-service/src/pricing-rules/pricing-rule.service.spec.tshmp-pos-service/src/orders/orders.service.spec.ts— discount integration
Flow tests:
- (not in 13-flow doc)
Real-backend smokes:
- none specific
Use cases referencing this story:
- none
SME notes:
- "Complimentary (100%) waives GST" — story AC #3 claims this; under Indian GST rules complimentary supply to staff/guests has nuanced treatment (Schedule I supply for related-party transactions); blanket "GST waived" is risky.
- Manager-approval threshold (story AC #2: 20% / Rs 1,000) is hard-coded today; should be per-property/per-outlet config.
Drift findings: (none net new)
- [SME-PROPOSED] Add AC: "Complimentary (100% discount) SHALL still post the GST line at full amount to the folio with
complimentaryHandledByStaff=truemarker; the GM-only month-end report uses this to file Schedule I supply where applicable. Today's blanket-waive is non-compliant for related-party comps." - [SME-PROPOSED] Add AC: "Manager-approval threshold SHALL be per-outlet config:
Outlet.discountSelfApprovalPercentMax(default 10) andOutlet.discountSelfApprovalAmountMax(default 500 INR)."
US-007-011 — Service Charge & Gratuity
Catalog status: Built (P0, 2 points) Verified status: Built
Code locations:
hmp-pos-service/src/orders/orders.controller.ts—POST /orders/:id/service-chargehmp-pos-service/src/orders/dto/service-charge.dto.ts—ServiceChargeDto,ServiceChargeTypeenumhmp-pos-service/src/orders/orders.service.ts:36-170— W1.A.9 single source of truth for service-charge GST
Unit + integration tests:
hmp-pos-service/src/orders/orders.service.spec.ts(service-charge cases)
Flow tests:
- (not in 13-flow doc)
Real-backend smokes:
- none specific
Use cases referencing this story:
- none
SME notes:
- CCPA 2022 (Central Consumer Protection Authority): service charge is voluntary, cannot be added without explicit consent, cannot be a precondition of service. Story body cites the rule but does NOT require explicit opt-in capture — today's auto-apply-with-opt-out UX is borderline non-compliant per the CCPA guideline interpretation.
- Tip pool distribution is mentioned in implementation status but no endpoint or model surface — Day-2 gap.
Drift findings:
D-010 — [P1] [CCPA-2022-compliance + Indian-hospitality-regulatory] Service charge auto-applied with passive opt-out is borderline non-compliant per CCPA 2022 guideline (consumer must explicitly accept the charge; cannot be added unless guest opts IN). Today's UX shifts burden to opt out — risk: consumer complaint to NCH, refund order from the District Consumer Disputes Redressal Commission.
- [SME-PROPOSED] Add AC: "Service charge SHALL be opt-IN (cashier checkbox at bill time, defaults UNCHECKED). Bill footer SHALL say
Service charge of X% included with your consentonly when checked. Without consent, no service-charge line is added; tip line is the only voluntary line." - [SME-PROPOSED] Add AC: "Tip pool:
TipDistributionmodel{ tipId, shiftId, distributedToUserIds[], perUserAmount, distributedByManagerId, at }. EndpointPOST /shifts/:id/tip-distributeruns at shift close."
US-007-012 — Shift Management (Open/Close/Reconciliation)
Catalog status: Built (P0, 5 points) Verified status: Built (per POS-8 hardening 2026-05-21)
Code locations:
hmp-pos-service/src/shifts/shifts.controller.ts— POST/shifts/open, POST/shifts/close, GET/shifts/current?outletId=(POS-8 — route order is load-bearing per Gotcha #13), GET/shifts/:id/report, POST/shifts/:id/safe-drophmp-pos-service/src/shifts/shifts.service.ts—openShiftduplicate-OPEN guard (POS-8 Gotcha #14)hmp-pos-service/src/shifts/dto/shift.dto.ts
Unit + integration tests:
hmp-pos-service/src/shifts/shifts.service.spec.ts
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/flow-7-pos-to-folio.spec.ts:: shift-bootstrap — 0/10 (LAUNCH BLOCKER)
Real-backend smokes:
- none specific
Use cases referencing this story:
- none
SME notes:
- POS-8 route ordering (Gotcha #13):
@Get('current')MUST come before@Get(':id/report'). Refactor risk: a future controller-decorator reorder breaks the dashboard's "current shift" panel with a silent 400. - Variance approval threshold (story AC #3: Rs 500) is hard-coded; should be per-property config.
- Denomination capture — story AC #1 enumerates "10x500, 5x100, rest coins"; today the API takes only a single
openingCashnumeric; no denomination breakdown for cash audit. Real-world result: cashier can fudge the float on close (just declare a number).
Drift findings:
D-011 — [P1] [Story-incomplete + cash-audit-critical] Shift open/close API takes only a numeric openingCash / closingCash — no denomination breakdown. Story body shows denomination capture in the UI but the backend cannot store it. Audit risk: a cashier short Rs 500 can declare any combination; manager cannot verify count.
- [SME-PROPOSED] Add AC: "
POST /shifts/openandPOST /shifts/closeSHALL accept optionaldenominationBreakdown: Record<DenominationKey, integer>whereDenominationKeyis'500' | '200' | '100' | '50' | '20' | '10' | '5' | '2' | '1' | 'coin'. Sum SHALL match the declared numeric within Rs 0 (exact match) or reject 400. Stored onPOSShift.openDenominations/closeDenominationsJSON columns." - [SME-PROPOSED] Add AC: "Variance threshold SHALL be per-outlet config (
Outlet.shiftVarianceAutoApprovePaisa, default 50000 = Rs 500). Variance > threshold blocks shift close until manager approves with reason note (≥10 chars)."
US-007-013 — Safe Drop During Shift
Catalog status: Built (P1, 2 points) Verified status: Built
Code locations:
hmp-pos-service/src/shifts/shifts.controller.ts—POST /shifts/:id/safe-drophmp-pos-service/src/shifts/shifts.service.ts
Unit + integration tests:
hmp-pos-service/src/shifts/shifts.service.spec.ts(safe-drop cases)
Flow tests:
- (not in 13-flow doc)
Real-backend smokes:
- none specific
Use cases referencing this story:
- none
SME notes:
- Same denomination-capture gap as US-007-012 — feeds D-011.
Drift findings: (none net new — covered by D-011)
- [SME-PROPOSED] Add AC: "Safe-drop accepts optional denomination breakdown (same schema as US-007-012). Witness
managerNameis replaced bywitnessUserIdFK so audit can prove the manager was on duty."
US-007-014 — Takeaway & Delivery Orders
Catalog status: Built (P1, 5 points) Verified status: Built
Code locations:
hmp-pos-service/src/orders/orders.service.ts— handlesorderType=TAKEAWAY | DELIVERY; AP-T07-006 mapping covers TAKEAWAY→POS_TAKEAWAY (DELIVERY → POS_OTHER today — see D-009 follow-up)hmp-pos-service/src/tokens/token.service.ts— token-number generation for takeaway pickuphmp-pos-service/src/tokens/token.controller.ts—/tokensendpoints
Unit + integration tests:
hmp-pos-service/src/tokens/token.service.spec.ts,token.controller.spec.ts
Flow tests:
- (not in 13-flow doc)
Real-backend smokes:
- (via smoke-pos-order.real.spec.ts variants)
Use cases referencing this story:
- none
SME notes:
- Delivery (in-campus pool/garden) charges Rs 0; external delivery (Swiggy/Zomato) is OUT OF SCOPE per EPIC body but
pos.order.receivedconsumer ingests UrbanPiper/Swiggy/Zomato events (CLAUDE.md "Consumed by this service"). Tension: out-of-scope-per-story vs in-the-code.
Drift findings:
D-012 — [P2] [Orphaned-consumer + scope-drift] EPIC body §"Out of Scope" says "Online food ordering / delivery app integration (Swiggy, Zomato) — separate integration epic". Yet PosOrderIngestionConsumer subscribes to pos.order.received for UrbanPiper / Swiggy / Zomato per pos-service CLAUDE.md. The consumer is opt-in via SKIP_EVENT_SUBSCRIPTIONS=true, but it's not "separate" — it lives in this service.
- [SME-PROPOSED] Add a new story
US-007-NEW-EXTERNAL-DELIVERY-INTEGRATIONto own thepos.order.receivedconsumer + its DELIVERY→POS_DELIVERY mapping + per-aggregator settlement reconciliation, OR explicitly fold it into US-007-014's scope and remove the "out of scope" line in the EPIC body.
US-007-015 — Menu Item Allergen & Dietary Tags
Catalog status: Built (P1, 3 points) Verified status: Built
Code locations:
hmp-pos-service/src/menu/dto/menu.dto.ts—dietaryTags,allergenTagsonCreateMenuItemDtohmp-pos-service/prisma/schema.prisma—MenuItem.isVeg, plus string-array tag columns
Unit + integration tests:
hmp-pos-service/src/menu/menu.service.spec.tscovers dietary tag CRUD
Flow tests:
- (not in 13-flow doc)
Real-backend smokes:
- none specific
Use cases referencing this story:
- none
SME notes:
- Dietary tag values (VEG / NON_VEG / VEGAN / JAIN / HALAL / GLUTEN_FREE) and allergen tags (DAIRY / NUTS / SHELLFISH / GLUTEN / SOY / EGG) — today free-form string arrays. Same canonical-taxonomy gap as EP-002 D-004.
- FSSAI labelling — India's Food Safety and Standards (Labelling and Display) Regulations 2020 requires veg/non-veg/egg green/red/yellow dots on packaged + displayed food. Story shows green VEG indicator dot on POS but no AC requires FSSAI dot rendering on the printed bill or KDS card.
- Jain warning — story has the right shape ("flag root vegetables, onion, garlic") but Jain compliance is more nuanced (no overnight curd, no fermented food, no certain veggies). Today's binary "Jain-compatible" tag is over-simplified.
Drift findings: (none net new — feeds D-004 amenity-taxonomy ask at item-tag granularity)
- [SME-PROPOSED] Add AC: "Dietary + allergen tags SHALL be drawn from a canonical taxonomy (Prisma enums, not free string). Migration adds
dietary_tag+allergen_tagenums and updates rows to enum-valid values; rejects free text after migration date." - [SME-PROPOSED] Add AC: "FSSAI veg/non-veg/egg dots SHALL render on the printed bill ESC/POS template and on the KDS card. Test:
receipt-templates.spec.tsasserts the dot byte sequence."
US-007-016 — Happy Hour Pricing (Time-Based)
Catalog status: Partial (P2, 5 points)
Verified status: Partial — happyHourPrice column exists, manual toggle; no time-based auto-activation
Code locations:
hmp-pos-service/prisma/schema.prisma—MenuItem.happyHourPrice(per EPIC body)hmp-pos-service/src/menu/menu.service.ts— manual toggle pathhmp-pos-service/src/pricing-rules/— pricing rule engine (could host happy-hour schedules but doesn't yet)
Unit + integration tests:
hmp-pos-service/src/pricing-rules/pricing-rule.service.spec.ts
Flow tests:
- (not in 13-flow doc)
Real-backend smokes:
- none
Use cases referencing this story:
- none
SME notes:
- Per EPIC body US-007-020: "toggle is forgotten 30% of the time per F&B Manager feedback" — the manual toggle is the root cause. A cron-based scheduler would close this.
- Pricing-rules engine exists; happy-hour is a natural fit (rules conditional on
dayOfWeek + timeOfDay).
Drift findings: (captured in summary as Partial)
- [SME-PROPOSED] Migrate happy-hour into the pricing-rules engine:
PricingRule { kind: 'HAPPY_HOUR', daysOfWeek[], startTime, endTime, outletId, appliesToCategoryIds[], discountPercent | flatPrice }. Cron tick every minute firespricing-rule.active.changedevent; POS terminal renders the active price.
US-007-017 — Recipe & Inventory Auto-Deduction
Catalog status: Not Started (P2, 8 points)
Verified status: Stub — recipe lookup endpoint exists (GET /menu/items/:id/recipe), auto-deduction loop does NOT
Code locations:
hmp-pos-service/src/menu/menu.controller.ts:117-123—@Get('menu/items/:id/recipe')(cross-service read)hmp-pos-service/src/menu/menu.service.ts:440—getRecipeByMenuItemreturns recipe + ingredients (P1-C3 / P3-2)- NO auto-deduction code path on order create/KOT/serve
Unit + integration tests:
- (recipe lookup coverage in menu.service.spec.ts; no auto-deduction test)
Flow tests:
- (not in 13-flow doc)
Real-backend smokes:
- none
Use cases referencing this story:
- none
SME notes:
- Catalog says "Not Started" but recipe lookup endpoint IS shipped — bumps the verified status to Stub (placeholder; not "Not Started" honest).
- Inventory ownership lives in
hmp-asset-serviceper platform CLAUDE.md (port 3016, owns "Assets, work orders, PPM/AMC, engineering spares, vendor master") — but F&B raw materials are a different beast than engineering spares. Likely needs a newhmp-inventory-serviceor extendshmp-pos-serviceto own F&B inventory. - Food cost percentage (story AC #3) is the highest-value KPI of any POS — without auto-deduction, the 28-32% target is uncalculable.
Drift findings:
D-013 — [P1] [Catalog-says-Not-Started-but-partial-code-exists] Recipe lookup endpoint ships; auto-deduction loop does not. Catalog "Not Started" is honest about the loop, not honest about the partial primitive.
- [SME-PROPOSED] Bump catalog status from
Not Started→Stub. Recipe lookup endpoint should be split into its own storyUS-007-NEW-RECIPE-LOOKUP(Built); the inventory auto-deduction story keeps US-007-017 (Not Started → Stub) with explicit ACs for the deduction loop firing onpos.kot.completedevent. - [SME-PROPOSED] Add AC: "On
pos.kot.completedevent, an inventory-deduction worker SHALL look up the recipe for each item, atomically decrementRawMaterial.currentStockper ingredient, and write anInventoryTransactionrow{ kotId, materialId, deltaQty, deltaCostPaisa, at }. Stock-out (currentStock < 0) SHALL emitinventory.material.stockoutfor ops alerting."
US-007-018 — Kitchen Display System Integration
Catalog status: Built (P0, 5 points) Verified status: Built
Code locations:
hmp-pos-service/src/gateway/—KitchenGateway(/kitchenSocket.IO namespace)hmp-kds-kiosk/packages/kds/— Electron KDS app (per repo layout)hmp-kds-kiosk/packages/shared/— shared between KDS + Kioskhmp-pos-service/src/kitchen/kitchen.controller.ts— HTTP backplane
Unit + integration tests:
hmp-pos-service/src/kitchen/kitchen.gateway.spec.ts— auth + scope + emit assertionshmp-pos-service/src/kitchen/kitchen.service.spec.ts
Flow tests:
hmp-infrastructure/testing/flow-tests/src/flows/pos-kot.flow.spec.ts— TBDhmp-infrastructure/testing/flow-tests/src/flows/flow-7-pos-to-folio.spec.ts:: KOT scenario — 0/10 (LAUNCH BLOCKER)
Real-backend smokes:
- none specific (KDS-kiosk has its own Vitest config but no real-backend smoke registered)
Use cases referencing this story:
- none
SME notes:
- AP-CC-3 (2026-04-29) —
KitchenGateway.onModuleInit()throws at boot in production withoutJWT_PUBLIC_KEY. RS256 required; HS256 only dev/test. Story body does NOT mention this hard gate — production-readiness AC missing. - KDS is on
hmp-kds-kioskrepo (separate from web-dashboard); the EPIC body says "Electron application" but it's a workspaces monorepo withkds,kiosk,sharedpackages.
Drift findings: (none net new — captured by D-007 dual-emit ask)
- [SME-PROPOSED] Add AC: "Production deployment SHALL have
JWT_PUBLIC_KEYset (RS256).KitchenGateway.onModuleInit()boot-time assertion is the gate. Test:kitchen.gateway.spec.tsassertsthrowinNODE_ENV=production+ missing key."
US-007-019 — POS Thermal Printer Integration
Catalog status: Partial (P2, 5 points) Verified status: Partial — ESC/POS templates ship with 47 byte-level tests; physical printer discovery + offline queue do NOT
Code locations:
hmp-shared-packages/packages/common/src/printing/receipt-templates.ts—generatePosReceipt, 48-char ESC/POShmp-web-dashboard/src/app/(dashboard)/pos/hardware/— hardware page
Unit + integration tests:
hmp-shared-packages/packages/common/src/printing/receipt-templates.spec.ts— 47 byte-level tests
Flow tests:
- (not in 13-flow doc)
Real-backend smokes:
- none
Use cases referencing this story:
- none
SME notes:
- EPIC body Gap correctly captures: no USB/network printer discovery, no WebSocket bridge, no offline queue. Today's printing is "the template prints"; the transport story is missing.
- Mock-vs-real:
HARDWARE_MOCK=trueis the platform CLAUDE.md mock flag — does this gate the print path? Verify.
Drift findings: (none net new — captured as Partial)
- [SME-PROPOSED] Add AC: "A
PrintQueueServiceSHALL queue print jobs to Redis with TTL 24h. When printer reports back online, the queue drains in arrival order. Failed jobs after 3 retries surface in a per-outletprint_failuresUI panel."
US-007-020 — F&B Revenue Reports (by Outlet, Item, Server)
Catalog status: Partial (P1, 5 points) Verified status: Partial — Plan 2.2 reporting-feeds + revenue.service ship; UI drill-down + voids/discounts breakdowns partial
Code locations:
hmp-pos-service/src/orders/revenue.controller.ts— revenue reportinghmp-pos-service/src/orders/revenue.service.ts+revenue.service.spec.tshmp-pos-service/src/reporting-feeds/reporting-feeds.module.ts— Plan 2.2 live-proxy endpoints (/reporting-feeds/pos/kot-register,/void-register,/discount-register)
Unit + integration tests:
hmp-pos-service/src/orders/revenue.service.spec.tshmp-pos-service/src/orders/__tests__/close-business-day.spec.ts
Flow tests:
- (not in 13-flow doc)
Real-backend smokes:
- none
Use cases referencing this story:
- none
SME notes:
- Reporting-feeds endpoints are read-only live-proxies consumed by analytics-service
LiveProxyResolverviaINTERNAL_SERVICE_TOKEN. Roles includeINTERNAL_SERVICE— story should reference this auth pattern. - "Top N selling items" + "food cost %" depend on US-007-019 inventory auto-deduction (Not Started/Stub) — until then, food cost % is uncalculable.
Drift findings: (none net new)
- [SME-PROPOSED] Add AC: "Reports SHALL drill from outlet → category → item → server. Food cost % column is conditional on US-007-019 ship; until then, render
—with tooltipRequires inventory auto-deduction (US-007-019)."
US-007-021 — Combo Meal / Set Menu Management
Catalog status: Partial (P2, 3 points) (EPIC body US-007-024 says Not Started; catalog says Partial — D-001 numbering drift) Verified status: Not Started — no combo/parent-child item model, no component-routing logic
Code locations: NONE FOUND (drift candidate — Task 6)
Unit + integration tests: NONE FOUND
Flow tests: NONE FOUND
Real-backend smokes: NONE FOUND
Use cases referencing this story:
- none
SME notes:
- Catalog "Partial" is wrong — verify status is Not Started. Possibly a stale catalog entry.
- Indian-thali model: composite price, multiple kitchen stations per combo, customizable swaps. Per-component HSN/SAC for IRN line builder is the hard part (story EPIC body AC #3).
Drift findings: (captured in summary as Not Started despite catalog "Partial")
- [SME-PROPOSED] Bump catalog status from
Partial→Not Started. Author storyUS-007-NEW-COMBO-PARENT-CHILDwith explicit ACs (MenuItem.parentItemIdself-FK; combo price-override column; per-component HSN/SAC for IRN; KOT routes per component station).
US-007-022 — Table Transfer (Move Guests to Another Table)
Catalog status: Built (P1, 2 points) Verified status: Built
Code locations:
hmp-pos-service/src/orders/orders.controller.ts—POST /orders/merge,POST /orders/transfer-itemshmp-pos-service/src/orders/dto/merge-transfer.dto.ts
Unit + integration tests:
- (covered in orders.service.spec.ts merge/transfer cases)
Flow tests:
- (not in 13-flow doc)
Real-backend smokes:
- none specific
Use cases referencing this story:
- none
SME notes:
- Story title says "Table Transfer" but endpoints are
/orders/merge+/orders/transfer-items— the noun is the order, not the table. Real-world: a server says "move table 5's order to table 7" — today's endpoint requires an explicit destination order, not "create-new-on-table".
Drift findings: (none net new)
- [SME-PROPOSED] Add AC: "
POST /orders/:id/move-to-tableaccepts{ destinationTableId }. If the destination is VACANT, the order's tableId is updated AND both tables' statuses flip atomically (source → VACANT, dest → OCCUPIED). If the destination already has an OPEN order, the call returns 409code=DEST_TABLE_HAS_OPEN_ORDER— caller must use/orders/merge."
US-007-023 — Order Modification After KOT
Catalog status: Built (P1, 3 points) Verified status: Built
Code locations:
hmp-pos-service/src/orders/orders.controller.ts—POST /orders/:id/items(add items post-KOT),POST /orders/:id/void(remove)hmp-pos-service/src/orders/orders.service.ts— re-emitsorder:updatedover WS (Gotcha #1 follow-up: any KOT mutation MUST publish both signals)
Unit + integration tests:
hmp-pos-service/src/orders/orders.service.spec.ts,orders.service.p6.spec.ts
Flow tests:
- (not in 13-flow doc)
Real-backend smokes:
- (via smoke-pos-order.real.spec.ts)
Use cases referencing this story:
- none
SME notes:
- Modifying items AFTER KOT is the highest-risk path — if the kitchen has already started cooking, the modification must produce a void + new KOT, not a silent edit. Per Gotcha #1, both signals (WS + Kafka) must fire — verify the modification path does it.
Drift findings: (none net new — feeds D-007 dual-emit)
- [SME-PROPOSED] Add AC: "Adding items to an OPEN order with at least one KOT generated SHALL automatically generate a NEW KOT (
KOT.kotNumberincremented) — never silently amend an existing KOT row. Test asserts a freshpos.kot.createdevent fires on the modification path."
US-007-024 — F&B Mobile App Integration
Catalog status: Not Started (P2, 5 points)
Verified status: Not Started — no React Native F&B app code in hmp-mobile-apps/
Code locations: NONE FOUND (catalog-row honest)
Unit + integration tests: NONE FOUND
Flow tests: NONE FOUND
Real-backend smokes: NONE FOUND
Use cases referencing this story:
- none
SME notes:
- "Not Started" is honest. EPIC body "Current Implementation Status" claims a React Native app exists but it's aspirational text, not shipped code.
Drift findings: (none — story-vs-reality matches Not Started)
- [SME-PROPOSED] When prioritized, the F&B mobile app SHALL reuse the
@hmp/shared-uipackage (per platform CLAUDE.md Shared UI Primitives) and connect to the/kitchennamespace viauseRealtimeKitchenhook (already exists inhmp-mobile-apps/packages/shared-realtimeper platform CLAUDE.md WebSocket section).