What was wrong
Job cards were written on paper, in a workshop or on the roadside, then typed up later by somebody who was not there.
Two things follow from that, and both cost money. The times were approximate, because they were remembered rather than recorded. And a card that never made it back was a job that had effectively not happened, which meant it was never invoiced.
What we built
An Android app that assumes offline is the normal state rather than an error.
Every action writes to local storage first and shows as saved immediately. The device time of the press is stamped and never overwritten, and the server records separately when it actually arrived. Both are kept forever, because the gap between them is sometimes the interesting part.
A queue drains on every reconnect rather than at end of day, with retries and backoff, and anything that fails to send is surfaced on the device instead of being swallowed. The office screen shows when each device last checked in, so a phone sitting in a bakkie with a day of work on it is visible the same morning rather than discovered a week later.
What changed
The record is now a by-product of doing the job rather than a second job done afterwards. Time on site is measured rather than estimated, and work that used to quietly evaporate between the roadside and the office gets invoiced.
The offline queue built here is the pattern we reuse whenever the work happens somewhere the signal does not.
Runs on
React Native
Expo
FastAPI
PostgreSQL
Android Enterprise