diff --git a/src/actions/time-tracking/field.ts b/src/actions/time-tracking/field.ts index 35b57af..123608f 100644 --- a/src/actions/time-tracking/field.ts +++ b/src/actions/time-tracking/field.ts @@ -94,8 +94,24 @@ export async function verifyTimeTrackingPin( // a small worker pool this is fine; if it grows we could index a // lookup table, but per-brand counts are typically <100. // Cycle 10: workers now live in the unified `field_workers` table. - // We filter to time-domain roles here so a water-only worker can't - // accidentally be matched against a time-tracking PIN. + // We accept anyone with a clocking-in role: 'worker' (default), + // 'time_admin' (manages tasks + workers), 'irrigator' (legacy role + // for water workers, can also clock in/out — these are field crew + // who do both jobs), and 'driver' (cycle 12 driver/crew role). + // + // Excluded on purpose: + // - 'water_admin' — admin who manages headgates/workers, not a + // clocking-in field worker + // - 'supervisor' — cycle 12 approver role; doesn't clock in/out, + // only reviews and approves timesheets + // + // This also matters because MobileWaterApp (the /water unified + // entrypoint) calls verifyTimeTrackingPin best-effort right after + // verifyWaterPin — if the role filter here is too narrow, the + // worker enters their PIN once, the Time tab mounts, asks for the + // PIN again, and the second attempt fails the same way. Allowing + // the broader set keeps the unified PIN flow working for everyone + // who's registered as a field crew member. const rows = await withBrand(brandId, async (db) => { return db .select() @@ -104,7 +120,7 @@ export async function verifyTimeTrackingPin( and( eq(fieldWorkers.brandId, brandId), eq(fieldWorkers.active, true), - sql`${fieldWorkers.role} IN ('worker', 'time_admin')`, + sql`${fieldWorkers.role} IN ('worker', 'time_admin', 'irrigator', 'driver')`, ), ); });