import { type AnyPgColumn, boolean, doublePrecision, integer, jsonb, pgEnum, pgTable, text, timestamp, uniqueIndex } from 'drizzle-orm/pg-core'; export const membershipRoleEnum = pgEnum('membership_role', ['owner', 'admin', 'member']); export const systemRoleEnum = pgEnum('system_role', ['standard', 'super_admin']); export const trainingTypeEnum = pgEnum('training_type', [ 'online', 'onsite', 'internal', 'external' ]); export const approvalStatusEnum = pgEnum('approval_status', [ 'draft', 'pending', 'approved', 'rejected', 'needs_revision' ]); export const trainingCategoryEnum = pgEnum('training_category', ['K', 'S', 'A']); export const notificationTypeEnum = pgEnum('notification_type', [ 'approved', 'rejected', 'announcement', 'reminder' ]); export const announcementStatusEnum = pgEnum('announcement_status', [ 'draft', 'pending_review', 'approved', 'published', 'returned', 'rejected', 'archived' ]); export const onlineLessonStatusEnum = pgEnum('online_lesson_status', [ 'draft', 'pending_review', 'approved', 'published', 'returned', 'rejected', 'archived' ]); export const importStatusEnum = pgEnum('import_status', ['processing', 'completed', 'failed']); export const permissionOverrideEffectEnum = pgEnum('permission_override_effect', ['allow', 'deny']); export const users = pgTable( 'users', { id: text('id').primaryKey(), name: text('name').notNull(), email: text('email').notNull(), username: text('username'), passwordHash: text('password_hash'), provider: text('provider').default('credentials').notNull(), providerUserId: text('provider_user_id'), phone: text('phone'), image: text('image'), systemRole: systemRoleEnum('system_role').default('standard').notNull(), isActive: boolean('is_active').default(true).notNull(), employeeCode: text('employee_code'), employeeId: integer('employee_id').references((): AnyPgColumn => employees.id, { onDelete: 'set null' }), departmentId: integer('department_id'), positionId: integer('position_id'), companyName: text('company_name'), hiredAt: timestamp('hired_at', { withTimezone: true }), activeOrganizationId: text('active_organization_id'), lastLoginAt: timestamp('last_login_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ emailIdx: uniqueIndex('users_email_idx').on(table.email), usernameIdx: uniqueIndex('users_username_idx').on(table.username), providerUserIdx: uniqueIndex('users_provider_user_id_idx').on( table.provider, table.providerUserId ) }) ); export const organizations = pgTable( 'organizations', { id: text('id').primaryKey(), name: text('name').notNull(), slug: text('slug').notNull(), imageUrl: text('image_url'), plan: text('plan').default('free').notNull(), isActive: boolean('is_active').default(true).notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }), deletedBy: text('deleted_by').references(() => users.id, { onDelete: 'set null' }), pendingMasterReview: boolean('pending_master_review').default(false).notNull(), createdBy: text('created_by') .notNull() .references(() => users.id, { onDelete: 'restrict' }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ slugIdx: uniqueIndex('organizations_slug_idx').on(table.slug) }) ); export const memberships = pgTable( 'memberships', { id: text('id').primaryKey(), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), role: membershipRoleEnum('role').default('owner').notNull(), permissions: text('permissions').array().default([]).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ membershipUniqueIdx: uniqueIndex('memberships_user_org_idx').on( table.userId, table.organizationId ) }) ); export const permissionTemplates = pgTable( 'permission_templates', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), code: text('code').notNull(), name: text('name').notNull(), description: text('description'), isSystem: boolean('is_system').default(false).notNull(), isDefault: boolean('is_default').default(false).notNull(), isActive: boolean('is_active').default(true).notNull(), deletedAt: timestamp('deleted_at', { withTimezone: true }), createdBy: text('created_by').references(() => users.id, { onDelete: 'set null' }), updatedBy: text('updated_by').references(() => users.id, { onDelete: 'set null' }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ permissionTemplateCodeUniqueIdx: uniqueIndex('permission_templates_org_code_idx').on( table.organizationId, table.code ), permissionTemplateNameUniqueIdx: uniqueIndex('permission_templates_org_name_idx').on( table.organizationId, table.name ) }) ); export const permissionTemplatePermissions = pgTable( 'permission_template_permissions', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), templateId: integer('template_id') .notNull() .references(() => permissionTemplates.id, { onDelete: 'cascade' }), permission: text('permission').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ permissionTemplatePermissionUniqueIdx: uniqueIndex( 'permission_template_permissions_template_permission_idx' ).on(table.templateId, table.permission) }) ); export const userPermissionTemplateAssignments = pgTable( 'user_permission_template_assignments', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), templateId: integer('template_id') .notNull() .references(() => permissionTemplates.id, { onDelete: 'restrict' }), isActive: boolean('is_active').default(true).notNull(), assignedBy: text('assigned_by').references(() => users.id, { onDelete: 'set null' }), assignedAt: timestamp('assigned_at', { withTimezone: true }).defaultNow().notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ userPermissionTemplateAssignmentUniqueIdx: uniqueIndex( 'user_permission_template_assignments_org_user_template_idx' ).on(table.organizationId, table.userId, table.templateId) }) ); export const userPermissionOverrides = pgTable('user_permission_overrides', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), permission: text('permission').notNull(), effect: permissionOverrideEffectEnum('effect').notNull(), reason: text('reason'), expiresAt: timestamp('expires_at', { withTimezone: true }), isActive: boolean('is_active').default(true).notNull(), createdBy: text('created_by').references(() => users.id, { onDelete: 'set null' }), revokedBy: text('revoked_by').references(() => users.id, { onDelete: 'set null' }), revokedAt: timestamp('revoked_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }); export const permissionAuditLogs = pgTable('permission_audit_logs', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id').references(() => organizations.id, { onDelete: 'set null' }), actorUserId: text('actor_user_id').references(() => users.id, { onDelete: 'set null' }), targetUserId: text('target_user_id').references(() => users.id, { onDelete: 'set null' }), templateId: integer('template_id').references(() => permissionTemplates.id, { onDelete: 'set null' }), assignmentId: integer('assignment_id').references(() => userPermissionTemplateAssignments.id, { onDelete: 'set null' }), overrideId: integer('override_id').references(() => userPermissionOverrides.id, { onDelete: 'set null' }), action: text('action').notNull(), reason: text('reason'), before: jsonb('before'), after: jsonb('after'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull() }); export const userEmployeeMaps = pgTable( 'user_employee_maps', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), employeeId: integer('employee_id') .notNull() .references(() => employees.id, { onDelete: 'cascade' }), provider: text('provider'), providerUserId: text('provider_user_id'), source: text('source').default('manual').notNull(), isPrimary: boolean('is_primary').default(true).notNull(), linkedAt: timestamp('linked_at', { withTimezone: true }).defaultNow().notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ userEmployeeMapUniqueIdx: uniqueIndex('user_employee_maps_org_user_employee_idx').on( table.organizationId, table.userId, table.employeeId ) }) ); export const products = pgTable('products', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), name: text('name').notNull(), category: text('category').notNull(), description: text('description').notNull(), photoUrl: text('photo_url').notNull(), price: doublePrecision('price').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }); export const departments = pgTable( 'departments', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), code: text('code'), name: text('name').notNull(), description: text('description'), isActive: boolean('is_active').default(true).notNull(), pendingMasterReview: boolean('pending_master_review').default(false).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ departmentCodeIdx: uniqueIndex('departments_org_code_idx').on(table.organizationId, table.code), departmentNameIdx: uniqueIndex('departments_org_name_idx').on(table.organizationId, table.name) }) ); export const positions = pgTable( 'positions', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), code: text('code'), name: text('name').notNull(), description: text('description'), isActive: boolean('is_active').default(true).notNull(), pendingMasterReview: boolean('pending_master_review').default(false).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ positionCodeIdx: uniqueIndex('positions_org_code_idx').on(table.organizationId, table.code), positionNameIdx: uniqueIndex('positions_org_name_idx').on(table.organizationId, table.name) }) ); export const employees = pgTable( 'employees', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), employeeCode: text('employee_code').notNull(), prefix: text('prefix'), firstNameTh: text('first_name_th'), lastNameTh: text('last_name_th'), firstNameEn: text('first_name_en'), lastNameEn: text('last_name_en'), displayName: text('display_name'), fullName: text('full_name').notNull(), email: text('email'), phone: text('phone'), companyName: text('company_name'), company: text('company'), departmentId: integer('department_id').references(() => departments.id, { onDelete: 'set null' }), positionId: integer('position_id').references(() => positions.id, { onDelete: 'set null' }), hiredAt: timestamp('hired_at', { withTimezone: true }), status: text('status').default('active').notNull(), isActive: boolean('is_active').default(true).notNull(), pendingMasterReview: boolean('pending_master_review').default(false).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ employeeCodeIdx: uniqueIndex('employees_org_employee_code_idx').on( table.organizationId, table.employeeCode ) }) ); export const courses = pgTable( 'courses', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), courseCode: text('course_code'), name: text('name').notNull(), category: text('category').notNull(), organizer: text('organizer'), standardHours: doublePrecision('standard_hours').default(0).notNull(), certificateValidityMonths: integer('certificate_validity_months'), isRequired: boolean('is_required').default(false).notNull(), certificateRequired: boolean('certificate_required').default(true).notNull(), trainingCost: doublePrecision('training_cost'), description: text('description'), isActive: boolean('is_active').default(true).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ courseNameIdx: uniqueIndex('courses_org_name_idx').on(table.organizationId, table.name) }) ); export const trainingRecords = pgTable('training_records', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), userId: text('user_id').references(() => users.id, { onDelete: 'set null' }), employeeId: integer('employee_id').references(() => employees.id, { onDelete: 'set null' }), courseId: integer('course_id').references(() => courses.id, { onDelete: 'restrict' }), courseName: text('course_name').notNull(), trainingDate: timestamp('training_date', { withTimezone: true }).notNull(), trainingType: trainingTypeEnum('training_type').notNull(), submittedMinutes: integer('submitted_minutes'), hours: doublePrecision('hours').notNull(), approvedMinutes: integer('approved_minutes'), approvedHours: doublePrecision('approved_hours'), category: trainingCategoryEnum('category'), organizer: text('organizer'), note: text('note'), approvalStatus: approvalStatusEnum('approval_status').default('pending').notNull(), reviewerNote: text('reviewer_note'), rejectReason: text('reject_reason'), submittedByUserId: text('submitted_by_user_id').references(() => users.id, { onDelete: 'set null' }), reviewedByUserId: text('reviewed_by_user_id').references(() => users.id, { onDelete: 'set null' }), reviewedBy: text('reviewed_by').references(() => users.id, { onDelete: 'set null' }), reviewedAt: timestamp('reviewed_at', { withTimezone: true }), createdBy: text('created_by') .notNull() .references(() => users.id, { onDelete: 'restrict' }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }); export const certificates = pgTable('certificates', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), trainingRecordId: integer('training_record_id') .notNull() .references(() => trainingRecords.id, { onDelete: 'cascade' }), fileName: text('file_name').notNull(), fileType: text('file_type').notNull(), fileSize: integer('file_size').notNull(), storageKey: text('storage_key').notNull(), fileUrl: text('file_url'), uploadedBy: text('uploaded_by') .notNull() .references(() => users.id, { onDelete: 'restrict' }), uploadedAt: timestamp('uploaded_at', { withTimezone: true }).defaultNow().notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }); export const trainingMatrices = pgTable('training_matrices', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), departmentId: integer('department_id').references(() => departments.id, { onDelete: 'cascade' }), positionId: integer('position_id').references(() => positions.id, { onDelete: 'cascade' }), courseId: integer('course_id') .notNull() .references(() => courses.id, { onDelete: 'cascade' }), isRequired: boolean('is_required').default(true).notNull(), renewalPeriodMonths: integer('renewal_period_months'), note: text('note'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }); export const trainingPolicies = pgTable( 'training_policies', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), year: integer('year').notNull(), totalTargetMinutes: integer('total_target_minutes'), totalHours: doublePrecision('total_hours').default(30).notNull(), kTargetMinutes: integer('k_target_minutes'), kHours: doublePrecision('k_hours').default(12).notNull(), sTargetMinutes: integer('s_target_minutes'), sHours: doublePrecision('s_hours').default(12).notNull(), aTargetMinutes: integer('a_target_minutes'), aHours: doublePrecision('a_hours').default(6).notNull(), isActive: boolean('is_active').default(true).notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ trainingPolicyYearIdx: uniqueIndex('training_policies_org_year_idx').on( table.organizationId, table.year ) }) ); export const employeeTrainingTargets = pgTable( 'employee_training_targets', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), userId: text('user_id').references(() => users.id, { onDelete: 'cascade' }), employeeId: integer('employee_id').references(() => employees.id, { onDelete: 'set null' }), year: integer('year').notNull(), totalTargetMinutes: integer('total_target_minutes'), totalHours: doublePrecision('total_hours').notNull(), kTargetMinutes: integer('k_target_minutes'), kHours: doublePrecision('k_hours').notNull(), sTargetMinutes: integer('s_target_minutes'), sHours: doublePrecision('s_hours').notNull(), aTargetMinutes: integer('a_target_minutes'), aHours: doublePrecision('a_hours').notNull(), createdBy: text('created_by') .notNull() .references(() => users.id, { onDelete: 'restrict' }), updatedBy: text('updated_by').references(() => users.id, { onDelete: 'set null' }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }, (table) => ({ employeeTrainingTargetUniqueIdx: uniqueIndex('employee_training_targets_org_user_year_idx').on( table.organizationId, table.userId, table.year ), employeeTrainingTargetEmployeeUniqueIdx: uniqueIndex( 'employee_training_targets_org_employee_year_idx' ).on(table.organizationId, table.employeeId, table.year) }) ); export const notifications = pgTable('notifications', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), userId: text('user_id') .notNull() .references(() => users.id, { onDelete: 'cascade' }), title: text('title').notNull(), message: text('message').notNull(), type: notificationTypeEnum('type').notNull(), referenceType: text('reference_type'), referenceId: text('reference_id'), isRead: boolean('is_read').default(false).notNull(), readAt: timestamp('read_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull() }); export const announcements = pgTable('announcements', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), title: text('title').notNull(), content: text('content').notNull(), attachmentFileName: text('attachment_file_name'), attachmentFileType: text('attachment_file_type'), attachmentFileSize: integer('attachment_file_size'), attachmentStorageKey: text('attachment_storage_key'), attachmentFileUrl: text('attachment_file_url'), startDate: timestamp('start_date', { withTimezone: true }).notNull(), endDate: timestamp('end_date', { withTimezone: true }), isPin: boolean('is_pin').default(false).notNull(), status: announcementStatusEnum('status').default('draft').notNull(), submittedAt: timestamp('submitted_at', { withTimezone: true }), submittedBy: text('submitted_by').references(() => users.id, { onDelete: 'set null' }), reviewedAt: timestamp('reviewed_at', { withTimezone: true }), reviewedBy: text('reviewed_by').references(() => users.id, { onDelete: 'set null' }), approvedAt: timestamp('approved_at', { withTimezone: true }), approvedBy: text('approved_by').references(() => users.id, { onDelete: 'set null' }), publishedAt: timestamp('published_at', { withTimezone: true }), publishedBy: text('published_by').references(() => users.id, { onDelete: 'set null' }), reviewComment: text('review_comment'), createdBy: text('created_by') .notNull() .references(() => users.id, { onDelete: 'restrict' }), updatedBy: text('updated_by').references(() => users.id, { onDelete: 'set null' }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }); export const onlineLessons = pgTable('online_lessons', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), title: text('title').notNull(), description: text('description'), category: text('category'), videoUrl: text('video_url'), videoFileName: text('video_file_name'), videoFileType: text('video_file_type'), videoFileSize: integer('video_file_size'), videoStorageKey: text('video_storage_key'), videoFileUrl: text('video_file_url'), attachmentFileName: text('attachment_file_name'), attachmentFileType: text('attachment_file_type'), attachmentFileSize: integer('attachment_file_size'), attachmentStorageKey: text('attachment_storage_key'), attachmentFileUrl: text('attachment_file_url'), thumbnailFileName: text('thumbnail_file_name'), thumbnailFileType: text('thumbnail_file_type'), thumbnailFileSize: integer('thumbnail_file_size'), thumbnailStorageKey: text('thumbnail_storage_key'), thumbnailUrl: text('thumbnail_url'), status: onlineLessonStatusEnum('status').default('draft').notNull(), submittedAt: timestamp('submitted_at', { withTimezone: true }), submittedBy: text('submitted_by').references(() => users.id, { onDelete: 'set null' }), reviewedAt: timestamp('reviewed_at', { withTimezone: true }), reviewedBy: text('reviewed_by').references(() => users.id, { onDelete: 'set null' }), approvedAt: timestamp('approved_at', { withTimezone: true }), approvedBy: text('approved_by').references(() => users.id, { onDelete: 'set null' }), isPublished: boolean('is_published').default(false).notNull(), publishedAt: timestamp('published_at', { withTimezone: true }), publishedBy: text('published_by').references(() => users.id, { onDelete: 'set null' }), reviewComment: text('review_comment'), createdBy: text('created_by') .notNull() .references(() => users.id, { onDelete: 'restrict' }), updatedBy: text('updated_by').references(() => users.id, { onDelete: 'set null' }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() }); export const auditLogs = pgTable('audit_logs', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id').references(() => organizations.id, { onDelete: 'set null' }), userId: text('user_id').references(() => users.id, { onDelete: 'set null' }), moduleName: text('module_name').notNull(), action: text('action').notNull(), entityName: text('entity_name'), entityId: text('entity_id'), oldValue: jsonb('old_value'), newValue: jsonb('new_value'), ipAddress: text('ip_address'), userAgent: text('user_agent'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull() }); export const importJobs = pgTable('import_jobs', { id: integer('id').primaryKey().generatedAlwaysAsIdentity(), organizationId: text('organization_id') .notNull() .references(() => organizations.id, { onDelete: 'cascade' }), importType: text('import_type').notNull(), fileName: text('file_name').notNull(), totalRows: integer('total_rows').default(0).notNull(), successRows: integer('success_rows').default(0).notNull(), failedRows: integer('failed_rows').default(0).notNull(), status: importStatusEnum('status').default('processing').notNull(), errorMessage: text('error_message'), createdBy: text('created_by') .notNull() .references(() => users.id, { onDelete: 'restrict' }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull() });