No description
  • Python 38.5%
  • Svelte 27.5%
  • TypeScript 21.5%
  • CSS 6.7%
  • Rust 4.2%
  • Other 1.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-21 22:30:32 +08:00
backend Add named/unknown UPCs 2026-10-21 22:30:32 +08:00
packaging Initial version 2026-09-29 20:16:57 +08:00
receipts Initial version 2026-09-29 20:16:57 +08:00
ui Add named/unknown UPCs 2026-10-21 22:30:32 +08:00
.gitignore Initial version 2026-09-29 20:16:57 +08:00
branding.py Initial version 2026-09-29 20:16:57 +08:00
CHANGELOG.md Add named/unknown UPCs 2026-10-21 22:30:32 +08:00
config.json.example Initial version 2026-09-29 20:16:57 +08:00
config.py Initial version 2026-09-29 20:16:57 +08:00
logo.png Initial version 2026-09-29 20:16:57 +08:00
main.py Initial version 2026-09-29 20:16:57 +08:00
portal_client.py Add named/unknown UPCs 2026-10-21 22:30:32 +08:00
pricing.py Initial version 2026-09-29 20:16:57 +08:00
pyproject.toml Initial version 2026-09-29 20:16:57 +08:00
README.md Initial version 2026-09-29 20:16:57 +08:00
theme.py Initial version 2026-09-29 20:16:57 +08:00
uv.lock Initial version 2026-09-29 20:16:57 +08:00

Member portal POS till

Self-service kiosk for workshop catalogue, tabs, guests, and card-terminal payments.

UI: Tauri 2 + Svelte (WebView2 on Windows)
Backend: Python FastAPI sidecar on 127.0.0.1 (portal client, pricing, receipts, ESC/POS)

Requirements

Setup

cd helpers/pos
uv sync
cp config.json.example config.json
# edit config.json — portal URL, API key, terminal id

cd ui
npm install

Run (development)

From helpers/pos/ui:

node ./pos-dev.mjs

Or npm run pos (same Node entrypoint). Prefer node ./pos-dev.mjs over npm run tauri dev on Windows so Ctrl+C is not wrapped by npm.cmd (which can prompt to terminate a batch job).

Tauri starts the Vite UI and spawns the Python sidecar via uv run python -m backend.app with a random loopback bearer. The webview never sees api_key. The sidecar runs in its own process group so Ctrl+C is handled by the till, which stops the sidecar tree cleanly.

Sidecar alone (browser/debug):

cd helpers/pos
export POS_SIDECAR_BEARER=dev-token   # optional in open-dev mode
uv run python -m backend.app
cd ui
npm run dev

Manual smoke path

  1. Confirm portal online (no offline lock). Catalogue tiles appear.
  2. Tap items (tier/quantity pads via soft keyboard). Lines stay local until sign-in.
  3. Sign in: claim QR → approve on phone, or scan wallet QR. Theme/avatar apply; cart flushes to open tab.
  4. Edit/remove a line (confirm modal). Confirm any estimate if present.
  5. Member: Pay on EFTPOS (complete or cancel on terminal overlay), or Send invoice, or Keep tab open.
  6. Guest path: Continue as guest (optional name) → EFTPOS only; 18+ cart greys pay with login required.
  7. Idle ~90s or Keep tab open → session clears; flushed tab lines remain on portal.
  8. Triple-tap brand → PIN → admin custom amount; triple-tap again for cut / test receipt / reprint / Exit POS.

Configuration

Copy config.json.example to config.json (gitignored). Override path with POS_CONFIG_PATH for packaged installs (keep config outside the read-only bundle). The sidecar exits on startup if config.json is missing or invalid — it will not fall back to a placeholder portal URL.

Optional resolution (e.g. "1024x768") opens a windowed client area of that size for hardware-preview debugging (Windows title bar is extra). Omit or set null for fullscreen kiosk mode.

Receipt printer

Register receipts always return ASCII for the success overlay. To also print on the USB ESC/POS printer:

"receipt": {
  "enabled": true,
  "printer_type": "escpos",
  "usb_vendor_id": "0x04c5",
  "usb_product_id": "0x1104",
  "paper_width_chars": 48,
  "logo_width_dots": 576
}
  • Set usb_product_id from the device (see below). Example product ID 0x1104 is a common “line thermal printer” listing — confirm on your unit.
  • paper_width_chars (default 48) is the column width for on-screen and printed text.
  • Logo for printed receipts is org_logo_wide.png fetched from the portal (/static/img/…) into the sidecar on launch (and refreshed with the till bootstrap). It is converted to 1-bit black and white before printing. The header uses portal org_logo.png the same way. On-screen ASCII still trims Ref: at the first -; the printer also adds a CODE128 barcode of the full reference.
  • When enabled is false or printer_type is ascii_debug, no USB traffic is sent.

Find USB IDs

  • Linux: lsusb (look for Fujitsu / the printer line, e.g. 04c5:1104).
  • Windows: Device Manager → printer → Properties → Details → Hardware Ids (VID_xxxx&PID_yyyy).

Windows USB access: the till bundles libusb-package so pyusb can talk to the device. Older printers need a WinUSB (or libusb) driver binding — e.g. via Zadig — if Windows only has a spooler printer driver and raw USB open fails.

Packaging

  1. Build the Python sidecar: packaging/build-sidecar.ps1
  2. Copy dist/pos-sidecar/ into the till install tree (or wire as Tauri externalBin — see script comments)
  3. From ui: npm run tauri build
  4. Place config.json next to the install / set POS_CONFIG_PATH

Project layout

  • ui/ — Tauri + Svelte kiosk shell
  • backend/ — FastAPI sidecar (/health, catalogue, pricing, identity, tab, payments, receipts)
  • portal_client.py / pricing.py / config.py / theme.py / receipts/
  • main.py — deprecated stub (prints how to launch Tauri)

Security notes

  • Sidecar binds 127.0.0.1 only
  • Tauri injects a one-time bearer; all sidecar routes require it when POS_SIDECAR_BEARER is set
  • api_key never leaves Python

API scopes

Recommended key scopes: workshop_tab:read, workshop_tab:write, workshop_tab:settle, catalogue:read, identity:resolve, identity:claim, payments:intent, print:read, print:write.