Fresclean POS · Chapter 7 of 8
Clocking in from bed
Jovanus Hartono2026
On this page
Staff clock in and out on their phones. The attendance screen preselects the nearest store, and clocking in checks that the phone really is near the store they picked.
That check answers an obvious question: what stops someone from clocking in from bed? Clocking in used to be just a button, and putting a rule behind it took two attempts.
A flag nobody reads
My first answer was gentle. Clocking in would record how far the phone was from the store, and anything over 1 km would be flagged on the shifts page. Distance alone would never refuse anyone, because indoor GPS is routinely a kilometre wrong about someone standing right at the counter.
I threw it out before it merged. As I wrote when I dropped it, "a flag only bites if a manager reads a column," and nobody was going to read this one. Someone clocking in from home would get a shift and get paid exactly like someone at the counter. And it meant keeping every worker's location forever, for a screen nobody opened.
The benefit of the doubt
The version that shipped refuses clock-ins more than 3 km from the store. There's a catch, though. Phones are vaguer about where they are than you'd think. Indoors, with no sky and no wifi, a phone might only manage "somewhere within a few kilometres of here", and 7 a.m. is the worst time for that to lock someone out of their shift.
The phone's position comes from the browser's Geolocation API, and every answer comes with an accuracy in metres. Think of it as a circle: the phone is fairly sure it's somewhere inside, and the worse the signal, the wider the circle. The POS sends that radius along with the coordinates:
navigator.geolocation.getCurrentPosition((position) =>
setState({
status: "ready",
coordinates: {
accuracy_m: position.coords.accuracy,
latitude: position.coords.latitude,
longitude: position.coords.longitude,
},
})
);
The server works out the distance to the store with the haversine formula, the straight-line distance over the curve of the Earth. Then the worker gets the benefit of the doubt. The server only refuses when even the near edge of the circle is out of range:
const nearestPossible = distance - coordinates.accuracy_m / 1000;
if (nearestPossible > CLOCK_IN_RADIUS_KM) {
throw new BadRequestException(
`You are ${distance.toFixed(1)} km from ${store.name}. Clock in once you are at the store.`
);
}
Say a phone reports 4.2 km from the store with a 500 m circle. The near edge of that circle is 3.7 km away, past the 3 km limit, so the clock-in is refused.
A wide circle can only ever let someone in, never keep them out. I thought about refusing when the phone is too unsure to judge, and about asking the worker to step outside and try again, but both punish someone for their phone's bad morning.
Why 3 km and not 1? A kilometre is inside the error a phone can produce indoors, so a 1 km ring would refuse people standing at the counter. And 3 km is, as I put it, "a number a manager can quote back to a worker without an argument." That's also why the refusal names the distance and the store. A worker who gets turned away needs to know whether they're actually far away or the app is broken.

What it doesn't do
A few rules sit around the check:
- Sharing your location is required. If permission is denied or the phone can't get a A position the phone has worked out, from GPS, wifi or nearby cell towers., the button stays disabled and says why. There's no "clock in anyway" option and no override for admins, because a check anyone can skip by tapping Deny wouldn't stop anyone.
- Nothing about the location is stored. No coordinates, no distance, no accuracy. If a shift exists, it passed the check. Keeping the numbers would have meant a permanent record of where each worker sleeps, for a column nobody would query.
- The distance is measured to the store the worker picked, not the nearest one, so picking a branch across town by mistake gets caught while they're still standing there. The app does preselect the nearest store, but it works that out on the phone from a list it already has.
- Couriers skip the whole thing. Their day doesn't start at any one store, and they're allowed to clock in anywhere.
- Clocking out is never refused. The check is there to show someone turned up, and refusing a clock-out would leave someone with a dead battery stuck in an open shift.
I also wrote down what the rule can't do. Location comes from the phone, so this only stops casual clocking in from bed. And now that store coordinates decide who can start work, a store pinned in the wrong place would stop that store's staff from clocking in at all, so the coordinates got a check script of their own.
The shift nobody closed
Forgotten clock-outs needed their own fix. A job at midnight closes shifts that were left open and marks them as auto-closed, so the worker isn't locked out of their next shift. It's a Vercel cron job, a scheduled request, set to 0 17 * * *: 17:00 UTC, which is midnight in Jakarta. But it deliberately reaches back a full day. Someone who started a late close at 22:30 is still working when midnight comes, and closing their shift then would kick them out mid-shift and file it as forgotten.
That means yesterday's forgotten clock-out can still be open the next morning. And a job that runs once a night can simply not run, which would leave a worker unable to start at 7 a.m. So instead of refusing, clocking in closes the old shift first and carries on.