Files
TurboHR/backend/prisma/schema.prisma
T
selfrelease 434c63c6d3 feat: 线下手签完整流程 + 证据链体系
## 数据库
- ESignRecord 增加 signMethod 字段(ELECTRONIC/PAPER)
- 增加线下手签专用字段:signedAt、signedLocation、witnessName、witnessPhone、scanFileUrls

## 后端
- esign.routes.ts 新增线下手签接口:
  - POST /esign/paper-upload:上传签署扫描件(multer多文件,10MB限制)
  - POST /esign/paper-sign:线下手签登记(创建COMPLETED记录+证据链+回写合同)
- esign.service.ts:autoCreateEsignRecord 设置 signMethod=ELECTRONIC
- 静态文件服务复用 /uploads 统一映射

## 前端管理端
- ESign.tsx 新增"线下手签登记"按钮和 Modal:
  - 选择员工、文件类型、签署日期、地点、见证人
  - 上传签署扫描件(多文件)
  - 登记后自动创建证据链
- 列表增加"签署方式"列(电子签/线下手签标签)
- 详情页增加签署方式标签 + 线下手签信息卡片 + 扫描件列表

## 前端员工端
- MyEsign.tsx 列表增加线下手签标签
- 详情页增加线下手签信息卡片(签署日期/地点/见证人/扫描件)
- 签署操作区域:线下手签记录显示"线下手签已登记",不显示验证码签署

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

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-15 16:36:46 +08:00

