fix: 用工体检诊断6维度改为与评分标准说明一致

原维度(合同管理/规章制度/社会保险/背景调查/工作时间/保密竞业)改为
标准维度(合同管理/薪酬社保/考勤加班/规章制度/解聘合规/证据链)
- 合同管理:未签合同+到期预警
- 薪酬社保:社保配置+公积金+工资条确认率+社保缴纳异常
- 考勤加班:加班超时+考勤记录完整性
- 规章制度:制度公示+民主程序
- 解聘合规:解聘流程完成率+合规检查清单
- 证据链:违纪/考勤/培训记录+制度+解聘+工资条覆盖度
This commit is contained in:
selfrelease
2026-08-01 15:01:15 +08:00
parent c8d8e5c292
commit 4d067e476e
+103 -59
View File
@@ -1557,7 +1557,7 @@ function fmtMoney(n: number): string {
/**
* 用工体检诊断:6 维度深度诊断 + 历史报告
* 维度:合同管理 / 规章制度 / 社会保险 / 背景调查 / 工作时间 / 保密竞业
* 维度:合同管理 / 薪酬社保 / 考勤加班 / 规章制度 / 解聘合规 / 证据链
*/
export async function getHealthCheck(orgId: string) {
const now = new Date()
@@ -1567,17 +1567,20 @@ export async function getHealthCheck(orgId: string) {
totalEmployees,
unsignedContracts,
expiringContracts,
contractsNoAttachment,
policiesWithoutPublish,
totalPolicies,
socialConfig,
housingConfig,
employeesNoSocial,
employeesNoHousing,
employeesNoBackgroundCheck,
overtimeExcessive,
employeesNoNDA,
employeesWithNDA,
totalTerminations,
completedTerminations,
terminationsWithChecklist,
disciplinaryRecords,
attendanceRecords,
trainingRecords,
unconfirmedPayslips,
totalPayslips,
] = await Promise.all([
prisma.employee.count({ where: { orgId, status: 'ACTIVE' } }),
prisma.laborContract.count({ where: { orgId, contractType: 'UNSIGNED', employee: { status: 'ACTIVE' } } }),
@@ -1588,22 +1591,28 @@ export async function getHealthCheck(orgId: string) {
endDate: { gte: now, lte: new Date(now.getTime() + 30 * 24 * 60 * 60 * 1000) },
},
}),
prisma.laborContract.count({ where: { orgId, attachmentUrl: null, employee: { status: 'ACTIVE' } } }),
// democracyProgress 是 JSONB,用 status 字段判断未完成
prisma.policyDocument.count({ where: { orgId, status: { not: 'PUBLISHED' } } }),
prisma.policyDocument.count({ where: { orgId } }),
prisma.socialInsuranceConfig.findFirst({ where: { orgId, isCurrent: true } }),
prisma.housingFundConfig.findFirst({ where: { orgId, isCurrent: true } }),
// 社保/公积金记录按员工去重
// 社保记录按员工去重
prisma.employeeSocialInsRecord.groupBy({ by: ['employeeId'], where: { orgId, endMonth: null }, _count: true }).then(r => r.length),
prisma.employeeHousingFundRecord.groupBy({ by: ['employeeId'], where: { orgId, endMonth: null }, _count: true }).then(r => r.length),
prisma.employee.count({ where: { orgId, status: 'ACTIVE', id: { notIn: [] } } }),
prisma.overtimeRecord.count({ where: { orgId, weekdayHours: { gt: 36 } } }),
prisma.employee.count({ where: { orgId, status: 'ACTIVE', contracts: { none: { contractType: 'FIXED' } } } }),
prisma.employee.count({ where: { orgId, status: 'ACTIVE', contracts: { some: { contractType: 'FIXED' } } } }),
// 解聘合规
prisma.terminationRecord.count({ where: { orgId } }),
prisma.terminationRecord.count({ where: { orgId, status: 'COMPLETED' } }),
prisma.terminationRecord.count({ where: { orgId, status: 'COMPLETED', checklist: { not: {} } } }),
// 证据链
prisma.disciplinaryRecord.count({ where: { orgId } }),
prisma.attendanceRecord.count({ where: { orgId } }),
prisma.trainingRecord.count({ where: { orgId } }),
// 薪酬
prisma.payslip.count({ where: { orgId, confirmedAt: null } }),
prisma.payslip.count({ where: { orgId } }),
])
// 6 维度诊断
// 6 维度诊断(与评分标准说明一致)
const dimensions = [
{
key: 'contract',
@@ -1618,6 +1627,42 @@ export async function getHealthCheck(orgId: string) {
expiringContracts > 0 ? '提前30天发送续签意向书,保留协商证据' : '',
].filter(Boolean),
},
{
key: 'salary_social',
name: '薪酬社保',
score: Math.round(
(socialConfig ? 40 : 0) +
(housingConfig ? 20 : 0) +
(totalPayslips > 0 ? Math.max(0, 20 - Math.round((unconfirmedPayslips / totalPayslips) * 20)) : 20) +
(employeesNoSocial === 0 ? 20 : Math.max(0, 20 - employeesNoSocial * 2))
),
findings: [
socialConfig ? '社保配置已设置' : '未配置社保比例,建议尽快设置',
housingConfig ? '公积金配置已设置' : '未配置公积金比例',
totalPayslips > 0
? `${unconfirmedPayslips}/${totalPayslips} 张工资条未确认`
: '暂无工资条记录',
employeesNoSocial > 0 ? `${employeesNoSocial} 名员工社保记录异常` : '社保缴纳记录正常',
].filter(Boolean),
recommendations: [
!socialConfig ? '前往社保管理页面设置企业/个人缴纳比例' : '',
!housingConfig ? '根据当地政策配置公积金比例' : '',
unconfirmedPayslips > 0 ? '提醒员工及时确认工资条,保留薪酬合规证据' : '',
].filter(Boolean),
},
{
key: 'attendance',
name: '考勤加班',
score: Math.max(0, 100 - overtimeExcessive * 10),
findings: [
overtimeExcessive > 0 ? `${overtimeExcessive} 条加班记录月超时36小时,存在违法风险` : '加班时长合规',
attendanceRecords > 0 ? `已记录 ${attendanceRecords} 条考勤数据` : '暂无考勤记录,建议建立考勤制度',
].filter(Boolean),
recommendations: [
overtimeExcessive > 0 ? '月加班不得超过36小时,建议调休或减少加班安排' : '',
attendanceRecords === 0 ? '建立考勤记录制度,保留出勤证据' : '',
].filter(Boolean),
},
{
key: 'policy',
name: '规章制度',
@@ -1634,56 +1679,55 @@ export async function getHealthCheck(orgId: string) {
].filter(Boolean),
},
{
key: 'social',
name: '社会保险',
score: (socialConfig ? 50 : 0) + (housingConfig ? 30 : 0) + (employeesNoSocial === 0 ? 20 : Math.max(0, 20 - employeesNoSocial * 2)),
findings: [
socialConfig ? '社保配置已设置' : '未配置社保比例,建议尽快设置',
housingConfig ? '公积金配置已设置' : '未配置公积金比例',
employeesNoSocial > 0 ? `${employeesNoSocial} 名员工社保记录异常` : '社保缴纳记录正常',
].filter(Boolean),
recommendations: [
!socialConfig ? '前往社保管理页面设置企业/个人缴纳比例' : '',
!housingConfig ? '根据当地政策配置公积金比例' : '',
].filter(Boolean),
},
{
key: 'background',
name: '背景调查',
score: 70,
findings: [
'背景调查功能待接入第三方服务',
totalEmployees > 0 ? `当前 ${totalEmployees} 名在管员工,建议关键岗位完成背调` : '',
].filter(Boolean),
recommendations: [
'建议对财务、管理、技术核心岗位入职前完成背景调查',
],
},
{
key: 'workingtime',
name: '工作时间',
score: Math.max(0, 100 - overtimeExcessive * 10),
findings: [
overtimeExcessive > 0 ? `${overtimeExcessive} 条加班记录月超时36小时,存在违法风险` : '加班时长合规',
].filter(Boolean),
recommendations: [
overtimeExcessive > 0 ? '月加班不得超过36小时,建议调休或减少加班安排' : '',
].filter(Boolean),
},
{
key: 'nda',
name: '保密竞业',
score: totalEmployees > 0
? Math.round((employeesWithNDA / Math.max(1, totalEmployees)) * 100)
key: 'termination',
name: '解聘合规',
score: totalTerminations > 0
? Math.round(
(completedTerminations / totalTerminations) * 60 +
(terminationsWithChecklist / Math.max(1, completedTerminations)) * 40
)
: 100,
findings: [
totalEmployees > 0
? `${employeesWithNDA}/${totalEmployees} 名员工签署保密/竞业协议`
: '暂无在管员工',
employeesNoNDA > 0 ? `${employeesNoNDA} 名员工未签保密协议,建议核心岗位补签` : '',
totalTerminations > 0
? `${totalTerminations} 起解聘,${completedTerminations} 起已完成流程`
: '暂无解聘记录',
totalTerminations > 0 && completedTerminations < totalTerminations
? `${totalTerminations - completedTerminations} 起解聘流程未完成,存在程序瑕疵风险`
: '',
terminationsWithChecklist > 0
? `${terminationsWithChecklist} 起解聘已保存合规检查清单`
: totalTerminations > 0 ? '建议完善解聘合规检查清单' : '',
].filter(Boolean),
recommendations: [
employeesNoNDA > 0 ? '对技术、财务、销售岗位补签保密竞业协议' : '',
completedTerminations < totalTerminations ? '完成所有解聘流程审批,确保程序合法' : '',
totalTerminations > 0 ? '保留解聘协议、补偿计算依据等关键证据' : '',
].filter(Boolean),
},
{
key: 'evidence',
name: '证据链',
score: totalEmployees > 0
? Math.min(100, Math.round(
((disciplinaryRecords > 0 ? 1 : 0) * 20) +
((attendanceRecords > 0 ? 1 : 0) * 30) +
((trainingRecords > 0 ? 1 : 0) * 10) +
(totalPolicies > 0 ? 20 : 0) +
(totalTerminations > 0 ? 10 : 10) +
(totalPayslips > 0 ? 10 : 0)
))
: 100,
findings: [
disciplinaryRecords > 0 ? `违纪记录 ${disciplinaryRecords}` : '无违纪记录',
attendanceRecords > 0 ? `考勤记录 ${attendanceRecords}` : '无考勤记录',
trainingRecords > 0 ? `培训记录 ${trainingRecords}` : '无培训记录',
totalEmployees > 0
? `覆盖 ${totalEmployees} 名在管员工的证据留存`
: '暂无在管员工',
].filter(Boolean),
recommendations: [
disciplinaryRecords === 0 ? '对违纪行为及时记录并保留证据' : '',
attendanceRecords === 0 ? '建立考勤记录制度,保留出勤证据' : '',
trainingRecords === 0 ? '记录员工培训情况,保留签到表' : '',
].filter(Boolean),
},
]