6.6 KiB
MEGA Clock: new-day scans and missed scan-outs
Agreed operator behavior
The installed phone app keeps its existing SITE MANAGER ACCESS PIN page, Person scanner, confirmation screen, and API request shape. Workers do not sign in. A scan after midnight in Africa/Johannesburg starts a new IN for that local day, even if an earlier day's IN has no matching OUT. The older scan remains unresolved; the new day's later scan closes only the new day's attendance.
The on-site dashboard has a fourth Needs attention column. It shows unmatched earlier-day IN scans with no seven-day expiry. A worker can be both on site today and in Needs attention for an older day. Clicking a card asks for the actual leaving date, leaving time, and a reason. The original QR event is retained. A correction creates a separate audit row and updates or creates the direct time total for the original local day once.
There is no new login screen. The manual correction route checks the existing shared PIN against server APP_PIN and requires a same-origin request. The PIN is currently displayed on the app's login page and does not identify an individual operator; this is the current app's shared-PIN trust level. A future main-app login should replace this with a named user and site authorization before relying on individual accountability.
Implementation boundaries
/api/clock/employee-lookupand/api/clock/scankeep the request and response fields used by installed phones. They compare the last scan's South African local date with today's local date. A prior-dayINyieldsnextDirection: "in"and creates a fresh rawINevent, with no hours for the old unresolved day.clock_eventsstays the QR audit log. No old event is edited or given a fabricatedOUT.- The dashboard derives unresolved scans from the full event history, independently of the seven-day Not arrived roster. Corrections are excluded by their unique
clock_in_eventlink. - The additive
clock_missed_out_correctionscollection stores the originalINevent, worker, site, actual out instant, reason, recording instant, and direct timesheet entry. A unique index prevents two corrections for oneIN. All direct browser rules are locked. - The internal
clock_correction_lockscollection serializes corrections for one worker. A stale lock must be reviewed and removed by an administrator after verifying the correction and time entry; it is never silently stolen. - A correction rejects a missing reason, leaving time before the original
IN, a future time, a time overlapping the nextIN, an already-resolved event, or duplicate direct time rows for the same employee/day. An imported sheet row is never updated. The direct time row and audit correction are written together in a PocketBase batch. The production batch setting was read-only checked as enabled (50 requests, 30-second timeout) on 2026-09-25. - The exact PocketBase date filter uses a space (
YYYY-MM-DD 00:00:00.000Z) even though app code writes ISO withT; this avoids missing an existing direct row. The timesheet's stored date remains midnight UTC for the local work date. - The production service worker normally caches API GET responses using NetworkFirst. The new build gives employee lookup a NetworkOnly rule and a
no-storeresponse so yesterday's confirmation is not reused when the network is unavailable. Scan POST already requires a network connection.
Read-only evidence and local verification
At the last read-only check, production had 119 QR events: 63 IN and 56 OUT. The seven unresolved 23 September badges were MCW025, MCW034, MCW070, MCW127, MCW177, SUB00109, and SUB0098. The local dashboard preview showed seven Needs attention cards and zero on-site rows before any new-day scans. The local lookup endpoint returned in for all seven. Of the seven, only SUB00109 had an existing direct 23 September time row; it was unique. No live schema, event, time, or correction record was written during these checks.
The prior shift-backfill prototype found nine historical anomalies outside the immediate seven-case flow. This design avoids a historical backfill. Corrections for any employee/day with duplicate direct entries fail closed for manual review.
Tests cover the South African midnight boundary, old unmatched IN followed by today's IN, direct-row date matching, correction overlap, duplicate correction, imported-sheet preservation, and the worker appearing in today's state and Needs attention at once. On 2026-09-25 the complete Vitest suite passed (121 files, 1,372 tests), the production build passed, TypeScript passed, and the migration's two Python tests passed. The generated service worker contains the NetworkOnly employee-lookup rule before the default runtime routes. The migration's offline dry run reported the two locked collections and made no writes.
Production sequence (pending review)
- Confirm a restorable PocketBase backup and save its local path. Do not remove or rewrite historical QR events.
- Run
scripts/migrations/2026-09-25-add-missed-clock-outs.pywithout flags usingPB_URL,PB_ADMIN_EMAIL, andPB_ADMIN_PASSWORD; it prints a read-only schema diff. Review the two locked collections and unique indexes. - After the deployment decision, run the migration with
--apply --backup-confirmed /absolute/path/to/backup. Read back the collection rules and indexes. - Deploy the app build as one server release. No phone app reinstall or new login is required. Open the installed PWA online so the updated service worker activates; verify the lookup reaches the network.
- Before any test write, read back all seven lookups: each must say
in. The dashboard must show seven Needs attention cards, with none falsely shown as On site now. Observe a real worker's new-dayINand laterOUT; verify the older card remains until corrected. Do not use a live worker badge for a synthetic test. - For each correction, obtain the real leaving date/time and reason from the supervisor. After saving, read back the correction row, unchanged original
INevent, and direct time entry for the original local date. Never guess a leaving time.
If the app release must be rolled back, pause worker scans first. Keep new correction rows and original QR events. Reconcile any new-day IN scans written after cutover before running the old scan server again, because the old server would treat an unmatched earlier-day IN as the next OUT.
Final gate
Do not apply the schema or deploy until this local diff, test/build results, backup, and the existing shared-PIN limitation have been reviewed. Production data remains untouched at this stage.