/** * Cycle 10 — Unified field worker table. * * Replaces `water_irrigators` + `time_tracking_workers` (one row per * person with two independent PIN hashes) with one row, one PIN hash, * one role vocabulary. * * Role vocabulary (matches the CHECK constraint in * `db/migrations/0097_field_workers.sql`): * - `worker` — default; can submit water entries + clock in/out * - `time_admin` — can manage time-tracking tasks, settings, and * other time workers * - `irrigator` — legacy role from `water_irrigators`; same powers * as `worker` but kept distinct for UI filtering * (the water admin UI shows this label) * - `water_admin` — can manage headgates, water workers, and water * entries * * The PIN is a scrypt self-describing hash from `@/lib/water-log-pin` * (column name `pin_hash`, NOT `pin` — see cycle 10 migration for the * rename). */ import { boolean, index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; import { brands } from "./brands"; export const fieldWorkers = pgTable( "field_workers", { id: uuid("id").primaryKey().defaultRandom(), brandId: uuid("brand_id") .notNull() .references(() => brands.id, { onDelete: "cascade" }), name: text("name").notNull(), pinHash: text("pin_hash").notNull(), role: text("role", { enum: ["worker", "time_admin", "irrigator", "water_admin", "driver", "supervisor"], }) .notNull() .default("worker"), languagePreference: text("language_preference").notNull().default("en"), phone: text("phone"), notes: text("notes"), active: boolean("active").notNull().default(true), lastUsedAt: timestamp("last_used_at", { withTimezone: true }), createdAt: timestamp("created_at", { withTimezone: true }) .notNull() .defaultNow(), updatedAt: timestamp("updated_at", { withTimezone: true }) .notNull() .defaultNow(), }, (t) => ({ brandIdx: index("field_workers_brand_idx").on(t.brandId), }), ); export type FieldWorker = typeof fieldWorkers.$inferSelect; export type NewFieldWorker = typeof fieldWorkers.$inferInsert; export type FieldWorkerRole = FieldWorker["role"];