Appendix — Data Flows

Top-to-bottom and bottom-to-top traces

Layer context: Master Map (UI → edge → API → DB → Bridge).

CEO Summary

Data flows trace user actions from UI to database and back. Top-to-bottom: User clicks → API → service → DB. Bottom-to-top: DB row → service → API → component → user. Essential for debugging and onboarding.

POS Sale — Top-to-Bottom (User Clicks Pay)

sequenceDiagram participant User participant ModernPOS participant API participant POSRoutes participant CoreInvoicing participant DB User->>ModernPOS: Click Finalize ModernPOS->>API: POST /api/pos/carts/:id/finalize API->>POSRoutes: requireSessionAuth, tenantResolver POSRoutes->>CoreInvoicing: createPOSSaleInvoice CoreInvoicing->>DB: INSERT Invoices, InvoiceLineItems DB-->>CoreInvoicing: OK CoreInvoicing-->>POSRoutes: invoice POSRoutes-->>ModernPOS: { ok, invoice } ModernPOS-->>User: Receipt, cart cleared
  1. User — Clicks Finalize in ModernPOS
  2. Component — ModernPOS.jsx → finalize handler
  3. APIposApi.finalize / client.post('/api/pos/carts/123/finalize') with Idempotency-Key + PPO headers when using posApi
  4. Middleware — tenantResolver → requireSessionAuth → posRouter
  5. Route — POST /api/pos/carts/:id/finalize handler
  6. Service — CoreInvoicingService.createPOSSaleInvoice
  7. DB — INSERT Invoices, InvoiceLineItems; UPDATE pos_carts status=paid

POS Sale — Bottom-to-Top (DB Row to User)

  1. Bottom: pos_carts row updated with items, payments, status: paid
  2. Service: CoreInvoicingService.createPOSSaleInvoice inserts Invoices, InvoiceLineItems
  3. Route: POST /api/pos/carts/:id/finalize handler
  4. Middleware: tenantResolver → requireSessionAuth → posRouter
  5. API: Frontend client.post('/api/pos/carts/123/finalize')
  6. Component: ModernPOS.jsx → finalize handler
  7. Top: User sees receipt, cart cleared

Login — Top-to-Bottom

sequenceDiagram participant User participant LoginForm participant API participant AuthRoutes participant Session participant DB User->>LoginForm: Submit email + password LoginForm->>API: POST /api/auth/login API->>AuthRoutes: (no auth yet) AuthRoutes->>DB: Query Users, Tenants AuthRoutes->>Session: createUserSession Session-->>AuthRoutes: OK AuthRoutes-->>LoginForm: { success, user, tenant } LoginForm-->>User: Redirect to dashboard
  1. User — Submits email + password
  2. Component — LoginForm / RegisterLoginScreen
  3. API — POST /api/auth/login
  4. Route — auth.routes.js
  5. DB — Master Users, tenant Employees
  6. Session — createUserSession(req, { user, tenant })
  7. Response — { success, user, tenant } → redirect

Kitchen tickets — POS finalize / send-to-kitchen → KDS poll

sequenceDiagram participant Staff participant POS as POS UI participant POSR as pos.routes.js participant KOps as kdsOps.service.js participant DB as Tenant DB kitchen_tickets participant KDS as KitchenDisplay.jsx participant KAPI as kds.routes.js Staff->>POS: Finalize or Send to kitchen POS->>POSR: POST finalize or POST send-to-kitchen POSR->>KOps: ensure schema + load routing settings KOps->>DB: kds_ops_settings, kds_category_routing POSR->>POSR: buildKitchenTicketsPayloadFromCart POSR->>DB: INSERT kitchen_tickets (per station if split) POSR-->>POS: ok loop Poll KDS->>KAPI: GET /api/kds/orders?lane=FIRE KAPI->>DB: SELECT by orgId + lane + optional station KAPI-->>KDS: tickets end Staff->>KDS: Bump ticket KDS->>KAPI: POST bump KAPI->>DB: UPDATE lane

