feat: 20260815 系统优化 - 全部31项问题修复(P0×6+P1×14+P2×9+P3×2)

P0紧急修复(6项):
- 草稿保存完整恢复所有字段(含socialAvgWage)
- 补偿金批次从compensationBreakdown读取
- 违法解除风险确认UI
- 合同结束日期前后校验(前后端双保险)

P1高优先级(14项):
- 离职日期联动社保/公积金截止月(15号规则)
- 合规检查+工作交接改为软阻断(生成待办)
- 补偿月数(N/N+1/2N/自定义)+计算基数(近12月/合同/自定义)
- 解聘并入花名册操作栏(类型选择跳转向导)
- 合同续签开始日期自动推导(原合同结束日+1天)
- 年龄合规筛查(童工阻断/未成年工/退休警告)
- 编辑入职日期后状态联动(待入职↔在职)
- 转正移植到花名册操作栏+薪资回写
- 男职工无法选择三期

P2体验优化(9项):
- "劳动合同"调整为"用工关系"
- 费用结算新增剩余年假折算(300%日工资)
- 身份证号全域改为"证件号码"(前后端18个文件)
- 手机号查重
- 开具证明+合同续签移植到花名册操作栏
- 批量转正+批量开具证明
- 去掉用工办理模块

P3规划(2项):
- 组织架构+审批流(Department/Position/ApprovalFlow/ApprovalInstance)
- 客服工作台(Ticket/ChatSession+SUPPORT角色)

新增模型: Department/Position/ApprovalFlow/ApprovalInstance/Ticket/TicketMessage/ChatSession/ChatMessage
新增字段: Employee.departmentId/supervisorId
新增角色: SUPPORT

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
selfrelease
2026-08-15 12:37:27 +08:00
parent 8cfdd566af
commit e1b5ae9aab
46 changed files with 3455 additions and 206 deletions
+175
View File
@@ -20,6 +20,7 @@ enum Role {
ADMIN
HR
VIEWER
SUPPORT
}
enum EmployeeStatus {
@@ -197,6 +198,14 @@ model Organization {
benefitEnrollments EmployeeBenefitEnrollment[]
eSignRecords ESignRecord[]
medicalPeriodPolicies MedicalPeriodPolicy[]
// 组织架构 + 审批流
departments Department[]
positions Position[]
approvalFlows ApprovalFlow[]
approvalInstances ApprovalInstance[]
// 客服工作台
tickets Ticket[]
chatSessions ChatSession[]
}
model User {
@@ -224,6 +233,9 @@ model Employee {
department String
position String? // 岗位
hireDate DateTime
// 组织架构扩展
departmentId String? // 关联 Department 表(兼容旧 department 字符串)
supervisorId String? // 直属上级员工ID
monthlySalary String // AES-256 加密存储
status EmployeeStatus @default(ACTIVE)
gender String?
@@ -285,6 +297,11 @@ model Employee {
commercialInsEnrollments CommercialInsuranceEnrollment[]
benefitEnrollments EmployeeBenefitEnrollment[]
eSignRecords ESignRecord[]
// 组织架构关联
dept Department? @relation("DeptEmployees", fields: [departmentId], references: [id])
supervisor Employee? @relation("SupervisorSubordinates", fields: [supervisorId], references: [id])
subordinates Employee[] @relation("SupervisorSubordinates")
approvalInstances ApprovalInstance[]
@@unique([orgId, idCardHash])
}
@@ -1528,3 +1545,161 @@ model ESignRecord {
@@index([employeeId])
@@index([contractId])
}
// ==================== 组织架构 + 审批流 ====================
/// 部门(树形结构,自引用 parent)
model Department {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
name String
parentId String?
parent Department? @relation("DeptChildren", fields: [parentId], references: [id], onDelete: SetNull)
children Department[] @relation("DeptChildren")
level Int @default(0) // 层级(0=根)
sortOrder Int @default(0) // 同级排序
description String?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
employees Employee[] @relation("DeptEmployees")
positions Position[]
@@unique([orgId, name, parentId])
@@index([orgId, parentId])
}
/// 岗位字典
model Position {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
name String
departmentId String?
department Department? @relation(fields: [departmentId], references: [id], onDelete: SetNull)
headcount Int @default(0) // 编制人数
level String? // 职级(如 P1/P2/M1
description String?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([orgId, name, departmentId])
@@index([orgId, departmentId])
}
/// 审批流配置
model ApprovalFlow {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
type String // LEAVE / TERMINATION / SALARY_CHANGE / OTHER
name String
enabled Boolean @default(true)
// 步骤配置 JSON: [{ step: 1, approverType: 'SUPERVISOR' | 'PERSON', approverId?: string, name: string }]
steps Json
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
instances ApprovalInstance[]
@@unique([orgId, type])
@@index([orgId])
}
/// 审批实例
model ApprovalInstance {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
flowId String
flow ApprovalFlow @relation(fields: [flowId], references: [id], onDelete: Cascade)
type String // 冗余 flow.type,便于查询
bizId String? // 关联业务记录ID(如 LeaveRequest.id
bizType String? // 业务类型
status String @default("PENDING") // PENDING / APPROVED / REJECTED / CANCELLED
currentStep Int @default(1)
// 审批记录 JSON: [{ step, approverId, approverName, result, comment, timestamp }]
approvals Json @default("[]")
employeeId String?
employee Employee? @relation(fields: [employeeId], references: [id], onDelete: SetNull)
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, status])
@@index([employeeId])
@@index([bizId])
}
// ==================== 客服工作台 ====================
/// 工单
model Ticket {
id String @id @default(cuid())
orgId String // 提交方租户
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
title String
content String
category String? // 分类(如:系统bug/功能咨询/数据问题)
priority String @default("NORMAL") // LOW / NORMAL / HIGH / URGENT
status String @default("OPEN") // OPEN / IN_PROGRESS / RESOLVED / CLOSED
assigneeId String? // 客服处理人 userId
createdBy String // 提交人 userId
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
messages TicketMessage[]
@@index([orgId, status])
@@index([assigneeId])
}
/// 工单回复
model TicketMessage {
id String @id @default(cuid())
ticketId String
ticket Ticket @relation(fields: [ticketId], references: [id], onDelete: Cascade)
content String
senderId String
senderRole String // USER / SUPPORT
createdAt DateTime @default(now())
@@index([ticketId])
}
/// 客服-客户会话
model ChatSession {
id String @id @default(cuid())
orgId String // 客户租户
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
supportUserId String // 客服 userId
lastMessage String?
lastMessageAt DateTime?
unreadByUser Int @default(0)
unreadBySupport Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
messages ChatMessage[]
@@unique([orgId, supportUserId])
@@index([supportUserId])
}
/// 会话消息
model ChatMessage {
id String @id @default(cuid())
sessionId String
session ChatSession @relation(fields: [sessionId], references: [id], onDelete: Cascade)
content String
senderId String
senderRole String // USER / SUPPORT
read Boolean @default(false)
createdAt DateTime @default(now())
@@index([sessionId])
}