Case study · Enterprise warehouse inventory
SmartScan
Every tag, accounted for.
I designed and built SmartScan, an RFID middleware platform that sits between Android handhelds and the ERP, beginning with NetSuite. It tracks every physical unit by its tag, runs the warehouse floor workflows, and posts only completed, consistent transactions to the ERP.
- Role
- Freelancer · end-to-end ownership
- Client
- InnovateNex
- Timeline
- Jun – Sep 2026 · live in production
- Surfaces
- REST API · Admin web · Android · AWS
API endpoints
Postgres tables
Permission modules
Handheld workflows
The problem
The floor moves faster than the ERP.
A handheld reader picks up dozens of tags a second. The ERP wants clean, complete transactions, one at a time, and it takes its time to answer. Wire the two together directly and you get timeouts on the floor, quantities typed in by hand, and no record of which physical unit went where.
I designed SmartScan to own the warehouse state between the two. Operators scan against it at floor speed, while the ERP remains the financial system of record and receives only the finished result.
No typed quantities
A quantity is the count of tags physically scanned. Operators never key in a number.
Nothing gets lost
Not on a dead battery, a dropped Wi-Fi link, or a Redis restart mid-sync.
Not married to one ERP
The core contains no ERP-specific code; each ERP is integrated through a registered connector class.
What I owned
Whiteboard to warehouse floor.
InnovateNex brought me in as a freelancer to deliver SmartScan for one of their clients. I owned the whole of it: the system design, the ERP integration, what shipped, the handheld experience, and the build and deploy.
Architecture
Designed the system before a line of it was written.
- Made the middleware the owner of warehouse state, with the ERP remaining the financial system of record: the decision the rest of the architecture depends on.
- Chose tag-level tracking over quantities, a Postgres outbox ahead of Redis, and database-enforced locks.
- Made the ERP a pluggable connector, and multi-tenancy a schema per tenant, so one deployment serves many companies, on any ERP.
Under the hood
Correctness, enforced by the database.
Built for the handheld
Operators work on an Android RFID reader. I integrated the reader through a native Kotlin module and scoped every operation to the operator's warehouse.

One statement, every edge case
Scanned tags are claimed in a single conditional UPDATE. Any tag it does not return is rejected, with a specific reason for each.
UPDATE epc_tags SET state = 'in_stock' WHERE epc = ANY(:scanned) AND item_id = :line AND state = :expected RETURNING epc;
Session locks
A partial unique index enforces one active session per bin or order.
Durable outbox
Every ERP job is written to Postgres before dispatch through Redis, with retries and backoff.
Granular permissions
Permissions are defined across more than 15 modules and three actions. Stock edits are separated from catalogue edits, and force-releasing a lock requires delete rights.
Schema per tenant
Each tenant is provisioned with a dedicated schema, cloned from a template.
Survives a dead battery
The scan buffer is synchronised to the server as a draft, so no work is lost if a device's battery fails.
The life of one tag
EPC E280·6894·A3F1
Scroll to follow a single unit through the warehouse. Every hop is one row in its append-only history.
01 / 06 · CSV import · batch B-0142
Imported
The tag enters the registry bound to one SKU. It exists on paper, not on a shelf — and a re-import can't rebind it once it has history.
epc_tags.bin_history
On the floor
Every workflow, one scanning model.
One scanning model
Every workflow starts the same way — pick a document or a bin, scan, review, complete — and every one is scoped to the operator's warehouse. Once an operator learns one, the rest follow the same pattern.
Back office
When the ERP rejects a sync, you know why.
I built the admin dashboard, where supervisors map records to the ERP and monitor the sync queue. Each failed post retains the exact payload sent and the ERP's response, so integration issues can be resolved in minutes rather than through log searches.

Architecture
The middleware is the only thing that talks to the ERP.
The ERP pushes items, bins and orders in over an API key (Flow A). Completed receipts, fulfillments, transfers and count variances go back out through the outbox (Flow B).
Key design decisions
The ledger.
Track tags, not quantities
Every quantity is derived from tags that were physically scanned, which eliminates manual entry errors.
One conditional UPDATE claims the tags
Wrong item, duplicate, already-shipped and two operators racing are all resolved by the database in one statement.
Locks live in the database
A partial unique index allows one active session per bin or order. A second operator receives a conflict response rather than a race condition.
Postgres outbox in front of Redis
Every ERP job is written to Postgres before it's queued. Losing Redis delays a post; it never drops one.
Keep the payload of every ERP attempt
When the ERP rejects a post, the queue shows what was sent next to what came back, so rejected posts can be diagnosed without searching the logs.
Schema per tenant, fail-closed
Each tenant has its own Postgres schema. A request without a resolved tenant is refused outright.
SmartScan was my first project working directly with RFID hardware, from the reader SDK to handling tag reads at floor speed. It also deepened my approach to integration design: synchronising with NetSuite idempotently, so that every transaction stays consistent across both systems and every change remains fully auditable.
Stack
API
Admin web
Handheld
Cloud
Honest limits
- One connector so far. The abstraction is proven against NetSuite only; SAP is the next class to write.
- Android only. The reader SDK is Android, so the handheld app is too.
- No inbound quantity sync. If someone edits a stock quantity by hand in the ERP, SmartScan doesn't pick it up — stock is expected to move through the floor, where every change is scanned.