Skip to content

PRD: Menu organizer & channel visibility

ModuleProduct (CORE-05)PRD IDPRD-SCH-001
StatusShippedOwnerProduct squad
Date2026-06-15Versionv1.0
Packages@nx/commerce · @nx/core · @nx/searchURDSCH · PRD

TL;DR

Lets a merchant organize what it sells into sale channels - named, multilingual, hierarchical menu surfaces (in-store, takeaway, delivery, a kiosk) - and control, per channel, which products appear. A product is assigned to one or more channels at creation; the assignment is the visibility switch. Each channel carries a priority-ordered inventory-location chain so the right stock source is auto-picked at fulfillment, has a lifecycle status (activated / deactivated / archived), and cannot be deactivated while it still has active orders. Every channel and product list is served filtered by the caller's policy grants - an owner sees only their merchants' channels, an admin sees all - so the menu a device receives is always scoped to who is asking.

1. Context & Problem

The catalogue (PRD-PRD-001) defines the products a merchant sells, but not where each product is offered. A merchant rarely sells the same list everywhere: the dine-in menu differs from delivery, a kiosk shows a subset, a seasonal pop-up needs its own surface. Without a channel concept, every device would see one flat catalogue and the merchant could not turn a product on for delivery while keeping it off the counter.

Two more gaps follow. First, a channel must know where its stock comes from - which inventory location fulfills its orders - and needs a sensible default when several are possible. Second, the menu a device receives must be scoped to who is asking: an owner of two merchants must see exactly those two merchants' channels, an employee only their assigned merchant, an admin everything - without each caller hand-filtering.

This increment closes those gaps. It introduces the sale channel as the menu surface, the product-to-channel assignment as the visibility control, a priority-ordered inventory-location chain per channel, a lifecycle status with a deactivation guard, and policy-scoped serving for every channel and product read.

2. Goals & Non-Goals

Goals

  • Let an owner create sale channels within a merchant - multilingual name/description, a system identifier, a per-merchant-unique slug.
  • Make product-to-channel assignment the visibility control: a product appears in a channel only when assigned to it.
  • Assign products to channels as part of the atomic product create, so a product ships visible on day one.
  • Give each channel a lifecycle status (activated / deactivated / archived) and refuse deactivation while active orders remain.
  • Carry a priority-ordered inventory-location chain per channel; the lowest-priority location is the auto-pick default.
  • Support a parent-child channel hierarchy for grouping menu surfaces.
  • Serve every channel and product list filtered by the caller's policy grants; admins bypass the filter.

Non-Goals

  • Product, variant, and identifier definition - specified in PRD-PRD-001.
  • Per-channel pricing tiers - handled by channel-context fares (FAR).
  • Stock levels and movement - owned by Inventory; a channel only references a location, it does not hold stock.
  • Walking the inventory chain as a multi-step fallback at fulfillment - only the default (lowest-priority) location is consumed today; the ordered chain is recorded for future use.
  • Order processing and checkout through a channel - owned by Orders.

3. Success Metrics

MetricTarget / signal
Visibility controlA product appears in a channel only when assigned to it; unassigning removes it
Scoped servingA caller's channel/product list returns only their merchants' rows; admins see all
Deactivation safetyNo channel with active orders can be deactivated
Stock-source clarityEvery channel resolves a single default inventory location with no ambiguity
Slug integrityNo two live channels in a merchant share a slug

4. Personas & Use Cases

PersonaGoal in this feature
Owner / ManagerBuild per-surface menus, turn products on/off per channel, set the channel's stock source
Cashier / DeviceReceive the menu scoped to the current merchant and channel
Admin / Super AdminInspect channels across all merchants without role filtering

Core scenario: an owner with a café creates a Delivery channel and a Counter channel. New products are assigned to Counter only; a subset is also assigned to Delivery, so the courier app shows a smaller menu. Delivery's inventory chain lists the back-store location first, so its orders auto-source from there. When the owner tries to deactivate Counter while a table still has an open order, the system refuses until the order is closed.

5. User Stories

  • As an owner, I create a sale channel per selling surface, so each device shows the right menu.
  • As an owner, I assign a product to the channels it should appear in, so visibility is a single switch.
  • As an owner, I set a channel's inventory-location chain, so its orders auto-source from the right place.
  • As an owner, I cannot accidentally deactivate a channel with live orders, so I never strand an open ticket.
  • As an employee, I only ever see channels and products for merchants I'm assigned to, so my menu is never another merchant's.
  • As an admin, I can read every merchant's channels without role filtering, so I can support any tenant.

6. Functional Requirements

