feat: AIHR 智能人力资源管理系统初始提交

- 员工花名册管理(加密存储、导入导出)
- 薪酬管理(发薪批次、薪酬模版、加班费计算、工资条)
- 社保公积金(多城市配置、版本管理、基数调整)
- 解聘管理(6步流程、证据链、工作交接)
- AI 助手(合同审查、风险预测、RAG 知识库)
- Dashboard 仪表盘
- 设置与通知
This commit is contained in:
selfrelease
2026-07-24 13:53:11 +08:00
commit 0df8aa77d9
109 changed files with 38190 additions and 0 deletions
+827
View File
@@ -0,0 +1,827 @@
import prisma from '../lib/prisma'
import { RiskAssessment, TerminationReason } from '@prisma/client'
function dateToMonth(date: Date): string {
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
return `${y}-${m}`
}
export interface ChecklistItem {
key: string
label: string
autoChecked?: boolean | null // null=无法自动判断,true/false=系统判断结果
autoSource?: string // 系统判断依据说明
suggestion?: string // 系统建议说明
suggestionType?: 'info' | 'warning' | 'required'
}
export function getChecklistForReason(reason: string, employee?: any): ChecklistItem[] {
switch (reason) {
case 'NEGOTIATED':
return [
{
key: 'compensation_paid', label: '是否已支付经济补偿金',
autoChecked: null,
suggestion: '协商解除需支付经济补偿金(N),建议在协商协议中明确金额',
suggestionType: 'required',
},
{ key: 'agreement_signed', label: '是否签署协商解除协议', autoChecked: null },
{ key: 'final_pay_ready', label: '是否结清最后工资', autoChecked: null },
]
case 'FAULT':
return [
{ key: 'has_rules', label: '是否有规章制度依据', autoChecked: null },
{ key: 'has_evidence', label: '是否有违纪证据', autoChecked: null },
{ key: 'notify_union', label: '是否事先通知工会', autoChecked: null },
{ key: 'written_notice', label: '是否出具书面解除通知', autoChecked: null },
]
case 'NONFAULT': {
const items: ChecklistItem[] = []
// 医疗期是否已届满 — 系统自动判断
if (employee?.isInMedicalPeriod) {
items.push({
key: 'medical_period_end', label: '医疗期是否已届满',
autoChecked: false,
autoSource: '系统记录显示该员工正处于医疗期内,医疗期未届满',
suggestion: '医疗期内不得以非过错理由解除,需等待医疗期届满',
suggestionType: 'warning',
})
} else {
items.push({
key: 'medical_period_end', label: '医疗期是否已届满',
autoChecked: null,
autoSource: '系统未记录该员工处于医疗期,如实际已届满请勾选确认',
})
}
// 是否经过培训或调岗 — 系统自动判断
const hasTraining = employee?.trainingRecords?.length > 0
items.push({
key: 'training_given', label: '是否经过培训或调岗',
autoChecked: hasTraining ? true : null,
autoSource: hasTraining
? `系统记录显示该员工有${employee.trainingRecords.length}条培训记录`
: '系统未找到培训或调岗记录,请人工确认',
suggestion: hasTraining
? '已有培训记录,满足"不胜任工作经培训或调岗"的前提条件'
: '以不胜任工作为由解除前,必须先经过培训或调岗,否则违法解除风险极高',
suggestionType: hasTraining ? 'info' : 'warning',
})
// 是否支付经济补偿金 — 系统建议
items.push({
key: 'compensation_paid', label: '是否支付经济补偿金',
autoChecked: null,
suggestion: '非过错解除需支付经济补偿金(N),并在Step 4费用结算中确认金额',
suggestionType: 'required',
})
// 是否提前30天通知或支付代通知金 — 系统建议
items.push({
key: 'advance_notice', label: '是否提前30天通知或支付代通知金',
autoChecked: null,
suggestion: '非过错解除需提前30天书面通知,或额外支付1个月工资作为代通知金(N+1)',
suggestionType: 'required',
})
return items
}
case 'LAYOFF':
return [
{ key: 'advance_notice_30', label: '是否提前30天向工会或全体职工说明', autoChecked: null },
{ key: 'listen_opinions', label: '是否听取工会或职工意见', autoChecked: null },
{ key: 'report_labor_dept', label: '是否向劳动行政部门报告', autoChecked: null },
{
key: 'compensation_paid', label: '是否支付经济补偿金',
autoChecked: null,
suggestion: '裁员需支付经济补偿金(N)',
suggestionType: 'required',
},
]
case 'EXPIRED':
return [
{
key: 'compensation_paid', label: '是否支付经济补偿金(如需)',
autoChecked: null,
suggestion: '公司提出不续签需支付经济补偿金(N);员工主动提出不续签则无需支付',
suggestionType: 'info',
},
{ key: 'written_notice', label: '是否提前通知员工不续签', autoChecked: null },
]
default:
return []
}
}
export function assessRisk(employee: any, reason: string): { level: RiskAssessment; warnings: string[] } {
const warnings: string[] = []
if (employee.isPregnant) {
warnings.push('该员工在孕期/哺乳期,法律禁止以非过错理由解除')
}
if (employee.isWorkInjured) {
warnings.push('工伤期间不得解除劳动合同')
}
if (employee.isInMedicalPeriod && reason !== 'FAULT') {
warnings.push('医疗期内不得解除劳动合同(非过错理由)')
}
let level: RiskAssessment = 'SAFE'
if (warnings.length > 0) {
level = 'DANGER'
}
return { level, warnings }
}
export async function createTermination(orgId: string, userId: string, data: any) {
const employee = await prisma.employee.findFirst({ where: { id: data.employeeId, orgId } })
if (!employee) {
throw { code: 'NOT_FOUND', message: '员工不存在' }
}
// 校验:已有离职/解聘记录且未重新雇佣则不允许再次解聘
const latestTerm = await prisma.terminationRecord.findFirst({
where: { employeeId: data.employeeId },
orderBy: { terminationDate: 'desc' },
})
if (latestTerm && latestTerm.terminationDate >= employee.hireDate) {
throw { code: 'CONFLICT', message: '该员工已有离职/解聘记录,如需再次解聘请先办理重新雇佣' }
}
const { level } = assessRisk(employee, data.reason)
const termDate = new Date(data.terminationDate)
const termMonth = dateToMonth(termDate)
const socialInsEndMonth = data.socialInsEndMonth || termMonth
const housingFundEndMonth = data.housingFundEndMonth || termMonth
const record = await prisma.terminationRecord.create({
data: {
orgId,
employeeId: data.employeeId,
type: 'TERMINATION',
reason: data.reason,
terminationDate: termDate,
compensation: data.compensation || 0,
socialInsEndMonth,
housingFundEndMonth,
riskLevel: level,
checklist: data.checklist || {},
remark: data.remark,
createdBy: userId,
},
})
// 关闭社保缴费记录(设置 endMonth)
await prisma.employeeSocialInsRecord.updateMany({
where: { employeeId: data.employeeId, endMonth: null },
data: { endMonth: socialInsEndMonth, changeRefId: record.id },
})
// 关闭公积金缴费记录
await prisma.employeeHousingFundRecord.updateMany({
where: { employeeId: data.employeeId, endMonth: null },
data: { endMonth: housingFundEndMonth, changeRefId: record.id },
})
// 根据解聘日期判断在职/离职状态
const today = new Date()
today.setHours(0, 0, 0, 0)
const isResigned = termDate <= today
await prisma.employee.update({
where: { id: data.employeeId },
data: {
status: isResigned ? 'RESIGNED' : 'ACTIVE',
socialInsEndMonth,
housingFundEndMonth,
},
})
await prisma.riskItem.updateMany({
where: { employeeId: data.employeeId, status: 'PENDING' },
data: { status: 'RESOLVED', resolvedAt: new Date() },
})
return { id: record.id }
}
// 员工主动离职
export async function createResignation(orgId: string, userId: string, data: any) {
const employee = await prisma.employee.findFirst({ where: { id: data.employeeId, orgId } })
if (!employee) {
throw { code: 'NOT_FOUND', message: '员工不存在' }
}
// 校验:已有离职/解聘记录且未重新雇佣则不允许再次离职
const latestTerm = await prisma.terminationRecord.findFirst({
where: { employeeId: data.employeeId },
orderBy: { terminationDate: 'desc' },
})
if (latestTerm && latestTerm.terminationDate >= employee.hireDate) {
throw { code: 'CONFLICT', message: '该员工已有离职/解聘记录,如需再次办理请先重新雇佣' }
}
const termDate = new Date(data.terminationDate)
const termMonth = dateToMonth(termDate)
const socialInsEndMonth = data.socialInsEndMonth || termMonth
const housingFundEndMonth = data.housingFundEndMonth || termMonth
const record = await prisma.terminationRecord.create({
data: {
orgId,
employeeId: data.employeeId,
type: 'RESIGNATION',
reason: 'RESIGNATION',
terminationDate: termDate,
resignationReason: data.resignationReason || null,
compensation: 0,
socialInsEndMonth,
housingFundEndMonth,
riskLevel: 'SAFE',
checklist: {},
remark: data.remark || null,
createdBy: userId,
},
})
// 关闭社保缴费记录
await prisma.employeeSocialInsRecord.updateMany({
where: { employeeId: data.employeeId, endMonth: null },
data: { endMonth: socialInsEndMonth, changeRefId: record.id },
})
// 关闭公积金缴费记录
await prisma.employeeHousingFundRecord.updateMany({
where: { employeeId: data.employeeId, endMonth: null },
data: { endMonth: housingFundEndMonth, changeRefId: record.id },
})
// 根据离职日期判断在职/离职状态
const today = new Date()
today.setHours(0, 0, 0, 0)
const isResigned = termDate <= today
await prisma.employee.update({
where: { id: data.employeeId },
data: {
status: isResigned ? 'RESIGNED' : 'ACTIVE',
socialInsEndMonth,
housingFundEndMonth,
},
})
await prisma.riskItem.updateMany({
where: { employeeId: data.employeeId, status: 'PENDING' },
data: { status: 'RESOLVED', resolvedAt: new Date() },
})
return { id: record.id }
}
// 撤回离职/解聘(仅未到日期可撤回)
export async function revokeTermination(orgId: string, recordId: string) {
const record = await prisma.terminationRecord.findFirst({
where: { id: recordId, orgId },
})
if (!record) {
throw { code: 'NOT_FOUND', message: '离职/解聘记录不存在' }
}
const today = new Date()
today.setHours(0, 0, 0, 0)
if (record.terminationDate <= today) {
throw { code: 'CONFLICT', message: '离职/解聘日期已到或已过,无法撤回' }
}
await prisma.terminationRecord.delete({ where: { id: recordId } })
// 恢复员工状态为 ACTIVE
await prisma.employee.update({
where: { id: record.employeeId },
data: { status: 'ACTIVE' },
})
return { id: recordId }
}
export async function getTerminations(orgId: string, page: number, pageSize: number) {
const skip = (page - 1) * pageSize
const [total, records] = await Promise.all([
prisma.terminationRecord.count({ where: { orgId } }),
prisma.terminationRecord.findMany({
where: { orgId },
include: { employee: true },
orderBy: { createdAt: 'desc' },
skip,
take: pageSize,
}),
])
return {
items: records.map((r) => ({
id: r.id,
employeeName: r.employee.name,
department: r.employee.department,
type: r.type,
reason: r.reason,
resignationReason: r.resignationReason,
terminationDate: r.terminationDate.toISOString().slice(0, 10),
compensation: r.compensation,
riskLevel: r.riskLevel,
remark: r.remark,
createdAt: r.createdAt.toISOString().slice(0, 10),
})),
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize),
}
}
export function calculateCompensation(hireDate: Date, leaveDate: Date, monthlyWage: number, socialAvgWage: number = 0): {
years: number
remainingMonths: number
compMonths: number
totalPay: number
capped: boolean
} {
const totalMonths = (leaveDate.getFullYear() - hireDate.getFullYear()) * 12 + (leaveDate.getMonth() - hireDate.getMonth())
const years = Math.floor(totalMonths / 12)
const remainingMonths = totalMonths % 12
let compMonths: number
if (remainingMonths >= 6) compMonths = years + 1
else if (remainingMonths > 0) compMonths = years + 0.5
else compMonths = years
if (compMonths <= 0) compMonths = 0.5
let wage = monthlyWage
let capped = false
if (socialAvgWage > 0 && monthlyWage > socialAvgWage * 3) {
wage = socialAvgWage * 3
compMonths = Math.min(compMonths, 12)
capped = true
}
return { years, remainingMonths, compMonths, totalPay: wage * compMonths, capped }
}
// 批量解聘:支持合规预检和执行
export interface BatchTerminatePreview {
employeeId: string
employeeName: string
department: string
reason: string
terminationDate: string
riskLevel: RiskAssessment | null
warnings: string[]
canTerminate: boolean
}
export async function batchTerminatePreview(
orgId: string,
items: Array<{ employeeId: string; reason: string; terminationDate: string }>
): Promise<BatchTerminatePreview[]> {
const results: BatchTerminatePreview[] = []
for (const item of items) {
const employee = await prisma.employee.findFirst({
where: { id: item.employeeId, orgId },
})
if (!employee) {
results.push({
employeeId: item.employeeId,
employeeName: '(未找到)',
department: '',
reason: item.reason,
terminationDate: item.terminationDate,
riskLevel: null,
warnings: ['员工不存在或无权操作'],
canTerminate: false,
})
continue
}
const { level, warnings } = assessRisk(employee, item.reason)
results.push({
employeeId: item.employeeId,
employeeName: employee.name,
department: employee.department,
reason: item.reason,
terminationDate: item.terminationDate,
riskLevel: level,
warnings,
canTerminate: warnings.length === 0,
})
}
return results
}
export interface BatchTerminateResult {
success: string[]
failed: Array<{ employeeId: string; reason: string }>
total: number
}
export async function batchTerminate(
orgId: string,
userId: string,
items: Array<{ employeeId: string; reason: string; terminationDate: string; compensation?: number }>
): Promise<BatchTerminateResult> {
const success: string[] = []
const failed: Array<{ employeeId: string; reason: string }> = []
for (const item of items) {
try {
const termDate = new Date(item.terminationDate)
const termMonth = dateToMonth(termDate)
// 校验:已有离职/解聘记录
const latestTerm = await prisma.terminationRecord.findFirst({
where: { employeeId: item.employeeId },
orderBy: { terminationDate: 'desc' },
})
const employee = await prisma.employee.findFirst({ where: { id: item.employeeId, orgId } })
if (!employee) {
failed.push({ employeeId: item.employeeId, reason: '员工不存在' })
continue
}
if (latestTerm && latestTerm.terminationDate >= employee.hireDate) {
failed.push({ employeeId: item.employeeId, reason: '该员工已有离职/解聘记录' })
continue
}
const { level } = assessRisk(employee, item.reason)
await prisma.terminationRecord.create({
data: {
orgId,
employeeId: item.employeeId,
type: 'TERMINATION',
reason: item.reason as TerminationReason,
terminationDate: termDate,
compensation: item.compensation || 0,
socialInsEndMonth: termMonth,
housingFundEndMonth: termMonth,
riskLevel: level,
checklist: {},
remark: '批量解聘',
createdBy: userId,
},
})
// 关闭社保和公积金
await prisma.employeeSocialInsRecord.updateMany({
where: { employeeId: item.employeeId, endMonth: null },
data: { endMonth: termMonth },
})
await prisma.employeeHousingFundRecord.updateMany({
where: { employeeId: item.employeeId, endMonth: null },
data: { endMonth: termMonth },
})
// 更新员工状态
const today = new Date()
today.setHours(0, 0, 0, 0)
const isResigned = termDate <= today
await prisma.employee.update({
where: { id: item.employeeId },
data: {
status: isResigned ? 'RESIGNED' : 'ACTIVE',
socialInsEndMonth: termMonth,
housingFundEndMonth: termMonth,
},
})
// 关闭风险项
await prisma.riskItem.updateMany({
where: { employeeId: item.employeeId, status: 'PENDING' },
data: { status: 'RESOLVED', resolvedAt: new Date() },
})
success.push(item.employeeId)
} catch (err: any) {
failed.push({ employeeId: item.employeeId, reason: err.message || '未知错误' })
}
}
return { success, failed, total: items.length }
}
// ============================================================
// 解聘流程状态机:DRAFT → PENDING_APPROVAL → APPROVED → EXECUTING → COMPLETED
// ↘ REJECTED → 可修改重新提交
// 任意非 COMPLETED → CANCELLED
// ============================================================
/** 标准工作交接清单模板 */
export function getDefaultHandoverItems(): Array<{ key: string; label: string; done: boolean; remark: string }> {
return [
{ key: 'work_handover', label: '工作交接完成', done: false, remark: '' },
{ key: 'equipment_return', label: '办公设备归还', done: false, remark: '' },
{ key: 'access_revoke', label: '系统权限收回', done: false, remark: '' },
{ key: 'docs_signed', label: '离职文件签署', done: false, remark: '' },
{ key: 'finance_settled', label: '财务结算完成', done: false, remark: '' },
{ key: 'contract_return', label: '劳动合同收回', done: false, remark: '' },
]
}
/** 创建草稿 */
export async function createDraft(orgId: string, userId: string, data: any) {
const employee = await prisma.employee.findFirst({ where: { id: data.employeeId, orgId } })
if (!employee) {
throw { code: 'NOT_FOUND', message: '员工不存在' }
}
const { level } = assessRisk(employee, data.reason || 'NEGOTIATED')
const record = await prisma.terminationRecord.create({
data: {
orgId,
employeeId: data.employeeId,
type: data.type || 'TERMINATION',
reason: data.reason || 'NEGOTIATED',
terminationDate: data.terminationDate ? new Date(data.terminationDate) : new Date(),
resignationReason: data.resignationReason || null,
compensation: data.compensation || 0,
socialInsEndMonth: data.socialInsEndMonth || null,
housingFundEndMonth: data.housingFundEndMonth || null,
riskLevel: level,
checklist: data.checklist || {},
remark: data.remark || null,
createdBy: userId,
status: 'DRAFT',
currentStep: data.currentStep || 0,
compensationBreakdown: data.compensationBreakdown || null,
checklistOverrides: data.checklistOverrides || null,
handoverItems: data.handoverItems || getDefaultHandoverItems(),
},
})
return { id: record.id }
}
/** 更新草稿(仅 DRAFT/REJECTED 状态可编辑) */
export async function updateDraft(orgId: string, recordId: string, userId: string, data: any) {
const record = await prisma.terminationRecord.findFirst({ where: { id: recordId, orgId } })
if (!record) {
throw { code: 'NOT_FOUND', message: '记录不存在' }
}
if (record.status !== 'DRAFT' && record.status !== 'REJECTED') {
throw { code: 'CONFLICT', message: '当前状态不可编辑' }
}
const updateData: any = { updatedBy: userId }
if (data.reason !== undefined) {
updateData.reason = data.reason
const employee = await prisma.employee.findFirst({ where: { id: record.employeeId, orgId } })
if (employee) {
const { level } = assessRisk(employee, data.reason)
updateData.riskLevel = level
}
}
if (data.terminationDate !== undefined) updateData.terminationDate = new Date(data.terminationDate)
if (data.compensation !== undefined) updateData.compensation = data.compensation
if (data.socialInsEndMonth !== undefined) updateData.socialInsEndMonth = data.socialInsEndMonth
if (data.housingFundEndMonth !== undefined) updateData.housingFundEndMonth = data.housingFundEndMonth
if (data.checklist !== undefined) updateData.checklist = data.checklist
if (data.remark !== undefined) updateData.remark = data.remark
if (data.currentStep !== undefined) updateData.currentStep = data.currentStep
if (data.compensationBreakdown !== undefined) updateData.compensationBreakdown = data.compensationBreakdown
if (data.checklistOverrides !== undefined) updateData.checklistOverrides = data.checklistOverrides
if (data.handoverItems !== undefined) updateData.handoverItems = data.handoverItems
if (data.resignationReason !== undefined) updateData.resignationReason = data.resignationReason
await prisma.terminationRecord.update({ where: { id: recordId }, data: updateData })
return { id: recordId }
}
/** 提交审批 */
export async function submitForApproval(orgId: string, recordId: string, userId: string) {
const record = await prisma.terminationRecord.findFirst({ where: { id: recordId, orgId } })
if (!record) {
throw { code: 'NOT_FOUND', message: '记录不存在' }
}
if (record.status !== 'DRAFT' && record.status !== 'REJECTED') {
throw { code: 'CONFLICT', message: '仅草稿状态可提交审批' }
}
await prisma.terminationRecord.update({
where: { id: recordId },
data: { status: 'PENDING_APPROVAL', updatedBy: userId },
})
return { id: recordId }
}
/** 审批通过 */
export async function approveTermination(orgId: string, recordId: string, userId: string, comment: string) {
const record = await prisma.terminationRecord.findFirst({ where: { id: recordId, orgId } })
if (!record) {
throw { code: 'NOT_FOUND', message: '记录不存在' }
}
if (record.status !== 'PENDING_APPROVAL') {
throw { code: 'CONFLICT', message: '仅待审批状态可审批' }
}
await prisma.terminationRecord.update({
where: { id: recordId },
data: {
status: 'APPROVED',
approvedBy: userId,
approvedAt: new Date(),
approvalComment: comment || null,
updatedBy: userId,
},
})
return { id: recordId }
}
/** 审批驳回 */
export async function rejectTermination(orgId: string, recordId: string, userId: string, comment: string) {
const record = await prisma.terminationRecord.findFirst({ where: { id: recordId, orgId } })
if (!record) {
throw { code: 'NOT_FOUND', message: '记录不存在' }
}
if (record.status !== 'PENDING_APPROVAL') {
throw { code: 'CONFLICT', message: '仅待审批状态可驳回' }
}
await prisma.terminationRecord.update({
where: { id: recordId },
data: {
status: 'REJECTED',
approvalComment: comment || '驳回',
updatedBy: userId,
},
})
return { id: recordId }
}
/** 执行解聘(APPROVED → EXECUTING → COMPLETED */
export async function executeTermination(orgId: string, recordId: string, userId: string) {
const record = await prisma.terminationRecord.findFirst({ where: { id: recordId, orgId } })
if (!record) {
throw { code: 'NOT_FOUND', message: '记录不存在' }
}
if (record.status !== 'APPROVED' && record.status !== 'DRAFT') {
throw { code: 'CONFLICT', message: '仅已审批或草稿状态可执行' }
}
// 标记为执行中
await prisma.terminationRecord.update({
where: { id: recordId },
data: { status: 'EXECUTING', updatedBy: userId },
})
const termDate = record.terminationDate
const termMonth = dateToMonth(termDate)
const socialInsEndMonth = record.socialInsEndMonth || termMonth
const housingFundEndMonth = record.housingFundEndMonth || termMonth
// 关闭社保缴费记录
await prisma.employeeSocialInsRecord.updateMany({
where: { employeeId: record.employeeId, endMonth: null },
data: { endMonth: socialInsEndMonth, changeRefId: record.id },
})
// 关闭公积金缴费记录
await prisma.employeeHousingFundRecord.updateMany({
where: { employeeId: record.employeeId, endMonth: null },
data: { endMonth: housingFundEndMonth, changeRefId: record.id },
})
// 更新员工状态
const today = new Date()
today.setHours(0, 0, 0, 0)
const isResigned = termDate <= today
await prisma.employee.update({
where: { id: record.employeeId },
data: {
status: isResigned ? 'RESIGNED' : 'ACTIVE',
socialInsEndMonth,
housingFundEndMonth,
},
})
// 关闭风险项
await prisma.riskItem.updateMany({
where: { employeeId: record.employeeId, status: 'PENDING' },
data: { status: 'RESOLVED', resolvedAt: new Date() },
})
// 标记为已完成
await prisma.terminationRecord.update({
where: { id: recordId },
data: { status: 'COMPLETED', updatedBy: userId },
})
return { id: recordId }
}
/** 撤销(状态→CANCELLED,不删除记录) */
export async function cancelTermination(orgId: string, recordId: string, userId: string) {
const record = await prisma.terminationRecord.findFirst({ where: { id: recordId, orgId } })
if (!record) {
throw { code: 'NOT_FOUND', message: '记录不存在' }
}
if (record.status === 'COMPLETED') {
throw { code: 'CONFLICT', message: '已完成的解聘不可撤销' }
}
await prisma.terminationRecord.update({
where: { id: recordId },
data: { status: 'CANCELLED', updatedBy: userId },
})
// 如果之前已执行(社保已关闭),恢复员工状态
if (record.status === 'EXECUTING' || record.status === 'COMPLETED') {
await prisma.employee.update({
where: { id: record.employeeId },
data: { status: 'ACTIVE' },
})
}
return { id: recordId }
}
/** 获取草稿列表 */
export async function getDrafts(orgId: string, status?: string) {
const where: any = { orgId }
if (status) {
where.status = status
} else {
where.status = { in: ['DRAFT', 'PENDING_APPROVAL', 'APPROVED', 'REJECTED'] }
}
const records = await prisma.terminationRecord.findMany({
where,
include: { employee: true },
orderBy: { updatedAt: 'desc' },
})
return records.map((r) => ({
id: r.id,
employeeId: r.employeeId,
employeeName: r.employee.name,
department: r.employee.department,
type: r.type,
reason: r.reason,
terminationDate: r.terminationDate.toISOString().slice(0, 10),
compensation: r.compensation,
riskLevel: r.riskLevel,
status: r.status,
currentStep: r.currentStep,
remark: r.remark,
createdAt: r.createdAt.toISOString().slice(0, 10),
updatedAt: r.updatedAt.toISOString().slice(0, 10),
}))
}
/** 获取单条记录详情(含所有流程字段) */
export async function getTerminationDetail(orgId: string, recordId: string) {
const record = await prisma.terminationRecord.findFirst({
where: { id: recordId, orgId },
include: { employee: true },
})
if (!record) {
throw { code: 'NOT_FOUND', message: '记录不存在' }
}
return {
id: record.id,
employeeId: record.employeeId,
employeeName: record.employee.name,
department: record.employee.department,
type: record.type,
reason: record.reason,
terminationDate: record.terminationDate.toISOString().slice(0, 10),
resignationReason: record.resignationReason,
compensation: record.compensation,
socialInsEndMonth: record.socialInsEndMonth,
housingFundEndMonth: record.housingFundEndMonth,
riskLevel: record.riskLevel,
checklist: record.checklist,
remark: record.remark,
status: record.status,
currentStep: record.currentStep,
compensationBreakdown: record.compensationBreakdown,
checklistOverrides: record.checklistOverrides,
handoverItems: record.handoverItems,
approvedBy: record.approvedBy,
approvedAt: record.approvedAt?.toISOString().slice(0, 10),
approvalComment: record.approvalComment,
createdBy: record.createdBy,
createdAt: record.createdAt.toISOString().slice(0, 10),
updatedAt: record.updatedAt.toISOString().slice(0, 10),
}
}