1716 lines
63 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"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ========== 枚举 ==========
enum Plan {
FREE
PRO
ENTERPRISE
}
enum Role {
SUPER_ADMIN
ADMIN
HR
VIEWER
SUPPORT
}
enum EmployeeStatus {
ACTIVE
RESIGNED
TERMINATED
}
enum FemaleWorkerType {
CADRE // 干部/管理岗
WORKER // 工人/操作岗
}
enum ContractType {
FIXED
UNFIXED
UNSIGNED
LABOR
INTERNSHIP
DISPATCH
OUTSOURCING
PARTTIME
}
enum SignMethod {
PAPER
ELECTRONIC
}
enum RiskType {
CONTRACT
SALARY
TERMINATION
MONTHLY
ONBOARDING
RETIREMENT
}
enum RiskLevel {
HIGH
MEDIUM
LOW
}
enum PayrollBatchType {
REGULAR // 常规发薪
TERMINATION // 离职结算
BONUS // 年终奖/奖金
SEVERANCE // 补偿金按月发放(无社保,个税按政策处理)
}
enum PayrollBatchStatus {
DRAFT // 草稿(可编辑)
ARCHIVED // 归档(已发薪,锁定)
}
enum PayslipItemType {
INPUT // 手工输入项(计算依据)
CALCULATED // 计算项(公式自动计算)
}
enum PayslipStatus {
PENDING // 待发布
PUBLISHED // 已发布到员工端
}
enum RiskStatus {
PENDING
RESOLVED
IGNORED
}
enum TerminationReason {
NEGOTIATED
FAULT
NONFAULT
LAYOFF
EXPIRED
RESIGNATION
}
enum RiskAssessment {
SAFE
WARNING
DANGER
}
enum OnboardingStatus {
PENDING
APPROVED
REJECTED
CANCELLED
}
enum ContractConfirmStatus {
UNCONFIRMED
CONFIRMED
EXPIRED
}
// ========== 核心表 ==========
model Organization {
id String @id @default(cuid())
name String
plan Plan @default(FREE)
maxEmployees Int @default(20)
city String?
contactName String?
contactPhone String?
payrollDays Json @default("[5]") // 每月发薪日期,如 [5, 20] 表示每月5号和20号
payrollReminderDays Int @default(3) // 发薪提前提醒天数
retirementReminderEnabled Boolean @default(false) // 退休提醒开关
esignPolicyEnabled Boolean @default(false) // 规章制度电子签
esignPayslipEnabled Boolean @default(false) // 工资条电子签
esignOnboardingEnabled Boolean @default(false) // 入职文件电子签
esignTrainingEnabled Boolean @default(false) // 培训记录电子签
esignPerformanceEnabled Boolean @default(false) // 绩效考核电子签
esignDisciplinaryEnabled Boolean @default(false) // 违纪记录电子签
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
users User[]
employees Employee[]
contracts LaborContract[]
overtimeRecords OvertimeRecord[]
terminations TerminationRecord[]
riskItems RiskItem[]
auditLogs AuditLog[]
payslips Payslip[]
payrollBatches PayrollBatch[]
payslipItems PayslipItem[]
salaryChangeRecords SalaryChangeRecord[]
onboardingLinks OnboardingLink[]
confirmLinks ContractConfirmLink[]
aiConversations AIConversation[]
aiReviewRecords AIReviewRecord[]
socialInsuranceConfig SocialInsuranceConfig[]
housingFundConfigs HousingFundConfig[]
socialInsRecords EmployeeSocialInsRecord[]
housingFundRecords EmployeeHousingFundRecord[]
departmentRecords EmployeeDepartmentRecord[]
notificationSetting NotificationSetting?
overtimeConfig OvertimeConfig?
notificationLogs NotificationLog[]
employeeAttachments EmployeeAttachment[]
disciplinaryRecords DisciplinaryRecord[]
attendanceRecords AttendanceRecord[]
trainingRecords TrainingRecord[]
performanceRecords PerformanceRecord[]
performanceTemplates PerformanceTemplate[]
retirementPolicies RetirementPolicy[]
socialMonthlyProcesses SocialMonthlyProcess[]
evidenceChains EvidenceChain[]
attendanceConfirmations AttendanceConfirmation[]
policyDocuments PolicyDocument[]
policyReadRecords PolicyReadRecord[]
healthCheckReports HealthCheckReport[]
annualValueReports AnnualValueReport[]
specialDeductionRecords SpecialDeductionRecord[]
shifts Shift[]
shiftAssignments ShiftAssignment[]
leaveRecords LeaveRecord[]
leaveRequests LeaveRequest[]
calendarEvents CalendarEvent[]
consultations Consultation[]
workProcesses WorkProcess[]
enterpriseTemplates EnterpriseTemplate[]
attendancePublishes AttendancePublish[]
specialStatuses EmployeeSpecialStatus[]
companyFiles CompanyFile[]
commercialInsPlans CommercialInsurancePlan[]
commercialInsEnrollments CommercialInsuranceEnrollment[]
benefitPlans EmployeeBenefitPlan[]
benefitEnrollments EmployeeBenefitEnrollment[]
eSignRecords ESignRecord[]
medicalPeriodPolicies MedicalPeriodPolicy[]
// 组织架构 + 审批流
departments Department[]
positions Position[]
approvalFlows ApprovalFlow[]
approvalInstances ApprovalInstance[]
// 客服工作台
tickets Ticket[]
chatSessions ChatSession[]
}
model User {
id String @id @default(cuid())
orgId String? // 平台管理员不绑定企业(null)
org Organization? @relation(fields: [orgId], references: [id], onDelete: Cascade)
phone String @unique
email String?
passwordHash String
name String
role Role @default(ADMIN)
disabled Boolean @default(false)
createdAt DateTime @default(now())
lastLoginAt DateTime?
}
// ========== 业务表 ==========
model Employee {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
name String
employeeNo String? // 工号
department String
position String? // 岗位
hireDate DateTime
// 组织架构扩展
departmentId String? // 关联 Department 表(兼容旧 department 字符串)
supervisorId String? // 直属上级员工ID
monthlySalary String // AES-256 加密存储
status EmployeeStatus @default(ACTIVE)
gender String?
phone String?
idCardNumber String? // AES-256 加密存储
idCardHash String? // SHA-256 哈希,用于按身份证号查询匹配
emergencyContact String?
emergencyPhone String?
address String?
bankAccount String? // AES-256 加密存储
bankName String?
passwordHash String? // 员工端登录密码
isPregnant Boolean @default(false)
isInMedicalPeriod Boolean @default(false)
isWorkInjured Boolean @default(false)
// 薪税扩展
socialInsBase Float? // 社保缴费基数(便捷字段,由Record同步)
housingFundBase Float? // 公积金缴费基数(便捷字段,由Record同步)
socialInsStartMonth String? // 当前社保开始年月(便捷字段)
socialInsEndMonth String? // 当前社保截止年月(便捷字段,null=在保)
housingFundStartMonth String? // 当前公积金开始年月(便捷字段)
housingFundEndMonth String? // 当前公积金截止年月(便捷字段)
specialDeduction Float @default(0) // 专项附加扣除(子女教育、赡养老人等,员工portal端填报)
city String? // 员工社保参保城市
birthDate DateTime? // 出生日期(从身份证号提取)
education String? // 学历(博士/硕士/本科/大专/高中/其他)
femaleWorkerType FemaleWorkerType? // 女性岗位类型(CADRE=干部/WORKER=工人,仅女性需要区分)
retirementDaysLeft Int? // 距退休天数(便捷字段,定期计算)
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
contracts LaborContract[]
overtimeRecords OvertimeRecord[]
terminations TerminationRecord[]
riskItems RiskItem[]
payslips Payslip[]
salaryChanges SalaryChangeRecord[]
batchEntries BatchEntry[]
attachments EmployeeAttachment[]
disciplinaryRecords DisciplinaryRecord[]
attendanceRecords AttendanceRecord[]
trainingRecords TrainingRecord[]
performanceRecords PerformanceRecord[]
socialInsRecords EmployeeSocialInsRecord[]
housingFundRecords EmployeeHousingFundRecord[]
departmentRecords EmployeeDepartmentRecord[]
aiReviewRecords AIReviewRecord[]
evidenceChains EvidenceChain[]
attendanceConfirmations AttendanceConfirmation[]
policyReadRecords PolicyReadRecord[]
specialDeductionRecords SpecialDeductionRecord[]
shiftAssignments ShiftAssignment[]
leaveRecords LeaveRecord[]
leaveRequests LeaveRequest[]
calendarEvents CalendarEvent[]
workProcesses WorkProcess[]
specialStatuses EmployeeSpecialStatus[]
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])
}
model LaborContract {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
signDate DateTime?
startDate DateTime
endDate DateTime?
contractType ContractType
signMethod SignMethod @default(PAPER)
contractYears Int @default(3)
probationMonths Int @default(0)
probationSalary Int @default(0)
renewalCount Int @default(0)
attachmentName String?
attachmentUrl String?
electronicContractNo String?
electronicContractUrl String?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
confirmLinks ContractConfirmLink[]
}
model OvertimeRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
month String // YYYY-MM
weekdayHours Float @default(0)
weekendHours Float @default(0)
holidayHours Float @default(0)
weekdayPay Float @default(0)
weekendPay Float @default(0)
holidayPay Float @default(0)
totalPay Float @default(0)
batchId String? // 关联的发薪批次(加入后锁定,不可重复加入)
createdAt DateTime @default(now())
@@unique([employeeId, month])
}
model TerminationRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
type String @default("TERMINATION") // TERMINATION=公司解聘, RESIGNATION=员工主动离职
reason TerminationReason
terminationDate DateTime
resignationReason String? // 主动离职原因(type=RESIGNATION时使用)
compensation Float @default(0)
socialInsEndMonth String? // 社保截止缴费年月 YYYY-MM
housingFundEndMonth String? // 公积金截止缴费年月 YYYY-MM
riskLevel RiskAssessment @default(SAFE)
checklist Json
remark String?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// 流程状态机
status String @default("DRAFT") // DRAFT|PENDING_APPROVAL|APPROVED|EXECUTING|COMPLETED|REJECTED|CANCELLED
currentStep Int @default(0) // 当前完成到第几步
// 补偿金分项明细 + 调整记录
compensationBreakdown Json? // { severance, noticePay, doublePay, other, adjustments: [{field, from, to, reason}] }
// 合规检查覆盖记录
checklistOverrides Json? // { key: { checked: bool, overrideReason: string } }
// 工作交接清单
handoverItems Json? // [{ key, label, done, remark }]
// 审批信息
approvedBy String?
approvedAt DateTime?
approvalComment String?
updatedBy String?
@@index([orgId, status])
}
model RiskItem {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String?
employee Employee? @relation(fields: [employeeId], references: [id], onDelete: SetNull)
type RiskType
level RiskLevel
status RiskStatus @default(PENDING)
title String
description String
actionUrl String?
// 风险量化:预估损失金额(元)
estimatedLoss Float?
// 风险量化:损失区间 [最低, 最高]
lossRange Json?
// 建议处理截止日期
deadline DateTime?
resolvedAt DateTime?
resolvedBy String?
remark String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, status])
@@index([orgId, type])
}
model AuditLog {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
userId String
action String
entity String
entityId String?
detail Json?
ip String?
createdAt DateTime @default(now())
@@index([orgId, createdAt])
}
// ========== 社保 & 通知 & 附件 ==========
model SocialInsuranceConfig {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
city String @default("北京")
pensionOrg Float @default(16) // 养老保险 企业比例 %
pensionEmp Float @default(8) // 养老保险 个人比例 %
medicalOrg Float @default(9.8) // 医疗保险 企业比例 %
medicalEmp Float @default(2) // 医疗保险 个人比例 %
unemploymentOrg Float @default(0.5) // 失业保险 企业比例 %
unemploymentEmp Float @default(0.5) // 失业保险 个人比例 %
injuryOrg Float @default(0.2) // 工伤保险 企业比例 %
maternityOrg Float @default(0.8) // 生育保险 企业比例 %
baseMin Float @default(6326) // 社保缴费基数下限(养老/失业/工伤)
baseMax Float @default(33891) // 社保缴费基数上限(养老/失业/工伤)
medicalBaseMin Float @default(0) // 医疗/生育保险基数下限(0 时 fallback 到 baseMin
medicalBaseMax Float @default(0) // 医疗/生育保险基数上限(0 时 fallback 到 baseMax
extraInsurances Json? // 附加险种配置 JSON: [{ name, orgRate, empRate, baseType: 'pension'|'medical'|'fixed', fixedAmount }]
effectiveFrom String // 生效月份 YYYY-MM
effectiveTo String? // 失效月份 YYYY-MMnull=当前有效)
isCurrent Boolean @default(true) // 是否当前生效版本
adjustmentDone Boolean @default(false) // 是否已执行过社保基数调整
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([orgId, city, effectiveFrom])
@@index([orgId, isCurrent])
}
model HousingFundConfig {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
city String @default("北京")
accountType String @default("BASIC") // BASIC=基本公积金, SUPPLEMENTARY=补充公积金
housingOrg Float @default(12) // 公积金 企业比例 %
housingEmp Float @default(12) // 公积金 个人比例 %
baseMin Float @default(6326) // 公积金缴费基数下限
baseMax Float @default(33891) // 公积金缴费基数上限
effectiveFrom String // 生效月份 YYYY-MM
effectiveTo String? // 失效月份 YYYY-MMnull=当前有效)
isCurrent Boolean @default(true) // 是否当前生效版本
adjustmentDone Boolean @default(false) // 是否已执行过公积金基数调整
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([orgId, city, accountType, effectiveFrom])
@@index([orgId, isCurrent])
}
model NotificationSetting {
id String @id @default(cuid())
orgId String @unique
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
contractExpiry Boolean @default(true)
expiryDays Int @default(30)
contractUnsigned Boolean @default(true)
overtimeAlert Boolean @default(true)
payslipReady Boolean @default(true)
// 月度事务提醒日(每月几号)
payrollDay Int @default(10) // 发薪日
socialInsDay Int @default(15) // 社保缴纳日
housingFundDay Int @default(15) // 公积金缴纳日
taxDay Int @default(15) // 个税申报日
wechatWebhook String?
emailNotify Boolean @default(false)
email String?
updatedAt DateTime @updatedAt
}
model OvertimeConfig {
id String @id @default(cuid())
orgId String @unique
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
weekdayRate Float @default(1.5) // 工作日加班倍率
weekendRate Float @default(2.0) // 休息日加班倍率
holidayRate Float @default(3.0) // 法定节假日加班倍率
monthlyDays Float @default(21.75) // 月计薪天数
dailyHours Float @default(8) // 每日工时
updatedAt DateTime @updatedAt
}
model MedicalPeriodPolicy {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
region String // 地区名称,如"全国"、"上海"、"广东"
legalBasis String // 法律依据
rules Json // 分档规则: [{ maxYears: 5, months: 3, cycleMonths: 6 }, ...]
isDefault Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([orgId, region])
@@index([orgId])
}
model NotificationLog {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
type String // CONTRACT_EXPIRY / CONTRACT_UNSIGNED / OVERTIME / PAYSLIP
title String
content String
channel String // WECHAT / EMAIL / IN_APP
status String @default("SENT") // SENT / FAILED
employeeId String?
createdAt DateTime @default(now())
@@index([orgId, createdAt])
}
model EmployeeAttachment {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
fileName String
fileType String // ID_CARD / BANK_CARD / CONTRACT_SCAN / EDUCATION / TERMINATION_DOC / RETIREMENT_DOC / INJURY_CERT / MEDICAL_CERT / PREGNANCY_CERT / DISCIPLINARY / OTHER
fileUrl String
fileSize Int @default(0)
// 关联违纪记录(可选)
disciplinaryRecordId String?
uploadedBy String
createdAt DateTime @default(now())
@@index([orgId, employeeId])
@@index([disciplinaryRecordId])
}
// ========== 公司备用文件 ==========
model CompanyFile {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
fileName String
fileType String // BUSINESS_LICENSE=营业执照 / WORK_HOURS=工时备案 / HR_POLICY=制度文件 / LABOR_CONTRACT_TEMPLATE=合同模板 / OTHER=其他
fileUrl String
fileSize Int @default(0)
remark String? // 备注
expiryDate DateTime? // 有效期(如营业执照到期日)
uploadedBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, fileType])
}
// ========== 仲裁证据链 ==========
model DisciplinaryRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
violationDate DateTime
violationType String // LATE/ABSENT/INSUBORDINATION/MISCONDUCT/VIOLATE_POLICY/OTHER
description String
severity String @default("WARNING") // WARNING/SERIOUS/SEVERE
action String @default("ORAL_WARNING") // ORAL_WARNING/WRITTEN_WARNING/DEDUCTION/DEMOTION/TERMINATION
actionDetail String?
employeeAck Boolean @default(false) // 员工是否签字确认
ackDate DateTime?
ackMethod String? // SIGN/ELECTRONIC/REFUSED
witness String? // 见证人
attachmentUrl String?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, employeeId])
}
model AttendanceRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
date DateTime
checkInTime String? // HH:mm
checkOutTime String? // HH:mm
status String @default("NORMAL") // NORMAL/LATE/EARLY_LEAVE/ABSENT/LEAVE/BUSINESS_TRIP
lateMinutes Int @default(0)
earlyMinutes Int @default(0)
workHours Float @default(0)
overtimeHours Float @default(0)
remark String?
createdBy String
createdAt DateTime @default(now())
@@unique([employeeId, date])
@@index([orgId, employeeId])
}
model TrainingRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
trainingDate DateTime
topic String // 培训主题/制度名称
content String? // 培训内容摘要
trainer String?
duration Float @default(0) // 培训时长(小时)
ackStatus String @default("PENDING") // PENDING/SIGNED/REFUSED
ackDate DateTime?
attachmentUrl String? // 签收单扫描件
remark String?
createdBy String
createdAt DateTime @default(now())
@@index([orgId, employeeId])
}
model PerformanceRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
period String // 考核周期 YYYY-MM 或 YYYY-Q1
periodType String @default("MONTHLY") // MONTHLY/QUARTERLY/YEARLY
score Float @default(0) // 考核得分
grade String @default("B") // A/B/C/D
result String @default("QUALIFIED") // EXCELLENT/QUALIFIED/NEED_IMPROVE/UNQUALIFIED
summary String? // 考核评语
improvementPlan String? // 改进计划(不胜任时)
templateId String? // 关联绩效模板(选填)
dimensionScores Json? // 各维度得分明细 { dimensionName: score }
employeeAck Boolean @default(false)
ackDate DateTime?
reviewer String?
createdBy String
createdAt DateTime @default(now())
@@unique([employeeId, period])
@@index([orgId, employeeId])
}
model PerformanceTemplate {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
name String // 模板名称
description String? // 模板说明
dimensions Json // 考核维度 [{ name, weight, maxScore, description }]
gradeRules Json? // 等级规则 [{ grade, result, minScore, maxScore }]
isDefault Boolean @default(false)
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId])
}
// ========== 员工端表 ==========
model Payslip {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
month String // YYYY-MM
// 薪酬构成
baseSalary Float @default(0)
overtimePay Float @default(0)
weekdayOvertimePay Float @default(0)
weekendOvertimePay Float @default(0)
holidayOvertimePay Float @default(0)
allowance Float @default(0)
deduction Float @default(0)
bonus Float @default(0) // 奖金/年终奖
totalPay Float @default(0) // 应发合计
// 扣除项
socialEmp Float @default(0) // 个人社保
housingEmp Float @default(0) // 个人公积金
tax Float @default(0) // 个人所得税
netPay Float @default(0) // 实发工资 = totalPay - socialEmp - housingEmp - tax
// 累计预扣法
ytdIncome Float @default(0) // 当年累计收入
ytdTaxDeducted Float @default(0) // 当年累计已扣税
ytdSocialEmp Float @default(0) // 当年累计个人社保
ytdHousingEmp Float @default(0) // 当年累计个人公积金
// 状态
status PayslipStatus @default(PENDING) // PENDING → PUBLISHED
confirmedAt DateTime?
confirmedIp String?
publishedAt DateTime? // 工资条发布到员工端的时间
publishStatus String? // UNPUBLISHED/PUBLISHED/SCHEDULED
scheduledAt DateTime? // 定时发送时间
viewedAt DateTime? // 员工查看工资条的时间
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([employeeId, month])
@@index([orgId, month])
@@index([orgId, status])
}
model PayrollBatch {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
month String // YYYY-MM
batchNo Int // 批次序号(1, 2, 3...
name String // 批次名称
type PayrollBatchType @default(REGULAR)
status PayrollBatchStatus @default(DRAFT)
employeeCount Int @default(0)
totalPay Float @default(0)
totalNetPay Float @default(0)
totalSocialOrg Float @default(0)
totalSocialEmp Float @default(0)
totalHousingOrg Float @default(0)
totalHousingEmp Float @default(0)
totalTax Float @default(0)
remark String?
createdBy String
createdAt DateTime @default(now())
archivedAt DateTime?
updatedAt DateTime @updatedAt
entries BatchEntry[]
@@unique([orgId, month, batchNo])
@@index([orgId, month])
@@index([orgId, status])
}
model BatchEntry {
id String @id @default(cuid())
batchId String
batch PayrollBatch @relation(fields: [batchId], references: [id], onDelete: Cascade)
orgId String
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
// 薪酬项(可编辑的输入项)
baseSalary Float @default(0)
overtimePay Float @default(0)
allowance Float @default(0)
deduction Float @default(0)
bonus Float @default(0)
// 细化薪资项
positionSalary Float @default(0) // 岗位工资
performanceSalary Float @default(0) // 绩效工资
senioritySalary Float @default(0) // 工龄工资
transportAllowance Float @default(0) // 交通补贴
mealAllowance Float @default(0) // 餐补
housingAllowance Float @default(0) // 住房补贴
communicationAllowance Float @default(0) // 通讯补贴
otherDeduction Float @default(0) // 其他扣款
// 自动计算项
socialEmp Float @default(0)
socialOrg Float @default(0)
housingEmp Float @default(0)
housingOrg Float @default(0)
tax Float @default(0)
totalPay Float @default(0) // 应发合计
netPay Float @default(0) // 实发工资
// 风险提示
riskWarnings Json?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([batchId, employeeId])
@@index([orgId, employeeId])
}
model PayslipItem {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
name String // 显示名称
code String // 字段代码
type PayslipItemType @default(INPUT)
formula String? // 计算公式(CALCULATED 类型),如 "baseSalary + overtimePay + allowance - deduction"
order Int @default(0)
isDefault Boolean @default(true) // 系统预置项不可删除
isEditable Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([orgId, code])
}
model SalaryChangeRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
oldSalary Float
newSalary Float
effectiveDate DateTime // 生效日期
effectiveMonth String // 生效年月 YYYY-MM(从 effectiveDate 转换)
endMonth String? // 失效年月 YYYY-MM(null=至今有效,被新版本覆盖时设置)
changeType String @default("SALARY_CHANGE") // ONBOARDING=入职, REHIRE=重新入职, SALARY_CHANGE=调薪
reason String?
createdBy String
createdAt DateTime @default(now())
@@index([orgId, employeeId])
@@index([employeeId, effectiveMonth, endMonth])
}
model EmployeeSocialInsRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
city String @default("北京") // 参保城市
startMonth String // 开始缴费年月 YYYY-MM
endMonth String? // 截止缴费年月 YYYY-MMnull=至今有效)
base Float // 缴费基数
changeType String // ONBOARDING=入职, REHIRE=重新入职, ADJUST=调基, TERMINATION=离职/解聘
changeRefId String? // 关联的 TerminationRecord ID(离职/解聘时)
remark String?
createdBy String
createdAt DateTime @default(now())
@@index([orgId, employeeId])
@@index([employeeId, startMonth, endMonth])
@@index([orgId, city])
}
model EmployeeHousingFundRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
city String @default("北京") // 参保城市
startMonth String // 开始缴费年月 YYYY-MM
endMonth String? // 截止缴费年月 YYYY-MMnull=至今有效)
base Float // 缴费基数
changeType String // ONBOARDING=入职, REHIRE=重新入职, ADJUST=调基, TERMINATION=离职/解聘
changeRefId String? // 关联的 TerminationRecord ID(离职/解聘时)
remark String?
createdBy String
createdAt DateTime @default(now())
@@index([orgId, employeeId])
@@index([employeeId, startMonth, endMonth])
}
/// 月度社保/公积金办理记录(标记某月已办理完成,保存快照)
model SocialMonthlyProcess {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
month String // 办理月份 YYYY-MM
type String // SOCIAL=社保, HOUSING=公积金
status String @default("COMPLETED") // COMPLETED=已办理
snapshot Json // 办理时的数据快照(增减员+在保人员+缴费明细)
processedBy String
processedAt DateTime @default(now())
createdBy String
createdAt DateTime @default(now())
@@unique([orgId, month, type])
@@index([orgId, month])
}
model EmployeeDepartmentRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
oldDepartment String // 调整前部门
newDepartment String // 调整后部门
oldPosition String? // 调整前职务
newPosition String? // 调整后职务
effectiveMonth String // 生效年月 YYYY-MM
endMonth String? // 失效年月 YYYY-MMnull=至今有效)
reason String? // 调动原因
changeType String // ONBOARDING=入职, REHIRE=重新入职, TRANSFER=调动
createdBy String
createdAt DateTime @default(now())
@@index([orgId, employeeId])
@@index([employeeId, effectiveMonth, endMonth])
}
model OnboardingLink {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
token String @unique
employeeName String?
phone String?
status OnboardingStatus @default(PENDING)
formData Json?
expiresAt DateTime
usedAt DateTime?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, status])
}
model ContractConfirmLink {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
contractId String
contract LaborContract @relation(fields: [contractId], references: [id], onDelete: Cascade)
token String @unique
status ContractConfirmStatus @default(UNCONFIRMED)
confirmedAt DateTime?
confirmedIp String?
expiresAt DateTime
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, status])
}
// ========== AI 会话 & 审查记录 ==========
model AIConversation {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
userId String
title String @default("新对话")
messages Json // [{ role, content }]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, userId])
}
model AIReviewRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String?
employee Employee? @relation(fields: [employeeId], references: [id], onDelete: SetNull)
type String // REVIEW=合同审查, CASE=案例匹配
input String // 用户输入的合同文本或争议情形
result String // AI 返回的审查/分析结果
createdBy String
createdAt DateTime @default(now())
@@index([orgId, employeeId])
}
// ========== RAG 知识库 ==========
model RagKnowledge {
id String @id
title String
content String
source String
category String
embedding Unsupported("vector(1536)")?
createdAt DateTime @default(now()) @map("created_at")
@@index([category])
@@map("rag_knowledge")
}
// ========== 退休政策版本管理 ==========
model RetirementPolicy {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
version Int @default(1)
content String // AI 返回的完整政策内容
contentHash String // 内容 hash,用于判断内容是否变化
status String @default("PENDING") // PENDING=待确认, CONFIRMED=已生效, SUPERSEDED=已被新版本替代
maleRetireAge Float @default(60) // 男性退休年龄
femaleRetireAge Float @default(55) // 女性退休年龄(干部55,工人50,由AI解析最新政策)
femaleWorkerAge Float @default(50) // 女性工人退休年龄
createdAt DateTime @default(now())
confirmedAt DateTime? // 用户确认时间
@@index([orgId, createdAt])
}
// ========== 证据链管理 ==========
model EvidenceChain {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
category String // CONTRACT_SIGN / ONBOARD / PAYSLIP_CONFIRM / DISCIPLINARY / ATTENDANCE / TERMINATION
refId String? // 关联记录 ID(合同ID/工资条ID等)
employeeId String?
employee Employee? @relation(fields: [employeeId], references: [id], onDelete: SetNull)
events Json // [{ action, timestamp, ip, userAgent, smsCode?, location? }]
hash String // 全链路 SHA-256 哈希(防篡改)
createdBy String
createdAt DateTime @default(now())
@@index([orgId, category, refId])
@@index([orgId, employeeId])
}
// ========== 考勤确认 ==========
model AttendanceConfirmation {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
month String // YYYY-MM
workDays Int @default(0)
weekdayHours Float @default(0)
weekendHours Float @default(0)
holidayHours Float @default(0)
overtimePay Float @default(0)
confirmedAt DateTime?
confirmedBy String?
confirmIp String?
status String @default("PENDING") // PENDING / CONFIRMED / DISPUTED
disputeNote String? // 员工有异议时的说明
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([orgId, employeeId, month])
@@index([orgId, month])
@@index([orgId, status])
}
// ========== 规章制度民主程序 ==========
model PolicyDocument {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
title String
content String
type String @default("RULES") // RULES=规章制度 / NOTICE=通知
status String @default("DRAFT") // DRAFT / DISCUSSING / CONSULTING / PUBLISHED
democracyProgress Json? // { currentStep: 1-4, steps: [{ name, status, date, note }] }
publishedAt DateTime?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
readRecords PolicyReadRecord[]
@@index([orgId, status])
}
// ========== 规章制度阅读签收 ==========
model PolicyReadRecord {
id String @id @default(cuid())
policyId String
policy PolicyDocument @relation(fields: [policyId], references: [id], onDelete: Cascade)
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
readAt DateTime @default(now())
ip String?
userAgent String?
@@unique([policyId, employeeId])
@@index([orgId, employeeId])
}
// ========== 用工体检诊断报告 ==========
model HealthCheckReport {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
year Int // 诊断年度
totalScore Int // 综合评分 0-100
level String // safe / warning / danger
dimensions Json // [{ key, name, score, findings: [], recommendations: [] }]
summary String // 诊断总结
createdBy String
createdAt DateTime @default(now())
@@index([orgId, year])
@@index([orgId, createdAt])
}
// ========== 年度价值报告 ==========
model AnnualValueReport {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
year Int // 报告年度
totalValue Int // 总价值(元)
roi Float // ROI 百分比
metrics Json // { risksResolved, lossAvoided, aiQueries, contractsReviewed, payrollProcessed, employeesManaged }
timeline Json // [{ month, event, value }] 月度事件时间轴
costSaved Int // 节约成本(元)
timeSaved Int // 节约工时(小时)
summary String // 年度总结
createdBy String
createdAt DateTime @default(now())
@@index([orgId, year])
@@index([orgId, createdAt])
}
// 专项附加扣除按月记录
model SpecialDeductionRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
month String // YYYY-MM 月份
amount Float @default(0) // 专项附加扣除总额
children Float @default(0) // 子女教育
elderly Float @default(0) // 赡养老人
housing Float @default(0) // 住房贷款利息/住房租金
education Float @default(0) // 继续教育
infant Float @default(0) // 3岁以下婴幼儿照护
remark String?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([employeeId, month])
@@index([orgId, month])
}
// ========== 班次管理 ==========
model Shift {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
name String // 班次名称,如"早班"、"白班"、"夜班"
startTime String // 上班时间 HH:mm
endTime String // 下班时间 HH:mm
flexibleMinutes Int @default(0) // 弹性时长(分钟)
restMinutes Int @default(0) // 休息时长(分钟)
color String @default("#3b82f6") // 日历显示颜色
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
assignments ShiftAssignment[]
@@index([orgId])
}
// ========== 排班记录 ==========
model ShiftAssignment {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
shiftId String
shift Shift @relation(fields: [shiftId], references: [id], onDelete: Cascade)
date DateTime // 排班日期
createdBy String
createdAt DateTime @default(now())
@@unique([employeeId, date])
@@index([orgId, date])
@@index([orgId, employeeId])
}
// ========== 休假记录(直接记录,无审批流程) ==========
model LeaveRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
leaveType String // SICK=病假 / PERSONAL=事假 / ANNUAL=年假 / MATERNITY=产假 / OTHER=其他
startDate DateTime
endDate DateTime
days Float // 请假天数
reason String?
remark String?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, employeeId])
@@index([orgId, startDate])
}
// ========== 休假审批流 ==========
model LeaveRequest {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
leaveType String // SICK=病假 / PERSONAL=事假 / ANNUAL=年假 / MATERNITY=产假 / OTHER=其他
startDate DateTime
endDate DateTime
days Float // 请假天数
reason String?
attachment String? // 附件URL(如病假条)
status String @default("PENDING") // PENDING=待审批 / APPROVED=已批准 / REJECTED=已驳回 / CANCELLED=已撤回
approverId String? // 审批人ID
approvedAt DateTime?
approveRemark String? // 审批意见
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, employeeId])
@@index([orgId, status])
@@index([orgId, startDate])
}
// ========== 自定义日历事件 ==========
model CalendarEvent {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
title String
date DateTime
endDate DateTime?
type String @default("CUSTOM") // CUSTOM=自定义, MEETING=会议, TEAM_BUILDING=团建, TRAINING=培训, INTERVIEW=面试
priority String @default("medium") // high/medium/low
location String?
description String?
employeeId String?
employee Employee? @relation(fields: [employeeId], references: [id], onDelete: SetNull)
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, date])
@@index([orgId, type])
}
// ========== 人工咨询服务 ==========
model Consultation {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
type String // LEGAL=法律咨询, ARBITRATION=仲裁代理, COURT=出庭服务
title String
description String
contactName String
contactPhone String
status String @default("PENDING") // PENDING=待处理, CONTACTED=已联系, COMPLETED=已完成, CANCELLED=已取消
aiConversationId String? // 关联 AI 会话
remark String?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, status])
@@index([orgId, type])
}
// ========== 用工办理工作流 ==========
model WorkProcess {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
type String // HIRE/ONBOARD/CUSTOM_CONTRACT/INFO_SUBMIT/CONFIRM/CHANGE/RENEW/SUSPEND/INCOME_CERT/TERMINATE/RESCIND/LEAVING_CERT/FLEXIBLE
title String
employeeId String?
employee Employee? @relation(fields: [employeeId], references: [id], onDelete: SetNull)
status String @default("DRAFT") // DRAFT/PENDING_APPROVAL/APPROVED/REJECTED/EXECUTING/COMPLETED/CANCELLED
formData Json // 表单数据 JSON
documents Json? // 生成的文书列表 [{name, content, type}]
approverId String?
approvedAt DateTime?
remark String?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, status])
@@index([orgId, type])
@@index([orgId, createdBy])
}
// ========== 企业自建文本模板 ==========
model EnterpriseTemplate {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
name String
category String // CONTRACT/RULES/NOTICE/AGREEMENT/OTHER
description String?
content String @db.Text
variables String[] // 变量名列表
status String @default("ACTIVE") // ACTIVE/ARCHIVED
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, category])
}
// ========== 考勤发布记录 ==========
model AttendancePublish {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
month String // YYYY-MM
title String // 如"2026年7月考勤表"
status String @default("PUBLISHED") // PUBLISHED/CANCELLED
publishDate DateTime @default(now())
createdBy String
createdAt DateTime @default(now())
@@unique([orgId, month])
@@index([orgId, month])
}
// ========== 员工特殊状态台账(三期/工伤/医疗期等) ==========
model EmployeeSpecialStatus {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
type String // PREGNANCY(三期) | WORK_INJURY(工伤) | MEDICAL_PERIOD(医疗期) | OTHER
status String @default("ACTIVE") // ACTIVE(进行中) | RESOLVED(已结束) | PENDING(待处理)
// 通用时间节点
startDate DateTime? // 开始日期
endDate DateTime? // 预计结束日期
actualEndDate DateTime? // 实际结束日期
// 三期专用
expectedDueDate DateTime? // 预产期
maternityLeaveStart DateTime? // 产假开始
maternityLeaveEnd DateTime? // 产假结束
nursingEndDate DateTime? // 哺乳期截止
// 工伤专用
injuryDate DateTime? // 受伤日期
injuryDescription String? // 伤情描述
certificationDate DateTime? // 工伤认定日期
certificationNo String? // 认定书编号
disabilityLevel Int? // 伤残等级(1-10级)
assessmentDate DateTime? // 鉴定日期
// 医疗期专用
medicalMonths Int? // 医疗期月数
medicalPeriodEnd DateTime? // 医疗期截止日
// 通用
description String? // 备注
attachments Json? // 附件列表 [{name, url}]
timeline Json? // 操作时间线 [{time, action, by}]
reminderDate DateTime? // 提醒日期
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, status])
@@index([orgId, type])
@@index([employeeId])
}
// ========== 验收测试 ==========
model AcceptanceTest {
id String @id @default(uuid())
orgId String
verifierName String // 验收人姓名
results Json // { "1.1": "pass", "1.2": "fail", ... }
remarks Json // { "1.1": "备注内容", ... }
conclusionName String? // 结论验收人
conclusionDate String? // 结论日期
conclusionResult String? // pass | conditional | fail — 系统自动计算
conclusionIssues String? // 遗留问题(系统自动汇总)
screenshots Json? // { "0-0": "data:image/png;base64,...", ... }
signature1 String? // 验收人签名 dataURL
signature2 String? // 项目经理签名 dataURL
status String @default("DRAFT") // DRAFT | SUBMITTED
submittedAt DateTime?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([orgId, verifierName])
@@index([orgId, status])
}
// ========== 商业保险 ==========
model CommercialInsurancePlan {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
name String
type String // ACCIDENT | SUPPLEMENTARY_MEDICAL | EMPLOYER_LIABILITY | CRITICAL_ILLNESS | GROUP_LIFE | OTHER
provider String // 保险公司
policyNo String?
premium Float // 年保费
coverageAmount Float // 保额
effectiveFrom String // YYYY-MM-DD
effectiveTo String? // null = 长期
description String?
status String @default("ACTIVE") // ACTIVE | EXPIRED | CANCELLED
enrollments CommercialInsuranceEnrollment[]
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, status])
@@index([orgId, type])
}
model CommercialInsuranceEnrollment {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
planId String
plan CommercialInsurancePlan @relation(fields: [planId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
premium Float // 个人保费
effectiveFrom String // YYYY-MM-DD
effectiveTo String?
status String @default("ACTIVE") // ACTIVE | TERMINATED
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, planId])
@@index([employeeId])
}
// ========== 员工福利 ==========
model EmployeeBenefitPlan {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
name String
category String // TRANSPORT | MEAL | HOUSING | COMMUNICATION | HEALTH_CHECK | HOLIDAY | BIRTHDAY | OTHER
amount Float // 每月金额(或每次金额)
frequency String @default("MONTHLY") // MONTHLY | QUARTERLY | YEARLY | ONE_TIME
taxDeductible Boolean @default(false) // 是否税前扣除
description String?
status String @default("ACTIVE")
enrollments EmployeeBenefitEnrollment[]
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, status])
@@index([orgId, category])
}
model EmployeeBenefitEnrollment {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
planId String
plan EmployeeBenefitPlan @relation(fields: [planId], references: [id], onDelete: Cascade)
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
effectiveFrom String // YYYY-MM
effectiveTo String? // null = 至今
amount Float? // 覆盖默认金额(个别调整)
status String @default("ACTIVE")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([orgId, planId])
@@index([employeeId])
}
// ========== 电子签署(易签宝) ==========
model ESignRecord {
id String @id @default(cuid())
orgId String
org Organization @relation(fields: [orgId], references: [id], onDelete: Cascade)
contractId String? // 关联 LaborContract
employeeId String
employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade)
scene String @default("CONTRACT") // CONTRACT | RESIGNATION | POLICY | PAYSLIP | ONBOARDING
signMethod String @default("ELECTRONIC") // ELECTRONIC=电子签署 | PAPER=线下手签
flowId String? // 易签宝流程ID
documentTitle String // 文件标题
documentContent String? // 文件内容(HTML/PDF base64
status String @default("PENDING") // PENDING | SIGNING | COMPLETED | REJECTED | EXPIRED | CANCELLED
signUrl String? // 签署链接
signedPdfUrl String? // 签署完成后的PDF链接
initiatedBy String // 发起人(HR用户ID
completedAt DateTime?
expiredAt DateTime?
callbackData Json? // 易签宝回调数据 / 线下手签证据
remark String?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// 线下手签专用字段
signedAt DateTime? // 线下签署日期
signedLocation String? // 签署地点
witnessName String? // 见证人姓名
witnessPhone String? // 见证人手机号
scanFileUrls Json? // 线下签署扫描件URL列表 [{name, url}]
@@index([orgId, status])
@@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])
}