prisma/schema.prisma
此代码来自构建时的仓库快照。修改源码后重新构建即可更新。
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
datasource db {
provider = "postgresql"
}
enum TicketStatus {
OPEN
IN_PROGRESS
RESOLVED
CLOSED
}
enum TicketPriority {
LOW
NORMAL
HIGH
URGENT
}
// 每张工单必须归属团队;旧数据归档,无创建者的新工单不允许通过业务接口产生。
model Ticket {
id String @id @default(uuid()) @db.Uuid
number Int @unique @default(autoincrement())
title String @db.VarChar(100)
description String @db.Text
priority TicketPriority @default(NORMAL)
status TicketStatus @default(OPEN)
version Int @default(0)
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
teamId String @db.Uuid
team Team @relation(fields: [teamId], references: [id])
creatorId String?
creator User? @relation(fields: [creatorId], references: [id], onDelete: SetNull)
assigneeMembershipId String? @db.Uuid
assignee Membership? @relation(fields: [assigneeMembershipId], references: [id], onDelete: SetNull)
replies TicketReply[]
events TicketEvent[]
dueAt DateTime? @db.Timestamptz(3)
reminderRevision Int @default(0)
backgroundTasks BackgroundTask[]
reminders ReminderNotification[]
@@unique([id, teamId])
@@index([teamId, assigneeMembershipId])
@@index([teamId, status, createdAt])
@@index([teamId, creatorId])
}
model User {
id String @id
name String
email String @unique
emailVerified Boolean @default(false)
image String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
sessions Session[]
accounts Account[]
memberships Membership[]
replies TicketReply[]
ticketEvents TicketEvent[]
backgroundTasks BackgroundTask[]
reminders ReminderNotification[]
tickets Ticket[]
@@map("user")
}
model Session {
id String @id
expiresAt DateTime
token String @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
ipAddress String?
userAgent String?
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@map("session")
}
model Account {
id String @id
accountId String
providerId String
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
accessToken String?
refreshToken String?
idToken String?
accessTokenExpiresAt DateTime?
refreshTokenExpiresAt DateTime?
scope String?
password String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@map("account")
}
model Verification {
id String @id
identifier String
value String
expiresAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([identifier])
@@map("verification")
}
enum MemberRole {
ADMIN
AGENT
CUSTOMER
}
model Team {
id String @id @default(uuid()) @db.Uuid
name String @db.VarChar(80)
createdAt DateTime @default(now())
members Membership[]
tickets Ticket[]
backgroundTasks BackgroundTask[]
}
// 角色属于“用户在某个团队的成员关系”,不是全站属性。
// 同一个人可以在甲团队是管理员,在乙团队是客户。
model Membership {
id String @id @default(uuid()) @db.Uuid
teamId String @db.Uuid
userId String
role MemberRole @default(CUSTOMER)
createdAt DateTime @default(now())
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
assignedTickets Ticket[]
@@unique([teamId, userId])
@@index([userId])
}
// 用工单与团队的复合外键,数据库也能阻止“回复关联了另一个团队”的错误。
model TicketReply {
id String @id @default(uuid()) @db.Uuid
ticketId String @db.Uuid
teamId String @db.Uuid
ticket Ticket @relation(fields: [ticketId, teamId], references: [id, teamId], onDelete: Cascade)
authorId String?
author User? @relation(fields: [authorId], references: [id], onDelete: SetNull)
authorName String
authorRole MemberRole
content String @db.Text
clientRequestId String @db.Uuid
createdAt DateTime @default(now()) @db.Timestamptz(3)
@@unique([ticketId, authorId, clientRequestId])
@@index([teamId, ticketId, createdAt])
}
enum TicketEventKind {
CREATED
STATUS_CHANGED
ASSIGNED
UNASSIGNED
REPLIED
ASSIGNEE_RELEASED
HISTORY_STARTED
REMINDER_SCHEDULED
TIMEOUT_REMINDED
}
// 操作记录只提供新增与读取接口;版本号使每次有效业务修改都有唯一的对应记录。
model TicketEvent {
id String @id @default(uuid()) @db.Uuid
ticketId String @db.Uuid
teamId String @db.Uuid
ticket Ticket @relation(fields: [ticketId, teamId], references: [id, teamId], onDelete: Cascade)
version Int
kind TicketEventKind
actorUserId String?
actor User? @relation(fields: [actorUserId], references: [id], onDelete: SetNull)
actorName String
actorRole MemberRole?
fromStatus TicketStatus?
toStatus TicketStatus?
fromAssigneeName String?
toAssigneeName String?
reason String? @db.VarChar(1000)
replyId String? @db.Uuid
createdAt DateTime @default(now()) @db.Timestamptz(3)
@@unique([ticketId, version])
@@index([teamId, ticketId, version])
}
enum BackgroundTaskKind {
EXPORT
REMINDER
}
enum BackgroundTaskStatus {
WAITING
RUNNING
COMPLETED
FAILED
}
// 这张表兼任任务账本与待投递箱,数据库提交后即算受理,不要求 Redis 同时在线。
model BackgroundTask {
id String @id @default(uuid()) @db.Uuid
teamId String @db.Uuid
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
ownerId String?
owner User? @relation(fields: [ownerId], references: [id], onDelete: SetNull)
ticketId String? @db.Uuid
ticket Ticket? @relation(fields: [ticketId, teamId], references: [id, teamId], onDelete: Cascade)
kind BackgroundTaskKind
status BackgroundTaskStatus @default(WAITING)
filters Json?
scopeCreatorId String?
reminderRevision Int?
run Int @default(0)
attempts Int @default(0)
progress Int @default(0)
executionId String? @db.Uuid
dispatchedAt DateTime?
runAt DateTime @default(now()) @db.Timestamptz(3)
lastError String?
resultCsv String? @db.Text
resultSummary String?
expiresAt DateTime? @db.Timestamptz(3)
createdAt DateTime @default(now()) @db.Timestamptz(3)
updatedAt DateTime @updatedAt @db.Timestamptz(3)
@@unique([ticketId, reminderRevision])
@@index([status, dispatchedAt, runAt])
@@index([teamId, ownerId, createdAt])
}
model ReminderNotification {
id String @id @default(uuid()) @db.Uuid
teamId String @db.Uuid
ticketId String @db.Uuid
ticket Ticket @relation(fields: [ticketId, teamId], references: [id, teamId], onDelete: Cascade)
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
reminderRevision Int
readAt DateTime? @db.Timestamptz(3)
createdAt DateTime @default(now()) @db.Timestamptz(3)
@@unique([ticketId, reminderRevision, userId])
@@index([teamId, userId, createdAt])
}