Northwest Inventory Services · 2024 to now
Quantify Inventory Services
A multi-system retail inventory counting and tracking platform. An offline-first Windows admin app, a companion Android app for data collection, and a cloud-backed master for synchronising across the fleet and data retention.
.NET 9 · React 19 · React Native · Kotlin · TypeScript · PostgreSQL · Realm · SQLite · Cloud Run · Cloud SQL · Firebase Hosting · QuestPDF
Three surfaces, one API
The admin app is React and TypeScript, served by a .NET API that runs on the field laptop itself against a local Postgres and installs as a signed MSIX. Store configuration, counting data as it arrives, printing, reconciliation, reporting and exports all live there.
The phone app is React Native with hand-written Kotlin underneath for the parts React Native does not reach. It keeps its data in Realm, with a local SQLite copy of the retailer's product file for barcode lookups, and drives a wired ten-key and a bluetooth scanner.
The cloud side is the same .NET API in a container on Cloud Run against Cloud SQL, with the admin app on Firebase Hosting. It holds the master copy and the history. The web client's entire API layer is generated from the OpenAPI document, so a change to a backend contract shows up as a TypeScript error rather than a runtime surprise.
One codebase, two build targets
Stores do not have internet you can rely on, so the whole system had to work without it. That shaped the architecture more than anything else. The API is one project multi-targeted two ways: Linux in a container for the cloud, and Windows for the field laptops, with a compile flag switching in the things only a laptop needs. Starting its own database container on boot, broadcasting over UDP so the phones can find it, and printing straight to the printer in the room.
The phones talk to the laptop over a local network with no route out. Nothing depends on a connection until the day's counts get synced up afterwards.
Keeping a fleet in sync
One master, many replicas. Only locked inventories sync, and a laptop pulls just the stores its office is scoped to, with the option to borrow one from another office. Underneath that sit the parts that make distributed editing survive: squashing, tombstones so a stale client cannot resurrect a deleted record, and a config identity system where every node keeps a permanent id, so historical counts stay attached to their area and section through any number of renames.
The fleet is also never all on the same version. Laptops cannot be updated at once and some are deliberately left behind, so an older phone build has to keep working against a newer API and migrations have to land in an order that strands nobody. A version gate on the API rejects writes from a client whose version does not match, with an HTTP 426, so an out-of-date laptop cannot pull a config down, round-trip it through a model that does not know half the fields, and push it back with those fields quietly gone.
Managing the devices themselves
A handset provisions itself as Device Owner from a QR code on a factory-reset phone, locks into kiosk mode so it cannot wander off into a browser mid-count, and updates itself from the API without anyone touching it. The APK is served by that same API in range requests, because store wifi has a habit of dropping partway through a large download.
There is a command channel alongside it for the manager on site: presence and heartbeats, per-device counting stats, remote rename, and an outbox that collapses duplicate commands so tapping a button five times does not make a phone beep five times.
Testing the part that breaks
Sync is where the bugs are, so that is where the tests are. Thirty-eight end-to-end journey tests drive real multi-laptop scenarios against a harness that injects failures on purpose: a peer that throws on its first push, one that cancels partway through a pull, one that refuses anything over a size limit.
599 tests in total, run against a real Postgres in a container rather than an in-memory stand-in, because in-memory databases lie about unique indexes, transactions and decimal precision, and this system counts money. On top of that, Playwright covers the flows a wrong answer would be expensive in: locking an inventory, reconciling orphaned data, moving counts between sections, and the rounding modes.
Reporting and exports
Eleven server-rendered PDF documents, from the posting sheets the crew audits against through to the cover sheet and area breakdown a store manager signs. Alongside those, CSV and PDF export from every table, user-defined export templates with a rule builder for filtering, and import and export packs with a versioned manifest that remaps ids on the way in.
Where it stands
In the field since June 2024, across roughly 170,000 lines of hand-written code in the three applications. The product line goes back to a v1 I also wrote, so this codebase is its third generation.