Skip to content

PRD: QR Self-order

ModuleSalePRD IDPRD-SLF-001
StatusNo itemFEATSLF · BANA-1432
EpicBANA-1325PlaneBANA-1562
Date2026-07-15Versionv0.2
Packages@nx/sale · @nx/commerceURDSLF
SurfaceSale · POS
OwnerHải Cao · Khoa Nguyễn · Phát Nguyễn

TL;DR

  • A guest at a table scans the QR, browses a menu filtered by policy (time/channel) right on their phone - no app - and submits.
  • Each submission creates its own order card, but the whole table shares one order: five guests at table 5 submitting five times still produces a single table-5 order holding five cards.
  • Staff confirm or reject each card on the POS; a confirmed card prints a kitchen ticket like any staff-entered order.
  • None of this exists today - the whole feature is 🚧 forward-spec.

1. Context & Problem

Peak-hour table service bottlenecks at order-taking - every table waits for a free-handed waiter to write the order down before the kitchen sees anything. There is no alternative path today: no guest-facing ordering page, no per-table QR code, no menu filtered by time/channel (policy), and no receive-and-confirm flow on the POS for an order staff never typed themselves.

Three pieces this feature needs, grounded in what already exists in code:

  • Tables and occupancy already exist via Table & floor allocation - each table (AllocationUnit) is occupied through an AllocationUsage (reserved/active/success = occupied, completed/cancelled = free), and the Sale POS already has a live table view reading this exact state. SLF does not rebuild table occupancy - it attaches guest-submitted orders to the table's existing usage.
  • Order lifecycle (SaleOrderStatuses: DRAFT/PROCESSING/PARTIAL/COMPLETED/CANCELLED) already exists in Sale ORD. SLF still needs to define how confirming/rejecting a card maps onto this enum (see §6, §9) - that transition is new and doesn't exist yet.
  • Deleting a table's order currently has no guard beyond checking that the order isn't already in a terminal status - PRD-SLF-002 closes that gap independently, ahead of SLF, and defines the "guest order can only be rejected, never deleted" rule (URD-SLF-009) that this PRD must slot into rather than redefine.

Entirely missing: the guest ordering page, per-table QR codes, the policy-based menu engine (a separate plan, epic BANA-902…907, not started), and the POS confirm/reject flow for guest-submitted orders.

2. Goals & Non-Goals

Goals

  • A guest scans the right table's QR and gets the menu on their phone, no app install, no login (URD-SLF-001).
  • The menu shown matches the policy in effect for the current time/channel (URD-SLF-002).
  • A guest builds a cart and submits it for their table; each submission is its own card, and multiple submissions/guests at the same table fold into one order for that table (URD-SLF-003, URD-SLF-008).
  • The POS receives each card tied to the right table; staff confirm or reject each card - no delete action (URD-SLF-004).
  • A confirmed card prints a kitchen ticket identically to a staff-entered order (URD-SLF-005).
  • The guest sees each card's status update in real time (URD-SLF-006).
  • Sold-out items are hidden or unorderable on the guest menu (URD-SLF-007).

Non-Goals

  • Online payment in v1 - the guest still pays at the counter/table through staff, as today.
  • Loyalty integration.
  • Offline operation - both the guest page and the POS require connectivity.
  • A separate order/tab per guest - not a thing; every card from a table folds into a single aggregate order, and payment is still settled per table as today (see §6 FR-004).
  • The policy-based menu engine itself (authoring/editing policies, assigning by time/channel) - owned by a separate epic, BANA-902…907; this PRD only consumes an existing policy, it does not define the policy admin screens.
  • The confirm-vs-delete guard and audit trail for deleting a table's order - owned by PRD-SLF-002 (URD-SLF-009); this PRD only complies with the "reject, never delete" rule that PRD sets, it does not redefine it.

3. Success Metrics

MetricTarget / signal
Pilot venues live1-2 real F&B venues, August
Guest order → kitchenFlows end-to-end with staff confirmation; no kitchen ticket is ever produced for a rejected card
Card rejection rateTracked per pilot venue - an abnormally high rate signals a menu/pricing mismatch or a broken confirmation flow
Confirmation latencyTime from a card appearing on the POS to staff confirming/rejecting it - tracked to catch a "forgotten" table

4. Personas & Scenarios

PersonaGoal in this feature
GuestOrder from their own phone, no app, no waiting for a free-handed waiter; trust that a submitted order is confirmed, rejected with a reason, or still pending
Waiter / CashierReceive guest orders tied to the right table on the POS, confirm or reject each submission, and keep full control before anything reaches the kitchen
KitchenReceive a ticket for a confirmed card identical to one from a staff-entered order - no new process to learn

Core scenario: five guests at one table each scan the QR on their own phone and submit a separate order. All five submissions fold into one order for table 5, showing up as five pending cards on the POS. The waiter confirms them one by one; the kitchen receives five matching tickets, none crossed with another table.

