feat: 员工特殊状态台账(三期/工伤/医疗期)完整实现

This commit is contained in:
selfrelease
2026-07-30 15:04:34 +08:00
parent 5a5ce7186b
commit 826c8fda65
11 changed files with 1275 additions and 6 deletions
+77 -2
View File
@@ -305,6 +305,80 @@ export async function detectTerminationRisks(orgId: string) {
return risks
}
/**
* 检测特殊状态相关风险(三期/工伤/医疗期员工合同到期不得终止)
*/
async function detectSpecialStatusRisks(orgId: string) {
const today = new Date()
today.setHours(0, 0, 0, 0)
// 查找所有进行中的特殊状态记录
const specialStatuses = await (prisma as any).employeeSpecialStatus.findMany({
where: { orgId, status: 'ACTIVE' },
include: {
employee: {
select: { id: true, name: true, status: true, contracts: { orderBy: { createdAt: 'desc' }, take: 1 } },
},
},
})
const risks: { employeeId: string; type: RiskType; level: RiskLevel; title: string; description: string; actionUrl: string }[] = []
for (const ss of specialStatuses) {
const emp = ss.employee
if (!emp || emp.status !== 'ACTIVE') continue
const latestContract = emp.contracts[0]
if (!latestContract || !latestContract.endDate) continue
const daysToExpire = daysBetween(latestContract.endDate, today)
const typeLabel = ss.type === 'PREGNANCY' ? '三期' : ss.type === 'WORK_INJURY' ? '工伤' : ss.type === 'MEDICAL_PERIOD' ? '医疗期' : '特殊状态'
// 合同即将到期但处于特殊状态期间,不得终止
if (daysToExpire >= 0 && daysToExpire <= 60) {
risks.push({
employeeId: emp.id,
type: 'CONTRACT' as RiskType,
level: 'HIGH' as RiskLevel,
title: `${emp.name}处于${typeLabel}期间,合同${daysToExpire}天后到期,依法不得终止`,
description: `${typeLabel}员工在合同到期时,用人单位不得依照《劳动合同法》第四十条、第四十一条终止合同,需顺延至相应情形消失。`,
actionUrl: `/special-status?type=${ss.type}`,
})
}
// 工伤认定超期提醒(受伤30天内未认定)
if (ss.type === 'WORK_INJURY' && ss.injuryDate && !ss.certificationDate) {
const daysSinceInjury = daysBetween(today, new Date(ss.injuryDate))
if (daysSinceInjury > 30) {
risks.push({
employeeId: emp.id,
type: 'CONTRACT' as RiskType,
level: 'HIGH' as RiskLevel,
title: `${emp.name}工伤已${daysSinceInjury}天未完成认定,超30天限期`,
description: `用人单位应自事故伤害发生之日起30日内提出工伤认定申请,逾期将影响工伤待遇。`,
actionUrl: `/special-status?type=WORK_INJURY`,
})
}
}
// 医疗期即将到期
if (ss.type === 'MEDICAL_PERIOD' && ss.medicalPeriodEnd) {
const daysToMedicalEnd = daysBetween(new Date(ss.medicalPeriodEnd), today)
if (daysToMedicalEnd >= 0 && daysToMedicalEnd <= 30) {
risks.push({
employeeId: emp.id,
type: 'CONTRACT' as RiskType,
level: daysToMedicalEnd <= 7 ? 'HIGH' as RiskLevel : 'MEDIUM' as RiskLevel,
title: `${emp.name}医疗期${daysToMedicalEnd}天后到期,需提前处理`,
description: `医疗期届满后,员工仍需治疗的,用人单位应按规定处理。医疗期满不能从事原工作的,可依法解除合同但需支付经济补偿。`,
actionUrl: `/special-status?type=MEDICAL_PERIOD`,
})
}
}
}
return risks
}
export async function detectMonthlyTasks(orgId: string) {
const setting = await prisma.notificationSetting.findUnique({ where: { orgId } })
if (!setting) return []
@@ -372,9 +446,10 @@ export async function runRiskDetection(orgId: string) {
const terminationRisks = await detectTerminationRisks(orgId)
const onboardingRisks = await detectOnboardingRisks(orgId)
const monthlyTasks = await detectMonthlyTasks(orgId)
const specialStatusRisks = await detectSpecialStatusRisks(orgId)
// 获取所有相关员工数据用于风险量化
const allEmployeeIds = [...contractRisks, ...terminationRisks, ...onboardingRisks]
const allEmployeeIds = [...contractRisks, ...terminationRisks, ...onboardingRisks, ...specialStatusRisks]
.map(r => r.employeeId)
.filter(Boolean) as string[]
const employees = allEmployeeIds.length > 0
@@ -383,7 +458,7 @@ export async function runRiskDetection(orgId: string) {
const empMap = new Map(employees.map(e => [e.id, e]))
// 月度任务用 monthlyKeys 去重,其他任务用 existingKeys 去重
const nonMonthlyRisks = [...contractRisks, ...terminationRisks, ...onboardingRisks]
const nonMonthlyRisks = [...contractRisks, ...terminationRisks, ...onboardingRisks, ...specialStatusRisks]
const toCreate = [
...nonMonthlyRisks.filter((r) => !existingKeys.has(`${r.employeeId}:${r.type}:${r.actionUrl}`)),
...monthlyTasks.filter((r) => !monthlyKeys.has(`${r.employeeId}:${r.title}`)),