#RequirementURD ref
FR-1Owner can create sale channels within a merchant, each with a multilingual name/description and an auto-generated system identifierURD-SCH-001
FR-2A channel slug is unique among live channels within the same merchantURD-SCH-002
FR-3A channel has a lifecycle status: activated, deactivated, archivedURD-SCH-003
FR-4Owner can update a channel; an empty update (no fields) is refusedURD-SCH-004
FR-5Deactivating a channel is refused while it still has active sale ordersURD-SCH-005
FR-6Channels support a parent-child hierarchyURD-SCH-006
FR-7A product is assigned to one or more channels; the assignment controls whether it appears in that channelURD-SCH-007 · URD-PRD-014
FR-8Product-to-channel assignments are written as part of the atomic product createURD-SCH-008
FR-9A channel carries a priority-ordered inventory-location chain; the lowest-priority location is the auto-pick default; the chain diff-syncs on updateURD-SCH-009
FR-10Deleting a channel unlinks its products and soft-deletes the channelURD-SCH-010
FR-11Channel and product lists/counts are served filtered by the caller's policy grants; admins bypass the filterURD-SCH-011 · URD-ACC-001..004
FR-12Channels and products are searchable, scoped to the caller's merchantsURD-SCH-012
FR-13Creating a merchant's first channel completes the merchant's channel onboarding stepURD-SCH-013

Full requirement text and acceptance criteria live in the Product URD - SCH. This PRD references them rather than restating them.

7. Non-Functional Requirements

AreaRequirement
AtomicityChannel create with its inventory chain is one transaction; product create writes its channel assignments in the same atomic step - partial failure rolls back fully
Tenancy & authzAll operations scoped per merchant (x-merchant-id) and gated by sale-channel permissions
Policy-scoped readsLists, counts, and search resolve the caller's merchant grants and filter to them; isAlwaysAllowed callers bypass
UniquenessChannel slug unique per merchant among live rows; an inventory location appears at most once per channel
Soft-deleteChannels and assignments are soft-deleted; nothing is physically removed
i18nChannel name and description are bilingual ({ en, vi })

8. UX & Flows

The channel surface lets the owner name a channel, set its slug, order its inventory-location chain (top = default source), and place it under a parent. The product surface lets the owner pick the channels a product appears in. Devices request the menu and receive only the channels and products their policy grants allow.

9. Data & Domain

EntityRole
SaleChannelA menu surface within a merchant - name, slug, status, optional parent
SaleChannelProductThe visibility link - a product appears in a channel only while this link is live
SaleChannelInventoryLocationOne location in the channel's priority-ordered stock-source chain (lowest priority = default)

Conceptual only - full schema and invariants live in the commerce domain model. Relations are soft references; integrity is enforced in the channel and product services, not by database foreign keys.

10. Dependencies & Assumptions

Depends on

  • Product catalogue (PRD-PRD-001, PRD) - products are what a channel makes visible.
  • Commerce / Merchant (Commerce) - channels are scoped to a merchant; the first channel completes a merchant onboarding step.
  • Inventory (Inventory) - the chain references inventory locations; a location must exist before it is added.
  • Orders (Orders) - the deactivation guard reads a channel's active orders.
  • @nx/search - channels and products are indexed and served scoped to the caller's merchants.

Assumptions

  • A merchant exists and the caller's policy grants resolve to the merchants they may see.
  • Inventory locations referenced by a channel's chain belong to the same merchant.

11. Risks & Open Questions

Risk / questionMitigation / status
A channel left without a stock sourceThe chain's lowest-priority location is always the default; duplicates in the input collapse to the first occurrence
Deactivating a channel mid-serviceRefused while active orders remain; owner must close or cancel them first
Cross-merchant menu leakageEvery list/count/search resolves the caller's grants and filters to them; only admins bypass
Slug collision within a merchantRefused before persistence; slug is unique per merchant among live channels
Inventory chain walked as a fallbackNot today - only the default location is consumed; the ordered chain is recorded for future fulfillment logic

12. Release Plan & Launch Criteria

AspectPlan
PhaseP2 - SCH in the URD feature catalog
RolloutAll merchants; no feature flag
MigrationNone - channels and assignments run on the existing commerce schema
Launch criteriaA product appears in a channel only when assigned; channel create resolves a default inventory location; deactivation with active orders is refused; every channel/product list is filtered to the caller's merchants; admins bypass
MonitoringDeactivation-refusal rate, channels without a default location, cross-merchant list-scope checks

13. FAQ

How do I hide a product from one channel but not another? Assign the product only to the channels it should appear in - the assignment is the visibility switch. Unassigning removes it from that channel without touching the others.

What is a channel's "default" inventory location? The first one in the channel's chain - the lowest priority value. Orders on that channel auto-source from it. You can reorder the chain on update; the rest is recorded for future fallback use.

Why can't I deactivate a channel? Because it still has active orders. Close or cancel them first, then deactivate.

Does an admin see every merchant's channels? Yes - admin and super-admin callers bypass role filtering; every other caller sees only the channels of merchants they are granted.

Can channels be nested? Yes - a channel can have a parent, so menu surfaces can be grouped.

References

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