5. User Stories

  • As a guest, I scan the table QR, browse the menu, order 2 dishes, and see when they're confirmed - so I don't have to flag down a waiter.
  • As a guest sharing a table with friends, each of us orders on our own phone, and I know the whole table still settles as one order.
  • As a waiter, guest orders appear on my POS under the right table as individual cards; I confirm or reject each one, keeping control before anything reaches the kitchen.
  • As kitchen staff, I receive a ticket for a confirmed card identical to one from a staff-entered order - I don't need to tell them apart.

6. Functional Requirements

#RequirementStatusURD ref
FR-001A guest scans a per-table QR code and opens the menu in their phone's browser - no app install, no login. An expired or invalid QR shows a message asking them to call staff, not a stale menu or a blank error page.🚧URD-SLF-001
FR-002The guest-facing menu is filtered by the policy in effect at scan time: a time window (by day of week), a sales channel (linked to the existing SaleChannel), and a scope (whole menu / by category / by item). Items or categories outside the window or the wrong channel don't appear on the guest menu.🚧 - hard dependency on the policy engine (BANA-902…907), which is not startedURD-SLF-002
FR-003A guest adds/removes items and quantities in a cart, adds an optional free-text note per item, sees a running subtotal, and submits the cart for their table. An empty cart cannot be submitted.🚧URD-SLF-003
FR-004Each cart submission creates its own order card. A table has at most one aggregate order at a time; every card - whether from the same guest submitting more than once or from different guests at the same table - attaches to that aggregate order, without spawning a separate order or occupying the table a second time.🚧URD-SLF-003 · URD-SLF-008
FR-005Each card appears on the POS tied to the right table, showing its items, quantities, notes, and submission time. Staff have exactly two actions per card: Confirm or Reject (reason required) - no delete action (per the URD-SLF-009 rule, fully defined in PRD-SLF-002).🚧URD-SLF-004
FR-006Confirming a card adds its items to the table's aggregate order and creates a kitchen ticket immediately, with the same structure and station routing as a staff-entered order's ticket.🚧URD-SLF-005
FR-007The guest sees each of their cards' status update in real time: Submitted → Confirmed (→ Served, if enabled), or Submitted → Rejected with a reason - no manual refresh needed.🚧URD-SLF-006
FR-008A sold-out item or variant does not appear on the guest menu, or appears greyed-out and cannot be added to the cart. An item marked sold-out after it's already in a guest's cart but before submission is auto-removed with a notice.🚧URD-SLF-007
FR-009Within the same session (QR not expired), a guest can submit another cart at any time; each additional submission creates a new card attached to the table's aggregate order under the same rule as FR-004, with no submission-count limit in v1.🚧URD-SLF-008

