You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
sims-hq/docs/02-ARCHITECTURE.md

12 KiB

02 — Architecture: Offline vs Online, Sync, Stack

1. The offline/online question (the brainstorm you asked for)

Option A — Pure offline (what Classic is)

Benefits

  • Counter never depends on internet; zero latency; works in tier-3 towns with bad connectivity.
  • No hosting cost; customer feels "my data is mine, in my shop."
  • Simple mental model, simple debugging (one machine, one DB).

Defects

  • No multi-store, no owner-away-from-shop visibility, no Purchase Inbox (needs cloud).
  • Support requires reaching into the shop machine; updates are a door-to-door problem.
  • Data loss when the shop PC dies and backups were "supposed to happen."
  • Licensing/piracy enforcement is weak; subscriptions are hard to enforce.
  • No telemetry: you learn about bugs from angry calls.

Option B — Pure cloud (browser POS, server is truth)

Benefits

  • One deployment, instant updates, central data, easy multi-store, easy licensing.
  • Any device with a browser becomes a counter.

Defects

  • Internet dies → billing dies. Fatal in India. This alone disqualifies pure cloud for the counter. Competitors who went cloud-first all retrofitted awkward offline modes.
  • Every scan pays network latency; "fast counter" becomes a fight with physics.
  • Browser hardware access (thermal printers, cash drawers, scales) is painful.
  • Customer trust: "my sales data lives on your server?" is a real sales objection.

Option C — Local-first hybrid (the answer)

The rule: the counter reads and writes a local database, always. The cloud is a sync target, a backup, a reporting brain, and a licensing authority — never a dependency for billing.

Benefits

  • Billing works identically with or without internet — offline is not a "mode," it's the architecture. Sync happens in the background when connectivity exists.
  • Gets everything cloud promises: multi-store, owner app, Purchase Inbox, central reporting, subscriptions, telemetry, auto-update, off-site backup.
  • Latency at counter is local-DB latency (~0 ms network).

Defects (and how we contain them)

  • Sync is genuinely hard. → Contain it with strict design rules (§3) and buy, don't build, where possible. Sync is the single biggest technical risk of this project — it gets a proof-of-concept before anything else (see roadmap Phase 0).
  • Conflicts. → Design them away: transactional documents are append-only and counter-scoped (they cannot conflict); only master-data edits can conflict, and those get last-write-wins + full audit trail + rare-enough-to-review.
  • Version skew (store app v2.3 talking to cloud v2.5). → Versioned sync protocol, additive-only schema changes between forced upgrades.
  • Harder debugging. → Sync health dashboard per store, op logs, replayability.

Decision: Option C. Back office can lean online (it tolerates a spinner; the counter cannot), owner app is online-first with cached reads, POS is local-first, period.

2. System topology

                         ┌──────────────────────────────────────────┐
                         │                 CLOUD                    │
                         │  Multi-tenant Postgres (RLS by tenant)   │
                         │  API + Sync service    Licensing/Plans   │
                         │  Purchase Inbox (email ⇢ AI parse)       │
                         │  Reporting/analytics   Backups           │
                         │  GSP bridge (e-Invoice / e-Way)          │
                         │  WhatsApp/SMS/UPI integrations           │
                         └─────────▲──────────────────▲─────────────┘
                                   │ sync (queued ops) │ https
              ┌────────────────────┴───┐          ┌────┴─────────────┐
              │      STORE (each)      │          │  Back-office Web │
              │  ┌──────────────────┐  │          │  (browser, admin │
              │  │ Store Hub (opt.) │  │          │   & reports)     │
              │  │ local sync fan-in│  │          └──────────────────┘
              │  └───▲────────▲─────┘  │          ┌──────────────────┐
              │  ┌───┴───┐ ┌──┴────┐   │          │  Owner Mobile    │
              │  │ POS 1 │ │ POS 2 │…  │          │  (online + cache)│
              │  │SQLite │ │SQLite │   │          └──────────────────┘
              │  └───────┘ └───────┘   │
              │  printers, scanner,    │
              │  scale, cash drawer,   │
              │  customer display      │
              └────────────────────────┘
  • POS client: desktop app, embedded SQLite is the source of truth for that counter.
  • Store Hub (optional, Standard+): one machine in the store acts as LAN relay so counters see each other's bills/stock instantly even when the internet is down; it also fan-ins sync to cloud. For single-counter Lite, the POS syncs direct to cloud.
  • Cloud: multi-tenant; per-tenant row isolation; all cross-store logic lives here.

3. Sync design rules (write these on the wall)

  1. Client-generated IDs everywhere (UUIDv7). No "wait for server to get an ID."
  2. Transactional documents are immutable events. A bill, once closed, is an append-only fact. Corrections are new documents (credit note, amendment) referencing the original. This kills 90% of conflict scenarios and satisfies audit requirements in one stroke.
  3. Invoice numbering is per-counter series — e.g. ST1C2-000123 (GST allows multiple series; ≤16 chars, unique & consecutive within a series per FY). Counters can therefore number bills offline with zero coordination.
  4. Outbox/inbox pattern: every local write that must reach the cloud goes into a local outbox table in the same SQLite transaction as the write itself; a background worker drains it with retries + idempotency keys. Downstream (cloud→store) is a cursor-based pull of changes since last sync.
  5. Master data flows down, transactions flow up. Masters are edited in back office (cloud) and pushed to stores; store-side master edits (allowed on lower plans) sync up with last-write-wins + audit log.
  6. Stock is derived, never synced as a number. Each store's stock = fold of its local events; cloud recomputes global views. Never ship "stock = 47" between nodes — ship the movements.
  7. Clocks lie. Order by (lamport counter, device id), keep wall-clock only for display.
  8. Sync must be observable: per-store last-sync time, pending-op count, error surface in both the POS status bar and a support dashboard.

