Skip to main content

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-service nationality 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.md until Task #12 re-measures.

Status legend​

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

Summary​

MetricCount
Total stories24
Catalog claims "Built"15
Verified-Built13 (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-Untested0
Partial7 (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)
Stub2 (US-007-014 room-charge folio validation; US-007-019 recipe-inventory auto-deduction — recipe lookup ships but auto-deduction does NOT)
Not Started2 (US-007-019 inventory auto-deduction loop is "Not Started" per catalog; US-007-024 F&B mobile + US-007-024 combo meals)
Drift findings13 (D-001..D-013; 6 P0 / 5 P1 / 2 P2 — LAUNCH BLOCKER pressure tilts the curve P0-heavy)
[SME-PROPOSED] refinements28

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 in reviews/EP-007/US-007-001-review.md)
  • hmp-pos-service/src/outlets/dto/outlet.dto.ts — CreateOutletDto, UpdateOutletDto, OutletFilterDto
  • hmp-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 — see reviews/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.outletType DTO 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 OutletType enum is RESTAURANT/BAR/LOUNGE/COFFEE_SHOP/ROOM_SERVICE/POOL_BAR/BANQUET/OTHER — MINIBAR and SPA_CAFE are folded into OTHER, while LOUNGE (not in the story) is added. Real-world impact: MINIBAR auto-deduction reports cannot key off outletType.
  • OutletsController delegates to MenuService for 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.outletType SHALL 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/status
  • hmp-pos-service/src/tables/tables.service.ts — createMany does NOT wrap in $transaction by design (POS-2 gotcha — Prisma 5.x no savepoints)
  • hmp-pos-service/src/tables/dto/table.dto.ts — CreateTableDto, UpdateTableDto, BulkCreateTablesDto; TABLE_STATUS_VALUES enum (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/posY floor-plan coordinates exist on POSTable per 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 $transaction and 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/:id allows direct status set with NO transition guard; PATCH /tables/:id/status is 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/status which enforces transitions per the TABLE_STATUS_VALUES state machine (VACANT → OCCUPIED requires open order; OCCUPIED → BILLED requires bill generated; BILLED → VACANT on payment). PATCH /tables/:id SHALL reject status field with 400 code=USE_STATUS_ENDPOINT."
  • [SME-PROPOSED] Add AC for floor-plan coordinates: "posX and posY are Decimal in 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 lookups
  • hmp-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 coverage
  • hmp-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 hsnCode and gstRate. Today's regression suite (12 specs) is the safety net; if the spec ever drops, drift returns.
  • gstRate is 0..28 per-item slab (5/12/18/28 are India slabs) with fallback to outlet taxRate when null — fallback semantics are not stated in the story body.
  • hsnCode is 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.gstRate is the source of truth. If null, the bill SHALL use Outlet.taxRate AND emit a pos.menu.gst_fallback analytics event so RMS can flag items missing a per-item slab. Items in categories LIQUOR, BEER, WINE, CIGARETTE SHALL reject gstRate: null with 400 code=GST_RATE_REQUIRED_FOR_SIN_GOODS (28% slab is mandatory)."
  • [SME-PROPOSED] Add AC: "MenuItem.hsnCode SHALL 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; tag codeType: HSN | SAC so 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 via OrdersService.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 paths
  • hmp-pos-service/src/orders/order-void.service.spec.ts — void/approval
  • hmp-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 template
  • hmp-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 Decimal fields 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_SERVICE but room-charge posting + folio handoff is the separate /orders/:id/room-charge path (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 an it.only rehearsal-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.md until 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.unitPrice SHALL snapshot MenuItem.price (or happyHourPrice if active) at the moment the item is added to the order. A subsequent menu price change does NOT mutate existing OrderItem rows. The pos.order.line.priceSnapshotted analytics 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/kot
  • hmp-pos-service/src/orders/orders.service.ts:~623 — generateKOT MUST call KitchenGateway.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/status
  • hmp-pos-service/src/kitchen/kitchen.service.ts — KOT state machine (PENDING → ACCEPTED → PREPARING → READY → COMPLETED → SERVED)
  • hmp-pos-service/src/gateway/ — KitchenGateway (/kitchen namespace, port 3007, Redis adapter via configureRedisAdapter when REDIS_HOST set; Gotcha #6 + #8 — JWT verified on handleConnection, RS256 required in prod)

Unit + integration tests:

  • hmp-pos-service/src/kitchen/kitchen.service.spec.ts
  • hmp-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:created over WebSocket AND publish pos.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 legacy pos.kot.ready for 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.generated and pos.kot.ready by sprint N+2 from the AP-T07-002 ship date. Until then, analytics-service consumers SHALL key off pos.kot.created / pos.kot.completed only and ignore the legacy aliases."
  • [SME-PROPOSED] Add AC: "Items with kitchenStation=null SHALL reject KOT generation with 400 code=ITEM_MISSING_KITCHEN_STATION listing 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/email
  • hmp-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: reads property.state via PropertyServiceClient, 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.ts
  • hmp-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: cardLast4 SHALL match ^[0-9]{4}$ and reject any longer numeric string with 400 code=CARD_PAN_REJECTED. upiVpa SHALL match ^[a-zA-Z0-9.\\-]{2,256}@[a-zA-Z]{2,64}$. Both rules covered by a dedicated payment-pii.spec.ts so PCI scope reduction has a regression test."
  • [SME-PROPOSED] Add AC: "Mixed payments SHALL sum to bill.total to within 0.01 (Indian paise) — variance > 0.01 returns 400 code=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-charge
  • hmp-pos-service/src/orders/orders.service.ts — postRoomCharge builds FolioChargePostedPayloadV2 (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 — covers postRoomCharge with orderType-mapping fixtures (mock must include orderType per 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 via POS_OTHER today — should map to POS_CANTEEN so 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-charge SHALL call ReservationServiceClient.assertRoomOccupiedAndWithinCreditLimit({ propertyId, roomNumber, additionalCharge }) BEFORE publishing folio.charge.posted. Reject with 409 code=ROOM_NOT_OCCUPIED or 409 code=CREDIT_LIMIT_EXCEEDED as 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_CANTEEN transactionCode 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|reject
  • hmp-pos-service/src/orders/orders.service.ts + order-void.service.spec.ts
  • hmp-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 OTHER to 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 OTHER SHALL require a non-empty notes field (≥10 chars). Reject with 400 code=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/split
  • hmp-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.ts with cases for 1000/3, 333/7, 1/3 (sub-rupee residual)."
  • [SME-PROPOSED] Add AC: "Per-item assignment at order-time: POSOrderItem.guestSlot int field (0..guestCount-1). At bill time, POST /orders/:id/split-by-guest-slot auto-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/discount
  • hmp-pos-service/src/pricing-rules/pricing-rule.service.ts — rule-based discount engine
  • hmp-pos-service/src/pricing-rules/pricing-rule.service.spec.ts

Unit + integration tests:

  • hmp-pos-service/src/pricing-rules/pricing-rule.service.spec.ts
  • hmp-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=true marker; 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) and Outlet.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-charge
  • hmp-pos-service/src/orders/dto/service-charge.dto.ts — ServiceChargeDto, ServiceChargeType enum
  • hmp-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 consent only when checked. Without consent, no service-charge line is added; tip line is the only voluntary line."
  • [SME-PROPOSED] Add AC: "Tip pool: TipDistribution model { tipId, shiftId, distributedToUserIds[], perUserAmount, distributedByManagerId, at }. Endpoint POST /shifts/:id/tip-distribute runs 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-drop
  • hmp-pos-service/src/shifts/shifts.service.ts — openShift duplicate-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 openingCash numeric; 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/open and POST /shifts/close SHALL accept optional denominationBreakdown: Record<DenominationKey, integer> where DenominationKey is '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 on POSShift.openDenominations / closeDenominations JSON 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-drop
  • hmp-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 managerName is replaced by witnessUserId FK 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 — handles orderType=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 pickup
  • hmp-pos-service/src/tokens/token.controller.ts — /tokens endpoints

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.received consumer 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-INTEGRATION to own the pos.order.received consumer + 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, allergenTags on CreateMenuItemDto
  • hmp-pos-service/prisma/schema.prisma — MenuItem.isVeg, plus string-array tag columns

Unit + integration tests:

  • hmp-pos-service/src/menu/menu.service.spec.ts covers 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_tag enums 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.ts asserts 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 path
  • hmp-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 fires pricing-rule.active.changed event; 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 — getRecipeByMenuItem returns 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-service per 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 new hmp-inventory-service or extends hmp-pos-service to 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 story US-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 on pos.kot.completed event.
  • [SME-PROPOSED] Add AC: "On pos.kot.completed event, an inventory-deduction worker SHALL look up the recipe for each item, atomically decrement RawMaterial.currentStock per ingredient, and write an InventoryTransaction row { kotId, materialId, deltaQty, deltaCostPaisa, at }. Stock-out (currentStock < 0) SHALL emit inventory.material.stockout for 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 (/kitchen Socket.IO namespace)
  • hmp-kds-kiosk/packages/kds/ — Electron KDS app (per repo layout)
  • hmp-kds-kiosk/packages/shared/ — shared between KDS + Kiosk
  • hmp-pos-service/src/kitchen/kitchen.controller.ts — HTTP backplane

Unit + integration tests:

  • hmp-pos-service/src/kitchen/kitchen.gateway.spec.ts — auth + scope + emit assertions
  • hmp-pos-service/src/kitchen/kitchen.service.spec.ts

Flow tests:

  • hmp-infrastructure/testing/flow-tests/src/flows/pos-kot.flow.spec.ts — TBD
  • hmp-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 without JWT_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-kiosk repo (separate from web-dashboard); the EPIC body says "Electron application" but it's a workspaces monorepo with kds, kiosk, shared packages.

Drift findings: (none net new — captured by D-007 dual-emit ask)

  • [SME-PROPOSED] Add AC: "Production deployment SHALL have JWT_PUBLIC_KEY set (RS256). KitchenGateway.onModuleInit() boot-time assertion is the gate. Test: kitchen.gateway.spec.ts asserts throw in NODE_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/POS
  • hmp-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=true is 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 PrintQueueService SHALL 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-outlet print_failures UI 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 reporting
  • hmp-pos-service/src/orders/revenue.service.ts + revenue.service.spec.ts
  • hmp-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.ts
  • hmp-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 LiveProxyResolver via INTERNAL_SERVICE_TOKEN. Roles include INTERNAL_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 tooltip Requires 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 story US-007-NEW-COMBO-PARENT-CHILD with explicit ACs (MenuItem.parentItemId self-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-items
  • hmp-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-table accepts { 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 409 code=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-emits order:updated over 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.kotNumber incremented) — never silently amend an existing KOT row. Test asserts a fresh pos.kot.created event 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-ui package (per platform CLAUDE.md Shared UI Primitives) and connect to the /kitchen namespace via useRealtimeKitchen hook (already exists in hmp-mobile-apps/packages/shared-realtime per platform CLAUDE.md WebSocket section).