feat: 移除ROI,新增待处理风险列表
- 移除ROI横幅和说明,改为年度总价值概览 - 后端新增 pendingRisks 查询,返回PENDING状态风险 - 前端新增待处理风险表格,按员工去重展示 - summary 改为包含待处理风险数量
This commit is contained in:
@@ -1553,6 +1553,7 @@ export async function getAnnualValueReport(orgId: string, year: number) {
|
||||
policiesPublished,
|
||||
monthlyRisks,
|
||||
employeeRiskDetails,
|
||||
pendingRisks,
|
||||
] = await Promise.all([
|
||||
prisma.riskItem.count({
|
||||
where: {
|
||||
@@ -1631,6 +1632,20 @@ export async function getAnnualValueReport(orgId: string, year: number) {
|
||||
},
|
||||
orderBy: { estimatedLoss: 'desc' },
|
||||
}),
|
||||
// 未解决的风险列表(PENDING状态),按预估损失降序,最多100条
|
||||
prisma.riskItem.findMany({
|
||||
where: {
|
||||
orgId, status: 'PENDING',
|
||||
estimatedLoss: { gt: 0 },
|
||||
},
|
||||
include: {
|
||||
employee: {
|
||||
select: { id: true, name: true, department: true, idCardHash: true },
|
||||
},
|
||||
},
|
||||
orderBy: { estimatedLoss: 'desc' },
|
||||
take: 100,
|
||||
}),
|
||||
])
|
||||
|
||||
// 按身份证号去重(同一人可能有多条 Employee 记录),无身份证号时回退到 employeeId
|
||||
@@ -1763,16 +1778,42 @@ export async function getAnnualValueReport(orgId: string, year: number) {
|
||||
policiesPublished,
|
||||
}
|
||||
|
||||
const summary = roi >= 1000
|
||||
? `${year} 年系统创造价值约 ${fmtMoney(totalValue)},ROI 达 ${roi}%,规避潜在损失 ${fmtMoney(lossAvoided)},节约 HR 工时 ${timeSaved} 小时`
|
||||
: roi >= 300
|
||||
? `${year} 年系统创造价值约 ${fmtMoney(totalValue)},ROI ${roi}%,处理风险 ${risksResolved} 项,AI 辅助 ${aiConversations + aiReviews} 次`
|
||||
: `${year} 年系统已运行,处理风险 ${risksResolved} 项,AI 辅助 ${aiConversations + aiReviews} 次,建议加强使用深度`
|
||||
const summary = `${year} 年规避潜在损失 ${fmtMoney(lossAvoided)},节约 HR 工时 ${timeSaved} 小时,仍有 ${pendingRisks.length} 项风险待处理`
|
||||
|
||||
// 未解决风险按员工去重+风险类别去重
|
||||
const pendingBreakdown: Record<string, {
|
||||
name: string
|
||||
department: string
|
||||
riskCount: number
|
||||
totalLoss: number
|
||||
details: { title: string; estimatedLoss: number; legalBasis: string }[]
|
||||
}> = {}
|
||||
for (const r of pendingRisks) {
|
||||
const personKey = r.employee?.idCardHash || r.employee?.name || r.employeeId || '_unknown'
|
||||
if (!pendingBreakdown[personKey]) {
|
||||
pendingBreakdown[personKey] = {
|
||||
name: r.employee?.name || '未知员工',
|
||||
department: r.employee?.department || '-',
|
||||
riskCount: 0,
|
||||
totalLoss: 0,
|
||||
details: [],
|
||||
}
|
||||
}
|
||||
pendingBreakdown[personKey].riskCount++
|
||||
pendingBreakdown[personKey].totalLoss += r.estimatedLoss || 0
|
||||
pendingBreakdown[personKey].details.push({
|
||||
title: r.title,
|
||||
estimatedLoss: r.estimatedLoss || 0,
|
||||
legalBasis: getLegalBasis(r.title),
|
||||
})
|
||||
}
|
||||
const pendingRiskDetails = Object.values(pendingBreakdown)
|
||||
.map(p => ({ ...p, details: p.details.sort((a, b) => b.estimatedLoss - a.estimatedLoss) }))
|
||||
.sort((a, b) => b.totalLoss - a.totalLoss)
|
||||
|
||||
return {
|
||||
year,
|
||||
totalValue,
|
||||
roi,
|
||||
metrics,
|
||||
timeline,
|
||||
costSaved,
|
||||
@@ -1780,6 +1821,7 @@ export async function getAnnualValueReport(orgId: string, year: number) {
|
||||
lossAvoided,
|
||||
adjustedLossAvoided,
|
||||
employeeDetails,
|
||||
pendingRiskDetails,
|
||||
summary,
|
||||
}
|
||||
}
|
||||
@@ -1795,7 +1837,6 @@ export async function saveAnnualValueReport(orgId: string, userId: string, year:
|
||||
orgId,
|
||||
year: result.year,
|
||||
totalValue: result.totalValue,
|
||||
roi: result.roi,
|
||||
metrics: result.metrics as any,
|
||||
timeline: result.timeline as any,
|
||||
costSaved: result.costSaved,
|
||||
|
||||
Reference in New Issue
Block a user