Professional
Seiri | Attendance tracking
Staff clock in with a photo and a location from a site or a route. The record is kept on the phone and uploads when there is signal, one clock-in a day.
- Mobile
- Organisation
- Kaizen Apps CRopens in a new tab
- Role
- Development
- Period
- 2026-05 — 2026-08
Stack
- React Native (Expo)
- TypeScript
- SQLite
- Next.js
- MySQL
- Render
Context
An attendance clock-in app for the companies running Kaizen’s systems. Staff clock in with a photo and a location. The entry is stored on the phone and uploaded when there is a connection, because much of the work happens on site and on the road.
Problem
Without a system, entries are written in a notebook or taken down name by name by a supervisor. At sites with large headcounts that queue consumes productive time every morning, before anyone reaches their post.
The goal was twofold. Clocking in should take seconds and require nobody taking notes, and the resulting record should stand on its own.
Each entry carries its time, its location and its identity check. Payroll is computed on data that does not depend on anyone confirming it afterwards.
Technical decisions
Offline, two devices can clock in the same worker without seeing each other. On reconnecting, both send an entry that was valid according to what each one observed. The previous scheme counted the day’s entries and then inserted, which are two steps with no guarantee between them.
Validation moved inside an InnoDB transaction at REPEATABLE READ isolation, which locks the day’s rows with SELECT ... FOR UPDATE before deciding.
Architecture
The phone writes to SQLite first and queues the entry. A background task drains the queue against the API when there is a connection. The token is held in the system’s secure store, with a migration from the location used previously.
The four rules that decide whether the entry is accepted are applied on the server.
The transaction locks the day's entries before deciding. Two simultaneous syncs can no longer both pass validation, because the second waits for the first to finish.
The day's window is computed in the country's timezone, not the process timezone. The server runs in UTC, and without this rule an entry at 23:30 local time would count towards the next day.
The losing entry is not discarded. It is stored and flagged for manual review, with its device and its time, so somebody can decide which one was right.
If the recognition service does not respond, the entry is still accepted and recorded as unverified. An infrastructure failure cannot stop somebody logging their shift.
Result
Staff clock in within seconds, with a photo and a location, with nobody taking notes and without depending on a signal. The entry uploads on its own as soon as there is a connection.
The geofence formula exists twice, on the server and on the client, because the client has to evaluate it offline. There is a test whose only job is to fail if the two versions stop agreeing.
What I learned
Counting the day’s entries on the client does not prevent a duplicate: each device keeps its own count and neither sees the other while they are offline. Inside the server transaction the conflict still happens, but it is detected and sent to review rather than settled by arrival order.