This commit is contained in:
longdayi 2024-12-30 13:44:30 +08:00
parent 56c5a35673
commit 3e47150b1a
7 changed files with 413 additions and 275 deletions

View File

@ -108,14 +108,14 @@ export class PostService extends BaseService<Prisma.PostDelegate> {
authorId: staff.id,
},
staff?.id && {
watchStaffs: {
watchableStaffs: {
some: {
id: staff.id,
},
},
},
deptId && {
watchDepts: {
watchableDepts: {
some: {
id: {
in: parentDeptIds,
@ -127,13 +127,13 @@ export class PostService extends BaseService<Prisma.PostDelegate> {
{
AND: [
{
watchStaffs: {
none: {}, // 匹配 watchStaffs 为空
watchableStaffs: {
none: {}, // 匹配 watchableStaffs 为空
},
},
{
watchDepts: {
none: {}, // 匹配 watchDepts 为空
watchableDepts: {
none: {}, // 匹配 watchableDepts 为空
},
},
],

View File

@ -22,7 +22,7 @@ datasource db {
}
model Taxonomy {
id String @id @default(uuid())
id String @id @default(cuid())
name String @unique
slug String @unique @map("slug")
deletedAt DateTime? @map("deleted_at")
@ -36,7 +36,7 @@ model Taxonomy {
}
model Term {
id String @id @default(uuid())
id String @id @default(cuid())
name String
taxonomy Taxonomy? @relation(fields: [taxonomyId], references: [id])
taxonomyId String? @map("taxonomy_id")
@ -55,6 +55,7 @@ model Term {
createdBy String? @map("created_by")
depts Department[] @relation("department_term")
hasChildren Boolean? @default(false) @map("has_children")
courses Course[] @relation("course_term")
@@index([name]) // 对name字段建立索引以加快基于name的查找速度
@@index([parentId]) // 对parentId字段建立索引以加快基于parentId的查找速度
@ -62,7 +63,7 @@ model Term {
}
model TermAncestry {
id String @id @default(uuid())
id String @id @default(cuid())
ancestorId String? @map("ancestor_id")
descendantId String @map("descendant_id")
relDepth Int @map("rel_depth")
@ -78,7 +79,7 @@ model TermAncestry {
}
model Staff {
id String @id @default(uuid())
id String @id @default(cuid())
showname String? @map("showname")
username String @unique @map("username")
avatar String? @map("avatar")
@ -90,7 +91,6 @@ model Staff {
domain Department? @relation("DomainStaff", fields: [domainId], references: [id])
department Department? @relation("DeptStaff", fields: [deptId], references: [id])
registerToken String? @map("register_token")
order Float?
createdAt DateTime @default(now()) @map("created_at")
@ -99,14 +99,15 @@ model Staff {
deletedAt DateTime? @map("deleted_at")
officerId String? @map("officer_id")
watchedPost Post[] @relation("post_watch_staff")
visits Visit[]
posts Post[]
sentMsgs Message[] @relation("message_sender")
receivedMsgs Message[] @relation("message_receiver")
enrollments Enrollment[]
courseReviews CourseReview[]
teachedCourses CourseInstructor[]
@@index([officerId])
@@index([deptId])
@ -117,7 +118,7 @@ model Staff {
}
model Department {
id String @id @default(uuid())
id String @id @default(cuid())
name String
order Float?
ancestors DeptAncestry[] @relation("DescendantToAncestor")
@ -133,7 +134,6 @@ model Department {
deptStaffs Staff[] @relation("DeptStaff")
terms Term[] @relation("department_term")
visits Visit[] @relation("visit_dept")
watchedPost Post[] @relation("post_watch_dept")
hasChildren Boolean? @default(false) @map("has_children")
@ -145,7 +145,7 @@ model Department {
}
model DeptAncestry {
id String @id @default(uuid())
id String @id @default(cuid())
ancestorId String? @map("ancestor_id")
descendantId String @map("descendant_id")
relDepth Int @map("rel_depth")
@ -161,7 +161,7 @@ model DeptAncestry {
}
model RoleMap {
id String @id @default(uuid())
id String @id @default(cuid())
objectId String @map("object_id")
roleId String @map("role_id")
domainId String? @map("domain_id")
@ -174,7 +174,7 @@ model RoleMap {
}
model Role {
id String @id @default(uuid())
id String @id @default(cuid())
name String @unique @map("name")
permissions String[] @default([]) @map("permissions")
roleMaps RoleMap[]
@ -187,7 +187,7 @@ model Role {
}
model AppConfig {
id String @id @default(uuid())
id String @id @default(cuid())
slug String @unique
title String?
description String?
@ -197,7 +197,7 @@ model AppConfig {
}
model Post {
id String @id @default(uuid())
id String @id @default(cuid())
type String?
title String?
content String?
@ -209,13 +209,15 @@ model Post {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
visits Visit[]
watchStaffs Staff[] @relation("post_watch_staff")
watchDepts Department[] @relation("post_watch_dept")
watchableStaffs Staff[] @relation("post_watch_staff")
watchableDepts Department[] @relation("post_watch_dept")
parentId String?
parent Post? @relation("PostChildren", fields: [parentId], references: [id])
children Post[] @relation("PostChildren")
deletedAt DateTime? @map("deleted_at")
Lecture Lecture? @relation(fields: [lectureId], references: [id])
lectureId String?
// 复合索引
@@index([type, domainId]) // 类型和域组合查询
@ -228,7 +230,7 @@ model Post {
}
model Message {
id String @id @default(uuid())
id String @id @default(cuid())
url String?
intent String?
option Json?
@ -247,24 +249,265 @@ model Message {
}
model Visit {
id String @id @default(uuid())
postId String? @map("post_id")
troubleId String? @map("trouble_id")
messageId String? @map("message_id")
id String @id @default(cuid())
visitType String? @map("visit_type")
visitorId String @map("visitor_id")
visitor Staff @relation(fields: [visitorId], references: [id])
deptId String? @map("dept_id")
department Department? @relation(name: "visit_dept", fields: [deptId], references: [id])
postId String? @map("post_id")
post Post? @relation(fields: [postId], references: [id])
message Message? @relation(fields: [messageId], references: [id])
messageId String? @map("message_id")
views Int @default(1)
createdAt DateTime? @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at")
sourceIP String? @map("source_ip")
visitType String? @map("visit_type")
post Post? @relation(fields: [postId], references: [id])
message Message? @relation(fields: [messageId], references: [id])
views Int @default(1)
@@index([postId, visitType, visitorId])
@@index([troubleId, visitType, visitorId])
@@index([messageId, visitType, visitorId])
@@map("visit")
}
model Course {
id String @id @default(cuid()) // 课程唯一标识符
title String // 课程标题
subTitle String? // 课程副标题(可选)
description String // 课程详细描述
thumbnail String? // 课程封面图片URL(可选)
level String // 课程难度等级
// 课程内容组织结构
terms Term[] @relation("course_term") // 课程学期
instructors CourseInstructor[] // 课程讲师团队
sections Section[] // 课程章节结构
enrollments Enrollment[] // 学生报名记录
reviews CourseReview[] // 学员课程评价
// 课程规划与目标设定
requirements String[] // 课程学习前置要求
objectives String[] // 具体的学习目标
skills String[] // 课程结束后可掌握的技能
audiences String[] // 目标受众群体描述
// 课程统计指标
totalDuration Int @default(0) // 课程总时长(分钟)
totalLectures Int @default(0) // 总课时数
averageRating Float @default(0) // 平均评分(1-5分)
numberOfReviews Int @default(0) // 评价总数
numberOfStudents Int @default(0) // 学习人数
completionRate Float @default(0) // 完课率(0-100%)
// 课程状态管理
status String // 课程状态(如:草稿/已发布/已归档)
isFeatured Boolean @default(false) // 是否为精选推荐课程
// 生命周期时间戳
createdAt DateTime @default(now()) // 创建时间
updatedAt DateTime @updatedAt // 最后更新时间
publishedAt DateTime? // 发布时间
archivedAt DateTime? // 归档时间
deletedAt DateTime? // 软删除时间
// 数据库索引优化
@@index([status]) // 课程状态索引,用于快速筛选
@@index([level]) // 难度等级索引,用于分类查询
@@index([isFeatured]) // 精选标记索引,用于首页推荐
}
model Section {
id String @id @default(cuid()) // 章节唯一标识符
title String // 章节标题
description String? // 章节描述(可选)
objectives String[] // 本章节的具体学习目标
order Float? @default(0) // 章节排序权重
// 关联关系
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
courseId String // 所属课程ID
lectures Lecture[] // 包含的所有课时
// 章节统计数据
totalDuration Int @default(0) // 本章节总时长(分钟)
totalLectures Int @default(0) // 本章节课时总数
// 时间管理
createdAt DateTime @default(now()) // 创建时间
updatedAt DateTime @updatedAt // 更新时间
deletedAt DateTime? // 软删除时间
@@index([courseId, order]) // 复合索引:用于按课程ID和顺序快速查询
}
model Lecture {
id String @id @default(cuid()) // 课时唯一标识符
title String // 课时标题
description String? // 课时描述(可选)
order Float? @default(0) // 课时排序权重
duration Int // 学习时长(分钟)
type String // 课时类型(video/article)
// 课时内容
content String? // Markdown格式文章内容
videoUrl String? // 视频URL地址
videoThumbnail String? // 视频封面图URL
// 关联内容
resources Resource[] // 课时附属资源
section Section @relation(fields: [sectionId], references: [id], onDelete: Cascade)
sectionId String // 所属章节ID
comments Post[] // 课时评论
progress LectureProgress[] // 学习进度记录
// 时间管理
publishedAt DateTime? // 发布时间
createdAt DateTime @default(now()) // 创建时间
updatedAt DateTime @updatedAt // 更新时间
deletedAt DateTime? // 软删除时间
@@index([sectionId, order]) // 章节内课时排序索引
@@index([type, publishedAt]) // 课时类型和发布时间复合索引
}
model Enrollment {
id String @id @default(cuid()) // 报名记录唯一标识符
status String // 报名状态(如:进行中/已完成/已过期)
// 关联关系
student Staff @relation(fields: [studentId], references: [id])
studentId String // 学员ID
course Course @relation(fields: [courseId], references: [id])
courseId String // 课程ID
progress LectureProgress[] // 课时学习进度记录
// 学习数据统计
completionRate Float @default(0) // 课程完成度(0-100%)
lastAccessedAt DateTime? // 最后访问时间
// 时间管理
createdAt DateTime @default(now()) // 报名时间
updatedAt DateTime @updatedAt // 更新时间
completedAt DateTime? // 完课时间
@@unique([studentId, courseId]) // 确保学员不会重复报名同一课程
@@index([status]) // 报名状态索引
@@index([completedAt]) // 完课时间索引
}
model LectureProgress {
id String @id @default(cuid()) // 进度记录唯一标识符
progress Float @default(0) // 完成进度(0-100%)
isCompleted Boolean @default(false) // 是否完成
// 关联关系
enrollment Enrollment @relation(fields: [enrollmentId], references: [id], onDelete: Cascade)
enrollmentId String // 报名记录ID
lecture Lecture @relation(fields: [lectureId], references: [id], onDelete: Cascade)
lectureId String // 课时ID
// 学习数据
lastPosition Int @default(0) // 视频播放位置(秒)
viewCount Int @default(0) // 观看次数
readCount Int @default(0) // 阅读次数
totalWatchTime Int @default(0) // 总观看时长(秒)
// 时间记录
lastWatchedAt DateTime? // 最后观看时间
createdAt DateTime @default(now()) // 创建时间
updatedAt DateTime @updatedAt // 更新时间
@@unique([enrollmentId, lectureId]) // 确保每个报名只有一条课时进度
@@index([isCompleted]) // 完成状态索引
@@index([lastWatchedAt]) // 最后观看时间索引
}
model CourseInstructor {
course Course @relation(fields: [courseId], references: [id])
courseId String // 课程ID
instructor Staff @relation(fields: [instructorId], references: [id])
instructorId String // 讲师ID
role String // 讲师角色
createdAt DateTime @default(now()) // 创建时间
order Float? @default(0) // 讲师显示顺序
@@id([courseId, instructorId]) // 联合主键
}
model CourseReview {
id String @id @default(cuid()) // 评价唯一标识符
rating Int // 评分(1-5星)
content String? // 评价内容
student Staff @relation(fields: [studentId], references: [id])
studentId String // 评价学员ID
course Course @relation(fields: [courseId], references: [id])
courseId String // 课程ID
helpfulCount Int @default(0) // 评价点赞数
createdAt DateTime @default(now()) // 评价时间
updatedAt DateTime @updatedAt // 更新时间
@@unique([studentId, courseId]) // 确保学员对同一课程只能评价一次
@@index([rating]) // 评分索引
}
model Resource {
id String @id @default(cuid()) // 资源唯一标识符
title String // 资源标题
description String? // 资源描述
type String // 资源类型
url String // 资源URL
fileType String? // 文件MIME类型
fileSize Int? // 文件大小(bytes)
lecture Lecture @relation(fields: [lectureId], references: [id], onDelete: Cascade)
lectureId String // 所属课时ID
downloadCount Int @default(0) // 下载次数
createdAt DateTime @default(now()) // 创建时间
updatedAt DateTime @updatedAt // 更新时间
@@index([lectureId, type]) // 课时资源类型复合索引
}
model Node {
id String @id @default(cuid())
title String
description String?
type String
// 节点之间的关系
sourceEdges NodeEdge[] @relation("from_node")
targetEdges NodeEdge[] @relation("to_node")
style Json?
position Json? // 存储节点在画布中的位置 {x: number, y: number}
data Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// 节点之间的关系
model NodeEdge {
id String @id @default(cuid())
// 关系的起点和终点
source Node @relation("from_node", fields: [sourceId], references: [id], onDelete: Cascade)
sourceId String
target Node @relation("to_node", fields: [targetId], references: [id], onDelete: Cascade)
targetId String
// 关系属性
type String?
label String?
description String?
// 自定义边的样式(可选)
style Json? // 存储边的样式,如 {color: string, strokeWidth: number}
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([sourceId, targetId, type])
@@index([sourceId])
@@index([targetId])
}

View File

@ -1,13 +1,10 @@
import { Prisma } from "@prisma/client";
import {
AppConfigSlug,
RiskState,
RolePerms,
TaxonomySlug,
TroubleParamsKey,
TroubleState,
TroubleType,
VisitType,
} from "./enum";
export const InitRoles: {
@ -58,7 +55,7 @@ export const InitRoles: {
name: "根管理员",
permissions: Object.keys(RolePerms) as RolePerms[],
},
];
];
export const InitTaxonomies: { name: string; slug: string }[] = [
{
name: "分类",

View File

@ -2,8 +2,6 @@ export enum SocketMsgType {
NOTIFY,
}
export enum PostType {
TROUBLE_PROGRESS = "trouble_progress",
TROUBLE_INSTRUCTION = "trouble_instrcution",
POST = "post",
POST_COMMENT = "post_comment",
}
@ -16,44 +14,13 @@ export enum VisitType {
STAR = "star",
READED = "read",
}
export enum TroubleState {
AUDITING = 0,
PROCESSING = 1,
CANCEL_REQUEST = 2,
CANCELED = 3,
}
export enum RiskState {
AUDITING = 0,
CONTROLLING = 4,
RELEASED = 5,
}
export enum QuadrantType {
URG_IMPORTANT = "super",
URGENT = "urgent",
IMPORTANT = "imporant",
TRIVIAL = "trival",
}
export enum TroubleType {
RISK = "安全风险",
TROUBLE = "问题隐患",
ALERT = "风险预警",
}
export enum AssessmentStatus {
ASSESSING = "评估中",
COMPLETED = "已完成",
}
export enum TermType {
RISK_UNIT = "RISK_UNIT",
RISK_INDICATOR = "RISK_INDICATOR",
RISK_CATEGORY = "RISK_CATEGORY",
}
export enum ObjectType {
DEPARTMENT = "department",
STAFF = "staff",
COMMENT = "comment",
TERM = "term",
TROUBLE = "trouble",
APP_CONFIG = "app_config",
ROLE = "role",
ROLE_MAP = "rolemap",
@ -100,112 +67,60 @@ export enum RolePerms {
MANAGE_ANY_ROLE = "MANAGE_ANY_ROLE",
MANAGE_DOM_ROLE = "MANAGE_DOM_ROLE",
}
export enum RemindType {
BOTH = "both",
CHECK = "check",
DUTY = "duty",
}
export const LevelColor = {
1: "#BBDDFF",
2: "#FFE6B3",
3: "#FFC2C2",
4: "#FFC2C2",
} as const;
export enum AppConfigSlug {
BASE_SETTING = "base_setting",
}
export const TroubleStateMap = {
[TroubleState.AUDITING]: "待审核",
[TroubleState.PROCESSING]: "处理中",
[TroubleState.CANCEL_REQUEST]: "待销帐",
[TroubleState.CANCELED]: "已销帐",
};
export const RiskStateMap = {
[RiskState.AUDITING]: "待审核",
[RiskState.CONTROLLING]: "管控中",
[RiskState.RELEASED]: "已解除",
};
export const TroubleTypeStateMap = new Map<string, string>([
[`${TroubleType.TROUBLE}_${TroubleState.AUDITING}`, "待审核"],
[`${TroubleType.TROUBLE}_${TroubleState.PROCESSING}`, `处理中`],
[`${TroubleType.TROUBLE}_${TroubleState.CANCEL_REQUEST}`, `待销帐`],
[`${TroubleType.TROUBLE}_${TroubleState.CANCELED}`, `已销帐`],
[`${TroubleType.RISK}_${RiskState.AUDITING}`, "待审核"],
[`${TroubleType.RISK}_${RiskState.CONTROLLING}`, `管控中`],
[`${TroubleType.RISK}_${RiskState.RELEASED}`, `已解除`],
[`${TroubleType.ALERT}_${RiskState.AUDITING}`, "待审核"],
[`${TroubleType.ALERT}_${RiskState.CONTROLLING}`, `管控中`],
[`${TroubleType.ALERT}_${RiskState.RELEASED}`, `已解除`],
]);
export const TroubleLevelMap = new Map<string, string>([
[`${TroubleType.TROUBLE}_0`, "全部级别"],
[`${TroubleType.TROUBLE}_1`, `四级隐患`],
[`${TroubleType.TROUBLE}_2`, `三级隐患`],
[`${TroubleType.TROUBLE}_3`, `二级隐患`],
[`${TroubleType.TROUBLE}_4`, `一级隐患`],
[`${TroubleType.RISK}_0`, "全部级别"],
[`${TroubleType.RISK}_1`, `一般风险`],
[`${TroubleType.RISK}_2`, `较大风险`],
[`${TroubleType.RISK}_3`, `重大风险`],
[`${TroubleType.RISK}_4`, `特大风险`],
[`${TroubleType.ALERT}_0`, "全部预警"],
[`${TroubleType.ALERT}_1`, `蓝色预警`],
[`${TroubleType.ALERT}_2`, `黄色预警`],
[`${TroubleType.ALERT}_3`, `橙色预警`],
[`${TroubleType.ALERT}_4`, `红色预警`],
]);
export function GetTroubleLevel(
type: string | undefined,
level: number | undefined
): string | undefined {
return TroubleLevelMap.get(`${type || "ELSE"}_${level}`) || "暂未评级";
}
export function GetTroubleState(
type: string | undefined,
state: number | undefined
): string | undefined {
return TroubleTypeStateMap.get(`${type || "ELSE"}_${state}`) || "无状态";
}
export enum SendMessageType {
TO_DUTY = "to_duty",
TO_CHECK = "to_check",
TO_REQUEST_DELAY = "to_request_delay",
TO_DELAY = "to_delay",
TO_REQUEST_CANCEL = "to_request_cancel",
INSTRUCTION = "instrcution",
PROGRESS = "progress",
// 资源类型的枚举,定义了不同类型的资源,以字符串值表示
export enum ResourceType {
VIDEO = "video", // 视频资源
PDF = "pdf", // PDF文档
DOC = "doc", // Word文档
EXCEL = "excel", // Excel表格
PPT = "ppt", // PowerPoint演示文稿
CODE = "code", // 代码文件
LINK = "link", // 超链接
IMAGE = "image", // 图片资源
AUDIO = "audio", // 音频资源
ZIP = "zip", // 压缩包文件
OTHER = "other" // 其他未分类资源
}
export enum DraftType {
TROUBLE = "trouble_darft",
POST = "post_darft",
// 课程等级的枚举,描述了不同学习水平的课程
export enum CourseLevel {
BEGINNER = "beginner", // 初级课程,适合初学者
INTERMEDIATE = "intermediate", // 中级课程,适合有一定基础的学习者
ADVANCED = "advanced", // 高级课程,适合高级水平学习者
ALL_LEVELS = "all_levels" // 适用于所有学习水平的课程
}
export enum ForwardType {
TROUBLE = "trouble",
POST = "post",
// 课时(课程内容)类型的枚举,定义了课程中可能包含的不同内容形式
export enum LessonType {
VIDEO = "video", // 视频课程
ARTICLE = "article", // 文章型课程内容
QUIZ = "quiz", // 测验类型
ASSIGNMENT = "assignment", // 作业类型
}
export enum ToWhoType {
DOMAIN = "本域可见",
DEPT = "本单位可见",
SELF = "仅自己可见",
CUSTOM = "自定义",
// 课程状态的枚举,定义了课程生命周期中的各个状态
export enum CourseStatus {
DRAFT = "draft", // 草稿状态的课程,尚未发布
UNDER_REVIEW = "under_review", // 正在审核中的课程
PUBLISHED = "published", // 已发布的课程,可以被学员报名学习
ARCHIVED = "archived" // 已归档的课程,不再对外展示
}
// 定义枚举来存储查询键
export enum TroubleParamsKey {
RISK_AUDITING = "RISK_AUDITING",
RISK_CONTROLLING = "RISK_CONTROLLING",
RISK_RELEASED = "RISK_RELEASED",
TROUBLE_AUDITING = "TROUBLE_AUDITING",
TROUBLE_PROCESSING = "TROUBLE_PROCESSING",
TROUBLE_CANCEL_REQUEST = "TROUBLE_CANCEL_REQUEST",
TROUBLE_CANCELED = "TROUBLE_CANCELED",
STAR = "STAR",
DUTY = "DUTY",
CHECK = "CHECK",
// 报名状态的枚举,描述了用户报名参加课程的不同状态
export enum EnrollmentStatus {
PENDING = "pending", // 报名待处理状态
ACTIVE = "active", // 活跃状态,用户可参与课程
COMPLETED = "completed", // 完成状态,用户已完成课程
CANCELLED = "cancelled", // 已取消的报名
REFUNDED = "refunded" // 已退款的报名
}
// 授课角色的枚举,定义了讲师在课程中的角色分配
export enum InstructorRole {
MAIN = "main", // 主讲教师
ASSISTANT = "assistant" // 助教
}

View File

@ -1,5 +1,5 @@
import { z } from "zod";
import { ObjectType, RiskState, TroubleState } from "./enum";
import { ObjectType } from "./enum";
export const AuthSchema = {
signUpRequest: z.object({
username: z.string(),
@ -170,10 +170,7 @@ export const DepartmentMethodSchema = {
};
export const TransformMethodSchema = {
importTrouble: z.object({
base64: z.string(),
domainId: z.string().nullish(),
}),
importStaffs: z.object({
base64: z.string(),
domainId: z.string().nullish(),
@ -189,21 +186,7 @@ export const TransformMethodSchema = {
domainId: z.string().nullish(),
parentId: z.string().nullish(),
}),
exportTroubles: z.object({
termIdFilters: z.map(z.string(), z.array(z.string())).nullish(),
deptIds: z.array(z.string()).nullish(),
search: z.string().nullish(),
type: z.string().nullish(),
levels: z.array(z.number()).nullish(),
createStartDate: z.string().nullish(),
createEndDate: z.string().nullish(),
domainId: z.string().nullish(),
states: z
.array(
z.union([z.nativeEnum(TroubleState), z.nativeEnum(RiskState)])
)
.nullish(),
}),
};
export const TermMethodSchema = {
getRows: RowRequestSchema.extend({

View File

@ -7,8 +7,8 @@ export const postDetailSelect: Prisma.PostSelect = {
content: true,
attachments: true,
referenceId: true,
watchDepts: true,
watchStaffs: true,
watchableDepts: true,
watchableStaffs: true,
updatedAt: true,
author: {
select: {

View File

@ -137,8 +137,8 @@ export type PostDto = Post & {
delete: boolean;
// edit: boolean;
};
watchDepts: Department[];
watchStaffs: Staff[];
watchableDepts: Department[];
watchableStaffs: Staff[];
};
export type TermDto = Term & {