gongxiangp2/prisma/schema.prisma
2026-09-14 20:29:32 +08:00

1562 lines
80 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

generator client {
provider = "prisma-client-js"
previewFeatures = []
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
engineType = "library"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
username String @unique
password String
role Role @default(USER)
isActive Boolean @default(false)
canDownload Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
department String @default("")
dingtalkUserId String? @unique
loginType String @default("normal")
realName String?
phone String?
avatar String?
managedDepartment String?
isSuperAdmin Boolean @default(false)
departmentId String?
employmentStatus String? @default("active")
resignationDate DateTime?
transferDate DateTime?
transferToDeptId String?
employmentNote String?
statusChangedBy String?
statusChangedAt DateTime?
status String @default("pending")
lastLoginAt DateTime?
lastLoginIp String?
loginCount Int @default(0)
archivedAt DateTime?
managedDepartments Department[] @relation("DepartmentManager")
downloadLogs MaterialDownloadLog[]
favorites MaterialFavorite[]
likes MaterialLike[]
materialLogs MaterialLog[]
materialTransfersFrom MaterialTransfer[] @relation("MaterialTransferFromUser")
materialTransfersTo MaterialTransfer[] @relation("MaterialTransferToUser")
materialTransfersAsOperator MaterialTransfer[] @relation("MaterialTransferOperator")
materials Material[] @relation("MaterialOwner")
taskDetailRequestedMaterials Material[] @relation("TaskDetailRequester")
productPackageUploadJobs ProductPackageUploadJob[] @relation("ProductPackageUploadJobOperator")
departmentRef Department? @relation("UserDepartment", fields: [departmentId], references: [id])
transferToDept Department? @relation("UserTransferDepartment", fields: [transferToDeptId], references: [id])
employeeBindings UserEmployeeBinding[]
permissionInherits UserPermissionInherit[]
submittedTaskDetailChangeRequests TaskDetailChangeRequest[] @relation("TaskDetailChangeRequesterUser")
managedTaskDetailChangeRequests TaskDetailChangeRequest[] @relation("TaskDetailChangeManagerUser")
brandKnowledgeUploads BrandKnowledge[] @relation("BrandKnowledgeUploader")
brandKnowledgeLikes BrandKnowledgeLike[]
brandKnowledgeFavorites BrandKnowledgeFavorite[]
@@index([status])
@@index([archivedAt])
@@index([departmentId])
@@index([employmentStatus])
@@index([transferToDeptId])
@@map("users")
}
model Material {
id String @id @default(uuid())
previewUrl String
productModel String
reportTitle String? @map("report_title")
categoryName String
sceneName String
materialType String @default("3DMAX")
renderer String @default("CR")
authorName String
zipFileName String @default("")
zipFilePath String @default("")
zipFileSize BigInt @default(0)
downloadCount Int @default(0)
likeCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId String
displayImages String[] @default([])
platformName String @default("")
uploadId String?
videoSourceUrl String?
videoPreview540Url String?
videoPreviewClipUrl String?
videoCoverUrl String?
videoDuration Int?
videoProcessingStatus String?
transcodeLastError String? @map("transcodelasterror")
transcodeAttempts Int? @map("transcodeattempts")
transcodeLeaseToken String? @map("transcode_lease_token")
transcodeLeaseExpiresAt DateTime? @map("transcode_lease_expires_at")
deletedAt DateTime?
productCategoryName String @default("")
originalOwnerId String?
transferredAt DateTime?
transferredBy String?
transferNote String?
/// 原归属人离职后,素材托管给的部门。托管只改变"谁负责维护",
/// 不改变 userId 与 authorName——素材是谁做的,永远记在谁名下。
custodyDepartmentId String? @map("custody_department_id")
custodyAt DateTime? @map("custody_at")
custodyReason String? @map("custody_reason")
taskNo String? @map("task_no")
dingtalkApprovalNo String? @map("dingtalk_approval_no")
taskStatus String? @map("task_status")
taskFetchedAt DateTime? @map("task_fetched_at")
isFullSet Boolean @default(false) @map("is_full_set")
isAiGenerated Boolean @default(false) @map("is_ai_generated")
taskDetailSourceType String? @map("task_detail_source_type")
taskDetailRequesterUserId String? @map("task_detail_requester_user_id")
taskDetailRequesterName String? @map("task_detail_requester_name")
taskDetailDemandType String? @map("task_detail_demand_type")
taskDetailPrivate Boolean @default(false) @map("task_detail_private")
/// 专用 init 建档时写 true,完成落库后写 false。列表/筛选只读这个标记,
/// 不再每次去资产表现场判断是不是上传空壳。
isIncompleteShell Boolean @default(false) @map("is_incomplete_shell")
assets MaterialAsset[]
downloadLogs MaterialDownloadLog[]
favorites MaterialFavorite[]
likes MaterialLike[]
logs MaterialLog[]
skus MaterialSku[]
transfers MaterialTransfer[]
transcodeProgresses VideoTranscodeProgress[] @relation("MaterialTranscodeProgress")
productPackageUploadJobs ProductPackageUploadJob[]
taskDetailChangeRequests TaskDetailChangeRequest[]
user User @relation("MaterialOwner", fields: [userId], references: [id], onDelete: Cascade)
taskDetailRequester User? @relation("TaskDetailRequester", fields: [taskDetailRequesterUserId], references: [id], onDelete: SetNull)
custodyDepartment Department? @relation("MaterialCustodyDepartment", fields: [custodyDepartmentId], references: [id], onDelete: SetNull)
@@index([userId])
@@index([custodyDepartmentId])
@@index([taskDetailRequesterUserId])
@@index([createdAt])
@@index([categoryName])
@@index([sceneName])
@@index([platformName])
@@index([productCategoryName])
@@index([materialType])
@@index([downloadCount])
@@index([likeCount])
@@index([deletedAt])
@@index([originalOwnerId])
@@index([transferredAt])
@@index([taskNo])
@@index([dingtalkApprovalNo])
@@index([taskStatus])
@@index([isIncompleteShell])
@@index([isFullSet])
@@index([productModel])
@@index([reportTitle])
@@index([authorName])
@@index([transcodeLeaseExpiresAt, transcodeLeaseToken], map: "materials_transcode_lease_idx")
@@map("materials")
}
model BrandKnowledge {
id String @id @default(uuid())
title String
knowledgeType String @map("knowledge_type")
brand String @default("")
productCategoryName String @default("") @map("product_category_name")
fileName String @map("file_name")
filePath String @map("file_path")
mimeType String @map("mime_type")
fileSize BigInt @map("file_size")
previewImageName String? @map("preview_image_name")
previewImagePath String? @map("preview_image_path")
previewImageSlices String[] @default([]) @map("preview_image_slices")
previewImageMimeType String? @map("preview_image_mime_type")
previewImageSize BigInt? @map("preview_image_size")
downloadCount Int @default(0) @map("download_count")
uploadedBy String @map("uploaded_by")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
uploader User @relation("BrandKnowledgeUploader", fields: [uploadedBy], references: [id], onDelete: Cascade)
likes BrandKnowledgeLike[]
favorites BrandKnowledgeFavorite[]
@@index([knowledgeType])
@@index([brand])
@@index([productCategoryName])
@@index([createdAt])
@@index([uploadedBy])
@@map("brand_knowledge")
}
model BrandKnowledgeLike {
id String @id @default(uuid())
knowledgeId String @map("knowledge_id")
userId String @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
knowledge BrandKnowledge @relation(fields: [knowledgeId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([knowledgeId, userId])
@@index([knowledgeId])
@@index([userId])
@@map("brand_knowledge_likes")
}
model BrandKnowledgeFavorite {
id String @id @default(uuid())
knowledgeId String @map("knowledge_id")
userId String @map("user_id")
createdAt DateTime @default(now()) @map("created_at")
knowledge BrandKnowledge @relation(fields: [knowledgeId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([knowledgeId, userId])
@@index([knowledgeId])
@@index([userId])
@@map("brand_knowledge_favorites")
}
model ProductPackageUploadJob {
id String @id @default(uuid())
materialId String @map("material_id")
operatorUserId String @map("operator_user_id")
taskNo String? @map("task_no")
dingtalkApprovalNo String? @map("dingtalk_approval_no")
assets Json
options Json?
status String
stage String
progress Int @default(0)
errorMessage String? @map("error_message")
createdAt DateTime @default(now()) @map("created_at")
startedAt DateTime? @map("started_at")
completedAt DateTime? @map("completed_at")
updatedAt DateTime @updatedAt @map("updated_at")
workerId String? @map("worker_id")
leaseToken String? @map("lease_token")
leaseExpiresAt DateTime? @map("lease_expires_at")
material Material @relation(fields: [materialId], references: [id], onDelete: Cascade)
operator User @relation("ProductPackageUploadJobOperator", fields: [operatorUserId], references: [id], onDelete: Cascade)
@@index([materialId])
@@index([operatorUserId, createdAt])
@@index([status, createdAt])
@@index([status, leaseExpiresAt], map: "product_package_upload_jobs_lease_idx")
@@map("product_package_upload_jobs")
}
model MaterialLike {
id String @id @default(uuid())
materialId String
userId String
createdAt DateTime @default(now())
material Material @relation(fields: [materialId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([materialId, userId])
@@index([materialId])
@@index([userId])
@@map("material_likes")
}
model MaterialFavorite {
id String @id @default(uuid())
materialId String
userId String
createdAt DateTime @default(now())
material Material @relation(fields: [materialId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([materialId, userId])
@@index([materialId])
@@index([userId])
@@map("material_favorites")
}
model MaterialDownloadLog {
id String @id @default(uuid())
materialId String
userId String
downloadedAt DateTime @default(now())
material Material @relation(fields: [materialId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([materialId])
@@index([userId])
@@index([downloadedAt])
@@index([materialId, userId, downloadedAt])
@@map("material_download_logs")
}
model MaterialLog {
id String @id @default(uuid())
materialId String
userId String
action String
changes String?
timestamp DateTime @default(now())
material Material @relation(fields: [materialId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([materialId])
@@index([userId])
@@index([timestamp])
@@map("material_logs")
}
model MaterialSku {
id String @id @default(uuid())
materialId String @map("material_id")
skuName String @map("sku_name")
filePath String @map("file_path")
fileSize BigInt @default(0) @map("file_size")
sortOrder Int @default(0) @map("sort_order")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
fileName String? @map("file_name")
previewUrl String? @map("preview_url")
deletedAt DateTime? @map("deleted_at")
whiteBackgroundSourceType String? @map("white_background_source_type")
whiteBackgroundUploadedBy String? @map("white_background_uploaded_by")
whiteBackgroundUploadedAt DateTime? @map("white_background_uploaded_at")
barcodes MaterialSkuBarcode[]
material Material @relation(fields: [materialId], references: [id], onDelete: Cascade)
@@index([materialId])
@@index([skuName, deletedAt])
@@index([sortOrder])
@@index([deletedAt])
@@index([whiteBackgroundSourceType])
@@index([whiteBackgroundUploadedBy])
@@index([whiteBackgroundUploadedAt])
@@map("material_skus")
}
model ProductCategory {
id String @id @default(uuid())
name String @unique
createdAt DateTime @default(now())
@@map("product_categories")
}
model Category {
id String @id @default(uuid())
name String @unique
createdAt DateTime @default(now())
@@map("categories")
}
model Scene {
id String @id @default(uuid())
name String @unique
createdAt DateTime @default(now())
@@map("scenes")
}
model Platform {
id String @id @default(uuid())
name String @unique
createdAt DateTime @default(now())
@@map("platforms")
}
model SystemSettings {
id String @id @default(uuid())
headerBgColor String @default("#1f2937")
siteName String @default("由壹点3D素材")
updatedAt DateTime @updatedAt
createdAt DateTime @default(now())
headerLogoUrl String @default("/brand/header-logo.png")
loginLogoUrl String @default("/brand/login-logo.png")
s3AccessKey String @default("")
s3Bucket String @default("")
s3Endpoint String @default("")
s3PublicUrl String @default("")
s3Region String @default("us-east-1")
s3SecretKey String @default("")
maintenanceEnabled Boolean @default(false) @map("maintenance_enabled")
maintenanceMessage String? @map("maintenance_message")
maintenanceStartTime DateTime? @map("maintenance_start_time")
maintenanceEndTime DateTime? @map("maintenance_end_time")
@@map("system_settings")
}
model Department {
id String @id @default(uuid())
name String @unique
createdAt DateTime @default(now())
parentId String?
level Int @default(1)
status String @default("active")
managerId String?
code String? @unique
description String?
sortOrder Int @default(0)
updatedAt DateTime @updatedAt
manager User? @relation("DepartmentManager", fields: [managerId], references: [id])
parent Department? @relation("DepartmentHierarchy", fields: [parentId], references: [id])
children Department[] @relation("DepartmentHierarchy")
users User[] @relation("UserDepartment")
transferredUsers User[] @relation("UserTransferDepartment")
custodyMaterials Material[] @relation("MaterialCustodyDepartment")
@@index([parentId])
@@index([status])
@@index([level])
@@index([managerId])
@@map("departments")
}
model Danmaku {
id String @id @default(uuid())
content String
username String
userId String
createdAt DateTime @default(now())
@@index([createdAt])
@@map("danmakus")
}
model DepartmentMaterialPermission {
id String @id @default(uuid())
department String
materialType String
canView Boolean @default(false)
canDownload Boolean @default(false)
canShare Boolean @default(true)
linkedToDepartment String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
canDownloadSource Boolean @default(false) @map("can_download_source")
@@unique([department, materialType])
@@index([department])
@@index([materialType])
@@index([linkedToDepartment])
@@map("department_material_permissions")
}
/// 权限组:一份权限,同时挂部门和人。部门进组是底子,人进组是加餐。
model MaterialPermissionPack {
id String @id @default(uuid())
slug String @unique
name String
videoEarlyAccess Boolean @default(false) @map("video_early_access")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
items MaterialPermissionPackItem[]
departments MaterialPermissionPackDepartment[]
inheritors UserPermissionInherit[]
@@map("material_permission_packs")
}
model MaterialPermissionPackItem {
id String @id @default(uuid())
packId String @map("pack_id")
materialType String @map("material_type")
canView Boolean @default(false) @map("can_view")
canDownload Boolean @default(false) @map("can_download")
canDownloadSource Boolean @default(false) @map("can_download_source")
canShare Boolean @default(true) @map("can_share")
canEditAssets Boolean @default(false) @map("can_edit_assets")
canAccessPrivate Boolean @default(false) @map("can_access_private")
pack MaterialPermissionPack @relation(fields: [packId], references: [id], onDelete: Cascade)
@@unique([packId, materialType], map: "material_permission_pack_item_pack_type_key")
@@index([packId], map: "material_permission_pack_item_pack_idx")
@@map("material_permission_pack_items")
}
/// 权限组里的部门。子部门没单独进组时,沿用最近的上级组。
model MaterialPermissionPackDepartment {
id String @id @default(uuid())
packId String @map("pack_id")
departmentPath String @map("department_path")
createdAt DateTime @default(now()) @map("created_at")
pack MaterialPermissionPack @relation(fields: [packId], references: [id], onDelete: Cascade)
@@unique([packId, departmentPath], map: "material_permission_pack_dept_pack_path_key")
@@unique([departmentPath], map: "material_permission_pack_dept_path_key")
@@map("material_permission_pack_departments")
}
/// 人可以进多组。只按 userId 绑定,不按姓名。
model UserPermissionInherit {
id String @id @default(uuid())
userId String @map("user_id")
packId String @map("pack_id")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
pack MaterialPermissionPack @relation(fields: [packId], references: [id], onDelete: Restrict)
@@unique([userId, packId], map: "user_permission_inherit_user_pack_key")
@@index([packId], map: "user_permission_inherit_pack_idx")
@@index([userId], map: "user_permission_inherit_user_idx")
@@map("user_permission_inherits")
}
model MaterialTransfer {
id String @id @default(uuid())
materialId String
fromUserId String
toUserId String
transferredBy String
transferNote String?
transferredAt DateTime @default(now())
fromUser User @relation("MaterialTransferFromUser", fields: [fromUserId], references: [id])
material Material @relation(fields: [materialId], references: [id], onDelete: Cascade)
toUser User @relation("MaterialTransferToUser", fields: [toUserId], references: [id])
operator User @relation("MaterialTransferOperator", fields: [transferredBy], references: [id])
@@index([materialId])
@@index([fromUserId])
@@index([toUserId])
@@index([transferredAt])
@@map("material_transfers")
}
model MaterialAsset {
id String @id @default(uuid())
materialId String @map("material_id")
assetType String @map("asset_type")
fileUrl String @map("file_url")
fileName String? @map("file_name")
fileSize BigInt @default(0) @map("file_size")
sortOrder Int @default(0) @map("sort_order")
skuCode String? @map("sku_code")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
material Material @relation(fields: [materialId], references: [id], onDelete: Cascade)
barcodes MaterialAssetBarcode[]
@@index([materialId])
@@index([assetType])
@@index([sortOrder])
@@map("material_assets")
}
model MaterialAssetBarcode {
id String @id @default(uuid())
assetId String @map("asset_id")
barcode String
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
asset MaterialAsset @relation(fields: [assetId], references: [id], onDelete: Cascade)
@@unique([assetId, barcode])
@@index([barcode])
@@index([assetId])
@@map("material_asset_barcodes")
}
model MaterialSkuBarcode {
id String @id @default(uuid())
skuId String @map("sku_id")
barcode String
deletedAt DateTime? @map("deleted_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
sku MaterialSku @relation(fields: [skuId], references: [id], onDelete: Cascade)
@@index([barcode])
@@index([skuId])
@@index([deletedAt])
@@map("material_sku_barcodes")
}
model ApiKey {
id String @id @default(uuid())
name String
key String @unique
secretCiphertext String? @map("secret_ciphertext")
secretNonce String? @map("secret_nonce")
secretAuthTag String? @map("secret_auth_tag")
isActive Boolean @default(true) @map("is_active")
createdBy String @map("created_by")
lastUsedAt DateTime? @map("last_used_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
callLogs ApiCallLog[]
externalMaterialImportJobs ExternalMaterialImportJob[]
idempotencyRecords ExternalApiIdempotencyRecord[]
@@index([key])
@@index([isActive])
@@map("api_keys")
}
model ApiKeyRevealAudit {
id String @id @default(uuid())
apiKeyId String @map("api_key_id")
viewerUserId String @map("viewer_user_id")
createdAt DateTime @default(now()) @map("created_at")
@@index([apiKeyId, createdAt])
@@index([viewerUserId, createdAt])
@@map("api_key_reveal_audits")
}
model ExternalApiIdempotencyRecord {
id String @id @default(uuid())
apiKeyId String @map("api_key_id")
operationType String @map("operation_type")
clientRequestId String @map("client_request_id")
requestHash String @map("request_hash")
status String
responseStatus Int? @map("response_status")
responseBody Json? @map("response_body")
completedAt DateTime? @map("completed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
apiKey ApiKey @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)
@@unique([apiKeyId, operationType, clientRequestId])
@@index([apiKeyId])
@@index([operationType])
@@index([clientRequestId])
@@index([status])
@@index([createdAt])
@@map("external_api_idempotency_records")
}
model ApiCallLog {
id String @id @default(uuid())
apiKeyId String @map("api_key_id")
endpoint String
method String
taskNo String? @map("task_no")
queryType String? @map("query_type")
queryValue String? @map("query_value")
statusCode Int @map("status_code")
duration Int
ipAddress String? @map("ip_address")
userAgent String? @map("user_agent")
createdAt DateTime @default(now()) @map("created_at")
apiKey ApiKey @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)
@@index([apiKeyId])
@@index([taskNo])
@@index([queryType])
@@index([queryValue])
@@index([createdAt])
@@map("api_call_logs")
}
model ExternalMaterialImportJob {
id String @id @default(uuid())
apiKeyId String @map("api_key_id")
clientRequestId String @map("client_request_id")
materialType String @map("material_type")
taskNo String @map("task_no")
status String
resolvedUserId String? @map("resolved_user_id")
requestPayload Json @map("request_payload")
submissionSnapshot Json @map("submission_snapshot")
resultMaterialId String? @map("result_material_id")
errorCode String? @map("error_code")
errorMessage String? @map("error_message")
completedAt DateTime? @map("completed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
apiKey ApiKey @relation(fields: [apiKeyId], references: [id], onDelete: Cascade)
@@unique([apiKeyId, clientRequestId])
@@index([taskNo, status])
@@index([status])
@@index([createdAt])
@@map("external_material_import_jobs")
}
model PerformanceSurveyEmployeeProfile {
id String @id @default(uuid())
dingtalkUserId String? @unique @map("dingtalk_user_id")
dingEmail String? @map("ding_email")
jobNumber String? @unique @map("job_number")
flowerName String? @map("flower_name")
realName String? @map("real_name")
displayName String @map("display_name")
departmentName String? @map("department_name")
departmentPath String? @map("department_path")
positionTitle String? @map("position_title")
positionLevel String? @map("position_level")
levelType String? @map("level_type")
isLeadership Boolean @default(false) @map("is_leadership")
isDingtalkAdmin Boolean @default(false) @map("is_dingtalk_admin")
isDingtalkBoss Boolean @default(false) @map("is_dingtalk_boss")
isDingtalkSenior Boolean @default(false) @map("is_dingtalk_senior")
managerDingtalkUserId String? @map("manager_dingtalk_user_id")
managerJobNumber String? @map("manager_job_number")
managerName String? @map("manager_name")
employmentStatus String? @map("employment_status")
/// 规范化的在职状态:ACTIVE / RESIGNED / UNKNOWN。
/// employmentStatus 是上游原样值,口径混杂(有数字码 2/3/5,也有「正式」「离职」「试用」等中文,
/// 还有大量空值),靠关键词匹配判断在职会大面积误判,因此单独规范化一列供运行时判定使用。
employmentState String? @default("UNKNOWN") @map("employment_state")
/// 最后工作日,来自上游离职表
resignedAt DateTime? @map("resigned_at")
/// 离职交接人的钉钉唯一值,上游填写率较低,仅作参考
handoverDingtalkUserId String? @map("handover_dingtalk_user_id")
workPlace String? @map("work_place")
sourcePayload Json? @map("source_payload")
syncedAt DateTime? @map("synced_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
evaluatorAssignments PerformanceSurveyAssignment[] @relation("PerformanceSurveyEvaluator")
targetRecords PerformanceSurveyTarget[] @relation("PerformanceSurveyTargetEmployee")
responses PerformanceSurveyResponse[] @relation("PerformanceSurveyRespondentEmployee")
submittedWorkItems PerformanceSurveyWorkItem[] @relation("PerformanceSurveyWorkItemSubmitter")
workItemScores PerformanceSurveyWorkItemScore[] @relation("PerformanceSurveyWorkItemScoreEvaluator")
notifications PerformanceSurveyNotification[]
manualPositionAssignments PerformanceSurveyEmployeePositionAssignment[]
departmentMemberships PerformanceSurveyEmployeeDepartmentMembership[]
userBindings UserEmployeeBinding[]
submittedWorkReviewProjects WorkReviewProject[] @relation("WorkReviewSubmitter")
managedWorkReviewProjects WorkReviewProject[] @relation("WorkReviewManager")
finalWorkReviewProjects WorkReviewProject[] @relation("WorkReviewFinalReviewer")
workReviews WorkReviewReview[] @relation("WorkReviewReviewer")
submittedTaskDetailChangeRequests TaskDetailChangeRequest[] @relation("TaskDetailChangeRequesterEmployee")
managedTaskDetailChangeRequests TaskDetailChangeRequest[] @relation("TaskDetailChangeManagerEmployee")
@@index([displayName])
@@index([dingEmail])
@@index([departmentName])
@@index([positionLevel])
@@index([levelType])
@@index([isLeadership])
@@index([isDingtalkAdmin])
@@index([managerDingtalkUserId])
@@index([employmentStatus])
@@index([employmentState])
@@map("performance_survey_employee_profiles")
}
/// 共享组织架构,直接镜像上游 HR 系统的部门树(rpt.hr_groups)。
///
/// 此前系统没有真正的组织表:部门树是遍历已同步员工的 departmentPath 字符串
/// 现场拼出来的,因而只能看到"有人的部门",且同名部门会被合并、父子归属取决于
/// 谁先被遍历到。上游其实是一棵完整的单根树(单一根节点、零孤儿节点),
/// 直接同步即可,不必倒推。
model OrganizationUnit {
id String @id @default(uuid())
/// 上游 hr_groups.Id
sourceDeptId Int @unique @map("source_dept_id")
name String
parentId String? @map("parent_id")
/// 上游 hr_groups.parent_id,同步期间用于重建父子关系
sourceParentId Int? @map("source_parent_id")
/// 以 / 分隔的全路径,展示与前缀匹配用
path String
level Int @default(1)
/// 本次同步是否仍存在于上游;上游删除的部门置为 false 而非物理删除
isActive Boolean @default(true) @map("is_active")
syncedAt DateTime? @map("synced_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
parent OrganizationUnit? @relation("OrganizationHierarchy", fields: [parentId], references: [id])
children OrganizationUnit[] @relation("OrganizationHierarchy")
@@index([parentId])
@@index([sourceParentId])
@@index([path])
@@index([isActive])
@@map("organization_units")
}
/// 素材库账号与权威人员档案的正式绑定关系。
/// 取代此前"运行时按钉钉ID/邮箱/sourcePayload 逐层软匹配"的隐式关联做法。
/// 活跃绑定的唯一性由部分唯一索引保证(仅约束 status='ACTIVE',见对应 migration),
/// 保证一个账号只对应一名权威人员、一名权威人员只对应一个活跃账号。
/// 冲突一律进人工处理,禁止按姓名自动合并。
model UserEmployeeBinding {
id String @id @default(uuid())
userId String @map("user_id")
employeeProfileId String @map("employee_profile_id")
bindingSource String @map("binding_source")
status String @default("ACTIVE")
verifiedAt DateTime? @map("verified_at")
note String?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
employeeProfile PerformanceSurveyEmployeeProfile @relation(fields: [employeeProfileId], references: [id], onDelete: Cascade)
@@index([userId], map: "user_employee_binding_user_idx")
@@index([employeeProfileId], map: "user_employee_binding_profile_idx")
@@index([status], map: "user_employee_binding_status_idx")
@@map("user_employee_bindings")
}
model PerformanceSurveyEmployeeDepartmentMembership {
id String @id @default(uuid())
employeeProfileId String @map("employee_profile_id")
sourceDeptId String @map("source_dept_id")
departmentName String? @map("department_name")
departmentPath String? @map("department_path")
isPrimary Boolean @default(false) @map("is_primary")
syncedAt DateTime? @map("synced_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
employeeProfile PerformanceSurveyEmployeeProfile @relation(fields: [employeeProfileId], references: [id], onDelete: Cascade)
@@unique([employeeProfileId, sourceDeptId], map: "ps_employee_dept_profile_dept_key")
@@index([sourceDeptId], map: "ps_employee_dept_source_dept_idx")
@@index([departmentName], map: "ps_employee_dept_name_idx")
@@index([departmentPath], map: "ps_employee_dept_path_idx")
@@index([employeeProfileId, isPrimary], map: "ps_employee_dept_profile_primary_idx")
@@map("performance_survey_employee_department_memberships")
}
/// 员工组织与在职状态的时间快照。
///
/// 人员主档会被每日同步更新,不能据此还原历史周的编制。该表按小时保存
/// 完整人员状态,团队产能按周末前最近一份快照计算历史人数;快照不依赖
/// 当前主档是否仍存在,保留离职与调岗前的事实记录。
model PerformanceSurveyEmployeeRosterSnapshot {
id String @id @default(uuid())
capturedAt DateTime @map("captured_at")
profileCount Int @map("profile_count")
employeeProfileId String @map("employee_profile_id")
dingtalkUserId String? @map("dingtalk_user_id")
name String
departmentName String? @map("department_name")
departmentPath String? @map("department_path")
employmentState String? @map("employment_state")
isBrandCenter Boolean @default(false) @map("is_brand_center")
createdAt DateTime @default(now()) @map("created_at")
@@unique([capturedAt, employeeProfileId], map: "ps_employee_roster_snapshot_capture_profile_key")
@@index([capturedAt], map: "ps_employee_roster_snapshot_captured_at_idx")
@@index([departmentName, capturedAt], map: "ps_employee_roster_snapshot_department_time_idx")
@@index([dingtalkUserId, capturedAt], map: "ps_employee_roster_snapshot_dingtalk_time_idx")
@@map("performance_survey_employee_roster_snapshots")
}
model PerformanceSurveyDepartmentProfile {
id String @id @default(uuid())
name String @unique
parentName String? @map("parent_name")
sourceDeptId String? @map("source_dept_id")
sortOrder Int @default(0) @map("sort_order")
employeeCount Int @default(0) @map("employee_count")
syncedAt DateTime? @map("synced_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([parentName])
@@index([sourceDeptId])
@@map("performance_survey_department_profiles")
}
model PerformanceSurveyTemplate {
id String @id @default(uuid())
code String @unique
name String
description String?
surveyType String @default("person_period") @map("survey_type")
weight Float @default(1)
questions Json
scoreBands Json @map("score_bands")
isActive Boolean @default(true) @map("is_active")
archivedAt DateTime? @map("archived_at")
archivedByUserId String? @map("archived_by_user_id")
archivedByName String? @map("archived_by_name")
archiveReason String? @map("archive_reason") @db.VarChar(500)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
versions PerformanceSurveyTemplateVersion[]
@@index([surveyType])
@@index([isActive])
@@index([archivedAt])
@@map("performance_survey_templates")
}
model PerformanceSurveyTemplateVersion {
id String @id @default(uuid())
templateId String @map("template_id")
version Int
status String @default("draft")
effectiveMonth String? @map("effective_month")
definition Json
publishedAt DateTime? @map("published_at")
createdByUserId String? @map("created_by_user_id")
createdByName String? @map("created_by_name")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
template PerformanceSurveyTemplate @relation(fields: [templateId], references: [id], onDelete: Cascade)
positionPlanItems PerformanceSurveyPositionPlanItem[]
surveys PerformanceSurvey[]
@@unique([templateId, version], map: "performance_survey_template_version_unique")
@@index([status, effectiveMonth], map: "performance_survey_template_version_status_month_idx")
@@map("performance_survey_template_versions")
}
model PerformanceSurveyPositionGroup {
id String @id @default(uuid())
code String @unique
name String
sortOrder Int @default(0) @map("sort_order")
isActive Boolean @default(true) @map("is_active")
isSystem Boolean @default(false) @map("is_system")
archivedAt DateTime? @map("archived_at")
archivedByUserId String? @map("archived_by_user_id")
archivedByName String? @map("archived_by_name")
archiveReason String? @map("archive_reason") @db.VarChar(500)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
rules PerformanceSurveyPositionRule[]
plans PerformanceSurveyPositionPlan[]
surveys PerformanceSurvey[]
manualAssignments PerformanceSurveyEmployeePositionAssignment[]
@@index([isActive, sortOrder], map: "performance_survey_position_group_active_sort_idx")
@@index([archivedAt, sortOrder], map: "performance_survey_position_group_archived_sort_idx")
@@map("performance_survey_position_groups")
}
model PerformanceSurveyPositionRule {
id String @id @default(uuid())
positionGroupId String @map("position_group_id")
departmentPathPrefix String? @map("department_path_prefix")
positionTitle String? @map("position_title")
sortOrder Int @default(0) @map("sort_order")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
positionGroup PerformanceSurveyPositionGroup @relation(fields: [positionGroupId], references: [id], onDelete: Cascade)
@@index([positionGroupId, isActive, sortOrder], map: "performance_survey_position_rule_group_idx")
@@index([positionTitle], map: "performance_survey_position_rule_title_idx")
@@map("performance_survey_position_rules")
}
model PerformanceSurveyPositionPlan {
id String @id @default(uuid())
positionGroupId String @map("position_group_id")
version Int
status String @default("draft")
effectiveMonth String? @map("effective_month")
matchRules Json @default("[]") @map("match_rules")
groupConfig Json? @map("group_config")
publishedAt DateTime? @map("published_at")
createdByUserId String? @map("created_by_user_id")
createdByName String? @map("created_by_name")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
positionGroup PerformanceSurveyPositionGroup @relation(fields: [positionGroupId], references: [id], onDelete: Cascade)
items PerformanceSurveyPositionPlanItem[]
@@unique([positionGroupId, version], map: "performance_survey_position_plan_version_unique")
@@index([positionGroupId, status, effectiveMonth], map: "performance_survey_position_plan_group_status_month_idx")
@@map("performance_survey_position_plans")
}
model PerformanceSurveyEmployeePositionAssignment {
id String @id @default(uuid())
employeeProfileId String @map("employee_profile_id")
assignmentType String @map("assignment_type")
positionGroupId String? @map("position_group_id")
startMonth String @map("start_month")
endMonth String? @map("end_month")
reason String @db.VarChar(500)
status String @default("active")
createdByUserId String @map("created_by_user_id")
createdByName String @map("created_by_name")
archivedAt DateTime? @map("archived_at")
archivedByUserId String? @map("archived_by_user_id")
archivedByName String? @map("archived_by_name")
createdAt DateTime @default(now()) @map("created_at")
employeeProfile PerformanceSurveyEmployeeProfile @relation(fields: [employeeProfileId], references: [id], onDelete: Restrict)
positionGroup PerformanceSurveyPositionGroup? @relation(fields: [positionGroupId], references: [id], onDelete: Restrict)
@@index([employeeProfileId, status, startMonth, endMonth], map: "performance_survey_manual_assignment_employee_range_idx")
@@index([positionGroupId], map: "performance_survey_manual_assignment_group_idx")
@@index([status, startMonth, endMonth], map: "performance_survey_manual_assignment_status_range_idx")
@@map("performance_survey_employee_position_assignments")
}
model PerformanceSurveyPositionPlanItem {
id String @id @default(uuid())
positionPlanId String @map("position_plan_id")
templateVersionId String @map("template_version_id")
sortOrder Int @default(0) @map("sort_order")
isEnabled Boolean @default(true) @map("is_enabled")
createdAt DateTime @default(now()) @map("created_at")
positionPlan PerformanceSurveyPositionPlan @relation(fields: [positionPlanId], references: [id], onDelete: Cascade)
templateVersion PerformanceSurveyTemplateVersion @relation(fields: [templateVersionId], references: [id], onDelete: Restrict)
@@unique([positionPlanId, templateVersionId], map: "performance_survey_position_plan_item_unique")
@@index([positionPlanId, isEnabled, sortOrder], map: "performance_survey_position_plan_item_sort_idx")
@@map("performance_survey_position_plan_items")
}
model PerformanceSurvey {
id String @id @default(uuid())
title String
description String?
surveyKind String @default("person_period") @map("survey_kind")
templateCode String @map("template_code")
templateSnapshot Json @map("template_snapshot")
templateVersionId String? @map("template_version_id")
positionGroupId String? @map("position_group_id")
positionGroupCode String? @map("position_group_code")
positionGroupSnapshot Json? @map("position_group_snapshot")
status String @default("draft")
creatorUserId String @map("creator_user_id")
creatorName String @map("creator_name")
creatorEmployeeId String? @map("creator_employee_id")
departmentName String? @map("department_name")
assessmentMonth String? @map("assessment_month")
dueAt DateTime? @map("due_at")
openedAt DateTime? @map("opened_at")
closedAt DateTime? @map("closed_at")
cancelledAt DateTime? @map("cancelled_at")
cancelledByUserId String? @map("cancelled_by_user_id")
cancelledByName String? @map("cancelled_by_name")
cancelReason String? @db.VarChar(500) @map("cancel_reason")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
targets PerformanceSurveyTarget[]
assignments PerformanceSurveyAssignment[]
responses PerformanceSurveyResponse[]
workItems PerformanceSurveyWorkItem[]
workItemScores PerformanceSurveyWorkItemScore[]
notifications PerformanceSurveyNotification[]
templateVersion PerformanceSurveyTemplateVersion? @relation(fields: [templateVersionId], references: [id], onDelete: SetNull)
positionGroup PerformanceSurveyPositionGroup? @relation(fields: [positionGroupId], references: [id], onDelete: SetNull)
@@index([status])
@@index([surveyKind])
@@index([templateCode])
@@index([templateVersionId])
@@index([positionGroupId, assessmentMonth], map: "performance_surveys_position_group_month_idx")
@@index([positionGroupCode, assessmentMonth], map: "performance_surveys_position_group_code_month_idx")
@@index([templateCode, assessmentMonth], map: "performance_surveys_template_month_idx")
@@index([creatorUserId])
@@index([departmentName])
@@index([assessmentMonth])
@@index([dueAt])
@@map("performance_surveys")
}
model PerformanceSurveyTarget {
id String @id @default(uuid())
surveyId String @map("survey_id")
targetType String @default("employee") @map("target_type")
employeeProfileId String? @map("employee_profile_id")
targetName String @map("target_name")
targetSnapshot Json @map("target_snapshot")
sortOrder Int @default(0) @map("sort_order")
createdAt DateTime @default(now()) @map("created_at")
survey PerformanceSurvey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
employeeProfile PerformanceSurveyEmployeeProfile? @relation("PerformanceSurveyTargetEmployee", fields: [employeeProfileId], references: [id])
assignments PerformanceSurveyAssignment[]
responses PerformanceSurveyResponse[]
workItems PerformanceSurveyWorkItem[]
workItemScores PerformanceSurveyWorkItemScore[]
notifications PerformanceSurveyNotification[]
@@index([surveyId])
@@index([targetType])
@@index([employeeProfileId])
@@index([employeeProfileId, surveyId], map: "performance_survey_targets_employee_survey_idx")
@@map("performance_survey_targets")
}
model PerformanceSurveyAssignment {
id String @id @default(uuid())
surveyId String @map("survey_id")
targetId String @map("target_id")
evaluatorEmployeeId String? @map("evaluator_employee_id")
evaluatorUserId String? @map("evaluator_user_id")
evaluatorDingtalkUserId String? @map("evaluator_dingtalk_user_id")
evaluatorKey String @map("evaluator_key")
evaluatorName String @map("evaluator_name")
evaluatorSnapshot Json @map("evaluator_snapshot")
workflowStageKey String @default("legacy") @map("workflow_stage_key")
workflowStageOrder Int @default(0) @map("workflow_stage_order")
reviewerSource String @default("manual_selection") @map("reviewer_source")
releasedAt DateTime? @map("released_at")
status String @default("pending")
submittedAt DateTime? @map("submitted_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
survey PerformanceSurvey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
target PerformanceSurveyTarget @relation(fields: [targetId], references: [id], onDelete: Cascade)
evaluatorEmployee PerformanceSurveyEmployeeProfile? @relation("PerformanceSurveyEvaluator", fields: [evaluatorEmployeeId], references: [id])
response PerformanceSurveyResponse?
workItemScores PerformanceSurveyWorkItemScore[]
notifications PerformanceSurveyNotification[]
@@unique([targetId, evaluatorKey, workflowStageKey], map: "performance_survey_assignment_target_evaluator_stage_key")
@@index([surveyId])
@@index([targetId])
@@index([evaluatorEmployeeId])
@@index([evaluatorUserId])
@@index([evaluatorDingtalkUserId])
@@index([evaluatorKey])
@@index([surveyId, targetId, workflowStageOrder], map: "performance_survey_assignment_stage_idx")
@@index([status])
@@map("performance_survey_assignments")
}
model PerformanceSurveyResponse {
id String @id @default(uuid())
surveyId String @map("survey_id")
targetId String @map("target_id")
assignmentId String @unique @map("assignment_id")
respondentUserId String @map("respondent_user_id")
respondentEmployeeId String? @map("respondent_employee_id")
respondentName String @map("respondent_name")
respondentSnapshot Json @map("respondent_snapshot")
scores Json
comment String?
totalScore Float @map("total_score")
achievementScore Float @map("achievement_score")
kpiScore Float @map("kpi_score")
weightedScore Float @map("weighted_score")
createdAt DateTime @default(now()) @map("created_at")
survey PerformanceSurvey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
target PerformanceSurveyTarget @relation(fields: [targetId], references: [id], onDelete: Cascade)
assignment PerformanceSurveyAssignment @relation(fields: [assignmentId], references: [id], onDelete: Cascade)
respondentEmployee PerformanceSurveyEmployeeProfile? @relation("PerformanceSurveyRespondentEmployee", fields: [respondentEmployeeId], references: [id])
@@index([surveyId])
@@index([targetId])
@@index([respondentUserId])
@@index([respondentEmployeeId])
@@index([createdAt])
@@map("performance_survey_responses")
}
model PerformanceSurveyWorkItem {
id String @id @default(uuid())
surveyId String @map("survey_id")
targetId String @map("target_id")
submitterUserId String? @map("submitter_user_id")
submitterEmployeeId String? @map("submitter_employee_id")
title String
description String?
imageUrls Json @map("image_urls")
sortOrder Int @default(0) @map("sort_order")
status String @default("submitted")
submittedAt DateTime? @map("submitted_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
survey PerformanceSurvey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
target PerformanceSurveyTarget @relation(fields: [targetId], references: [id], onDelete: Cascade)
submitterEmployee PerformanceSurveyEmployeeProfile? @relation("PerformanceSurveyWorkItemSubmitter", fields: [submitterEmployeeId], references: [id])
scores PerformanceSurveyWorkItemScore[]
@@index([surveyId])
@@index([targetId])
@@index([submitterUserId])
@@index([submitterEmployeeId])
@@index([status])
@@map("performance_survey_work_items")
}
model PerformanceSurveyWorkItemScore {
id String @id @default(uuid())
workItemId String @map("work_item_id")
surveyId String @map("survey_id")
targetId String @map("target_id")
assignmentId String @map("assignment_id")
evaluatorUserId String @map("evaluator_user_id")
evaluatorEmployeeId String? @map("evaluator_employee_id")
evaluatorName String @map("evaluator_name")
scores Json
comment String?
totalScore Float @map("total_score")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
workItem PerformanceSurveyWorkItem @relation(fields: [workItemId], references: [id], onDelete: Cascade)
survey PerformanceSurvey @relation(fields: [surveyId], references: [id], onDelete: Cascade)
target PerformanceSurveyTarget @relation(fields: [targetId], references: [id], onDelete: Cascade)
assignment PerformanceSurveyAssignment @relation(fields: [assignmentId], references: [id], onDelete: Cascade)
evaluatorEmployee PerformanceSurveyEmployeeProfile? @relation("PerformanceSurveyWorkItemScoreEvaluator", fields: [evaluatorEmployeeId], references: [id])
@@unique([workItemId, assignmentId], map: "performance_survey_work_item_score_unique")
@@index([surveyId])
@@index([targetId])
@@index([assignmentId])
@@index([evaluatorUserId])
@@index([evaluatorEmployeeId])
@@map("performance_survey_work_item_scores")
}
model PerformanceSurveyNotification {
id String @id @default(uuid())
surveyId String? @map("survey_id")
targetId String? @map("target_id")
assignmentId String? @map("assignment_id")
recipientEmployeeId String? @map("recipient_employee_id")
recipientUserId String? @map("recipient_user_id")
dingtalkUserId String? @map("dingtalk_user_id")
recipientName String @map("recipient_name")
eventType String @map("event_type")
businessKey String @map("business_key")
title String
content String
actionUrl String? @map("action_url")
status String @default("pending")
attemptCount Int @default(0) @map("attempt_count")
maxAttempts Int @default(5) @map("max_attempts")
nextRetryAt DateTime? @map("next_retry_at")
lastStatusCode Int? @map("last_status_code")
lastResponseBody String? @map("last_response_body")
lastError String? @map("last_error")
providerType String @default("work_notice") @map("provider_type")
providerMessageId String? @map("provider_message_id")
readStatus String @default("unknown") @map("read_status")
readAt DateTime? @map("read_at")
readStatusCheckedAt DateTime? @map("read_status_checked_at")
sentAt DateTime? @map("sent_at")
completedAt DateTime? @map("completed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
survey PerformanceSurvey? @relation(fields: [surveyId], references: [id], onDelete: Cascade)
target PerformanceSurveyTarget? @relation(fields: [targetId], references: [id], onDelete: Cascade)
assignment PerformanceSurveyAssignment? @relation(fields: [assignmentId], references: [id], onDelete: Cascade)
recipientEmployee PerformanceSurveyEmployeeProfile? @relation(fields: [recipientEmployeeId], references: [id])
@@unique([eventType, businessKey], map: "performance_survey_notification_event_key")
@@index([surveyId])
@@index([targetId])
@@index([assignmentId])
@@index([recipientEmployeeId])
@@index([recipientUserId])
@@index([dingtalkUserId])
@@index([status, nextRetryAt])
@@index([readStatus, readStatusCheckedAt], map: "performance_survey_notifications_read_status_checked_at_idx")
@@index([createdAt])
@@map("performance_survey_notifications")
}
model PerformanceSurveyConfigAuditLog {
id String @id @default(uuid())
action String
targetType String @map("target_type")
targetId String? @map("target_id")
actorUserId String @map("actor_user_id")
actorName String @map("actor_name")
reason String? @db.VarChar(500)
beforeSnapshot Json? @map("before_snapshot")
afterSnapshot Json? @map("after_snapshot")
metadata Json?
createdAt DateTime @default(now()) @map("created_at")
@@index([targetType, targetId, createdAt], map: "performance_survey_config_audit_target_idx")
@@index([actorUserId, createdAt], map: "performance_survey_config_audit_actor_idx")
@@index([createdAt], map: "performance_survey_config_audit_created_idx")
@@map("performance_survey_config_audit_logs")
}
model WorkReviewProject {
id String @id @default(uuid())
title String
brand String?
description String?
workDate DateTime @db.Date @map("work_date")
submitterUserId String @map("submitter_user_id")
submitterEmployeeId String @map("submitter_employee_id")
submitterName String @map("submitter_name")
submitterSnapshot Json @map("submitter_snapshot")
managerEmployeeId String @map("manager_employee_id")
managerUserId String? @map("manager_user_id")
managerDingtalkUserId String? @map("manager_dingtalk_user_id")
managerName String @map("manager_name")
finalReviewerEmployeeId String @map("final_reviewer_employee_id")
finalReviewerUserId String? @map("final_reviewer_user_id")
finalReviewerDingtalkUserId String? @map("final_reviewer_dingtalk_user_id")
finalReviewerName String @map("final_reviewer_name")
status String @default("pending_review")
latestVersion Int @default(1) @map("latest_version")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
submitterEmployee PerformanceSurveyEmployeeProfile @relation("WorkReviewSubmitter", fields: [submitterEmployeeId], references: [id], onDelete: Restrict)
managerEmployee PerformanceSurveyEmployeeProfile @relation("WorkReviewManager", fields: [managerEmployeeId], references: [id], onDelete: Restrict)
finalReviewerEmployee PerformanceSurveyEmployeeProfile @relation("WorkReviewFinalReviewer", fields: [finalReviewerEmployeeId], references: [id], onDelete: Restrict)
versions WorkReviewVersion[]
notifications WorkReviewNotification[]
@@index([submitterUserId, workDate], map: "work_review_projects_submitter_date_idx")
@@index([submitterEmployeeId, workDate], map: "work_review_projects_submitter_employee_date_idx")
@@index([managerEmployeeId, status], map: "work_review_projects_manager_status_idx")
@@index([finalReviewerEmployeeId, status], map: "work_review_projects_final_reviewer_status_idx")
@@index([status, workDate], map: "work_review_projects_status_date_idx")
@@index([updatedAt], map: "work_review_projects_updated_at_idx")
@@map("work_review_projects")
}
model WorkReviewVersion {
id String @id @default(uuid())
projectId String @map("project_id")
version Int
submissionNote String? @map("submission_note")
imageUrls Json @map("image_urls")
status String @default("pending_review")
submittedAt DateTime @default(now()) @map("submitted_at")
completedAt DateTime? @map("completed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
project WorkReviewProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
reviews WorkReviewReview[]
notifications WorkReviewNotification[]
@@unique([projectId, version], map: "work_review_versions_project_version_key")
@@index([projectId, submittedAt], map: "work_review_versions_project_submitted_idx")
@@index([status, submittedAt], map: "work_review_versions_status_submitted_idx")
@@map("work_review_versions")
}
model WorkReviewReview {
id String @id @default(uuid())
versionId String @map("version_id")
reviewerRole String @map("reviewer_role")
reviewerEmployeeId String @map("reviewer_employee_id")
reviewerUserId String? @map("reviewer_user_id")
reviewerName String @map("reviewer_name")
reviewerSnapshot Json @map("reviewer_snapshot")
decision String
imageComments Json @map("image_comments")
generalComment String? @map("general_comment")
reviewedAt DateTime @default(now()) @map("reviewed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
version WorkReviewVersion @relation(fields: [versionId], references: [id], onDelete: Cascade)
reviewerEmployee PerformanceSurveyEmployeeProfile @relation("WorkReviewReviewer", fields: [reviewerEmployeeId], references: [id], onDelete: Restrict)
@@unique([versionId, reviewerRole], map: "work_review_reviews_version_role_key")
@@index([reviewerEmployeeId, reviewedAt], map: "work_review_reviews_reviewer_reviewed_idx")
@@index([decision], map: "work_review_reviews_decision_idx")
@@map("work_review_reviews")
}
model WorkReviewNotification {
id String @id @default(uuid())
projectId String @map("project_id")
versionId String @map("version_id")
recipientEmployeeId String? @map("recipient_employee_id")
recipientUserId String? @map("recipient_user_id")
dingtalkUserId String? @map("dingtalk_user_id")
recipientName String @map("recipient_name")
eventType String @map("event_type")
businessKey String @map("business_key")
title String
content String
actionUrl String? @map("action_url")
status String @default("pending")
attemptCount Int @default(0) @map("attempt_count")
maxAttempts Int @default(5) @map("max_attempts")
nextRetryAt DateTime? @map("next_retry_at")
providerType String @default("work_notice") @map("provider_type")
providerMessageId String? @map("provider_message_id")
lastError String? @map("last_error")
sentAt DateTime? @map("sent_at")
completedAt DateTime? @map("completed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
project WorkReviewProject @relation(fields: [projectId], references: [id], onDelete: Cascade)
version WorkReviewVersion @relation(fields: [versionId], references: [id], onDelete: Cascade)
@@unique([eventType, businessKey], map: "work_review_notifications_event_key")
@@index([projectId, createdAt], map: "work_review_notifications_project_created_idx")
@@index([versionId], map: "work_review_notifications_version_idx")
@@index([recipientEmployeeId], map: "work_review_notifications_recipient_employee_idx")
@@index([recipientUserId], map: "work_review_notifications_recipient_user_idx")
@@index([dingtalkUserId], map: "work_review_notifications_dingtalk_user_idx")
@@index([status, nextRetryAt], map: "work_review_notifications_status_retry_idx")
@@map("work_review_notifications")
}
/// 任务详情图片变更申请。正式素材只在直属主管批准后才应用。
model TaskDetailChangeRequest {
id String @id @default(uuid())
materialId String @map("material_id")
requesterUserId String @map("requester_user_id")
requesterEmployeeId String @map("requester_employee_id")
requesterName String @map("requester_name")
requesterSnapshot Json @map("requester_snapshot")
requesterPositionLevel String? @map("requester_position_level")
requesterLevelType String? @map("requester_level_type")
requesterIsLeadership Boolean @default(false) @map("requester_is_leadership")
managerEmployeeId String @map("manager_employee_id")
managerUserId String? @map("manager_user_id")
managerDingtalkUserId String @map("manager_dingtalk_user_id")
managerName String @map("manager_name")
managerSnapshot Json @map("manager_snapshot")
status String @default("pending_manager_approval")
operations Json
baseMaterialUpdatedAt DateTime @map("base_material_updated_at")
submittedAt DateTime @default(now()) @map("submitted_at")
expiresAt DateTime @map("expires_at")
decidedAt DateTime? @map("decided_at")
decidedByUserId String? @map("decided_by_user_id")
decisionComment String? @map("decision_comment")
appliedAt DateTime? @map("applied_at")
applyError String? @map("apply_error")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
material Material @relation(fields: [materialId], references: [id], onDelete: Cascade)
requesterUser User @relation("TaskDetailChangeRequesterUser", fields: [requesterUserId], references: [id], onDelete: Restrict)
managerUser User? @relation("TaskDetailChangeManagerUser", fields: [managerUserId], references: [id], onDelete: SetNull)
requesterEmployee PerformanceSurveyEmployeeProfile @relation("TaskDetailChangeRequesterEmployee", fields: [requesterEmployeeId], references: [id], onDelete: Restrict)
managerEmployee PerformanceSurveyEmployeeProfile @relation("TaskDetailChangeManagerEmployee", fields: [managerEmployeeId], references: [id], onDelete: Restrict)
notifications TaskDetailChangeNotification[]
@@index([materialId, createdAt], map: "task_detail_change_requests_material_created_idx")
@@index([requesterUserId, createdAt], map: "task_detail_change_requests_requester_created_idx")
@@index([managerDingtalkUserId, status], map: "task_detail_change_requests_manager_status_idx")
@@index([status, expiresAt], map: "task_detail_change_requests_status_expires_idx")
@@index([status, updatedAt], map: "task_detail_change_requests_status_updated_idx")
@@map("task_detail_change_requests")
}
/// 任务详情变更审批通知 outbox;投递失败可恢复,不影响审批主状态。
model TaskDetailChangeNotification {
id String @id @default(uuid())
requestId String @map("request_id")
recipientUserId String? @map("recipient_user_id")
dingtalkUserId String @map("dingtalk_user_id")
recipientName String @map("recipient_name")
eventType String @map("event_type")
businessKey String @map("business_key")
title String
content String
actionUrl String @map("action_url")
status String @default("pending")
attemptCount Int @default(0) @map("attempt_count")
maxAttempts Int @default(5) @map("max_attempts")
nextRetryAt DateTime? @map("next_retry_at")
providerMessageId String? @map("provider_message_id")
lastError String? @map("last_error")
sentAt DateTime? @map("sent_at")
completedAt DateTime? @map("completed_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
request TaskDetailChangeRequest @relation(fields: [requestId], references: [id], onDelete: Cascade)
@@unique([eventType, businessKey], map: "task_detail_change_notifications_event_key")
@@index([requestId, createdAt], map: "task_detail_change_notifications_request_created_idx")
@@index([status, nextRetryAt], map: "task_detail_change_notifications_status_retry_idx")
@@map("task_detail_change_notifications")
}
/// 钉钉审批流程的本地只读快照。
///
/// 上游 rpt.ding_Workflow / ding_WorkflowTask / ding_WorkflowScheme 均为无索引大表,
/// 部门看板不得在用户请求链路中直连扫描。后台任务首次建立全量基线,之后每小时
/// 增量同步主流程,并为全部未完结流程刷新任务与表单字段汇总。
model DingWorkflowSnapshot {
processInstanceId String @id @map("process_instance_id")
businessId String? @map("business_id")
title String?
result String?
originatorUserId String? @map("originator_user_id")
originatorDeptId String? @map("originator_dept_id")
originatorDeptName String? @map("originator_dept_name")
status String
bizAction String? @map("biz_action")
processCode String? @map("process_code")
flowType String? @map("flow_type")
joinedAtRaw String? @map("joined_at_raw")
sourceCreatedAtRaw String? @map("source_created_at_raw")
sourceUpdatedAtRaw String? @map("source_updated_at_raw")
sourceFinishedAtRaw String? @map("source_finished_at_raw")
isOngoing Boolean @default(false) @map("is_ongoing")
schemeFieldCount Int @default(0) @map("scheme_field_count")
taskCount Int @default(0) @map("task_count")
activeTaskCount Int @default(0) @map("active_task_count")
latestTaskStatus String? @map("latest_task_status")
latestTaskAtRaw String? @map("latest_task_at_raw")
brand String?
demandType String? @map("demand_type")
requesterName String? @map("requester_name")
requesterDepartment String? @map("requester_department")
projectType String? @map("project_type")
formFields Json? @map("form_fields")
activeTasks Json? @map("active_tasks")
syncedAt DateTime @map("synced_at")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([isOngoing, sourceUpdatedAtRaw], map: "ding_workflow_snapshots_ongoing_updated_idx")
@@index([status], map: "ding_workflow_snapshots_status_idx")
@@index([originatorDeptName], map: "ding_workflow_snapshots_department_idx")
@@index([brand], map: "ding_workflow_snapshots_brand_idx")
@@index([demandType], map: "ding_workflow_snapshots_demand_type_idx")
@@index([requesterDepartment], map: "ding_workflow_snapshots_requester_department_idx")
@@map("ding_workflow_snapshots")
}
/// 钉钉审批流程同步游标、租约与可观测状态。
model DingWorkflowSyncState {
id String @id
status String @default("idle")
sourceCursorRaw String? @map("source_cursor_raw")
lastStartedAt DateTime? @map("last_started_at")
lastCompletedAt DateTime? @map("last_completed_at")
lastError String? @map("last_error")
leaseOwner String? @map("lease_owner")
leaseExpiresAt DateTime? @map("lease_expires_at")
rowsUpserted Int @default(0) @map("rows_upserted")
currentOngoingCount Int @default(0) @map("current_ongoing_count")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@index([leaseExpiresAt], map: "ding_workflow_sync_states_lease_idx")
@@map("ding_workflow_sync_states")
}
model VideoTranscodeProgress {
id String @id @default(uuid())
materialId String @map("material_id")
stage String
progress Int @default(0)
status String @default("waiting") @map("status")
priority Int @default(0)
startedAt DateTime @default(now()) @map("started_at")
completedAt DateTime? @map("completed_at")
errorMessage String? @map("error_message")
retryCount Int @default(0) @map("retry_count")
lastHeartbeat DateTime? @map("last_heartbeat")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
material Material @relation("MaterialTranscodeProgress", fields: [materialId], references: [id], onDelete: Cascade)
@@unique([materialId, stage])
@@index([status])
@@index([priority, status])
@@index([materialId])
@@index([lastHeartbeat])
@@map("video_transcode_progresses")
}
enum Role {
USER
ADMIN
DEPT_ADMIN
SYSTEM_ADMIN
}