Chapter: 09 — KDS & Kitchen. Admin configures category-to-station rules at /admin/kitchen-displays (GET/PATCH /api/admin/kds/*).

Signage — Admin save → bridge → local refresh (optional)

sequenceDiagram participant Admin as Admin UI participant API as PUT /api/signage/ads/:id participant DB as Tenant DB participant BCM as bridgeConnectionManager participant WS as Bridge WS participant Player as Local player Admin->>API: Save ad API->>DB: UPDATE SignageAds DB-->>API: OK API->>BCM: pushEnvelope(signageUpdate) BCM->>WS: catalogChanged JSON API-->>Admin: 200 WS->>Player: optional: reload playlist Player->>API: GET /api/signage/playlist/... API-->>Player: items

Chapter: 26 — Signage & Displays. If the bridge is offline, the push is skipped; cloud data is still consistent.

Signup — Top-to-Bottom

Operations note: Set DISABLE_PUBLIC_SIGNUP to block new signups and provisioning (403 on POST /api/public/signup; SPAs read GET /api/public/signup-status). See 01 — Infrastructure.

  1. User — Fills signup form (company, email, plan)
  2. Component — Dashboard or public-site /signup (or HTML fallback GET /api/public/signup-form)
  3. APIPOST /api/public/signup (JSON); optional ?redirect=1 for form POST redirect flow
  4. Routepublic.signup.routes.js
  5. ServiceprovisionTenant (create tenant main, analytics, HR DBs; owner user; routing)
  6. DB — Master Tenants, Organizations, Users; new tenant databases from templates
  7. Response — JSON 201 or redirect to /signup/success

Modern POS — device settings and vertical modules

After register login, Modern POS loads device context so optional modules (e.g. fuel) can render. Module IDs and forecourt JSON are read from the device row via GET /api/pos/settings (not invented client-side).

sequenceDiagram participant MP as ModernPOS.jsx participant API as GET /api/pos/settings participant DB as Devices + Locations participant PM as posModules.js participant FS as fuelSite.js participant Host as PosModuleHost MP->>API: device_key from session / selector API->>DB: SELECT device metadata DB-->>API: row API->>PM: parsePosModulesFromDeviceRow PM-->>API: e.g. fuel API->>FS: parseFuelSiteFromDeviceRow FS-->>API: fuelSite or null API-->>MP: posModules + fuelSite + catalogs + location MP->>Host: moduleIds + fuelSite Host-->>MP: FuelModulePanel if fuel

Admin configuration: 13 — Bridge & Devices (device registry, metadata). Chapter: 06 — POS & Invoicing.

Modern POS — register status, PIN session, and sale gate

After platform login, Modern POS loads GET /api/pos/register/status with device_key and (from PIN session) staff_id. The UI sets canTransactOnRegister when the open shift is owned by the session user or the PIN staff matches shift.staffMemberId / shift.staff.id. blockIfRegisterNotReadyForSale uses that flag. If a saved cart exists but the first status response looks closed, the client may retry status briefly before prompting opening float. PIN + cart ids persist in localStorage (pos_pin_session_v1:*, pos_active_cart_v1:*) via posLocalPersistence.js.

sequenceDiagram participant MP as ModernPOS.jsx participant LS as localStorage PIN session participant API as GET /api/pos/register/status participant DB as register_shifts MP->>LS: read staff id + device key MP->>API: device_key + staff_id (if PIN) API->>DB: open shift by org+device or device fallback DB-->>API: shift row + staff resolution API-->>MP: isOpen, isOpenForCurrentUser, shift.staffMemberId MP->>MP: canTransactOnRegister, applyRegisterStatusFromPayload Note over MP: blockIfRegisterNotReadyForSale gates cart/tender/finalize

Markdown: Appendix B — Register and shift before cart.

POS mutation — online vs queued (same idempotency)

Applies to cart item/tender/customer/finalize and similar POST/DELETE under /api/pos/carts/... when using posApi or tryOnlineThenQueue.

flowchart LR subgraph ui [POS UI] M[ModernPOS Standard Kiosk Storefront] end subgraph fe [frontend src] PA[posApi] TO[tryOnlineThenQueue] ME[mutationEnvelope] Q[queue.js] IDBQ[(IndexedDB pawspos_offline)] end subgraph be [backend] POSR[pos.routes idempotency] INV[services] end M --> PA PA --> ME PA --> TO TO -->|2xx| POSR TO -->|net or 5xx| Q Q --> IDBQ IDBQ -->|replay| POSR POSR --> INV

Key Files for Tracing