|
|
# 09 — Pages, Menus & Flows (UX Blueprint)
|
|
|
|
|
|
This is the screen-level blueprint for all four surfaces — POS Counter (Electron), Back Office (web), Owner App (React Native), and Customer Display — plus the cross-cutting systems they share. It applies the locked decisions everywhere: local-first POS, keyboard-first flows, config-driven content (labels, menus, thresholds, keymaps, and messages are DB rows per 07-DB-AND-CONFIG — nothing below implies a frontend release to change), immutable documents, and edition gating via `plan_feature`. Keystroke convention: a scan = 0 keystrokes (wedge scanner sends code + Enter); every key press = 1; lane budget 40 items/min means any per-item UI interaction beyond zero keystrokes in the common path is a defect.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 1. POS Counter
|
|
|
|
|
|
### 1.1 Billing screen — zones
|
|
|
|
|
|
```
|
|
|
┌──────────────────────────────────────────────────────────────────────────────┐
|
|
|
│ HEADER SiMS · MegaMart T.Nagar · Counter 2 · Ramesh (Cashier) · Shift #14 │
|
|
|
│ Bill: (assigned on close) · Token 07 Tue 08 Jul 2026 6:42 PM │
|
|
|
├──────────────────────────────────────────────┬───────────────────────────────┤
|
|
|
│ ┌──────────────────────────────────────────┐ │ CUSTOMER STRIP (F6) │
|
|
|
│ │ SCAN / SEARCH ▌ (48px, always focused) │ │ 98400 12345 · Lakshmi │
|
|
|
│ └──────────────────────────────────────────┘ │ Khata ₹1,240 dr · 320 pts │
|
|
|
│ LINES GRID (virtualized, 36px rows) ├───────────────────────────────┤
|
|
|
│ # Item Qty MRP Rate Amt │ TOTALS Items 12 · Qty 14.750 │
|
|
|
│ 1 Aashirvaad Atta 5kg 1 285 270 270 │ Gross 1,412.00 · Disc −42.00 │
|
|
|
│ 2 Tomato (loose) 1.250 — 32/kg 40 │ GST (breakup ▾) +61.20 │
|
|
|
│ ▶ last-added row highlighted │ ┌───────────────────────────┐ │
|
|
|
│ │ │ PAYABLE ₹1,431 (64pt) │ │
|
|
|
│ │ └───────────────────────────┘ │
|
|
|
│ QUICK-KEYS ROW (Ctrl+1…Ctrl+0, touch tiles) │ You saved ₹57 vs MRP │
|
|
|
│ [Tomato][Onion][Potato][Bag ₹5] … │ [F12 Cash] [F11 Pay] [F7 Hold]│
|
|
|
├──────────────────────────────────────────────┴───────────────────────────────┤
|
|
|
│ STATUS BAR ● Synced 2m ago · Printer OK · Scale 0.000kg · Held: 2 · │
|
|
|
│ Drawer ₹8,140 · Keymap: Classic · v2.3.1 │
|
|
|
└──────────────────────────────────────────────────────────────────────────────┘
|
|
|
```
|
|
|
|
|
|
| Zone | Notes |
|
|
|
|---|---|
|
|
|
| **Scan/search box** | The sacred line. Focus always returns here after any action, overlay, or error — enforced by a focus sentinel; no dialog, toast, or sync event may steal it. |
|
|
|
| **Lines grid** (~62%) | Columns: #, name (+ regional secondary name, HSN small, batch/expiry chip when tracked), qty, unit, MRP, rate, disc, GST%, amount. Last-added row highlighted, auto-scrolled; ↑/↓ selects for edits. Repeat scans merge into one line (default ON; auto OFF for batch-tracked items). |
|
|
|
| **Totals panel** (~38%) | Mirrors the customer display so both parties read the same numbers. PAYABLE huge (cashier speaks it aloud); GST breakup collapsible — compliance lives on the print. "You saved ₹X vs MRP" is the trust line. |
|
|
|
| **Customer strip** | Optional per bill: phone, name, khata balance chip, loyalty points. Sits by totals because khata/loyalty affect payment. F6 focuses. |
|
|
|
| **Quick-keys row** | 10 configurable tiles (produce PLUs, carry bag), Ctrl+1…Ctrl+0 and touch. Per-counter DB config; hidden if unconfigured (typical Lite). |
|
|
|
| **Status bar** | Sync dot, printer, live scale weight, held-bills badge, drawer cash (role-visible), keymap preset, version. Everything informs, nothing interrupts. |
|
|
|
| **Header** | Store, counter, cashier, shift, clock. Bill number reads "(assigned on close)" — the per-counter GST series number is allocated only at payment commit, so held/abandoned carts never create series gaps. Held carts carry a Token number instead. |
|
|
|
|
|
|
Touch: totals buttons and quick-key tiles ≥ 48 px; touch never adds steps to the keyboard flow.
|
|
|
|
|
|
### 1.2 The smart scan box — four grammars, no modes
|
|
|
|
|
|
| Input | Behavior |
|
|
|
|---|---|
|
|
|
| Digits + Enter (or scan) | Barcode/item-code lookup → line added. Weighing-scale EAN-13 (`20`–`29` prefix) parsed per counter profile (PLU digits + embedded weight/price, configurable divisor). |
|
|
|
| Letters | Live fuzzy/phonetic search < 150 ms (see §5.1); ↑/↓ + Enter picks; Esc clears. |
|
|
|
| `n*` prefix then scan/code | Quantity multiplier: `3*` + scan = qty 3 on one line. |
|
|
|
| Enter on empty box | No-op (double-Enter guard). |
|
|
|
|
|
|
### 1.3 Keymap — "Next" preset (DB profile; per preset / counter / user override)
|
|
|
|
|
|
| Key | Action | Gate |
|
|
|
|---|---|---|
|
|
|
| Enter / Esc | Confirm · Cancel/back one level (Esc never destroys lines) | — |
|
|
|
| ↑ ↓ PgUp PgDn | Line/list navigation | — |
|
|
|
| `*` | Qty-multiplier prefix | — |
|
|
|
| F1 | Keymap cheat-sheet overlay (printable card) | — |
|
|
|
| F2 / F3 | Edit qty / price override on selected line | F3: supervisor beyond ±X% band |
|
|
|
| F4 / F5 | Line / bill discount (`5` = ₹, `5%` = percent) | Supervisor above X% |
|
|
|
| F6 | Customer strip: attach by phone | — |
|
|
|
| F7 / F8 | Hold bill / resume tray | — |
|
|
|
| F9 | Returns/exchange mode | Setting: open vs supervisor |
|
|
|
| F10 | Bill history & reprint (day-scoped) | — |
|
|
|
| F11 | Payment overlay (all tenders, split) | — |
|
|
|
| **F12 / Numpad +** | **Cash close — the one key** | — |
|
|
|
| Del / Ctrl+Del | Remove line (inline confirm) / cancel cart (reason) | Supervisor per setting |
|
|
|
| Ctrl+P | Reprint last bill | Copies > N → supervisor |
|
|
|
| Ctrl+F / Ctrl+M | Open drawer no-sale / cash in-out | Supervisor (drawer always audit-logged; paid-out above ₹X) |
|
|
|
| Ctrl+1…Ctrl+0 | Quick-key tiles | — |
|
|
|
| Ctrl+L / Alt+S / Ctrl+, | Lock counter · salesman tag (Standard+) · settings | Settings: supervisor/manager |
|
|
|
|
|
|
Numpad-only right hand is deliberate: scanner in left hand, right hand never leaves the numpad (digits, `*`, `+` = cash close, Enter). A second shipped preset, **Classic keys**, mirrors SiMS Classic's Oracle Forms bindings key-for-key — migrating cashiers keep 100% muscle memory on day one. Rebinds are per-action with conflict detection; changes sync as counter-scope settings.
|
|
|
|
|
|
### 1.4 Billing flows (keystrokes excl. scans and free-text typing)
|
|
|
|
|
|
**Normal cash sale (the 90% path):** scan items (0) → state total → **F12**: series number assigned, bill committed (immutable document + stock movements + outbox row in one SQLite transaction), drawer kicks, print spools async, customer display thanks, scan box refocused. **1 keystroke.** With change: type tendered digits first, then F12 → change flashes 4 s on both displays. **5 keystrokes.**
|
|
|
|
|
|
**Weighed produce:** (A) integrated scale, Pro — quick-key tile + Enter on live stable weight = **2**; (B) manual weight, all editions day 1 — tile + `.750` Enter ≈ **6**; (C) label-scale barcode (`2x` EAN-13) — **0**, the correct answer for high-volume supermarket produce.
|
|
|
|
|
|
**Edits:** re-scan merges qty (0) · `3*`+scan (2) · F2 qty (3) · F3 price (5, +PIN out of band) · F4/F5 discount (4–5, +PIN over threshold) · Del remove (2–3). All overrides write before/after values to the append-only audit log with cashier + approver identity.
|
|
|
|
|
|
**Unknown barcode (errors never trap):** no match → non-blocking inline panel under the scan box: "Unknown code… [Enter] Quick-add · [Esc] Skip" — further scans queue behind it. Enter → 3-field micro-form (Name, Price/MRP, GST picklist defaulting to store slab) → item saved as `draft`, line added, flagged for back-office completion. **3 keystrokes + typing**; skip = 1.
|
|
|
|
|
|
**Customer attach:** F6 → 10-digit phone (live match after 4 digits) → Enter. **≤12.** No match → Enter again creates the customer phone-only. Attached customer ⇒ WhatsApp e-bill offered at close (auto/ask/off setting).
|
|
|
|
|
|
**Hold/resume:** F7 parks the cart under its Token instantly (**1**); held carts are journaled — they survive restarts and power cuts, and have no invoice number. F8 → tray → Enter (**2–3**). Day-end forces the tray empty.
|
|
|
|
|
|
**Payment overlay (F11)** — for everything that isn't exact cash. Payable on top; tender legs left; amount field prefilled with remainder; method hotkeys **1** Cash **2** UPI **3** Card **4** Khata **5** Other. Grammar: *(optional amount) + method key* adds a leg; Enter on zero remainder commits + prints; Esc abandons, cart untouched.
|
|
|
|
|
|
| Tender | Flow | Keys |
|
|
|
|---|---|---|
|
|
|
| Cash w/ change via overlay | F11 → `2000` → `1` → Enter | 7 |
|
|
|
| UPI dynamic QR (Standard+, online) | F11 → `2` → amount-embedded QR on customer display → webhook auto-confirms → auto-close | **2** |
|
|
|
| UPI offline | Static VPA QR; cashier sights success screen → Enter (payment flagged `manually_confirmed`) | 3 |
|
|
|
| Card | F11 → `3` → amount pushed to terminal → Enter on approval | 3 |
|
|
|
| Khata | F11 → `4` → requires attached customer; shows new balance vs credit limit → Enter; over limit → supervisor PIN | 3 (+PIN) |
|
|
|
| Split ₹500 cash + UPI | F11 → `500` → `1` → `2` → QR remainder | 6 |
|
|
|
|
|
|
Bill-close latency budget: commit + print spool < 1 s; the lane never waits on the printer.
|
|
|
|
|
|
**Cancel/void (bills are immutable — a "void" is a new document):** Ctrl+Del cancels an open cart with reason picklist (+supervisor PIN if it has lines; abandoned carts logged with line snapshot for shrink analytics). Voiding a closed bill (same day, pre-filing): F10 → find → `V` → reason → supervisor PIN → a **cancellation document** is appended referencing the bill; the bill stays in the series marked CANCELLED (series stays consecutive — GST-safe), stock reversed by counter-movements, payments reversed. Registered IRN → IRN-cancel queued within the 24 h window; outside it the flow refuses and routes to Returns (credit note).
|
|
|
|
|
|
**Returns/exchange (F9):** find the bill by scanning the receipt QR (0), bill no, or phone lookup → original lines listed with returnable qty netted → Space toggles, F2 partial → grid enters RETURN mode (red accent, watermark, negative lines; batch items return to their original batch). Exchange = keep scanning positive lines in the same document. Settle net via F11 (cash refund / khata credit); committed as a **credit note** in its own per-counter series referencing the original. No-receipt return: supervisor PIN, current-rate floor, audit-flagged. Simple 1-line refund ≈ **6 keystrokes**.
|
|
|
|
|
|
**Reprint:** Ctrl+P = last bill (**1**). Older: F10 → find → `P`; `W` resends the WhatsApp e-bill. Beyond N copies → supervisor; every reprint audit-logged and printed "DUPLICATE".
|
|
|
|
|
|
### 1.5 Offline-degraded state — almost nothing changes
|
|
|
|
|
|
Offline is the architecture, not a mode. Scanning, search, lines, totals, print, drawer, cash/card/khata/hold/returns/reprint: identical. What differs: status dot goes grey ("Offline — 34 queued", count ticks — that's the whole ceremony); UPI falls back to static VPA QR + manual confirm; WhatsApp e-bills queue; e-Invoice bills print with "IRN pending" and register on reconnect; customer/khata lookups use the local replica (limit checked against last-synced balance, flagged; multi-counter stores keep a live LAN view via Store Hub); offline loyalty redemption capped per setting. No banners, no modals, no focus theft. Reconnect drains the outbox silently.
|
|
|
|
|
|
### 1.6 Shift & day flows
|
|
|
|
|
|
Lite skips the ceremony — login straight to billing, day-end report auto-generates. Standard+:
|
|
|
|
|
|
- **Login:** cashier tiles + 4–6 digit PIN, < 5 s cold start; 5 wrong PINs → 60 s lockout + audit event. Ctrl+L locks mid-day (held carts and open bill preserved).
|
|
|
- **Shift open:** denomination grid (₹500/200/100/50/20/10/coins, Tab advances, live total ≈ 25 keys); variance vs previous shift's declared float; over tolerance → supervisor + reason. Quick-open setting for small stores: single declared amount, ≈ 6 keys. Emits immutable `shift_opened`.
|
|
|
- **Cash in/out (Ctrl+M):** direction → amount → reason picklist (float top-up, safe drop, petty expense, owner drawing) → drawer kicks, voucher slip prints. Above ₹X → supervisor. ≈ 6–10 keys.
|
|
|
- **Shift close:** **blind count** by default (expected hidden until submitted) → variance with reason + supervisor PIN outside tolerance → held-cart tray must be empty → declare leave-behind float → X-report prints (bills, ABV, tenders, discounts, voids, returns, cash movements, variance, names). Synced; owner app gets the summary.
|
|
|
- **Day-end:** one Enter → Z-report consolidating all counters (via Store Hub): GST summary by rate, tenders vs counted cash, top items, voids/returns, khata sales, pending-sync count. Prints + queues to owner digest; triggers the cloud backup snapshot. Fully offline-capable (report syncs later, with a pending-sync warning line if the outbox isn't empty).
|
|
|
|
|
|
### 1.7 Supervisor overlay — the one sanctioned interruption (always user-initiated)
|
|
|
|
|
|
Gated actions (all thresholds are DB settings per tenant/store): price override beyond band, discounts above X%, cancel-with-lines/void, no-receipt return, khata over limit, drawer no-sale, reprint > N, paid-out > ₹X, shift variance, offline loyalty above cap, tax-exempt sale, settings/keymap entry, training-mode toggle (manager).
|
|
|
|
|
|
Flow: compact overlay atop the visible bill — action description, before → after values, reason picklist (list is DB config), PIN field. Supervisor authorizes by user-code + PIN, **badge barcode/card swipe** (0 keys — the fast path), or (Phase 3, online) **remote approve** pushed to the owner app — overlay shows "awaiting approval", cashier can Esc and hold the bill so the queue keeps moving. Approve → executes; audit row (action, cashier, approver, counter, bill ref, before/after, timestamp) syncs to the back-office audit browser and owner feed. Deny/30 s timeout → clean cancel, focus → scan box. 3 wrong PINs → 60 s counter lockout + owner alert. Supervisor authority is itself a role permission from the DB matrix.
|
|
|
|
|
|
### 1.8 POS settings (Ctrl+, — supervisor gated; never required mid-bill)
|
|
|
|
|
|
Counter-scope DB settings (port/device bindings stay local-only): **Printers** (profiles, per-document routing, test print/drawer kick, template preview — templates edited in back office); **Scale** (port + protocol presets shipped as data, live readout, label-barcode scheme editor with test-parse); **Customer display** (monitor, orientation, playlist preview, language); **Sync status** (pending ops by type, per-item retry, force-sync, Store Hub state, one-click "send diagnostics" bundle — no PII); **Keymap** (preset picker Next/Classic/Custom, rebind grid, quick-key tile editor, print cheat card); **Hardware wizard** (first-run guided detection, re-runnable); **About/License** (edition, locked features as discoverable upsells, update channel).
|
|
|
|
|
|
### 1.9 Customer display (Pro; second monitor, same Electron app, local)
|
|
|
|
|
|
| State | Content |
|
|
|
|---|---|
|
|
|
| Idle/promo | DB-driven playlist: offers, house ads, WhatsApp e-bill opt-in card. |
|
|
|
| Billing | Last 5–6 lines large (customer's script, MRP struck-through when rate < MRP — kills above-MRP disputes), running total huge, "You saved ₹X", updates inside the 150 ms scan budget. |
|
|
|
| QR payment | Full-screen dynamic QR with amount ("Scan to pay ₹1,431") → green PAID on webhook; offline: static VPA QR + amount. |
|
|
|
| Change due / Thank-you | "Change ₹569" 4 s · total, saved, loyalty earned, "bill sent on WhatsApp", catalog message per language, 5 s. |
|
|
|
| Return mode | Refund lines + net, red accent — transparency during returns. |
|
|
|
|
|
|
The display never shows sync/printer/internal errors.
|
|
|
|
|
|
### 1.10 Keystroke scoreboard (published, telemetry-verified)
|
|
|
|
|
|
Cash exact **1** · cash w/ change 5 · UPI 2 (offline 3) · card 3 · khata 3 · split 6 · scale produce 2 (manual ~6, label 0) · qty 0/2/3 · price/discount 4–5 · quick-add 3+typing · customer attach ≤12 · hold 1 / resume 2–3 · cancel cart 2–3+PIN · void 5–10+PIN · 1-line return ~6 · reprint 1 · login 5–8 · shift open/close ~25–35 · day-end 1. Telemetry: per-bill timing, scan-to-line histogram, keystrokes-per-bill, overlay dwell — the "fastest counter in India" number we publish.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 2. Back-Office IA & Menu Tree
|
|
|
|
|
|
### 2.1 Design stance (defaults, not options)
|
|
|
|
|
|
Table-first workbench: every module opens on a virtualized, keyboard-navigable grid; details are drawers by default, full pages on demand. **Two levels of navigation, never three** — Module → Page; anything deeper is a tab or filter. **Hide by role, lock by edition**: a user never sees a page their role can't use; owners/managers see edition-locked pages as discoverable upsells. The whole menu is data: `menu_item(code, module_code, route, icon, sort, min_edition, label_key)` + `menu_item_role` — support reorders/relabels/regates with zero frontend release.
|
|
|
|
|
|
### 2.2 Menu tree — eleven modules, ordered by daily usage `[L/S/P/E]` = min edition
|
|
|
|
|
|
- **🏠 Dashboard** — Overview `L` (today's sales/payments live; alert cards deep-linking to the fixing page) · Approvals & Tasks `S` (discount/void approvals, adjustment approvals, POS quick-adds needing completion, failed IRNs).
|
|
|
- **🧾 Sales** — Bills `L` · Returns & Credit Notes `L` · Shifts & Day-End `L` (recon features `S`).
|
|
|
- **📥 Purchases** — Purchase Inbox `P` (flagship queue, unread badge, first item) · Purchase Invoices `L` · Orders & GRNs `P` · Purchase Returns / Debit Notes `L` · Suppliers `L` · GSTR-2B Reconciliation `P`. (Label printing stays under Catalog — purchases hand off to it.)
|
|
|
- **📦 Inventory** — Stock on Hand `L` · Stock Movements `L` (the append-only ledger behind every number — stock is derived, never typed) · Adjustments `L` (reason-coded, approval workflow) · Stock Takes `P` · Transfers `E` (in-transit state, e-Way hook) · Expiry & Ageing `P` (action lists, not charts) · Reorder `S` (velocity suggestions → one-click draft PO).
|
|
|
- **🏷️ Catalog** — Items `L` · Categories & Brands `L` · Price Lists `S` (effective-dated, store overrides) · Schemes & Offers `S` · Label Printing `S`.
|
|
|
- **👥 Customers** — Customers `L` (phone-keyed) · Khata & Collections `L` (WhatsApp reminders + UPI collect links, single or bulk) · Loyalty `P` · Campaigns `P`.
|
|
|
- **📒 Accounting** — Ledgers `L` · Vouchers `S` · Cash & Bank Books `S` · Day Book `P` · Financial Statements `P` · Tally Export `S` · Year Close & Locks `S`.
|
|
|
- **🏛️ GST & Compliance** — GST Returns `S` (GSTR-1/3B prep, JSON export, filing status) · e-Invoice Queue `P` · e-Way Bills `P` · HSN & Tax Rates `L` (effective-dated — the GST-2.0-proof page).
|
|
|
- **📊 Reports** — Sales `L` · Stock & Valuation `L` · Party Outstanding `L` · GST Registers `L` (the CA hand-off pack) · Staff & Counters `S` · Analytics `P` · Scheduled Reports `S`.
|
|
|
- **🏢 Head Office** `E` — Store Comparison · Central Pricing · Central Purchasing (Ph 4) · Consolidated GST (multi-GSTIN) · Outlet P&L (Ph 4).
|
|
|
- **⚙️ Admin** — Users & Roles `S` · Stores & Counters `L` (per-counter series, print profiles, Store Hub role) · Settings `L` (tenant→store→counter→user hierarchy) · Templates `L` (visual editor + preview) · Integrations `L` (WhatsApp, UPI, GSP, email-in address) · Subscription & Plan `L` · Audit Log `S` · Sync & Devices `L` · Backups `L` · Data Import `L` (Excel/CSV + Classic migration tool with verification report) · API Access `E`.
|
|
|
|
|
|
**Not in the tenant menu:** plan/price-book editing, the global message catalog, staged rollouts, and cross-tenant telemetry live in the internal **SiMS Control Panel** — same engine, vendor tenant scope. Never expose vendor-level config in customer IA.
|
|
|
|
|
|
### 2.3 Navigation model
|
|
|
|
|
|
- **Left sidebar** (240 px, collapsible to icon rail): accordion modules, active one auto-expands. **Top bar**: scope switcher (store selector — "All stores" for Enterprise owners — plus a date-range chip every page inherits), Ctrl+K, notifications bell (fed by Approvals & Tasks), sync dot, user menu (per-user language). **Breadcrumb** `Module / Page / Record`; every record has a stable, copyable URL.
|
|
|
- **Ctrl+K command palette** — the second navigation system. Four ranked groups: Records (bill no, item, party, GSTIN — same fuzzy/phonetic engine as POS), Pages, Actions ("Export GSTR-1 June", "Send khata reminders"), Recents (last 15 records per user, server-side, cross-device). Prefix operators: `#` bill/doc no · `@` party · `>` action · `?` help/settings. The palette respects role visibility and edition gating — locked actions show a lock glyph and open the upsell sheet; role-hidden items never appear.
|
|
|
- **Keyboard:** `g` + module key (`g s` Sales, `g p` Purchases, `g i` Inventory, `g c` Catalog, `g a` Accounting, `g g` GST, `g r` Reports, `g x` Admin); `/` focuses the grid filter; `n` = page's primary create; `e` = export. Keymaps are DB data, same mechanism as POS.
|
|
|
|
|
|
### 2.4 Visibility & gating rules
|
|
|
|
|
|
| Rule | Behavior |
|
|
|
|---|---|
|
|
|
| Role lacks access | Page absent from sidebar, palette, routes (deep link → 403 with catalog message). |
|
|
|
| Read-only role | Page visible; create/edit/post disabled with tooltip; export still works. |
|
|
|
| Below edition, viewer owner/manager | Lock badge; click → **upsell sheet**: 30-sec demo, one-paragraph pitch, "Included in Pro — Upgrade" upgrading in place. |
|
|
|
| Below edition, viewer staff | Hidden — upsells pitch buyers, not cashiers. |
|
|
|
| Cashier | Zero back-office pages; login rejected with "use the counter app". Cashiers exist only in POS. |
|
|
|
| Module empties after filtering | Module header disappears too — except Purchases, GST, and Reports keep their locked flagship pages (Purchase Inbox, e-Invoice, Analytics) visible to owners: the top three upgrade drivers. |
|
|
|
|
|
|
**Default role → module map** (the Admin permission matrix refines to page level with None/Read/Full cells; Auditor is read-everything by definition; Owner is Full at their edition): Supervisor — Dashboard, Sales, Inventory (no Transfers), Customers, Catalog (read), sales/stock Reports. Purchaser — Purchases, Catalog, Inventory (Stock/Reorder/Expiry), stock/purchase Reports. Accountant — Accounting, GST, Sales/Purchases (read), Khata, GST/party Reports. Net effect: the Lite solo owner sees a 7-module ~20-page app; a Pro accountant sees a compliance cockpit; the Enterprise owner sees all eleven — same engine, same routes, different rows in `menu_item_role` + `plan_feature`.
|
|
|
|
|
|
### 2.5 Anatomy of the pages that matter most
|
|
|
|
|
|
- **Catalog → Items** (biggest grid): virtualized columns (name, code, barcodes, category, HSN, GST, MRP, cost, margin %, stock, status incl. **incomplete** — POS quick-adds land here); saved-filter tabs; side-peek preview on row focus; bulk actions (effective-dated category/GST/price edits, labels, merge duplicates); empty state = *Import from Excel / Migrate from Classic / Add first item* — replaced by the "N counter quick-adds need details" worklist when applicable.
|
|
|
- **Item editor**: sections — Identity (multi-language names, HSN with rate auto-fill), Barcodes (many, incl. `2x` PLU config on Pro), Units & Packs conversion matrix, Pricing (per-price-list effective-dated rows), Batch & Expiry toggle (Pro), Reorder. Right rail: live stock by store, last 5 purchases/sales, audit drawer. Only name + GST rate hard-required — everything else completable later. Effective-dated fields edit via a "Change from…" timeline dialog, never in-place.
|
|
|
- **Sales → Bills**: grid with payment-mode chips, e-bill and IRN status; side-peek renders the bill exactly as printed (same template engine) with the banner "Bills are permanent records — corrections create a credit note." Actions: Reprint, Send e-Bill, Create Return, Generate e-Way. Per-counter sync-lag indicator so "no data" and "not synced yet" are never confused.
|
|
|
- **Inventory → Stock on Hand**: group-by item/category/store/batch; valuation (FIFO/weighted avg per tenant setting); row expansion shows the last 30 movements inline — "why is this number what it is" is a first-class answer. Filters include negative!, zero, below-reorder, dead > 90 days, expiry windows.
|
|
|
- **GST → GST Returns**: period board Draft → Reviewed → Exported → Filed (ARN stored); **exceptions worklist first** (missing GSTIN, rate mismatches, missing HSN, unregistered IRNs) — the return can't reach Reviewed with open exceptions (owner-PIN override, audit-logged). `Prepare` locks a snapshot; fixes deep-link and re-snapshot deltas. Cross-link to GSTR-2B Recon for the inward side.
|
|
|
- **Admin → Users & Roles**: Users tab (surfaces allowed: POS/back office/owner app) + Roles tab (permission matrix + sensitive-action gates: max discount %, void rights, override band, adjustment threshold, PIN policy). Deactivate, never delete; at least one owner must remain; matrix edits show "affects N users" and land in the audit log.
|
|
|
|
|
|
### 2.6 Cross-page conventions (the engine's grammar)
|
|
|
|
|
|
| Convention | Behavior |
|
|
|
|---|---|
|
|
|
| List → detail | `↵` = side-peek drawer; `Shift+↵` = full page with own URL; browser back restores grid scroll/filter state. |
|
|
|
| Saved filters | Named views as tabs above the grid; personal by default, shareable tenant-wide; support can seed per-vertical presets as DB rows. |
|
|
|
| Export everywhere | `e` → Excel/CSV/PDF honoring filters/columns/sort; > 10k rows async with bell notification; exports audit-logged. |
|
|
|
| Audit drawer | Every record: chronological who/what/when with field-level before → after diffs from `audit_log`; same component everywhere. |
|
|
|
| Effective-dated edits | Timeline dialog: past (grayed, immutable) → current → "new value from [date]"; scheduled changes show as a chip; deleting a scheduled change is allowed, rewriting history is not. |
|
|
|
| Immutable documents | Posted docs render read-only with "Permanent record — correct via Credit Note / Debit Note / Reversal"; the correction pre-fills a linked document; both cross-link forever. |
|
|
|
| Import on every master | Typed template → upload → staged preview with row-level errors → partial commit; error rows re-downloadable. Same component serves Classic-migration verification. |
|
|
|
| Empty states teach | One sentence (from `message_catalog`, localized) + primary CTA + import path. Never a blank table. |
|
|
|
| Sync honesty | Store-originated data shows a freshness chip ("Store 2: synced 2 min ago"); stale > 15 min turns amber and links to Sync & Devices. |
|
|
|
| Money & scope | Tabular figures, Indian grouping from the one shared formatter; pack context ("2 box + 3 pc"); top-bar store + date scope follows the user across pages. |
|
|
|
|
|
|
---
|
|
|
|
|
|
## 3. Purchases & Purchase Inbox
|
|
|
|
|
|
All three entry paths — manual, PO/GRN, Inbox — converge on **one posting pipeline** producing the same immutable `purchase_invoice` document.
|
|
|
|
|
|
### 3.1 Placement — POS "Back Room" mode
|
|
|
|
|
|
The Electron POS gets a supervisor-gated **Back Room mode** (separate from the sacred billing screen) running the *same React screens* as the web back office, but against local SQLite, syncing up. Rationale: Lite shops have one machine and shaky internet; goods receiving happens where the scanner and label printer are; purchase documents flow up cleanly and can never conflict. Zero new frontend surface.
|
|
|
|
|
|
| Function | POS Back Room (offline) | Back office web | Owner app |
|
|
|
|---|---|---|---|
|
|
|
| Manual entry, GRN, purchase returns, label printing | ✔ default home (scanner + label printer here) | ✔ identical screens (label queue mgmt only) | — |
|
|
|
| PO create/approve | — | ✔ | approve/deny |
|
|
|
| **Purchase Inbox review** | — (AI + raw docs live in cloud) | ✔ only | badge + camera capture |
|
|
|
| Suppliers | read-only ledger lookup | ✔ full | outstanding summary |
|
|
|
| GSTR-2B recon | — | ✔ only | — |
|
|
|
|
|
|
Inbox postings sync the document *down* to the assigned store, which applies stock-in movements locally — stock stays derived from local events.
|
|
|
|
|
|
### 3.2 Manual purchase entry — the speed flow (MVP, Lite+)
|
|
|
|
|
|
Creed mirrors the counter: nothing interrupts line entry; all prompts (price changes, labels, mismatches) batch to post time; focus returns to the scan box. Layout: header strip (supplier · invoice no · date · Local/Interstate auto from GSTIN state · terms); lines grid `Item | Batch | Expiry | Qty | Free | Unit | Cost | Disc% | Tax% | MRP | Margin% | Sell | Amount` — which columns the cursor **stops in** is a per-store "column profile" setting (a kirana stops at Qty+Cost; a pharmacy adds Batch/Expiry); right rail item card (photo, stock, **last 3 costs from this supplier with dates**, live margin %); footer tax breakup + **"Bill total (as printed)"** field — the single most effective error catcher in purchase entry.
|
|
|
|
|
|
Flow: `Ctrl+P` new → supplier by 3-char fuzzy match → invoice no (inline duplicate check on supplier + no + FY) → date defaults today → scan (line pre-fills last cost, HSN tax, current MRP) → qty → cost (deviation beyond a % setting shows an inline chip `↑ ₹2.10 vs 04-Jun`, never a dialog) → Enter blows through skipped columns; new MRP silently queues a price decision → `Ctrl+↵` close → **totals-check overlay**: type the printed grand total; delta beyond tolerance (default ₹0.99) shows per-line hints and an inline "other charges" line → Post → **one consolidated price-change dialog** (suggested sell per the item's pricing rule, checkboxes default ON, audit-logged) → label prompt ("Print labels for 5 new/changed items?", quantities default to received qty, jobs to the Label Print Queue).
|
|
|
|
|
|
**Budget: ~5 keys/line, ~24 keys overhead → a 12-line bill in ≈ 85 keystrokes, under 2.5 minutes.** Publish this like scan-to-line < 150 ms. Unknown barcode → inline quick-create flagged `needs_completion`, exactly like POS quick-add. Unit conversion from the pack master (CASE(24) → PCS), cost per stocking unit live. Below-floor margin renders amber, never blocks. Posting = one local transaction: immutable document, batch-wise stock-in movements, supplier ledger credit, price-change audit entries, label jobs. Corrections are debit notes or reversal + re-entry — never edits.
|
|
|
|
|
|
### 3.3 PO → GRN → Invoice (Pro, Phase 3)
|
|
|
|
|
|
Direct entry stays the default; PO mode is a store setting (`purchases.mode = direct | po_optional | po_required`). **PO**: `Draft → Approved (threshold-gated; owner-app approve/deny) → Sent (PDF via email/WhatsApp from template) → Partially Received → Received → Closed | Short-closed | Cancelled`, lines carrying ordered/received/invoiced qtys; reorder suggestions can prefill from sales velocity. **GRN** (POS Back Room, scanner-first): pick PO → lines prefill remaining qty → scan to verify (scan-vs-typed mismatch highlighted) → over-receipt beyond tolerance % → supervisor PIN → batch/expiry per flagged line, damaged-on-arrival to wastage → post: **stock-in immediately** (goods are sellable the moment they hit the shelf — never make the counter wait for accounts) + "GRN pending invoice" accrual at PO cost; the label prompt here is the natural supermarket labeling moment. **Invoice** matches one or more GRNs (or one partially) and does only the financial part — ledger credit, cost true-up via weighted-avg adjustment movement, tax capture; no double stock-in. Inbox drafts auto-propose open GRNs for the supplier; variances beyond tolerance route to approval.
|
|
|
|
|
|
### 3.4 Purchase Inbox (Pro, Phase 3 — the flagship)
|
|
|
|
|
|
North star: **≥ 70% of purchase lines via Inbox** (Phase-3 exit ≥ 50%). The pitch: *forward the bill, confirm, done.*
|
|
|
|
|
|
**Intake:** auto-provisioned email address `bills-{tenant}@in.sims.app` (+`+{store}` aliases for routing; printed on our POs so suppliers self-onboard; sender learned per supplier) · drag-drop/bulk upload with auto-split of stacked-scan PDFs · owner-app camera (fast-follow) · WhatsApp forward (fast-follow) · **e-Invoice JSON/QR parsed deterministically** — no AI, 100% confidence; scanning the IRN QR with the Back Room scanner also creates a doc.
|
|
|
|
|
|
**Pipeline:** SHA-256 dedupe → object storage (Mumbai) → classify (invoice / CDN / statement / junk — non-invoices to an "Other" tab) → parse (deterministic for e-invoice JSON and text-layer PDFs; Claude vision for photos) yielding header + lines, **each field with confidence + source bounding box** → supplier match (GSTIN exact → auto; fuzzy + learned sender → suggested) → item match in three tiers: **A** alias-memory hit via `supplier_item_alias` (mapped once, remembered forever, green) · **B** barcode or HSN+fuzzy suggestion (amber, one-key confirm) · **C** unmatched (red) → validations (duplicate by supplier+no+FY plus fuzzy same-amount-same-week, totals recompute, line tax vs our dated rate, CGST/SGST vs IGST vs state, MRP/cost jumps) → state `Ready` (all green) or `Needs Review`. `inbox.autopost_trusted_suppliers` (default off) auto-posts clean docs from whitelisted suppliers — the endgame for FMCG distributors. States are an append-only log: `Received → Parsing → Needs Review | Ready → Posted | Discarded | Duplicate`.
|
|
|
|
|
|
**Review screen (the demo moment):** three zones — queue rail (`J/K` next/prev, source badge, age, confidence %), center source viewer (zoom/rotate/thumbnails), right draft form (**the same grid component as manual entry**). Clicking any field highlights its source region in the PDF and vice versa — trust is built by making verification effortless. Confidence tinting: green ≥ 95 auto, amber 70–94 (`↵` accepts, `Tab` edits), red < 70. Header verdict math: `Printed ₹12,480.50 · Computed ₹12,480.50 ✓`. Tax mismatch banner offers three actions: *use bill rate for this purchase* (ITC follows the bill; default), *update item master* (dated edit, never silent), *flag to accountant*. Red-line mapping keeps the supplier's raw text visible with type-ahead or pre-filled Create-item; differing units get a one-time "CTN = how many PCS? [24]" stored on the alias. Microcopy under every mapping: *"Will be remembered for Vikram Distributors."* That sentence is the product promise. `Ctrl+↵` posts → syncs down → store applies movements; price-change and label prompts fire on the store machine. Target: a 30-line bill confirmed in under 60 seconds; no bulk-post by design — every posting is a human confirmation. Header stat: "This month: 84% of purchase lines via Inbox."
|
|
|
|
|
|
**Exceptions (never a dead end):** unknown supplier → GSTIN public lookup pre-fills create, or link-to-existing fuzzy list; unknown items → per-line map-or-create (bulk "create all N" gated to purchaser role); exact duplicates can never post, fuzzy ones show side-by-side compare with reason-coded override; total mismatch → ranked causes (add-charges one-click, tax-inclusive basis toggle, jump-to-worst-cell), post blocked until resolved or supervisor-overridden; unreadable scans → request re-upload (polite catalog template) or manual entry pre-filled with whatever parsed; multi-invoice files auto-split; statements file under Other and feed ledger reconciliation later.
|
|
|
|
|
|
### 3.5 Suppliers
|
|
|
|
|
|
List: name, GSTIN, outstanding, overdue, last purchase, MTD volume. Detail tabs: **Overview** (ageing buckets, terms vs actual days-to-pay, 12-month trend, top items) `L/1` · **Ledger** (running balance, drill into any document, Record-payment with oldest-first allocation, WhatsApp statement) `L/1` · **Price history** (item × date cost grid, trend flags — the data behind the last-cost chips) `S/2` · **Pending POs/GRNs** (accrual exposure totaled, one-click remind) `P/3` · **Items & aliases** (the matching memory, editable, CSV-seedable for big distributors) `P/3` · **Documents** (all Inbox docs) `P/3` · **Settings** (GSTIN, terms/limit, tax basis, recognized senders, trusted-auto-post, bank details) `L/1`. Back Room gets read-only ledger lookup — the supplier's rep at the back door gets his balance offline.
|
|
|
|
|
|
### 3.6 GSTR-2B reconciliation (Pro, Phase 3)
|
|
|
|
|
|
Pick period → pull via GSP (or upload portal JSON — fallback always available) → summary tiles (*ITC per 2B · per books · Matched % · At-risk ₹*) → work buckets top-down; period locks when 3B files. Buckets: **Exact** (auto, collapsed) · **Fuzzy** (side-by-side, `↵` confirms — confirmations train the per-supplier invoice-no normalizer) · **Value mismatch** (accept-with-note / raise DN-CN / flag supplier) · **In 2B, not in books** — the killer feature: **"Create draft in Purchase Inbox"** from the 2B row (header pre-filled, lines pending until the paper bill surfaces); nothing missed stays missed · **In books, not in 2B** — ITC-at-risk list with WhatsApp/email nudge to the supplier, ITC-hold flag surfacing in 3B prep, auto-recheck next period. All actions audit-logged; recon state append-only per period.
|
|
|
|
|
|
### 3.7 Purchase returns / debit notes (MVP, Lite+)
|
|
|
|
|
|
Immutable DNs in their own per-store series (`ST1-DN-00042`), referencing the original where linked. Flow: pick supplier → pick original invoice (scan any item to filter) or unlinked mode → return qty per line with **mandatory DB-configured reason codes** (damaged/expired/short-supplied/rate difference/scheme not passed/quality), batch defaulting to received batch → **rate-difference-only type**: amounts without quantities — no stock movement, pure ledger + GST document → tax auto-reverses at the **original invoice's rates** (rates are dated config; the original document is the truth) → post (above-₹X → supervisor PIN): stock-out movements, ledger debit → print/PDF/WhatsApp travels with the goods; e-Way queue picks it up over threshold. Settlement nets against the next payment allocation or a replacement GRN referencing the DN.
|
|
|
|
|
|
### 3.8 Config & metrics
|
|
|
|
|
|
Everything tunable is `setting` rows (`purchases.mode/column_profile/cost_warn_pct/margin_floor_pct/margin_basis/total_tolerance/over_receipt_pct`, `inbox.autopost_trusted_suppliers`, `dn.approval_threshold`, `gstr2b.amount_tolerance`) plus `supplier_item_alias`, `inbox_document/extraction`, `price_change_event`, `label_job` tables. Instrument from day one: keys/seconds per manual line (≤ 5 / ≤ 8 s), % lines via Inbox, % docs auto-Ready, median review time (< 60 s), alias hit rate (→ 95% per supplier by the third bill), duplicate and total-mismatch catches, 2B matched-% and ITC-at-risk per tenant.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 4. Owner App & Onboarding
|
|
|
|
|
|
### 4.1 Owner app (React Native, Android-first; Phase 3)
|
|
|
|
|
|
**Stance:** online-first, cache-always — every screen renders instantly from a local SQLite snapshot, then refreshes; a permanent freshness ribbon ("As of 12:41 PM · Store synced 3 min ago") because "live" is really "as-synced"; never a spinner over a cached screen. **One write action: approve/deny** — everything else is read; new alert types, digest sections, and report tiles are DB config, not app releases. Language per user setting, all text from `message_catalog` (Hindi + English at launch). Auth: phone + OTP, then a 4-digit app PIN (doubles as approval PIN) with biometric; max 3 devices, revocable from Admin. On **Lite**, only Today tiles and the Day-End Digest render; Alerts/Approvals/Reports show as locked cards with one-tap upgrade.
|
|
|
|
|
|
**Pages** (four bottom tabs + header store switcher/profile): **Today** — net sales vs same weekday last week, bills count + bills/hour sparkline, payments donut (Cash/UPI/Card/Khata), ABV, returns, khata given, per-counter open/closed status, sync freshness chips, Day-2 checklist card, top 3 alerts; 60 s auto-refresh. **Store switcher** — sheet with per-store sales + freshness dots (green ≤ 5 min / amber ≤ 30 / red > 30 during open hours), "All stores" rollup first; single-store tenants never see it. **Alerts feed** — 30 days cached, day-grouped, filter chips (Stock/Expiry/Khata/Sync/Discounts/System); every alert deep-links; long-press mutes the type (user-scope setting); dismiss state syncs later. **Approvals** — pending cards (type, store+counter, cashier, bill detail, **live countdown to POS timeout**, Approve/Deny behind PIN) + 7-day history showing who decided; **approve/deny never queue offline** — a stale approval is worse than none; offline shows "counter will use supervisor PIN". **Reports Lite** — deliberately five, no builder: sales trend, top-20 items, category performance, khata outstanding (tap → server-side WhatsApp reminder), month-to-date GST snapshot; tile definitions are DB rows, so a sixth report is config. **Day-End Digest** — rendered digest per store + date, last 14 cached (the guaranteed-offline artifact), back-swipe browses days. **Settings** — language, per-event Push/Digest/Off, quiet hours, PIN, devices, plan (read + upgrade CTA), support routed to the dealer first via tenant config.
|
|
|
|
|
|
**Flow — remote approval (the one write):** the invariant rules it — billing never waits on network, so remote approval is a fast path *layered on* the always-available supervisor-PIN gate, never a replacement. Threshold trips → POS overlay offers Supervisor PIN (always) and "Ask owner" (only when online with a live owner push token) → `approval_request` outbox doc → high-priority FCM, body resolved server-side per device language → owner taps, PIN, decides → decision pushes down. Round-trip target < 10 s; POS timeout 45 s (counter setting); on timeout/offline/deny the overlay falls back to PIN or cancel, and the cashier can hold the bill and serve the next customer. Late decisions are discarded and logged. Every override — however resolved — still emits a post-facto owner alert: remote approval is convenience; the audit trail is the guarantee. Offline owner (train, basement): every tab renders cached with a grey ribbon, zero blank screens; reconnect refreshes silently.
|
|
|
|
|
|
### 4.2 Notification strategy
|
|
|
|
|
|
**The owner's phone is sacred**: default posture is digest; events earn a push; one coalesced push beats twelve. Three priority classes assigned per event in a `notification_rule` table (event → class, coalesce window, quiet-hours override, channel — team-editable, no release): **P0** interrupt now (bypasses quiet hours) · **P1** push, batched, respects quiet hours (default 22:00–07:00) · **P2** digest/feed only. All texts resolve server-side from `message_catalog` keyed `(code, channel, lang)` — push is one line, WhatsApp is a structured template. Channels: FCM primary; WhatsApp for the day-end digest to app-less owners; SMS only for license-expiry final notice; no email. Per-user overrides to Push/Digest/Off — except P0 sync-down and license expiry, reducible only to Digest.
|
|
|
|
|
|
Defaults: approval requests **P0** (uncoalesced) · store sync-silent > 30 min during open hours **P0** (one per incident + recovery push) · license expiry **P0** T-7/3/1 (+SMS at T-1 if push undelivered) · day-end digest **P1** at close or 21:30 · cash variance > ₹100 **P1** · big discount/void **P1** coalesced in 15-min batches ("3 overrides at Store 1") · khata limit crossed **P1** (max 1/customer/day) · khata ageing, low stock (coalesced counts), near-expiry (30/15/7-day horizons), record-day delight, app updates, backup status **P2** (backup failed 2 days running escalates to P1).
|
|
|
|
|
|
**The Day-End Digest** is the product's daily heartbeat: one push per store (+ chain rollup) — net sales & bills vs last same weekday → payments split → cash variance per counter → khata given/collected → top 5 items → alerts summary → one Day-2-checklist nudge. Each section is a DB-configured digest block, reorderable without release. Ships in **Phase 2 as WhatsApp-only** (needs no app); the Phase 3 app is mostly a rendering client.
|
|
|
|
|
|
### 4.3 Onboarding wizard (Phase 2; back office `/setup`, resumable)
|
|
|
|
|
|
Target: **first real bill within 15 minutes**, measured. Every completed step writes real tenant config immediately (no final submit — abandonment leaves a usable half-configured tenant); every skip is a stored decision that creates its Day-2 checklist item; per-step 30-second Hindi/English videos make both self-serve and dealer-assisted modes work without us on a call (the Phase-2 exit criterion). No step can dead-end: GSTIN API down → manual entry + auto-retry verify; printer undetected → skip; import errors → quarantine.
|
|
|
|
|
|
| # | Step | Highlights | Skippable |
|
|
|
|---|---|---|---|
|
|
|
| 0 | Account & plan | Phone + OTP → tenant; plan picker rendered from `plan`/`price_book` rows; default 14-day full-Pro trial; language sets tenant + user default | Plan deferrable via trial |
|
|
|
| 1 | GSTIN → business | GSP auto-fetch of legal name/address/state/registration type; **"No GSTIN"** path = unregistered mode (Bill of Supply) — the mini-shop reality; adding GSTIN later upgrades the invoice format automatically | Yes |
|
|
|
| 2 | Store & counters | Store 1 pre-filled from GSTIN; each counter auto-assigns a GST-legal series prefix (`ST1C1-`, editable) in `doc_series` | Counters addable later |
|
|
|
| 3 | Tax & pricing defaults | Three pre-answered confirmations: MRP tax-inclusive ON, round to nearest rupee, B2C thermal default; composition preset; GST rate master preloaded system-wide — never owner-entered | Yes |
|
|
|
| 4 | Items | (a) Excel upload with synonym-dictionary column auto-mapping ("MRP/Rate/Price", "विवरण"), 20-row preview, good rows import in background, bad rows quarantined — never block on row 4,812; (b) business-type template catalogs (kirana, pharmacy, apparel… — DB data, dealer-extendable); (c) skip — POS quick-add covers day one | **Yes — the most important skip** |
|
|
|
| 5 | Staff | Cashiers with POS PINs; owner is default supervisor until staff added | Yes |
|
|
|
| 6 | Install & pair POS | Download link SMS'd to the counter PC; POS first-run takes a 6-digit pairing code (15-min TTL) → binds machine, pulls license, starts master sync; import progress visible | Pairing deferrable |
|
|
|
| 7 | Printer setup (on POS) | Auto-detect ESC/POS, paper width, test print (₹ + Hindi line verifies code page), drawer-kick test | **Yes** — "No printer / WhatsApp e-bills only" is a first-class choice |
|
|
|
| 8 | First test bill | Guided overlay on the real billing screen; **TRAINING watermark, separate `doc_type=TRAINING` series** — never pollutes the GST series; the dealer's proof-of-done | No — the "aha" |
|
|
|
| 9 | Go live | Confirm series starts → Start Billing flips training off; confetti once; owner-app link SMS'd | No |
|
|
|
|
|
|
Progress lives in `onboarding_step(tenant, step_code, status, completed_by, at)` rows, feeding the checklist and the dealer hand-off record; every step is reopenable from Settings forever.
|
|
|
|
|
|
### 4.4 Day-2 checklist
|
|
|
|
|
|
One data-driven "Finish setting up" surface: `checklist_item(code, title_msg_code, deep_link, completion_probe, edition_min, priority, snooze_days)` — items added/retired without release; **completion is detected by server-side probes, never self-reported** (UPI item completes the moment the setting row exists). Surfaces: back-office dashboard card (progress ring + next 3, deep-linked), owner-app Today card, one idle POS status-bar chip ("Setup 70%" — never on the billing path), and exactly one line in the day-end digest. **No pushes for checklist items, ever.** Default items in priority order: complete/verify GSTIN → fix quarantined import rows → items missing HSN/GST rate (blocks clean GSTR-1 — chase early) → MRP/cost on template items (auto-completes via purchases) → UPI ID for dynamic QR → WhatsApp e-bills → top-5 suppliers → reorder levels (auto-suggest from 2 weeks of sales) → khata opening balances → staff + discount threshold → shop logo → owner-app install (auto-completes on first login) → barcode labels → store timings (drives sync-health alerts and digest timing) → backup/sync green (surfaces only if red). Done (auto) / Snooze 7 days / Dismiss forever; the card retires at 100% or 60 days and moves to Settings → Setup.
|
|
|
|
|
|
### 4.5 Dealer machinery (Phase 2 minimum; portal is Phase 4)
|
|
|
|
|
|
`dealer(code, name, phone, region, white_label_profile)` rows; every tenant carries `dealer_code` from signup — attribution for margins from day one, before any dealer UI exists. Dealer-assisted signup: dealer enters code + owner's phone; **the owner receives the OTP** and reads it out (consent stays with the owner; a consent SMS names the dealer). The dealer's login gets a scoped `installer` role: wizard steps 1–8, settings, imports, printer — **no sales data, ledgers, or reports** — auto-expiring 7 days after go-live, owner-re-grantable. The wizard's resumability *is* the dealer workflow: pre-provision steps 1–5 from the office the evening before, then on-site pairing → printer → training bill with the actual cashier at the keyboard → go-live in under 10 minutes. USB offline installer (shop bandwidth reality; pairing needs one moment of connectivity, initial payload masters-only). Classic customers: the migration tool replaces step 4 and marks steps 1–5 done from imported data. On go-live a **hand-off summary** generated from `onboarding_step` rows goes to the owner by WhatsApp — disputes settled by data. The training series doubles as the post-go-live staff-training tool; `support.channel` routes the owner's Support button to the dealer first. Deferred to the portal: commission statements, fleet view, white-label UI — the attribution rows above make them pure additions.
|
|
|
|
|
|
---
|
|
|
|
|
|
## 5. Cross-Cutting UX Systems
|
|
|
|
|
|
One meta-rule governs everything here: *nothing on the billing path may steal focus, block on network, or require reading a paragraph.* Each system is one pure-TS package (`@sims/search-core`, `ui-states`, `i18n`, `print-queue`, `gates`, `error-catalog`) consumed by all four surfaces; behavior ships in git once, content is tuned in the DB forever.
|
|
|
|
|
|
### 5.1 Search — one engine, two profiles
|
|
|
|
|
|
The POS scan box (local SQLite, items only, results painted **< 100 ms** — headroom inside the 150 ms scan-to-line budget) and the back-office Ctrl+K omnibox (Postgres `pg_trgm` + the same key functions ported, < 400 ms p95, items/parties/bills/actions/settings with `#`/`@`/`>` prefixes) share one normalizer, phonetic function, and ranking — a cashier promoted to back-office work never relearns search.
|
|
|
|
|
|
**Match tiers (short-circuit):** 1 barcode exact (≥ 8 digits or wedge suffix; `2x` scale barcodes parsed *before* search) → 2 item-code exact (Classic codes carry over) → 3 token prefix on every word of every name/alias → 4 phonetic key → 5 fuzzy (edit-distance ≤ 2), running only when tiers 1–4 return < 8 results. Rank within tier by 90-day sales velocity, then shorter name — popularity re-ranking makes 100k SKUs feel small.
|
|
|
|
|
|
**Hinglish, three mechanisms, all DB data:** multi-script names auto-transliterated at save (चावल → `chaval`); a shared Indic phonetic key applied to index and query (`aa→a, ee→i, w→v, ph→f, chh→ch, sh→s`… so typed `chawal` and indexed `chaval` both collapse to `caval`; dhaniya/dhania/daniya collide correctly); and `item_alias(item_id, alias_text, source)` fed by imports, Purchase Inbox matching memory, and a one-key POS gesture — when a cashier's search fails but they find the item another way, POS offers "remember *chawal* for this item? [Enter]". The catalog teaches itself the shop's vocabulary; a Hindi commodity-term seed pack ships as rows.
|
|
|
|
|
|
**Performance at 100k SKUs:** FTS5 with 2–3-char prefixes + a phonetic `search_key` table; top-5,000-by-velocity hot set in memory at startup (< 300 ms, inside the 5 s cold start) painting in < 20 ms with full-index merge behind it; no debounce (debounce = felt lag) — each keystroke cancels the in-flight query on a worker thread; results capped 50, fuzzy candidates bounded ≤ 500. p50/p95 latency is telemetry with regression alerts — we publish the number.
|
|
|
|
|
|
### 5.2 State & feedback vocabulary
|
|
|
|
|
|
Five tokens, one meaning each, everywhere: `ok` green (done/synced/paid) · `busy` amber (queued/pending) · `alert` red (a human must act **now**) · `idle` grey (offline/disabled — *calm*) · `info` blue. **Offline is grey, never red** — offline is the architecture, not a failure; red spent on "meh" kills the vocabulary. Every state renders icon + color + text/number via shared components that physically cannot render color alone. Back office uses skeletons (never a bare spinner > 300 ms); the POS billing path has no loading states at all. One document-status chip set spans POS lines, the bill browser, and the owner app: Draft · Held · Closed · Print queued/Printed · e-bill sent · IRN pending/registered · Sync pending/Synced · Cancelled.
|
|
|
|
|
|
**The sync chip (canonical spec, same component on all surfaces):** Synced (grey-green check) · Syncing (amber + `↑ 12`) · Offline (grey slash + quiet count) · Attention (red triangle — sync *errors* retry won't fix). State changes animate the chip only — no toast, no sound, no focus change. Clicking it (or Ctrl+, → Sync) opens a slide-over: last sync, pending ops by type, per-item errors with codes and action buttons, [Sync now], [Copy diagnostics]; Esc returns focus to the scan box.
|
|
|
|
|
|
**The Focus Doctrine:** on the billing screen, no system-initiated toast, dialog, popover, or focus change — not sync, not updates, not license warnings. Ambient information goes to a non-focusable **notice rail** above the status bar (icon + short text + key hint chips, max 3, overflow collapses to a count). The only sanctioned overlays are user-initiated by keystroke, and Esc always returns to the scan box. Back office may toast and dialog normally — it tolerates interruption; the counter does not.
|
|
|
|
|
|
### 5.3 i18n & bilingual printing
|
|
|
|
|
|
The full `message_catalog` (keyed by code, per language, per channel `ui|print|whatsapp|sms`, ICU MessageFormat) syncs into POS SQLite like any master — language never depends on network; per-user language applies instantly at login. Fallback: user → store default → English → (dev builds) the raw key, so gaps surface before rollout; a missing-strings report + machine-draft/human-confirm workflow makes language N a data task. Rendering rules: bundle Noto Sans + Devanagari (+ launch scripts) in app and print renderer — never trust shop-PC fonts; **ASCII digits always**, in every language, wherever money/qty appears; one shared `formatINR()` and FY-aware date formatter; layouts CI-tested against a +40% pseudo-locale (Hindi runs ~25% longer); key hints (`F8`) never localize.
|
|
|
|
|
|
Printing language policy lives in the template, not code: GSTIN/HSN/tax summary/legal footer always English (stable for auditors); header and thank-you in the store print language; item names per `{{item.name:print}}` policy (English-only default for thermal, bilingual default for A4/A5). ESC/POS text mode can't render Indic scripts — non-Latin receipts rasterize in bit-image mode; if raster pushes close-to-print past 1 s, POS suggests English thermal + bilingual A4. Fast by default, pretty on request.
|
|
|
|
|
|
### 5.4 Print system
|
|
|
|
|
|
`print_profile(counter, purpose ∈ receipt|invoice_a4|label|report, printer, paper, template, copies, drawer_kick…)` — **routing is a rule, not a question**: B2C → thermal, B2B-with-GSTIN → A4, labels → label printer; the cashier never picks a printer mid-bill (Ctrl+P on a closed bill offers the override list). Profiles are settings rows editable from POS *and* back office — support fixes a store's printing remotely; the back-office template editor can queue a test page to any counter via sync. **The iron rule: the bill is legal the moment it commits to SQLite; printing is a consequence, never a condition.** Drawer-kick fires on payment confirm, not print success — cash must open even with a jammed printer. Failed prints stay `Queued` (billing is already on the next customer), retry every 15 s, auto-drain oldest-first on recovery (prompt-first is a config alternative); a job queued > 60 s with a customer phone offers the WhatsApp e-bill from the notice rail. Reprints: same-day from F10 ungated; every copy prints "DUPLICATE" and is audit-logged; older/> 2 copies → supervisor. Silent printing always — the OS dialog exists only in Settings → Advanced.
|
|
|
|
|
|
### 5.5 Permissions UX — two gates, two looks
|
|
|
|
|
|
**Edition gates lock (and sell); role gates hide — except in-flow actions, which challenge.** Locked features render normally + lock glyph + edition badge (`<GateBadge>` reading license-token flags); activation opens the upsell panel (screenshot/15 s demo, all DB content marketing can tune) — CTA by role: owner → in-place upgrade, staff → "Ask owner" request. Staff hitting paywalls become the sales channel. Never gate mid-bill: edition-gated POS features simply don't bind keys on the billing screen; the lock lives in Settings/menu. Role gates: navigation hidden (the cashier's world contains no Accounting menu — a smaller world trains in 10 minutes), but in-flow sensitive actions stay *visible* and raise the supervisor overlay (§1.7) — hiding them breaks flow and teaches nothing.
|
|
|
|
|
|
### 5.6 Accessibility & the training reality
|
|
|
|
|
|
Design target: a cashier who reads numbers fluently, reads text slowly, may be new to computers, and must be productive in 10 minutes. Defaults: distinct audio for scan-OK / error / attention (cashiers scan without looking — sound *is* the primary feedback channel); totals the largest element on screen, tabular numerals, WCAG AA high-contrast dense theme with dark mode for night shifts; every actionable button shows its key (`भुगतान F11`), with a printed keyboard sticker sheet in the dealer install kit — the F-row is the UI for a non-reader; touch targets ≥ 48 px, quick-keys as image + name + code (recognition beats reading); counter-scope Normal/Large font setting. Back office is semantic HTML + ARIA, keyboard-complete; POS v1's accessibility investment is keyboard + audio, full screen-reader support honestly deferred.
|
|
|
|
|
|
**Practice mode (Phase 2):** a `Practice` button on the login screen, no role needed. Separate SQLite DB seeded from a read-only copy of the store's *real* item master (muscle memory forms on the real catalog) + fake customers/khata, reset nightly. Unmistakable: diagonal `PRACTICE / अभ्यास` watermark, a reserved purple top bar, PRACTICE banner on the customer display. Contained: `TRAIN-####` series (the same mechanism as onboarding step 8's training bills — never touches the GST series), prints blocked by default, no transactional sync ever leaves, drawer-kick disabled. A guided first run coaches a 5-bill script (scan, qty, unknown-barcode recovery, hold/resume, cash close, UPI, one return) with completion recorded per user ("trained ✓" in Admin); script content is catalog rows, dealer-localizable. Practice mode doubles as the dealer's in-shop demo and the upsell demo surface.
|
|
|
|
|
|
### 5.7 Errors a human can act on
|
|
|
|
|
|
Every error, every surface: **[icon] what happened (one plain sentence) · what to do (imperative, one or two steps) · action buttons that perform the fix · a small code** (`E-2101`) — the support-call handshake replacing five minutes of description. Codes: `E-1xxx` billing · `2xxx` print/hardware · `3xxx` sync · `4xxx` payment/UPI · `5xxx` GST · `6xxx` auth/license · `7xxx` master data · `9xxx` internal. `error_catalog(code, severity, title_key, body_key, actions, support_note)` — texts translated and support-editable without release; `support_note` is the internal playbook line; code frequencies feed the tickets-per-store metric.
|
|
|
|
|
|
Presentation: billing-path errors are inline only (attached to the line or beside the scan box — never modal); ambient failures go to the notice rail + chips; **blocking is reserved for exactly three conditions** — shift not open, local DB integrity failure, license hard-expiry after the grace path (grace itself is a notice; never strand a shop with a queue mid-day). No raw exceptions ever: anything unmapped renders as `E-9001` ("Your bills are safe. Restart; if it repeats, call support with this code") and auto-files a crash report. Destructive confirmations exist only off the billing path and name the object — never "Are you sure?". Day-one catalog examples: E-1102 barcode not found ([Enter] quick-add · [Esc] skip) · E-1310 batch expired ([Pick batch] · [Supervisor]) · E-1405 over credit limit ([Take payment] · [Supervisor]) · E-2101 printer not responding ("Bill is saved… prints automatically when it's back" · [Retry] · [Send e-bill]) · E-4203 UPI unconfirmed ([Mark paid] · [Wait] · [Cash instead]) · E-3302 cloud rejected changes ("Nothing is lost" · [Open sync panel]) · E-5110 IRN pending offline (notice only) · E-6201 subscription expiring ([Remind owner]).
|
|
|
|
|
|
### Phase mapping
|
|
|
|
|
|
Search (POS profile), state vocabulary, print system, and errors are MVP-critical — they *are* the counter. i18n ships its mechanism in MVP (from first commit) with Hindi content in Phase 2. The supervisor PIN overlay is MVP; practice mode and the onboarding wizard + WhatsApp digest + Day-2 checklist are Phase 2; owner app, push notifications, remote approvals, Purchase Inbox, PO/GRN, and GSTR-2B recon are Phase 3. Every metric named in this document — keystrokes per flow, scan-to-line, wizard-to-first-bill, Inbox line share, approval round-trip, push opt-out rate — is instrumented from day one: we publish what we measure. |