6.1 Acceptance Criteria

  • A guest scans table 5's valid QR → the menu page opens directly in the phone browser, no login/app needed; the header shows the venue name and "Table 5". 🚧
  • Table 5's QR has expired → the page shows "This QR code has expired - please call staff", not a stale cached menu. 🚧
  • Outside the active policy's time window or on the wrong channel → the matching item/category doesn't appear on the guest menu (distinct from sold-out - no separate badge, the item simply isn't on the menu at that moment). 🚧 - depends on the policy engine, not yet built
  • A guest adds 3 items to the cart and submits → a new card appears on the POS tied to table 5, listing all 3 items, quantities, and the submission time. 🚧
  • Five different guests at table 5 each scan the QR and submit their own cart → five separate cards appear on the POS, all attached to the same aggregate order for table 5 - not five separate orders, not five new table occupancies (AllocationUsage). 🚧
  • A guest submits a second cart within the same session → a new card is added to table 5's aggregate order; the existing aggregate order and table occupancy are not recreated. 🚧
  • Staff confirm a card → its items are added to the table's aggregate order, a kitchen ticket is created immediately, and the guest sees the card's status change to "Confirmed" without reloading the page. 🚧
  • Staff try to reject a card without entering a reason → the system blocks it, requiring a reason before the rejection is confirmed; the guest is notified "Rejected" with the reason (the full confirm/reject/audit behaviour is defined in PRD-SLF-002, URD-SLF-009 - not redefined here). 🚧
  • An item in a guest's cart is marked sold-out before they submit → it's auto-removed from the cart with a notice; if the cart is empty afterward, the submit button is disabled. 🚧

7. Non-Functional Requirements

AspectRequirement
PerformanceThe guest page renders the menu within ≤3 seconds at p95 on a slow connection (~150kbps, roughly weak 3G) - 🚧 proposed figure, to be confirmed after real measurement at a pilot venue
QR securityThe per-table token is a random string of at least 128 bits, not derivable from the table number or scan time; a 12-hour TTL is proposed (covering one shift) before it must be rescanned - 🚧 proposed figures, pending Design (July) and a security review
Tenancy & authzThe guest page only reads the menu/policy of the merchant and table encoded in its token; it has no write access beyond submitting a cart for that table
Real-timeCard status (confirmed/rejected) pushes to the guest page and every POS device over the same real-time channel already used for the live table view (Table & floor allocation), no manual refresh needed
i18nThe guest menu and confirm/reject copy are bilingual (EN + VI), following the merchant's preferred language

8. UX & Flows

One guest orders, gets confirmed, reaches the kitchen

Multiple guests, one table - one order, many cards

Key surfaces: the guest page (menu → cart → status) runs entirely in the phone browser, no install; the POS gains a "pending" area per table where each card is its own row with Confirm/Reject buttons - no delete icon on a guest-submitted card.

9. Data & Domain

ConceptRole
AllocationUnit / AllocationUsageThe table and its occupancy, already provided by Table & floor allocation; SLF builds no new occupancy mechanism, it only attaches guest orders to a table's existing usage (reserved/active/success = occupied)
SaleOrder (table's aggregate order)The existing order entity, carrying SaleOrderStatuses (DRAFT/PROCESSING/PARTIAL/COMPLETED/CANCELLED); a table has at most one open aggregate order for the guest-ordering flow at a time
Order card (new, conceptual)One guest cart submission; multiple cards attach to the same table aggregate order, each carrying its own state (pending / confirmed / rejected with reason) independent of the aggregate order's status
Per-table QR token (new)The identifier used to open the guest page, carrying an expiry (TTL) - see NFR
Menu policy (out of scope, consumed via API)Determines which items/categories show by time/channel; owned by epic BANA-902…907, not started - this PRD only consumes its output, it does not define policies

Conceptual only - "order card" and "QR token" are proposed models; the real schema is pending Design/BE in July-August.

10. Dependencies & Assumptions

Depends on

  • Policy-based menu engine (BANA-902…907) - it's the menu the guest sees (FR-002). 🚧 Not started.
  • Table & floor allocation - AllocationUsage is the source of truth for which table is open; guest orders attach to it. ✅ Built, except FR-006 (tier/room/table/seat quantity caps), which is currently 🔶 partial.
  • PRD-SLF-002 (table-order delete guard) - defines the "guest card/order can only be rejected, never deleted" rule (URD-SLF-009) that this PRD's FR-005 must slot into. 🚧 Planned, may ship ahead of SLF.
  • Order lifecycle (SaleOrderStatuses, ORD) - confirming/rejecting a card must map onto the existing order status enum. ✅ Built.
  • Design (July) - guest UI + POS confirm/reject UX, plus the NFR figures still 🚧 (token TTL, page-load SLA). Not started.

Assumptions

  • At most one live AllocationUsage occupies a given table at a time (per the FLR model), so "which table's aggregate order" is always unambiguous when folding in multiple cards.
  • Once complete, the policy engine (BANA-902…907) exposes an API to read "available menu by merchant + time + channel" that SLF consumes directly, without re-implementing the filtering logic.
  • A pilot venue has enough staff on hand to confirm/reject cards within a reasonable time - this PRD does not set a hard response-time SLA in v1 (see Risks).

11. Risks & Open Questions

Risk / questionMitigation / status
Abuse/garbage orders from guestsStaff confirmation on every card is the v1 guard - no card reaches the kitchen without it
Unlimited submissions per session (FR-009) could be abused for spamNo hard limit in v1; monitored via the "card rejection rate" metric (§3), revisit if pilot operations surface a real problem
QR token TTL and page-load SLA figures aren't finalizedProposed values live in §7 NFR; pending Design (July) and real pilot measurement to confirm
Staff never respond to a pending cardOut of scope for v1 - no SLA or auto-reminder yet; add in a future increment if pilot operations need it
The policy engine (BANA-902…907) hasn't started and may slip past SLF's July-August milestonesFR-002 is a hard dependency; if policy slips, the guest menu could temporarily run unfiltered (showing the whole menu) as a fallback - Design must sign off on this before it's built

12. Release Plan & Criteria

AspectPlan
PhaseP2 (Jul-Aug) - SLF in the URD feature catalog
RolloutPilot F&B venues first (1-2, August), wider rollout once launch criteria pass
MigrationNone needed - the table's aggregate order reuses the existing SaleOrder/AllocationUsage model; order cards are additive new data
Launch criteriaAC-SLF-01 (URD) passes at a pilot venue: a guest submits a 2-item order → POS shows it pending on the right table → confirming it sends a ticket to the kitchen; sold-out items are unorderable; multiple guests at one table correctly fold into one order with multiple cards
MonitoringCard rejection rate, confirmation latency by table/venue, guest-page load failure rate (expired/invalid QR), average submissions per table per session

13. FAQ

  • Does the guest pay online? Not in v1 - ordering only; payment stays with staff at the counter/table as today.
  • What happens when multiple guests at one table order at the same time? Each submission creates its own card, but every card from that table folds into one aggregate order - there's no separate tab per guest, and payment is still settled per table (see FR-004).
  • Can staff delete a guest card submitted by mistake? No - the only removal action is reject, with a required reason; delete never applies to a guest-submitted order/card, per the rule in PRD-SLF-002 (URD-SLF-009).
  • What if the policy-based menu engine isn't ready when SLF is due to build? FR-002 hard-depends on BANA-902…907; if it slips, Design must sign off on a fallback (e.g. showing the whole menu unfiltered) before build - the filtering step is never silently skipped.

References

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.