Build vs buy: evaluate PowerSync and ElectricSQL (Postgres ⇄ SQLite sync engines) against a hand-rolled outbox in the Phase-0 spike. Rule of thumb: buy the pipe if it fits, but the semantic rules above stay ours either way.

4. Multi-tenancy & audit

  • Single Postgres cluster, tenant_id on every row, enforced with Row-Level Security; the API sets tenant context from the auth token. (Schema-per-tenant only if a large Enterprise chain demands isolation — don't start there.)
  • Tenant = the client business; stores, counters, users hang off it. All client-specific compliance settings (GST scheme, FY, invoice formats, rounding rules) are tenant config.
  • Audit: append-only audit_log (who, what, before, after, when, where) written in the same transaction as the change, synced up, never deletable — this covers the MCA audit-trail requirement for company clients and your own support forensics.
  • Backups: continuous cloud PITR + nightly encrypted per-tenant export; store-side SQLite snapshots shipped to cloud so a dead shop PC costs minutes, not the business.
  • DPDP Act hygiene: customer phone numbers are personal data — encrypt at rest, purge on request, keep telemetry free of PII.

5. GST/compliance layer (data-driven, never hard-coded)

  • GST rates, cess, and slab structures are dated configuration, not code — the Sept 2025 GST 2.0 slab overhaul is the proof that rates change under you.
  • e-Invoice (IRN) and e-Way bill via a GSP aggregator API (ClearTax / Masters India / similar) rather than direct IRP integration — one decision in 06-DECISIONS.md.
  • Queue-and-forward: bills that need IRN but were made offline are queued and registered when online, within the reporting window; the POS shows pending-IRN status.

6. Tech stack — recommendation

Primary recommendation: TypeScript end-to-end. One language, the largest hiring pool in India, one skill set across all four surfaces, and the web back office shares components with the POS UI.

Layer Choice Why
POS desktop Electron + React + SQLite Mature hardware story on Windows (ESC/POS, serialport for scales/drawers/displays), offline SQLite, auto-update solved, huge ecosystem. Tauri is the leaner alternative — decide after the Phase-0 hardware spike.
Back office React SPA (same design system as POS) Browser-only, no install; admins tolerate online-only.
Owner app React Native Shares TS models/API client; Android-first (your market), iOS later.
API/backend Node.js + NestJS + PostgreSQL + Redis Structured enough for a long-lived product; trivially hireable.
Sync PowerSync / ElectricSQL or custom outbox — Phase-0 spike decides Biggest risk, prove it first.
AI (Purchase Inbox) Claude API (vision + structured output) behind our own parsing service PDF/photo invoice → structured draft purchase; item-matching memory in Postgres.
Infra Managed Postgres + containers on an Indian region (data residency comfort) Mumbai region; boring and reliable.

Note (D12, decided 2026-07-09): the store-tier DB remains the existing Oracle 12c for now — it is shared infrastructure running other systems. Counters keep their local SQLite buffer (offline bill queue + item cache); the Store Hub role is played by the Oracle box; sync is a custom outbox (D3 → build). Adapted topology, guardrails, and revisit triggers: 07-DB-AND-CONFIG.md §3.

Staging (D14, 2026-07-09): the cloud tier in the topology above is switched on after the local-only v1 ships. From the first build, all writes carry client UUIDs and dormant outbox rows so cloud enablement is a switch-on, not a migration.

Considered alternatives (kept honest in one line each):

  • Flutter everywhere: one codebase POS-desktop + Android-POS + owner app; weaker Windows-desktop/hardware maturity and you'd still want a web back office. Strong option if Android POS terminals are a v1 target — see 06-DECISIONS.
  • .NET (WPF/WinUI + ASP.NET Core): best raw Windows hardware story; smaller shared-code story across web/mobile, second hiring pool. Solid, less leverage.

7. Hardware integration checklist (POS)

Barcode scanners (keyboard wedge — free), ESC/POS thermal printers 2"/3" (network/USB), A4/A5 laser printing, cash drawers (printer-kick), weighing scales (RS-232/USB, and label-scale EAN-13 2x barcodes), customer-facing display (second monitor or pole display), UPS-friendly crash recovery (journaled SQLite, resume mid-bill after power cut).

8. Non-functional targets

Metric Target
Scan/type → line rendered < 150 ms
Item search (50k SKUs) < 150 ms
Bill close + print spool < 1 s
POS cold start to billable < 5 s
Offline duration tolerated Unlimited (sync catches up)
Store→cloud sync lag when online < 5 min p99
POS crash data loss Zero committed lines (journaled writes)