URD: Device
| Module | Version | Updated |
|---|---|---|
CORE-04 | v0.5 | 2026-07-21 |
What the Device module does
Defines user-facing requirements for device management and hardware integration - how an organizer registers, manages, and monitors physical devices (POS terminals, mobile apps, web clients), and how the platform integrates peripherals (printers, barcode scanners, SoundBox, SoftPOS/NFC, external displays).
A device is not just a management record - it is the runtime anchor that a login session, a sales shift, a cash drawer, and a report all attach to (see the DSX chapter). That's why a device must be recognized correctly, and exactly once, per physical hardware unit - misrecognition spawns duplicate devices, loses shift history, and fragments reporting.
Four things to understand first
This is the most important part of the document. Understanding the four points below is understanding the core of the module.
1. One device, one durable identifier
Every device carries a Device Identifier (D_YYYYMMDD_<snowflakeId>) generated exactly once when the device is created, and unchanged across app uninstall/reinstall on the same hardware. This is the module's single most important invariant: if the identifier drifts on every reinstall, the same POS terminal gets misread as multiple different devices - a new record, a new lifecycle, a fragmented shift history each time.
The identifier therefore cannot live only in the app's local storage (which is wiped on uninstall or when a browser clears its data) - it must either be derivable from the hardware itself, or recoverable through a mechanism that doesn't depend on local storage (see point 3).
2. Login is check-first, create-second
A device does not automatically exist in the system just because someone installed the app on it. On every login, the app calls an API that asks: "does this identifier already belong to the current merchant?"
- Yes → the device is already known, everything works normally, nothing new is created.
- No → the system creates a new device record, in status
NEW(not yet activated).
This check runs on every client type (POS, mobile, browser), not just one platform.
3. Activation is a discrete action, not a hidden step
This is the point most likely to be misunderstood: creating a new device does not mean activating it. A NEW device becomes ACTIVATED through exactly one path: someone scans or enters that device's Activation Code and presses "Activate."
When a new device is created, the system auto-generates an Activation Code in two parallel forms - a QR code (scanned by camera) and a Barcode (scanned by a barcode scanner or typed by hand). There are two forms for two different reasons:
- Not every device has a camera to scan a QR code - the Barcode serves exactly that group (dedicated POS hardware, webcam-less machines).
- A browser sometimes cannot retain the identifier because of ITP (Intelligent Tracking Prevention) - a policy that blocks cookies or scripts tracking behavior across different websites, which in turn limits client-side storage duration - when that happens, the automatic check in point 2 isn't reliable, and code-based activation gives a path to completing activation that doesn't depend on browser storage.
Every Activation Code is single-use - reusing it is rejected, even on the same device.
4. Operational lifecycle and physical condition are two different axes
Operational Status (NEW → ACTIVATED → DEACTIVATED → SUSPENDED → ARCHIVED) answers "is this device allowed to transact." Physical Condition (Working / Under Maintenance / Faulty) answers a completely different question: "is this machine actually running well." A device can be ACTIVATED (authorized to sell) while Faulty (physically broken) - and that exact gap is why Device Condition exists as its own axis, separate from operational Status.
1. Definitions
| Term | Definition |
|---|---|
| Device | A registered hardware unit (POS terminal, mobile device, workstation) that interacts with the platform |
| Device Identifier | System-generated unique string D_YYYYMMDD_<snowflakeId>, assigned once at device creation and unchanged across reinstalls |
| Device Code | Optional human-readable code, unique within the merchant scope |
| Organizer | Top-level owner of one or more merchants and devices |
| Merchant | A business unit within an organizer; every device is required to be assigned to exactly one merchant |
| Hardware Info | Manufacturer, model, serial number, IMEI, MAC, processor, RAM, storage, screen, battery |
| Software Info | OS, OS version, app version, firmware, last update, peripheral drivers |
| Device Status | Operational lifecycle state: NEW → ACTIVATED → DEACTIVATED → SUSPENDED → ARCHIVED |
| Device Condition (new) | Physical condition: Working / Under Maintenance / Faulty - independent of Device Status |
| Activation Code (new) | A single-use code generated when a new device is created, represented as both a QR code and a Barcode, used to move the device from NEW to ACTIVATED |
| Heartbeat | Periodic online ping (every 5 min); offline after 15 min of silence |
| Machine UID | Hardware-derived identifier sent in the x-device-info HTTP header |
| ITP (Intelligent Tracking Prevention) | Browser policy (default on Safari) that blocks cookies or scripts tracking behavior across different websites; the side effect is a limited client-side storage duration, so an identifier stored in the browser (IndexedDB - see WEB) may not persist reliably across sessions |
| ESC/POS | Thermal-printer command language |
| HID Keyboard-Wedge | USB/Bluetooth scanner mode that emulates keyboard input |
| SoftPOS | NFC contactless card acceptance on an Android phone, activated via VNPAY KYC |
| SoundBox | VNPAY Bluetooth speaker announcing payment confirmations |
| POS Point | Logical sales station a device must be assigned to before processing sales |
2. Conceptual Model
Conceptual only - full schema lives in the Commerce domain model.
3. Features
DEV - Device Registration, Identity & Activation Partial
Feature ID: device/DEV · Phase: P2 · PRDs: PRD-DEV-001 · Dev: @nx/commerce
What it does for users: owners register each piece of hardware with an i18n name, type, and required merchant; every device carries a durable identifier that survives reinstalls; logging in on a new device auto-creates a pending record; activation completes by scanning/entering a single-use code; the device moves through a status lifecycle; and owners record hardware, software, and maintenance details, search, and import/export their fleet.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-DEV-001 | Must | Owner can register a device with a name, type, and required merchant assignment | Built |
URD-DEV-002 | Must | System auto-generates a unique Device Identifier D_YYYYMMDD_<snowflakeId> when a device is created | Built |
URD-DEV-003 | Must | Device name supports i18n (English + Vietnamese) | Built |
URD-DEV-004 | Must | Operational Status takes 5 ordered values: NEW → ACTIVATED → DEACTIVATED → SUSPENDED → ARCHIVED. • Valid-transition validation is not enforced - can be set to any value regardless of the current one. | Partial |
URD-DEV-005 | Must | Owner can activate a registered device (NEW → ACTIVATED) via its Activation Code (see URD-DEV-027/028) | Partial |
URD-DEV-006 | Must | Owner can deactivate (ACTIVATED → DEACTIVATED) and reactivate (DEACTIVATED → ACTIVATED) a device | Built |
URD-DEV-007 | Must | Owner can suspend a device (ACTIVATED → SUSPENDED) for a security concern | Built |
URD-DEV-008 | Must | Owner can archive a device (permanently read-only, hidden from POS), picking a Retirement Reason (see URD-DEV-023) | Partial |
URD-DEV-009 | Must | All device records use soft-delete; records are never physically removed | Built |
URD-DEV-010 | Must | Device API access requires authentication (JWT or Basic Auth) | Built |
URD-DEV-011 | Must | Device code, when provided, must be unique within the merchant scope | Built |
URD-DEV-012 | Should | Owner can record maintenance info (purchase date, warranty expiry, vendor, last/next maintenance dates, notes) | Built |
URD-DEV-013 | Must | On app login, the device is automatically checked and registered if it doesn't already belong to the current merchant (see URD-DEV-026); an existing device is reused as-is, with no new record created | Partial |
URD-DEV-014 | Must | Owner can search devices by name, identifier, code, status, and type | Built |
URD-DEV-015 | Should | Owner can import and export device records via CSV | Built |
URD-DEV-016 | Must | Owner can assign / reassign a device to a different merchant; every device always belongs to exactly one merchant | Built |
URD-DEV-017 | Should | Owner can record hardware information for a device | Built |
URD-DEV-018 | Should | Owner can record software information for a device | Built |
URD-DEV-019 | Should | Owner can set a PIN on a device for local authentication | Partial |
URD-DEV-020 | Should | The fleet list flags a device as due/overdue for maintenance once today's date passes its recorded next-maintenance date, with a reminder notification as that date approaches | Not built |
URD-DEV-021 | Must | A device carries a Condition attribute (Working / Under Maintenance / Faulty), independent of operational Status; marking it Faulty blocks the device from opening new POS sessions or processing sales until cleared back to Working | Not built |
URD-DEV-022 | Should | Marking a device Faulty publishes a real-time notification event to the merchant's designated device/maintenance contact | Not built |
URD-DEV-023 | Must | Archiving a device requires picking a Retirement Reason (Liquidated / Lost / Stolen / Replaced / Other); filterable/reportable by this reason | Not built |
URD-DEV-024 | Must | A Cash Drawer registers as its own device type, going through the same create/edit/lifecycle/search flow as any other device; offered as a pick-list when opening a shift on a cash-drawer-enabled POS terminal | Built |
URD-DEV-025 | Must | The Device Identifier must be durable across the install lifecycle of the same physical hardware - derivable or recoverable from the hardware itself, not living only in the app's local storage. • Uninstalling and reinstalling the app on the same machine must recover the exact same identifier, never generate a new one. | Not built |
URD-DEV-026 | Must | On login, the app calls an API checking whether the device (by Device Identifier) already belongs to the current merchant. • If it does - the device operates normally, no new record is created. • If it doesn't - the system creates a new device record in status NEW for that merchant; this new device isn't authorized to transact until activated (URD-DEV-028). | Not built |
URD-DEV-027 | Must | When a new device is created (per URD-DEV-026), the system auto-generates a single unique Activation Code, represented in two parallel forms: a QR code (scanned by camera) and a Barcode (scanned by a barcode scanner or typed by hand). • The Barcode form exists alongside the QR to serve devices without a camera / QR-scan capability. • The mechanism does not depend on the browser retaining the identifier, so it still works when ITP policy limits client-side storage. | Not built |
URD-DEV-028 | Must | Activating a NEW device completes by scanning or entering its Activation Code (Barcode/QR) and pressing "Activate"; this moves the device from NEW to ACTIVATED. • Every Activation Code is usable exactly once - reusing it (even on the same device) is rejected by the system. | Not built |
Device Operational Statuses
| Status | Meaning |
|---|---|
| NEW | Registered (auto or manual), not yet activated (default) |
| ACTIVATED | Active and authorized to process transactions |
| DEACTIVATED | Temporarily disabled; reactivatable |
| SUSPENDED | Suspended pending investigation; cannot transact |
| ARCHIVED | Permanently retired; read-only; requires a Retirement Reason |
Device Physical Condition (new, conceptual)
| Condition | Meaning |
|---|---|
| Working | Operating normally |
| Under Maintenance | Being serviced by a technician on schedule |
| Faulty | Unscheduled breakdown; blocks new POS sessions/sales even while operational Status remains ACTIVATED |
Device Types
| Type Code | Label |
|---|---|
| 100_POS_TERMINAL | POS Terminal |
| 101_POS_WORKSTATION | POS Workstation |
| 200_MOBILE_POS | Mobile POS |
| 300_BROWSER | Web Browser |
| 400_TABLET | Tablet |
| 401_BARCODE_SCANNER | Barcode Scanner Device |
| 500_CASH_DRAWER | Cash Drawer |
| 999_OTHER | Other |
AC-DEV-01: Manual registration
| Given | When | Then |
|---|---|---|
| Owner | Registers a device with name + type + merchant | Device created with status NEW; unique identifier auto-generated |
| Code provided | Code is unique within the merchant scope |
AC-DEV-02: Durable identity across reinstall
| Given | When | Then |
|---|---|---|
| A device already activated on a POS terminal | App is uninstalled and reinstalled on the same machine | Logging in again recovers the same Device Identifier; no new identifier is generated |
AC-DEV-03: Check device ownership on login
| Given | When | Then |
|---|---|---|
| Device identifier already belongs to the current merchant | Login | System recognizes the known device; no new record is created; operates normally |
| Device identifier belongs to no merchant yet (new hardware, or merchant change) | Login | System creates a new device in status NEW for the current merchant |
AC-DEV-04: Generating an Activation Code for a new device
| Given | When | Then |
|---|---|---|
| A new device was just created (per AC-DEV-03) | System initializes the record | A single unique Activation Code is generated, shown in both QR and Barcode form |
AC-DEV-05: Single-use code activation
| Given | When | Then |
|---|---|---|
| A NEW device carries a still-valid Activation Code | Scan/enter the correct code and press "Activate" | Device moves NEW → ACTIVATED; the code is voided immediately after |
| The Activation Code was already used | Scan/enter the same code again, press "Activate" | Rejected - code already used, even from the same device |
MOB - Mobile App Built
Feature ID: device/MOB · Phase: P1 · PRDs: - · Dev: sale-renderer
What it does for users: owners and cashiers run the POS, orders, payments, invoicing, and reporting on an iOS/Android app built with Tauri, with Bluetooth peripherals and offline capture that syncs on reconnect.
iOS/Android app built with Tauri; primary app for owners and cashiers.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-MOB-001 | Must | App is available on iOS 14+ and Android 8+ | Built |
URD-MOB-002 | Must | Owner can sign in and access POS, orders, payments, invoicing, and reporting | Built |
URD-MOB-003 | Must | Cashier can sign in and access the POS screen (owner-only screens restricted) | Built |
URD-MOB-004 | Should | App supports Bluetooth peripheral connectivity (scanner, printer, SoundBox) | Built |
URD-MOB-005 | Should | App supports offline mode: transactions captured locally and synced on reconnect | Built |
URD-MOB-006 | Must | Login creates a device session linked to the device record, through DEV's check-then-create flow | Built |
AC-MOB-01: Mobile sign-in & session
| Given | When | Then |
|---|---|---|
| Owner or cashier on iOS 14+ / Android 8+ | Signs in to the app | A device session is created and linked to the device record (check-then-create per URD-DEV-026 if new) |
WEB - Web Application Built
Feature ID: device/WEB · Phase: P1 · PRDs: - · Dev: client
What it does for users: owners run full back-office management - devices, products, orders, reports, settings - and a basic Web POS in modern browsers, over HTTPS with session expiry.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-WEB-001 | Must | Web app runs on modern browsers (Chrome, Edge, Firefox, Safari) | Built |
URD-WEB-002 | Must | Web app provides full back-office management (devices, products, orders, reports, settings) | Built |
URD-WEB-003 | Should | Web app supports a basic Web POS mode | Built |
URD-WEB-004 | Must | Web app enforces HTTPS and session expiry/logout | Built |
URD-WEB-005 | Should | Web app is usable at 1024px+ width; warns below the minimum | Built |
URD-WEB-006 | Must | The web app stores the device identity in the browser's IndexedDB, not localStorage. • When the device identity can't be recovered from IndexedDB (ITP policy), the web screen surfaces an Activation Code (URD-DEV-027) as the alternative activation path. | Not built |
AC-WEB-01: Back-office access
| Given | When | Then |
|---|---|---|
| Owner on a modern browser | Signs in over HTTPS | Full back-office management is available; session expires/logs out per policy |
AC-WEB-02: ITP limits storage, fallback code activation
| Given | When | Then |
|---|---|---|
| Safari with ITP enabled, unable to retain the prior identifier across sessions (even from IndexedDB) | Logs in on that browser | System creates a new device and displays an Activation Code; activation completes by scan/entry, independent of IndexedDB |
POS - POS Terminal Partial
Feature ID: device/POS · Phase: P2 · PRDs: - · Dev: sale-renderer
What it does for users: dedicated Android hardware runs the POS app full-screen with integrated peripherals; a device must be assigned to a POS Point and holds one active session before it can process sales.
Dedicated Android hardware running the POS app full-screen with integrated peripherals.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-POS-001 | Must | POS app runs on Android 8+ with ≥2 GB RAM and ≥16 GB storage | Built |
URD-POS-002 | Must | A device must be assigned to a POS Point before it can process sales; one active session per device | Built |
URD-POS-003 | Must | POS app supports built-in peripherals (card reader, printer, scanner, cash drawer) where present | Partial |
URD-POS-004 | Should | POS app runs on certified hardware (VNPAY V-POS, Sunmi T2) | Built |
URD-POS-005 | Must | POS app supports peripheral setup via Settings > Devices | Built |
AC-POS-01: POS Point assignment
| Given | When | Then |
|---|---|---|
| Activated device on Android 8+ | Assigned to a POS Point | Device can process sales with one active session at a time |
PRN - Printer Partial
Feature ID: device/PRN · Phase: P2 · PRDs: - · Dev: sale-main
What it does for users: the system prints ESC/POS receipts over USB and network thermal printers - text, images, QR, and barcodes - with Vietnamese code page 28, 58/80mm paper, auto-cut, and cash-drawer trigger.
USB (Tauri/WebUSB) and network (WebSocket) thermal printers; ESC/POS with Vietnamese code page 28.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-PRN-001 | Must | System connects to printers over Bluetooth, USB, and network | Built |
URD-PRN-002 | Must | System detects USB printers by USB class code 0x07 | Built |
URD-PRN-003 | Must | ESC/POS engine prints text, embedded image, QR code, and barcode | Built |
URD-PRN-004 | Must | ESC/POS markup supports alignment, bold, underline, font size, barcode, QR, image | Built |
URD-PRN-005 | Must | System rasterizes PDF content with Floyd-Steinberg dithering for thermal output | Built |
URD-PRN-006 | Must | Vietnamese characters print correctly via code page 28 | Built |
URD-PRN-007 | Must | System supports 58mm and 80mm paper widths, configurable per printer | Built |
URD-PRN-008 | Should | System supports auto-cut and a cash drawer triggered on print | Partial |
URD-PRN-009 | Should | Cash drawer trigger command is sent via the printer on cash payment | Partial |
URD-PRN-010 | Must | Print lifecycle (connect, disconnect, send job) surfaces a clear error on failure; no silent drop | Built |
URD-PRN-011 | Should | System connects to network printers via WebSocket and handles disconnection gracefully | Built |
URD-PRN-012 | Must | System falls back to WebUSB when the Tauri USB plugin is unavailable | Built |
AC-PRN-01: Receipt Printing
| Given | When | Then |
|---|---|---|
| Paired printer | Transaction completed | Receipt printed (auto or manual) |
| Print fails | Error shown with retry option | |
| Vietnamese content | Code page 28 set automatically |
SCN - Barcode Scanner Partial
Feature ID: device/SCN · Phase: P2 · PRDs: - · Dev: sale-main
What it does for users: HID keyboard-wedge and mobile camera/QR scanning resolve against Product Identifiers - a successful scan on the POS screen adds the matched product to the cart. The same scanning hardware is also used to enter a device's Activation Code (see DEV). On desktop, users get a keyboard shortcut to actively switch between scan mode (listening for barcode-scanner input) and manual-entry mode, avoiding conflicts between the two input sources on the same field.
HID keyboard-wedge (USB/Bluetooth) and mobile camera/QR scanning resolved against Product Identifiers.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-SCN-001 | Must | System captures HID keyboard-wedge scanner input automatically (no pairing UI) | Built |
URD-SCN-002 | Must | Scanner input must be 10-20 characters, completed within a 100ms keystroke timeout | Built |
URD-SCN-003 | Must | Enter key (or 100ms pause) triggers the scan and lookup | Built |
URD-SCN-004 | Must | Camera QR scanner is available on mobile via the Tauri barcode plugin | Built |
URD-SCN-005 | Must | On a successful scan in the POS screen, the matched product is added to the cart | Built |
URD-SCN-006 | Should | System supports 1D (EAN-13/8, UPC-A/E, Code 128/39) and 2D (QR, Data Matrix) formats | Built |
URD-SCN-007 | Should | Scanning works across multiple contexts (product search, invoice lookup, entering a device Activation Code) | Partial |
URD-SCN-008 | Should | On desktop, users have a keyboard shortcut to quickly switch between scan mode (listening for HID barcode-scanner input) and manual-entry mode (typing directly into the search/code field), avoiding conflicts between the two input sources on the same field | Not built |
AC-SCN-01: Barcode Scanning
| Given | When | Then |
|---|---|---|
| HID scanner | Scan a barcode | Product looked up and added to cart |
| Camera scanner (mobile) | Scan a QR | Product/invoice looked up |
| Unknown barcode | Scan | Clear error message shown |
AC-SCN-02: Desktop scan/manual-entry mode shortcut
| Given | When | Then |
|---|---|---|
| Desktop screen is in scan mode | User presses the mode-switch shortcut | UI switches to manual-entry mode; the input field receives focus |
| Desktop screen is in manual-entry mode | User presses the mode-switch shortcut | UI switches back to scan mode; HID scanner input is listened to again |
SBX - SoundBox Built
Feature ID: device/SBX · Phase: P1 · PRDs: - · Dev: sale-renderer
What it does for users: a VNPAY Bluetooth speaker announces payment confirmations in Vietnamese/English - amount and method - per device, with configurable volume, LED status, and graceful disconnection.
VNPAY Bluetooth speaker announcing payment confirmations in Vietnamese/English, paired per device.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-SBX-001 | Must | System pairs a VNPAY SoundBox (V1/V2) to a device via Bluetooth | Built |
URD-SBX-002 | Must | On payment confirmation, system triggers a voice announcement; if disconnected, payment still succeeds (no blocking error) | Built |
URD-SBX-003 | Must | Announcement language follows configuration (Vietnamese / English), including amount and method | Built |
URD-SBX-004 | Should | SoundBox LED states reflect status: blue blinking (pairing), blue solid (connected), green (success), red (error/low battery) | Built |
URD-SBX-005 | Should | User can configure volume (1-10) and toggle whether amount / method are announced | Built |
URD-SBX-006 | Should | SoundBox operates within its rated Bluetooth range (~10 m); beyond range, disconnection is handled gracefully | Built |
AC-SBX-01: SoundBox Confirmation
| Given | When | Then |
|---|---|---|
| Paired SoundBox | Payment confirmed | Amount + method announced in configured language |
| SoundBox disconnected | Payment confirmed | Payment still succeeds; announcement silently skipped |
NFC - SoftPOS / NFC Planned
Feature ID: device/NFC · Phase: P3 · PRDs: - · Dev: sale-main
What it does for users: Android phones accept contactless card payments over NFC via VNPAY after KYC activation; the option is hidden on iOS and blocked when NFC is unavailable.
NFC contactless acceptance on Android via VNPAY; requires KYC.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-NFC-001 | Must | System checks NFC hardware availability before enabling SoftPOS | Not built |
URD-NFC-002 | Must | SoftPOS requires VNPAY KYC activation before first use | Not built |
URD-NFC-003 | Must | SoftPOS activation is blocked when NFC is unavailable or disabled, with a clear message | Not built |
URD-NFC-004 | Should | SoftPOS is Android-only; the option is hidden on iOS | Not built |
AC-NFC-01: SoftPOS activation
| Given | When | Then |
|---|---|---|
| Android device with NFC available, KYC completed | SoftPOS is activated | Contactless card acceptance is enabled |
| NFC unavailable or disabled | Activation attempted | Activation is blocked with a clear message |
MON - Device Health Monitoring & Notifications Partial
Feature ID: device/MON · Phase: P2 · PRDs: PRD-MON-001 · Dev: @nx/commerce · @nx/signal
What it does for users: administrators see each device's online/offline status, last-seen time, and app version via a 5-minute heartbeat, can remotely deactivate or wipe a compromised device, and receive a real-time notification when a device is marked Faulty.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-MON-001 | Must | Active device sends a heartbeat every 5 minutes; platform records last activity | Not built |
URD-MON-002 | Must | Platform marks a device offline after 15 minutes without a heartbeat; back online on next ping | Not built |
URD-MON-003 | Must | Administrators can view device health (online/offline, last seen, app version) and filter by status; the real-time notification backbone (ingest → worker → persisted record → live push) is built and awaiting a producer | Partial |
URD-MON-004 | Must | System can remotely deactivate a device and revoke its active session | Not built |
URD-MON-005 | Should | System supports remote data wipe for a compromised device (queued if offline) | Not built |
AC-MON-01: Health Monitoring
| Given | When | Then |
|---|---|---|
| Active device | Sends a heartbeat | Last-activity timestamp updated |
| No heartbeat for 15 min | Device marked offline | |
| Admin views device list | Online/offline status visible |
DSX - Device Shift & Printer Binding Built
Feature ID: device/DSX · Phase: P2 · PRDs: PRD-DSX-001 · Dev: @nx/sale · @nx/commerce
What it does for users: the registered device becomes the runtime anchor of a sales station - the device identity resolves every shift action to a device and derives its merchant, a single shift is opened or joined per sale channel, cash-drawer-enabled devices get a per-device drawer, the current context reports device participation, X/Z reports key on the device, and each station binds to its own printer.
| ID | Priority | Requirement | Status |
|---|---|---|---|
URD-DSX-001 | Must | Every device-scoped shift action resolves its device from the x-device-info header; the merchant is derived from that device record, never supplied by the client | Built |
URD-DSX-002 | Must | A missing x-device-info header on a device-scoped action is rejected with a clear error; back-office reads (e.g. shift list) may run without a device using the active-merchant scope | Built |
URD-DSX-003 | Must | The device must exist, be assigned to a merchant, and (when a merchant is also supplied) match it; otherwise the action is rejected | Built |
URD-DSX-004 | Must | Tenant access is enforced on the resolved merchant for every shift action | Built |
URD-DSX-005 | Must | Opening a shift opens or joins the single open shift for the device's sale channel; a concurrent open by another device joins the same shift rather than creating a duplicate | Built |
URD-DSX-006 | Must | A sale channel supplied on open must belong to the resolved merchant | Built |
URD-DSX-007 | Must | The opening/joining device is enrolled in the shift, and the opening employee is checked in alongside it | Built |
URD-DSX-008 | Should | A per-device cash drawer is created on open only for cash-drawer-enabled devices; other devices join without a drawer | Built |
URD-DSX-009 | Must | The current-context endpoint returns the device's shift, its enrollment, optional drawer, expected cash, and device participation (active count, joined count, pending devices) | Built |
URD-DSX-010 | Must | Expected cash is omitted from the context when the merchant's blind-count policy is enabled or there is no drawer | Built |
URD-DSX-011 | Must | X (mid-shift) and Z (end-of-shift) reports can be generated per device and per shift, keyed on the resolved device | Built |
URD-DSX-012 | Must | A device is registered against a merchant; the device record is the merchant-scoped anchor every shift, drawer, and report binds to | Built |
URD-DSX-013 | Must | Device API access is merchant-scoped and authenticated (JWT or Basic Auth); device search is limited to the merchants the signed-in user has joined | Built |
URD-DSX-014 | Must | A device declares whether it drives a cash drawer; the flag governs drawer creation at shift open | Built |
URD-DSX-015 | Should | A print destination is bound per kitchen station (printer, copy count, auto-print); print jobs route to the bound printer | Partial |
AC-DSX-01: Device-scoped shift resolution
| Given | When | Then |
|---|---|---|
Registered device, x-device-info header present | A shift action is called | The device is resolved, the merchant is derived from it, and tenant access is enforced |
| Header missing on a device-scoped action | The action is called | Rejected with a clear error |
| Device assigned to another merchant | The action is called | Rejected - merchant mismatch / forbidden |
AC-DSX-02: Open or join one shift per channel
| Given | When | Then |
|---|---|---|
| No open shift for the channel | A device opens a shift | A new open shift is created and the device is enrolled |
| An open shift exists for the channel | Another device opens | It joins the same shift; no duplicate is created |
| Two devices open the same channel concurrently | Both open | They converge on one shift |
AC-DSX-03: Drawer & participation
| Given | When | Then |
|---|---|---|
| Cash-drawer-enabled device | Opens a shift | A per-device drawer is created with the opening float |
| Drawer-less device | Opens / joins a shift | It joins without a drawer |
| Merchant blind-count enabled | Current context is read | Expected cash is omitted; participation still returned |
AC-DSX-04: Printer binding
| Given | When | Then |
|---|---|---|
| A kitchen station bound to a printer (printer, copies, auto-print) | A print job is produced for that station | The job routes to the bound printer with the configured copies |
4. Constraints
| ID | Constraint |
|---|---|
C-01 | Every device must be registered (auto or manual) before it can operate |
C-02 | A device must be assigned to a POS Point before processing sales |
C-03 | Only one active session is permitted per device at a time |
C-04 | The Device Identifier is auto-generated once, immutable, and durable across reinstalls on the same hardware |
C-05 | Device codes must be unique within the merchant scope |
C-06 | All device records use soft-delete |
C-07 | Status transitions should follow the defined lifecycle; invalid transitions should be rejected (validation is not enforced yet - URD-DEV-004) |
C-08 | SoftPOS is Android-only and requires VNPAY KYC |
C-09 | Each POS should have exactly one default printer |
C-10 | Maximum devices per organizer is configurable by the operator |
C-11 | Heartbeat is every 5 min; offline threshold is 15 min |
C-12 | HID scanner input must be 10-20 chars within a 100ms timeout |
C-13 | USB printer code page 28 (Vietnamese) is set automatically on connection |
C-14 | Marking a device Faulty while operational Status remains ACTIVATED only blocks new POS sessions/sales - it does not itself change the underlying operational Status - proposed rule, pending Design/Engineering confirmation of the exact enforcement point |
C-15 | Each Activation Code (Barcode/QR) is usable exactly once; reuse is always rejected regardless of which device calls it |
5. Non-Goals
Device deliberately does not do the following.
| ID | Not Done |
|---|---|
N-01 | VNPAY Terminal backend API wiring (table exists, no backend connected - QE finding) |
N-02 | Label printer integration; Windows POS terminal support |
N-03 | Kitchen display, waiter/service, delivery, and inventory specialized apps |
N-04 | Physically driving the cash drawer (kick-out signal); advanced peripheral driver-management UI |
N-05 | Technical API specifications and payment-gateway integration details |
N-06 | A full maintenance-ticket / technician-dispatch workflow (assignment, SLA, repair-progress tracking) - routes through the Helpdesk module, independent of this URD |
Version History
| Date | Author | Description | Ver |
|---|---|---|---|
| 2026-02-28 | Q. Do - QE | Initial URD from code-level and documentation analysis | v0.1 |
| 2026-06-04 | Claude (AI pair) | Reorganize by feature (Feature Spine) | v0.2 |
| 2026-06-15 | Sale team | Add DSX - Device Shift & Printer Binding (Built) | v0.3 |
| 2026-07-17 | Claude (AI pair) | Rewrote to the dense DocTabs format (Inventory standard). Added URD-DEV-020..024 (already referenced by PRD-DEV-001 but missing from the URD): maintenance due-date, Device Condition, fault notification, Retirement Reason, Cash Drawer. Added new URD-DEV-025..028 and URD-WEB-006: durable device identity across reinstalls, the check-device-belongs-to-merchant flow on login, auto-generating an Activation Code (Barcode + QR) on new-device creation, and single-use code-based activation - clarifying the business logic per PO request, replacing the previously vague "auto-activates" description. Added the "Four things to understand first" mental-model chapter. Added constraints C-14, C-15. | v0.4 |
| 2026-07-21 | Claude (AI pair) | Clarified the ITP definition (blocks cross-site tracking cookies/scripts, not just "blocks storage") - updated point 3, added the ITP term to the Definitions table, and made the consequence wording (limits client-side storage) consistent throughout. Switched the web client's device-identity storage from localStorage to IndexedDB (URD-WEB-006, AC-WEB-02). Added URD-SCN-008 - a desktop keyboard shortcut to switch between scan/manual-entry mode for the barcode scanner, plus AC-SCN-02 | v0.5 |