From a77a37a32ea02b3a72ea10b48c269aca965f6986 Mon Sep 17 00:00:00 2001 From: selfrelease Date: Thu, 30 Jul 2026 19:30:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B9=B4=E5=BA=A6=E4=BB=B7=E5=80=BC?= =?UTF-8?q?=E6=8A=A5=E5=91=8A=E5=A2=9E=E5=8A=A0=E5=91=98=E5=B7=A5=E7=BB=B4?= =?UTF-8?q?=E5=BA=A6=E8=A7=84=E9=81=BF=E6=8D=9F=E5=A4=B1=E6=98=8E=E7=BB=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 后端按员工汇总已解决风险明细,含风险标题、法律依据、预估损失 - 前端新增员工规避损失明细表格,展示每员工的原始损失、规避率(30%)、打折后损失 - 表格含合计行,与总价值计算逻辑一致 - 修复RISK_AVOIDANCE_RATE重复声明 --- backend/src/services/risk.service.ts | 71 ++++++++++++++++++- .../src/pages/tools/AnnualValueReport.tsx | 56 +++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/backend/src/services/risk.service.ts b/backend/src/services/risk.service.ts index 7900a0a..4a6da43 100644 --- a/backend/src/services/risk.service.ts +++ b/backend/src/services/risk.service.ts @@ -1549,6 +1549,7 @@ export async function getAnnualValueReport(orgId: string, year: number) { auditActions, policiesPublished, monthlyRisks, + employeeRiskDetails, ] = await Promise.all([ prisma.riskItem.count({ where: { @@ -1611,10 +1612,77 @@ export async function getAnnualValueReport(orgId: string, year: number) { }).then((count) => ({ month: m + 1, count })), ), ), + // 按员工维度的已解决风险明细 + prisma.riskItem.findMany({ + where: { + orgId, status: 'RESOLVED', + resolvedAt: { gte: yearStart, lte: yearEnd }, + }, + include: { + employee: { + select: { id: true, name: true, department: true, monthlySalary: true, hireDate: true }, + }, + }, + orderBy: { estimatedLoss: 'desc' }, + }), ]) const lossAvoided = lossAvoidedAgg._sum.estimatedLoss || 0 + // 按员工维度汇总规避损失 + const RISK_AVOIDANCE_RATE = 0.3 + const employeeBreakdown: Record = {} + + for (const r of employeeRiskDetails) { + const empId = r.employeeId || '_unknown' + if (!employeeBreakdown[empId]) { + employeeBreakdown[empId] = { + employeeId: empId, + name: r.employee?.name || '未知员工', + department: r.employee?.department || '-', + riskCount: 0, + lossAvoided: 0, + adjustedLoss: 0, + details: [], + } + } + const emp = employeeBreakdown[empId] + emp.riskCount++ + emp.lossAvoided += r.estimatedLoss || 0 + + // 根据风险标题推断法律依据 + let legalBasis = '—' + const title = r.title || '' + if (title.includes('未签合同')) legalBasis = '第82条 双倍工资≤11个月' + else if (title.includes('到期') && title.includes('未续签')) legalBasis = '第47条 经济补偿N≤12个月' + else if (title.includes('孕期') || title.includes('哺乳期') || title.includes('工伤') || title.includes('医疗期')) legalBasis = '第87条 违法解除2N≤24个月' + else if (title.includes('试用期')) legalBasis = '第20条 试用期工资差额' + else if (title.includes('社保') || title.includes('公积金')) legalBasis = '滞纳金 每日0.05%' + else if (title.includes('工资') || title.includes('个税')) legalBasis = '拖欠工资/个税' + + emp.details.push({ + title: r.title, + estimatedLoss: r.estimatedLoss || 0, + legalBasis, + }) + } + + // 计算每个员工的打折后规避损失 + const employeeDetails = Object.values(employeeBreakdown) + .map((e) => ({ + ...e, + adjustedLoss: Math.round(e.lossAvoided * RISK_AVOIDANCE_RATE), + })) + .sort((a, b) => b.adjustedLoss - a.adjustedLoss) + // 月度事件时间轴 const timeline = monthlyRisks.map((mr) => ({ month: mr.month, @@ -1626,7 +1694,6 @@ export async function getAnnualValueReport(orgId: string, year: number) { // 1. 规避损失 = 已解决风险的预估损失之和 × 0.3(规避率30%,非每项风险都会演变为实际仲裁) // 2. 节约工时 = AI 查询 * 0.5h + 合同审查 * 1h + 算薪批次 * 2h + 制度公示 * 1h // 3. 节约成本 = 节约工时 * 100 元/h(HR 平均时薪) - const RISK_AVOIDANCE_RATE = 0.3 // 规避率:约30%的未处理风险会演变为实际劳动争议 const adjustedLossAvoided = Math.round(lossAvoided * RISK_AVOIDANCE_RATE) const aiTimeSaved = aiConversations * 0.5 + aiReviews * 1.0 const payrollTimeSaved = payrollBatches * 2.0 @@ -1671,6 +1738,8 @@ export async function getAnnualValueReport(orgId: string, year: number) { costSaved, timeSaved, lossAvoided, + adjustedLossAvoided, + employeeDetails, summary, } } diff --git a/frontend/src/pages/tools/AnnualValueReport.tsx b/frontend/src/pages/tools/AnnualValueReport.tsx index cc3a4fa..4b6b6d1 100644 --- a/frontend/src/pages/tools/AnnualValueReport.tsx +++ b/frontend/src/pages/tools/AnnualValueReport.tsx @@ -220,6 +220,62 @@ export default function AnnualValueReport() { + {/* 员工规避损失明细 */} + {report.employeeDetails && report.employeeDetails.length > 0 && ( + +

员工规避损失明细

+
+ + + + + + + + + + + + + + {report.employeeDetails.map((emp: any, idx: number) => ( + + + + + + + + + + ))} + + + + + + + + + + +
员工部门风险数原始损失规避率规避损失(×30%)风险明细
{emp.name}{emp.department}{emp.riskCount}¥{emp.lossAvoided.toLocaleString()}30%¥{emp.adjustedLoss.toLocaleString()} +
+ {emp.details.map((d: any, i: number) => ( +
+ {d.title} + · + {d.legalBasis} + · + ¥{d.estimatedLoss.toLocaleString()} +
+ ))} +
+
合计¥{(report.lossAvoided || 0).toLocaleString()}¥{(report.adjustedLossAvoided || 0).toLocaleString()}
+
+
+ )} + {/* 价值计算说明 */}

价值计算说明