import { pgTable, pgEnum, uuid, text, boolean, timestamp, smallint, integer, date, jsonb, customType, } from 'drizzle-orm/pg-core' // --------------------------------------------------------------------------- // Custom type: pgvector // --------------------------------------------------------------------------- const vector = customType<{ data: number[]; driverData: string }>({ dataType(config) { return `vector(${(config as { dimensions?: number }).dimensions ?? 1536})` }, }) // --------------------------------------------------------------------------- // Enums // --------------------------------------------------------------------------- export const userRoleEnum = pgEnum('user_role', [ 'reporter', 'supervisor', 'hse', 'capa_owner', 'management', 'admin', ]) export const incidentTypeEnum = pgEnum('incident_type', [ 'injury', 'near_miss', 'hazard', 'asset_damage', 'environmental', 'security', 'fire', 'transport', ]) export const incidentStatusEnum = pgEnum('incident_status', [ 'reported', 'triaged', 'investigating', 'capa_pending', 'verification', 'closed', ]) export const medicalStatusEnum = pgEnum('medical_status', [ 'none', 'first_aid', 'medical_treatment', 'lti', ]) export const evidenceStageEnum = pgEnum('evidence_stage', [ 'report', 'response', 'investigation', 'capa', 'verification', ]) export const rcaMethodEnum = pgEnum('rca_method', ['five_why', 'fishbone', 'other']) export const capaPriorityEnum = pgEnum('capa_priority', ['low', 'med', 'high']) export const capaStatusEnum = pgEnum('capa_status', [ 'open', 'in_progress', 'overdue', 'pending_verification', 'verified', 'reopened', 'closed', ]) export const doshFormTypeEnum = pgEnum('dosh_form_type', ['jkkp6', 'jkkp7', 'jkkp8']) export const doshStatusEnum = pgEnum('dosh_status', ['not_required', 'pending', 'submitted']) export const notificationChannelEnum = pgEnum('notification_channel', [ 'email', 'whatsapp', 'in_app', ]) // --------------------------------------------------------------------------- // Tables (dependency order) // --------------------------------------------------------------------------- export const sites = pgTable('sites', { id: uuid('id').primaryKey().defaultRandom(), name: text('name').notNull(), address: text('address'), region: text('region'), active: boolean('active').notNull().default(true), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }) export const zones = pgTable('zones', { id: uuid('id').primaryKey().defaultRandom(), siteId: uuid('site_id').notNull().references(() => sites.id), name: text('name').notNull(), qrCodeToken: text('qr_code_token').notNull().unique(), active: boolean('active').notNull().default(true), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }) export const users = pgTable('users', { id: uuid('id').primaryKey().defaultRandom(), name: text('name').notNull().default(''), email: text('email').notNull().default('').unique(), phone: text('phone'), role: userRoleEnum('role').notNull().default('reporter'), department: text('department'), siteId: uuid('site_id').references(() => sites.id), active: boolean('active').notNull().default(true), passwordHash: text('password_hash').notNull().default(''), emailVerifiedAt: timestamp('email_verified_at', { withTimezone: true }), lastLoginAt: timestamp('last_login_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }) export const trucks = pgTable('trucks', { id: uuid('id').primaryKey().defaultRandom(), truckNo: text('truck_no').notNull().unique(), carrier: text('carrier'), active: boolean('active').notNull().default(true), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }) export const incidents = pgTable('incidents', { id: uuid('id').primaryKey().defaultRandom(), referenceNo: text('reference_no').unique(), incidentType: incidentTypeEnum('incident_type').notNull(), siteId: uuid('site_id').notNull().references(() => sites.id), zoneId: uuid('zone_id').references(() => zones.id), truckId: uuid('truck_id').references(() => trucks.id), reportedBy: uuid('reported_by').notNull().references(() => users.id), reportedAt: timestamp('reported_at', { withTimezone: true }).notNull().defaultNow(), description: text('description').notNull(), severity: smallint('severity'), status: incidentStatusEnum('status').notNull().default('reported'), injuryInvolved: boolean('injury_involved').notNull().default(false), assetInvolved: boolean('asset_involved').notNull().default(false), medicalStatus: medicalStatusEnum('medical_status'), lostDays: integer('lost_days'), isFatality: boolean('is_fatality').notNull().default(false), isSeriousBodilyInjury: boolean('is_serious_bodily_injury').notNull().default(false), isDangerousOccurrence: boolean('is_dangerous_occurrence').notNull().default(false), isOccupationalDisease: boolean('is_occupational_disease').notNull().default(false), triageNotes: text('triage_notes'), triagedBy: uuid('triaged_by').references(() => users.id), triagedAt: timestamp('triaged_at', { withTimezone: true }), typeDetails: jsonb('type_details'), embedding: vector('embedding', { dimensions: 768 }), closedAt: timestamp('closed_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }) export const evidenceFiles = pgTable('evidence_files', { id: uuid('id').primaryKey().defaultRandom(), incidentId: uuid('incident_id').notNull().references(() => incidents.id), stage: evidenceStageEnum('stage').notNull(), fileUrl: text('file_url').notNull(), fileType: text('file_type').notNull(), fileHash: text('file_hash').notNull(), uploadedBy: uuid('uploaded_by').notNull().references(() => users.id), uploadedAt: timestamp('uploaded_at', { withTimezone: true }).notNull().defaultNow(), deleted: boolean('deleted').notNull().default(false), }) export const investigations = pgTable('investigations', { id: uuid('id').primaryKey().defaultRandom(), incidentId: uuid('incident_id').notNull().references(() => incidents.id), investigatorId: uuid('investigator_id').notNull().references(() => users.id), method: rcaMethodEnum('method').notNull().default('five_why'), findingsText: text('findings_text'), rootCauseSummary: text('root_cause_summary'), alcoholTestResult: text('alcohol_test_result'), urineTestResult: text('urine_test_result'), witnessStatementRefs: text('witness_statement_refs').array(), fiveWhySteps: jsonb('five_why_steps'), fishboneCategories: jsonb('fishbone_categories'), completedAt: timestamp('completed_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }) export const capaActions = pgTable('capa_actions', { id: uuid('id').primaryKey().defaultRandom(), incidentId: uuid('incident_id').notNull().references(() => incidents.id), rootCauseRef: text('root_cause_ref'), description: text('description').notNull(), ownerUserId: uuid('owner_user_id').notNull().references(() => users.id), department: text('department').notNull(), dueDate: date('due_date').notNull(), priority: capaPriorityEnum('priority').notNull().default('med'), status: capaStatusEnum('status').notNull().default('open'), completedAt: timestamp('completed_at', { withTimezone: true }), verifiedBy: uuid('verified_by').references(() => users.id), verifiedAt: timestamp('verified_at', { withTimezone: true }), effectivenessRecheckDate: date('effectiveness_recheck_date'), effectivenessRecheckRound: integer('effectiveness_recheck_round').notNull().default(0), ownerNotes: text('owner_notes'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }) export const doshReports = pgTable('dosh_reports', { id: uuid('id').primaryKey().defaultRandom(), incidentId: uuid('incident_id').notNull().references(() => incidents.id), formType: doshFormTypeEnum('form_type').notNull(), status: doshStatusEnum('status').notNull().default('not_required'), submittedAt: timestamp('submitted_at', { withTimezone: true }), submittedBy: uuid('submitted_by').references(() => users.id), fileUrl: text('file_url'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }) export const notificationsLog = pgTable('notifications_log', { id: uuid('id').primaryKey().defaultRandom(), incidentId: uuid('incident_id').references(() => incidents.id), capaId: uuid('capa_id').references(() => capaActions.id), channel: notificationChannelEnum('channel').notNull(), recipient: text('recipient').notNull(), recipientUserId: uuid('recipient_user_id').references(() => users.id), readAt: timestamp('read_at', { withTimezone: true }), title: text('title'), link: text('link'), sentAt: timestamp('sent_at', { withTimezone: true }).notNull().defaultNow(), status: text('status').notNull().default('sent'), }) export const auditLog = pgTable('audit_log', { id: uuid('id').primaryKey().defaultRandom(), tableName: text('table_name').notNull(), recordId: uuid('record_id').notNull(), action: text('action').notNull(), changedBy: uuid('changed_by').references(() => users.id), changedAt: timestamp('changed_at', { withTimezone: true }).notNull().defaultNow(), oldValue: jsonb('old_value'), newValue: jsonb('new_value'), }) export const appSettings = pgTable('app_settings', { key: text('key').primaryKey(), value: text('value').notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), updatedBy: uuid('updated_by').references(() => users.id), }) export const incidentAddenda = pgTable('incident_addenda', { id: uuid('id').primaryKey().defaultRandom(), incidentId: uuid('incident_id').notNull().references(() => incidents.id), author: uuid('author').notNull().references(() => users.id), body: text('body').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }) // password_reset_tokens: added in Phase 3 for custom auth // accessed via app_admin only (BYPASSRLS); no user-facing policies needed export const passwordResetTokens = pgTable('password_reset_tokens', { id: uuid('id').primaryKey().defaultRandom(), userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }), tokenHash: text('token_hash').notNull().unique(), expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), usedAt: timestamp('used_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), })