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)
- Client-generated IDs everywhere (UUIDv7). No "wait for server to get an ID."
- 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.
- 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. - 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.
- 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.
- 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.
- Clocks lie. Order by (lamport counter, device id), keep wall-clock only for display.
- 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_idon 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) |