commit
This commit is contained in:
730
src/db/schema.ts
Normal file
730
src/db/schema.ts
Normal file
@@ -0,0 +1,730 @@
|
||||
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()
|
||||
});
|
||||
Reference in New Issue
Block a user