From 6da7f2ca22a408e6a2f05a983ed759c97b0de544 Mon Sep 17 00:00:00 2001 From: selfrelease Date: Sat, 1 Aug 2026 14:18:06 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E9=A3=8E=E9=99=A9=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. runRiskDetection开头清理重复PENDING记录(同employeeId:type只保留最新) 2. onboardingRisks过滤掉已被contractRisks覆盖的员工,避免同一员工出现'未签合同'+'入职手续未完成'两条重复待办 --- backend/src/services/risk.service.ts | 31 ++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/backend/src/services/risk.service.ts b/backend/src/services/risk.service.ts index 19e9a5e..1c11c98 100644 --- a/backend/src/services/risk.service.ts +++ b/backend/src/services/risk.service.ts @@ -508,6 +508,29 @@ export async function detectRetirementRisks(orgId: string) { } export async function runRiskDetection(orgId: string) { + // 清理重复 PENDING 风险:同一 employeeId:type 只保留最新一条 PENDING + const pendingRisks = await prisma.riskItem.findMany({ + where: { orgId, status: 'PENDING', employeeId: { not: null } }, + select: { id: true, employeeId: true, type: true, createdAt: true }, + orderBy: { createdAt: 'desc' }, + }) + const seenKeys = new Set() + const duplicateIds: string[] = [] + for (const r of pendingRisks) { + const key = `${r.employeeId}:${r.type}` + if (seenKeys.has(key)) { + duplicateIds.push(r.id) + } else { + seenKeys.add(key) + } + } + if (duplicateIds.length > 0) { + await prisma.riskItem.updateMany({ + where: { id: { in: duplicateIds } }, + data: { status: 'RESOLVED', resolvedAt: new Date() }, + }) + } + // 非月度风险去重:检查所有状态(含 RESOLVED/IGNORED),避免已处理的风险被重新创建 // 按 employeeId:type 归并,不依赖 actionUrl(actionUrl 可能因天数变化而不同) const existingRisks = await prisma.riskItem.findMany({ @@ -538,8 +561,12 @@ export async function runRiskDetection(orgId: string) { const specialStatusRisks = await detectSpecialStatusRisks(orgId) const retirementRisks = await detectRetirementRisks(orgId) + // 去重:contractRisks 已覆盖"未签合同",onboardingRisks 中相同员工不再重复生成 + const contractEmployeeIds = new Set(contractRisks.map(r => r.employeeId)) + const filteredOnboardingRisks = onboardingRisks.filter(r => !contractEmployeeIds.has(r.employeeId)) + // 获取所有相关员工数据用于风险量化 - const allEmployeeIds = [...contractRisks, ...terminationRisks, ...onboardingRisks, ...specialStatusRisks, ...retirementRisks] + const allEmployeeIds = [...contractRisks, ...terminationRisks, ...filteredOnboardingRisks, ...specialStatusRisks, ...retirementRisks] .map(r => r.employeeId) .filter(Boolean) as string[] const employees = allEmployeeIds.length > 0 @@ -548,7 +575,7 @@ export async function runRiskDetection(orgId: string) { const empMap = new Map(employees.map(e => [e.id, e])) // 月度任务用 monthlyKeys 去重,其他任务用 existingKeys 去重 - const nonMonthlyRisks = [...contractRisks, ...terminationRisks, ...onboardingRisks, ...specialStatusRisks, ...retirementRisks] + const nonMonthlyRisks = [...contractRisks, ...terminationRisks, ...filteredOnboardingRisks, ...specialStatusRisks, ...retirementRisks] const toCreate = [ ...nonMonthlyRisks.filter((r) => !existingKeys.has(`${r.employeeId}:${r.type}`)), ...monthlyTasks.filter((r) => !monthlyKeys.has(`${r.employeeId}:${r.title}`)),