TTeamDesk 学习手册

prisma/schema.prisma

此代码来自构建时的仓库快照。修改源码后重新构建即可更新。

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])
}

搜索教程

输入关键词,搜索全部章节。

阅读设置

外观
正文字号

从一张工单出发,读懂数据如何流转。

设置与进度仅保存在当前浏览器。