From aea88fd385ff7af4eebad93d641fac9a516384d4 Mon Sep 17 00:00:00 2001 From: selfrelease Date: Mon, 17 Aug 2026 17:33:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B7=A5=E4=BD=9C=E5=8F=B0=E5=90=88?= =?UTF-8?q?=E8=A7=84=E8=AF=84=E5=88=86=E4=BC=98=E5=8C=96=E3=80=81=E9=9D=A2?= =?UTF-8?q?=E5=8C=85=E5=B1=91=E8=A1=A5=E5=85=A8=E3=80=81=E8=8A=B1=E5=90=8D?= =?UTF-8?q?=E5=86=8C=E6=96=91=E9=A9=AC=E7=BA=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 合规评分维度拆分:薪酬社保拆为薪酬工资+社保公积金两个独立维度 - 合同管理评分纳入待签署合同(signDate为null)扣分 - 维度评分hover显示得分依据findings和建议recommendations - 概览统计卡片支持点击跳转(在管员工/高风险/待办/工资条) - 面包屑补全10个缺失路由,统一分组名与侧边栏一致 - 移除子Tab中重复的PageGuide组件 - 花名册列表添加斑马纹隔行变色 - 薪酬结构模版表格列宽优化,计算公式列截断+title显示 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- backend/src/services/risk.service.ts | 58 ++++++++++---- frontend/src/components/layout/Breadcrumb.tsx | 65 ++++++++++------ frontend/src/pages/Attendance.tsx | 15 ---- frontend/src/pages/Dashboard.tsx | 76 ++++++++++++------- frontend/src/pages/Roster.tsx | 4 +- frontend/src/pages/SocialInsurance.tsx | 3 - frontend/src/pages/money/BatchTab.tsx | 3 - frontend/src/pages/money/PayslipTab.tsx | 3 - frontend/src/pages/money/TemplateTab.tsx | 25 +++--- .../src/pages/roster/DisciplinaryRecords.tsx | 2 +- 10 files changed, 146 insertions(+), 108 deletions(-) diff --git a/backend/src/services/risk.service.ts b/backend/src/services/risk.service.ts index 2308ede..d691c40 100644 --- a/backend/src/services/risk.service.ts +++ b/backend/src/services/risk.service.ts @@ -1441,6 +1441,7 @@ export async function getComplianceScore(orgId: string) { const [ totalEmployees, unsignedContracts, + pendingSignContracts, expiringContracts, pendingRisks, highRisks, @@ -1456,6 +1457,14 @@ export async function getComplianceScore(orgId: string) { prisma.employee.count({ where: { orgId, status: 'ACTIVE' } }), // 未签合同:完全无合同记录的在职员工 prisma.employee.count({ where: { orgId, status: 'ACTIVE', contracts: { none: {} } } }), + // 待签署:有合同记录但 signDate 为 null(合同已创建但未完成签署) + prisma.employee.count({ + where: { + orgId, + status: 'ACTIVE', + contracts: { some: { signDate: null } }, + }, + }), prisma.laborContract.count({ where: { orgId, @@ -1483,8 +1492,8 @@ export async function getComplianceScore(orgId: string) { }), ]) - // 5 维度评分计算 - const contractScore = Math.max(0, 100 - unsignedContracts * 15 - expiringContracts * 5) + // 5 维度评分计算(未签合同 + 待签署都扣分) + const contractScore = Math.max(0, 100 - unsignedContracts * 15 - pendingSignContracts * 10 - expiringContracts * 5) const policyScore = Math.max(0, 100 - policiesWithoutPublish * 12) const attendanceScore = Math.max(0, 100 - attendanceUnconfirmed * 8) const salaryScore = totalPayslips > 0 @@ -1493,7 +1502,7 @@ export async function getComplianceScore(orgId: string) { const socialScore = (socialConfig ? 50 : 0) + (housingConfig ? 50 : 0) const dimensions = [ - { key: 'contract', name: '合同管理', score: contractScore, todoCount: unsignedContracts + expiringContracts }, + { key: 'contract', name: '合同管理', score: contractScore, todoCount: unsignedContracts + pendingSignContracts + expiringContracts }, { key: 'policy', name: '规章制度', score: policyScore, todoCount: policiesWithoutPublish }, { key: 'attendance', name: '考勤工时', score: attendanceScore, todoCount: attendanceUnconfirmed }, { key: 'salary', name: '薪酬工资', score: salaryScore, todoCount: unconfirmedPayslips }, @@ -1613,6 +1622,7 @@ export async function getHealthCheck(orgId: string) { const [ totalEmployees, unsignedContracts, + pendingSignContracts, expiringContracts, policiesWithoutPublish, totalPolicies, @@ -1632,6 +1642,14 @@ export async function getHealthCheck(orgId: string) { prisma.employee.count({ where: { orgId, status: 'ACTIVE' } }), // 未签合同:完全无合同记录的在职员工 prisma.employee.count({ where: { orgId, status: 'ACTIVE', contracts: { none: {} } } }), + // 待签署:有合同记录但 signDate 为 null(合同已创建但未完成签署) + prisma.employee.count({ + where: { + orgId, + status: 'ACTIVE', + contracts: { some: { signDate: null } }, + }, + }), prisma.laborContract.count({ where: { orgId, @@ -1665,37 +1683,49 @@ export async function getHealthCheck(orgId: string) { { key: 'contract', name: '合同管理', - score: Math.max(0, 100 - unsignedContracts * 15 - expiringContracts * 5), + score: Math.max(0, 100 - unsignedContracts * 15 - pendingSignContracts * 10 - expiringContracts * 5), findings: [ - unsignedContracts > 0 ? `${unsignedContracts} 名员工未签书面合同,存在双倍工资风险` : '全员已签书面合同', + unsignedContracts > 0 ? `${unsignedContracts} 名员工未签书面合同,存在双倍工资风险` : '全员已有合同记录', + pendingSignContracts > 0 ? `${pendingSignContracts} 份合同待签署(已创建未完成签署)` : '', expiringContracts > 0 ? `${expiringContracts} 份合同 30 天内到期,需及时续签` : '无即将到期合同', ].filter(Boolean), recommendations: [ unsignedContracts > 0 ? '立即补签书面合同,超过1个月未签需支付双倍工资' : '', + pendingSignContracts > 0 ? '尽快完成待签署合同的签署流程,未签署合同在仲裁中可能无效' : '', expiringContracts > 0 ? '提前30天发送续签意向书,保留协商证据' : '', ].filter(Boolean), }, { - key: 'salary_social', - name: '薪酬社保', + key: 'salary', + name: '薪酬工资', + score: totalPayslips > 0 + ? Math.max(0, 100 - Math.round((unconfirmedPayslips / totalPayslips) * 100)) + : 100, + findings: [ + totalPayslips > 0 + ? `${unconfirmedPayslips}/${totalPayslips} 张工资条未确认` + : '暂无工资条记录', + ].filter(Boolean), + recommendations: [ + unconfirmedPayslips > 0 ? '提醒员工及时确认工资条,保留薪酬合规证据' : '', + ].filter(Boolean), + }, + { + key: 'social', + name: '社保公积金', score: Math.round( - (socialConfig ? 40 : 0) + - (housingConfig ? 20 : 0) + - (totalPayslips > 0 ? Math.max(0, 20 - Math.round((unconfirmedPayslips / totalPayslips) * 20)) : 20) + + (socialConfig ? 50 : 0) + + (housingConfig ? 30 : 0) + (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), }, { diff --git a/frontend/src/components/layout/Breadcrumb.tsx b/frontend/src/components/layout/Breadcrumb.tsx index 705f5a9..3bc36ba 100644 --- a/frontend/src/components/layout/Breadcrumb.tsx +++ b/frontend/src/components/layout/Breadcrumb.tsx @@ -12,29 +12,46 @@ interface BreadcrumbItem { } const ROUTE_MAP: Record = { - '/': { group: '工作台', label: '总览' }, - '/calendar': { group: '工作台', label: '工作日历' }, - '/roster': { group: '员工管理', label: '花名册' }, - '/work-process': { group: '员工管理', label: '批量流程' }, - '/attendance': { group: '员工管理', label: '考勤确认' }, - '/leave-approval': { group: '员工管理', label: '休假审批' }, - '/termination': { group: '员工管理', label: '解聘补偿' }, - '/special-status': { group: '员工管理', label: '特殊状态' }, - '/training-records': { group: '员工管理', label: '培训记录' }, - '/performance-records': { group: '员工管理', label: '绩效考核' }, - '/disciplinary-records': { group: '员工管理', label: '违纪记录' }, - '/money': { group: '薪税社保', label: '薪税管理' }, - '/social': { group: '薪税社保', label: '社保公积金' }, - '/evidence': { group: '合规风控', label: '证据链' }, - '/policies': { group: '合规风控', label: '规章制度' }, - '/tools/health-check': { group: '合规风控', label: '用工体检' }, - '/tools/medical-period': { group: '合规风控', label: '医疗期计算' }, - '/tools/annual-value': { group: '合规风控', label: '年度价值' }, - '/ai-assistant': { group: 'AI 辅助', label: 'AI 顾问' }, - '/templates': { group: 'AI 辅助', label: '文本模板' }, - '/notifications': { group: '系统', label: '通知管理' }, - '/audit': { group: '系统', label: '操作日志' }, - '/settings': { group: '系统', label: '设置' }, + // 首页 + '/': { group: '首页', label: '工作台' }, + '/calendar': { group: '首页', label: '工作日历' }, + // 团队 + '/roster': { group: '团队', label: '花名册' }, + '/org-chart': { group: '团队', label: '组织架构' }, + '/esign': { group: '团队', label: '待签合同' }, + '/termination': { group: '团队', label: '离职管理' }, + '/training-records': { group: '团队', label: '培训记录' }, + '/performance-records': { group: '团队', label: '绩效考核' }, + '/disciplinary-records': { group: '团队', label: '违纪记录' }, + '/special-status': { group: '团队', label: '特殊员工' }, + // 时间 + '/attendance': { group: '时间', label: '考勤排班' }, + '/leave-approval': { group: '时间', label: '休假审批' }, + // 薪酬 + '/money': { group: '薪酬', label: '薪税管理' }, + '/social': { group: '薪酬', label: '社保公积金' }, + '/overtime': { group: '薪酬', label: '加班费计算' }, + '/commission-bonus': { group: '薪酬', label: '提成奖金' }, + '/salary-dashboard': { group: '薪酬', label: '薪酬分析' }, + // 福利保障 + '/commercial-insurance': { group: '福利保障', label: '商业保险' }, + '/benefits': { group: '福利保障', label: '员工福利' }, + // 合规 + '/risk-center': { group: '合规', label: '风险中心' }, + '/evidence': { group: '合规', label: '证据链条' }, + '/policies': { group: '合规', label: '规章制度' }, + // 工具 + '/tools/health-check': { group: '工具', label: '用工体检' }, + '/tools/medical-period': { group: '工具', label: '医疗期' }, + '/tools/annual-value': { group: '工具', label: '年度价值' }, + // 更多 + '/ai-assistant': { group: '更多', label: 'AI顾问' }, + '/templates': { group: '更多', label: '文本模板' }, + '/notifications': { group: '更多', label: '通知管理' }, + '/audit': { group: '更多', label: '操作日志' }, + '/company-files': { group: '更多', label: '公司文件' }, + '/settings': { group: '更多', label: '系统设置' }, + '/support': { group: '更多', label: '客服支持' }, } export default function Breadcrumb() { @@ -50,7 +67,7 @@ export default function Breadcrumb() { 首页 - {item.group !== '工作台' && ( + {item.group !== '首页' && ( <> {item.group} diff --git a/frontend/src/pages/Attendance.tsx b/frontend/src/pages/Attendance.tsx index 9a3a273..dc75573 100644 --- a/frontend/src/pages/Attendance.tsx +++ b/frontend/src/pages/Attendance.tsx @@ -692,9 +692,6 @@ function ShiftsTab() { return (
- - 班次管理用于定义考勤班次(如早班、晚班、全天班)。设置班次名称、上下班时间、弹性时间等。排班时引用此处定义的班次。 -