Files
TurboHR/20260727-优化-梳理.md
T
selfrelease 255af519d2 feat: Phase 1-3 优化全部完成
Phase 1 紧急修复(8项):
- 社保城市选择改为可输入
- 社保上下限拆分(三险/医保独立基数)
- 公积金试算结果展示修复
- 花名册合同保存修复(日期ISO格式)
- 薪酬批次创建失败修复(城市过滤+错误处理)
- 证据链查看修复
- 个税计算修复(blank_employees读取基本工资)
- 加班费倍率读取配置

Phase 2 功能完善(3项):
- 批量导入per-row异常捕获+导入按钮
- 单人发薪UI入口优化
- 解除协议模板补充(员工提出离职版)

Phase 3 后期规划(4项):
- 工资表导入功能(POST /import/payroll + 前端入口)
- 大病险/长护险附加险种(extraInsurances JSON + 计算适配)
- 专项附加扣除按月录入(SpecialDeductionRecord模型 + 前端Tab)
- 预置河北省社保政策(seed数据)
2026-07-27 18:55:08 +08:00

30 KiB
Raw Blame History

TurboHR 20260727 优化需求梳理

来源HR 用户测试反馈
日期2026-07-27
测试地区:河北省(重点)
梳理方式:逐条对照源码分析根因,标注涉及文件和行号


一、需求分类总览

# 模块 优先级 类型 简述 根因已定位
1 社保政策 🔴 Bug+功能 仅北京/上海,无法添加其他城市,河北无法使用
2 社保政策 🔴 功能 河北五险不同上下限,三险与医保需分开
3 社保政策 🟡 功能 大病险、长护险各地市收费不同,需单独添加险种
4 公积金 🔴 Bug 公积金模块无法测算数据,显示报错
5 专项附加扣除 🟡 功能 无法关联自然人数据,需手动录入,是否可按月累计
6 花名册-合同 🔴 Bug 花名册中添加劳动合同信息无法正常保存
7 花名册-导入 🔴 Bug+UI 批量导入仅员工基本信息成功,合同/加班/考勤均失败
8 花名册-UI 🟡 UI 缺少明显的批量导入按钮入口
9 薪酬管理 🔴 Bug 创建批次总提示创建失败
10 薪酬管理 🔴 功能 无法单独添加一个人发薪,必须全员生成再删除
11 薪酬管理 🟡 功能 缺少工资表导入功能
12 个税计算 🔴 Bug 导入工资后显示无个税,未匹配自动算税
13 证据链 🔴 Bug 证据链无法查看(手动录入的员工也无法查看)
14 加班费 🟡 功能 加班费倍率硬编码(1.5/2.0/3.0),需支持公司自定义标准
15 考勤对接 🟢 功能 考勤制度是否可关联企微等外部系统
16 电子签 🟢 功能 合同签署是否可关联电子签平台
17 文本模板 🟡 功能 缺少「个人提出离职」的解除协议模板

二、详细分析(含源码根因)

2.1 社保政策 — 多城市支持(#1, #2, #3)

根因分析

  1. 城市选择为下拉固定列表,无法手动输入

    • SocialInsurance.tsx:340-347 — 城市选择是 <select> 下拉,选项来自 GET /social/config/cities 返回的已有城市列表
    • 如果数据库中只有北京/上海,用户无法选择其他城市
    • 新建版本表单 SocialInsurance.tsx:570 中城市字段是 <Input> 文本框,可以手动输入
    • 真正问题:城市选择下拉限制了查看范围,用户在新建版本时可以输入「河北」,但切换城市查看时下拉没有「河北」选项
  2. 社保上下限单一,无法区分三险与医保

    • schema.prisma:389-390SocialInsuranceConfig 仅有 baseMin / baseMax 两个字段
    • payroll.service.ts:37-42calcSocialInsurance() 使用单一 actualBase 计算所有险种
    • 河北省政策:养老/失业/工伤保险基数上下限 ≠ 医疗/生育保险基数上下限
  3. 无大病险/长护险字段

    • schema.prisma:376-401SocialInsuranceConfig 无大病险、长护险相关字段
    • payroll.service.ts:39-40 — 计算仅包含养老/医疗/失业/工伤/生育五险

修复方案

改动 文件 具体内容
Schema 扩展 prisma/schema.prisma:376-401 新增 medicalBaseMin Float @default(0) medicalBaseMax Float @default(0) extraInsurances String? // JSON
社保计算适配 backend/src/services/payroll.service.ts:37-42 三险用 baseMin/baseMax,医保用 medicalBaseMin/medicalBaseMax(为 0 时 fallback 到 baseMin/baseMax
前端城市选择 frontend/src/pages/SocialInsurance.tsx:340-347 <select> 改为 <input list> + <datalist>,支持手动输入城市名
新建版本表单 frontend/src/pages/SocialInsurance.tsx:568-590 新增医保上下限输入框 + 附加险种配置区
预置河北数据 seed 脚本或手动 添加河北省社保配置(养老/失业/工伤基数 392019602,医疗/生育基数 536026796

河北省 2024 社保参考数据

养老/失业/工伤:基数下限 3920,上限 19602
医疗/生育:      基数下限 5360,上限 26796
养老 企业16%  个人8%
失业 企业0.7%  个人0.3%
工伤 企业0.2~1.9%(按行业)
医疗 企业8%    个人2%
生育 企业1%(已并入医疗,河北单独列)

2.2 公积金测算报错(#4

根因分析

  • social.routes.ts:557-606/housing-calculate 接口

  • 核心问题在第 580-583 行:当查不到公积金配置时,自动创建一条默认配置:

    config = await prisma.housingFundConfig.create({
      data: { orgId, effectiveFrom: ..., city: city || '北京', createdBy: ... },
    })
    

    创建的配置使用 schema 默认值(housingOrg: 12, housingEmp: 12, baseMin: 6326, baseMax: 33891),但 city 参数可能为 undefined

  • 前端调用链SocialInsurance.tsx:611calcHousingMutate() 调用 POST /social/housing-calculate,传参 { base, month, city }

  • 可能原因

    1. city 未正确传递(undefined),创建的配置城市为「北京」而非用户期望的城市
    2. 前端 housingResultitems 字段不存在(公积金返回的是 housingOrg/housingEmp 而非 items 数组),但前端试算结果展示复用了社保的 r.items.map() 逻辑,导致 undefined.map() 报错
  • 前端结果展示 BugSocialInsurance.tsx:625-677 — 试算结果展示区同时用于社保和公积金,使用 r.items.map() 渲染表格。但公积金计算接口返回 { housingOrg, housingEmp, total }没有 items 数组,导致 r.itemsundefined.map() 抛出 TypeError

修复方案

改动 文件 具体内容
前端结果展示 frontend/src/pages/SocialInsurance.tsx:625-677 公积金试算结果单独渲染(显示企业/个人比例和金额),不复用社保的 items 表格
后端兜底优化 backend/src/routes/social.routes.ts:580-583 不自动创建默认配置,改为返回提示「该城市暂无公积金配置,请先创建」

2.3 专项附加扣除(#5

根因分析

  • payroll.service.ts:179 — 个税累计预扣计算:
    const ytdSpecialDeduction = employee.specialDeduction * Number(month.slice(5, 7))
    
  • Employee.specialDeduction 是单一 Float 字段,表示每月专项附加扣除金额
  • 个税计算时直接乘以月份序号作为累计扣除额
  • 问题:不支持按月不同金额(如某月子女教育扣除变更),且需手动在员工档案中录入

修复方案

改动 文件 具体内容
新建 Prisma 模型 prisma/schema.prisma model SpecialDeductionRecord { id, orgId, employeeId, month, amount, type(子女教育/住房贷款/赡养老人/...), createdBy, createdAt }
个税计算适配 backend/src/services/payroll.service.ts:179 改为查询 SpecialDeductionRecord 按月累加,fallback 到 employee.specialDeduction * 月份
前端录入入口 frontend/src/pages/roster/BasicInfo.tsx 在社保/公积金基数旁增加「专项附加扣除」按月录入区

2.4 花名册合同保存失败(#6

根因分析

  • 前端调用ContractInfo.tsx:19api.post('/employees/contracts', { ...data, employeeId })
  • 后端路由employee.routes.ts:206-209router.post('/contracts', ...)addContractSchema.parse(req.body)addContract()
  • Schema 校验contract.schema.ts:54-64
    signDate: z.string().datetime().nullable(),   // 必须是 ISO datetime 字符串
    startDate: z.string().datetime(),              // 必须是 ISO datetime 字符串
    endDate: z.string().datetime().nullable(),
    
  • 前端提交ContractInfo.tsx 表单中日期用 <Input type="date">,值为 YYYY-MM-DD 格式(如 2026-07-27),不是 ISO datetime 格式2026-07-27T00:00:00.000Z
  • 根因Zod 校验 z.string().datetime() 要求 RFC 3339 格式,YYYY-MM-DD 不通过校验 → ZodError → 返回 400 → 前端显示「保存失败」

修复方案

改动 文件 具体内容
Schema 日期校验放宽 backend/src/schemas/contract.schema.ts:56-58 改为 z.string().nullable() + 在 addContract() 中用 new Date() 解析
或前端提交时转换 frontend/src/pages/roster/ContractInfo.tsx 提交前将日期转为 ISO 格式:new Date(form.signDate).toISOString()

推荐方案:前端转换(改动最小,且 Contracts.tsx 新建员工时已用 new Date(form.signDate).toISOString() 转换,ContractInfo.tsx 遗漏了同样的转换)


2.5 批量导入问题(#7, #8

根因分析

  • import.routes.ts:214-390 — 多 Sheet 导入逻辑
  • Sheet 名称精确匹配:代码中硬编码 Sheet 名称为中文(如「员工信息」「劳动合同」「加班记录」「考勤记录」),如果用户修改了 Sheet 名或模板格式不一致,则无法匹配
  • 合同匹配逻辑:先按 idCardHash 匹配,再按 name 匹配。如果员工信息 Sheet 和合同 Sheet 中的身份证号或姓名不一致(空格、别称),则匹配失败
  • 错误信息未充分展示:后端返回 errors 数组,但前端可能只显示了「成功 N 条」的汇总,未展示详细错误

修复方案

改动 文件 具体内容
花名册增加导入按钮 frontend/src/pages/Roster.tsxContracts.tsx 在列表页顶部增加「批量导入」按钮,点击后弹出导入向导
导入模板下载 frontend 调用 GET /import/template 下载标准模板
错误详情展示 frontend 导入结果弹窗中展示 errors[] 数组的每条错误(行号+原因)
Sheet 名称容错 backend/src/routes/import.routes.ts Sheet 名称匹配改为包含关键词即可(如包含「合同」即视为劳动合同 Sheet)

2.6 薪酬管理创建失败(#9

根因分析

  • payroll2.routes.ts:220-403 — 批次创建逻辑
  • 关键链路createBatchSchema.parse(req.body) → 查询员工 → 循环 calcBatchEntry() → 创建 BatchEntry
  • calcBatchEntry() 可能抛异常payroll.service.ts:109-127 — 查询 socialInsuranceConfighousingFundConfig不带 city 过滤
    prisma.socialInsuranceConfig.findFirst({
      where: { orgId, effectiveFrom: { lte: month }, OR: [...] },
      orderBy: { effectiveFrom: 'desc' },
    })
    
    如果组织有多个城市的配置,可能取到错误城市的配置;如果无配置,socialConfignull,社保为 0(不报错)
  • 更可能的根因payroll.service.ts:329Number(decrypt(emp.monthlySalary)) 如果 monthlySalary 加密格式异常,decrypt 抛出错误,虽然有 catch 回退到 Number(emp.monthlySalary),但如果 monthlySalary 本身是加密后的非数字字符串,Number() 返回 NaN,后续计算 NaN 传播可能导致 Prisma 写入失败
  • 另一个可能payroll2.routes.ts:346calcBatchEntry() 内部 prisma.payslip.findMany() 查询历史工资条,如果数据量大可能超时

修复方案

改动 文件 具体内容
calcBatchEntry 增加城市过滤 backend/src/services/payroll.service.ts:111-118 查询社保配置时加入 city: employee.city 过滤
错误处理增强 backend/src/routes/payroll2.routes.ts:346 calcBatchEntry() 调用加 try-catch,单条失败跳过并记录,不阻塞整批
前端错误展示 frontend/src/pages/Money.tsx:111-120 onError 时展示后端返回的具体错误信息

2.7 单人发薪(#10

根因分析

  • 后端已有接口payroll2.routes.ts:489-547POST /batches/:batchId/employees 支持向批次添加员工
  • 前端已有调用Money.tsx:735-739addMutation 调用 api.post('/payroll2/batches/${batchId}/employees', { employeeIds })
  • 结论:功能已存在,用户可能未找到入口。需检查前端 UI 是否暴露了「添加员工」按钮

修复方案

  • 检查 Money.tsx 批次详情页中是否有「添加员工」按钮
  • 如果按钮存在但隐藏,调整 UI 使其更明显
  • 如果按钮不存在,在批次详情页增加「添加员工」操作

2.8 工资表导入(#11

现状:无工资表导入功能

修复方案

改动 文件 具体内容
后端导入接口 backend/src/routes/import.routes.ts 新增 POST /import/payroll 解析 Excel 工资表(员工姓名/身份证 + 基本工资/津贴/奖金/扣款)
前端导入入口 frontend/src/pages/Money.tsx 批次详情页增加「导入工资表」按钮

2.9 个税计算问题(#12

根因分析

  • payroll.service.ts:160-183 — 累计预扣法个税计算逻辑完整
  • 个税为 0 的正常情况
    • totalPay < 5000ytdTaxableIncome ≤ 0 → 个税 = 0
    • totalPay - 社保 - 公积金 - 5000*月份 - 专项附加扣除 ≤ 0 → 个税 = 0
  • 个税为 0 的异常情况
    • 社保配置缺失 → socialEmp = 0(不会导致个税为 0,反而个税应更高)
    • specialDeduction 为 0 → 减除费用仅 5000/月,如果工资 > 5000 应有个税
    • 真正问题:如果 baseSalary = 0(使用 blank_allblank_employees 模式创建批次),totalPay = 0 → 个税 = 0
  • 用户反馈场景:用户说「导入工资后显示无个税」,说明工资数据已导入但个税仍为 0
    • 可能原因:导入工资数据后未触发 calcBatchEntry() 重新计算
    • 或前端显示的个税字段映射有误

修复方案

改动 文件 具体内容
编辑条目时重算 backend/src/routes/payroll2.routes.ts PUT /batches/:batchId/entries/:employeeId 已调用 calcBatchEntry(),确认前端编辑后是否触发重算
前端字段映射 frontend/src/pages/Money.tsx 确认 entry.tax 字段正确显示
导入后自动重算 backend/src/routes/import.routes.ts 如果新增工资表导入,导入后自动调用 calcBatchEntry()

2.10 证据链无法查看(#13

根因分析

  • 后端 API 完整roster.routes.ts:240-494GET /:id/evidence-chain 返回 { employee, evidence[], risks[], summary }
  • 前端组件完整EvidenceChain.tsx:14-136 — 使用 useQuery 调用 api.get('/roster/${employeeId}/evidence-chain'),渲染证据列表和风险提醒
  • 前端引用正确EmployeeProfile.tsx:96{tab === 'evidence' && <EvidenceChain employeeId={employeeId} />}
  • 可能根因
    1. Tab 未显示EmployeeProfile.tsx 的 Tab 列表中是否有「证据链」Tab?需检查 TAB_GROUPS 定义
    2. API 路由前缀:前端 api.get('/roster/${employeeId}/evidence-chain') → 实际请求路径需确认是否匹配后端路由挂载前缀
    3. 新员工无数据:手动录入的新员工如果没有合同/工资条/考勤等关联数据,evidence[] 数组可能为空,前端显示「无数据」

修复方案

改动 文件 具体内容
检查 Tab 定义 frontend/src/pages/roster/shared.ts 确认 TAB_GROUPS 中包含证据链 Tab
空数据提示优化 frontend/src/pages/roster/EvidenceChain.tsx:24 `if (!data
API 路径验证 frontend/src/lib/api.ts 确认 baseURL + /roster/:id/evidence-chain 是否匹配后端挂载路径

2.11 加班费自定义标准(#14

根因分析

  • 后端已有 OvertimeConfig 模型和 API
    • schema.prisma:444-454OvertimeConfig 模型,含 weekdayRate/weekendRate/holidayRate/monthlyDays/dailyHours
    • payroll.routes.ts:334-370GET /overtime/configPOST /overtime/config 接口
  • 但加班费计算仍用硬编码payroll.routes.ts:45-47107-109
    const weekdayPay = hourlyWage * 1.5 * data.weekdayHours
    const weekendPay = hourlyWage * 2.0 * data.weekendHours
    const holidayPay = hourlyWage * 3.0 * data.holidayHours
    
    未读取 OvertimeConfig 中的倍率,直接硬编码 1.5/2.0/3.0

修复方案

改动 文件 具体内容
加班费计算读取配置 backend/src/routes/payroll.routes.ts:44-47 先查 OvertimeConfig,用配置中的倍率替代硬编码
同上 backend/src/routes/payroll.routes.ts:107-109 PUT 接口同样修复
前端增加配置入口 frontend/src/pages/Money.tsxSettings.tsx 增加加班费倍率配置 UI

2.12 考勤企微对接(#15

现状:无企微对接

需求:评估对接企业微信考勤数据的可行性

方案:后期规划,需企微 API 文档调研。企微提供考勤数据接口 checkin/getcheckindata,可定时拉取同步到系统


2.13 电子签对接(#16

现状:合同记录有 signMethod 字段(PAPER/ELECTRONIC),ContractInfo.tsx 表单已支持选择签署方式并填写电子合同编号/链接

需求:评估对接电子签平台(如法大大/上上签)的可行性

方案:后期规划,需第三方平台 API 调研。当前可先完善手动录入电子合同信息的流程


2.14 文本模板补充(#17

根因分析

  • template.service.ts:15-249 — 硬编码模板数组 documentTemplates
  • 已有模板:固定期限劳动合同、无固定期限劳动合同、协商解除劳动合同协议书tpl_termination_agreement)、员工手册公示通知、规章制度讨论通知、违纪处分通知书、试用期转正通知书、合同到期不续签通知书
  • 缺少:员工主动提出离职的解除协议书模板

修复方案

改动 文件 具体内容
新增模板 backend/src/services/template.service.ts documentTemplates 数组中新增 tpl_voluntary_termination_agreement(员工提出离职版解除协议)

模板内容要点

  • 乙方主动提出离职,甲方同意
  • 无经济补偿金(员工主动辞职,法定无需支付)
  • 工作交接条款
  • 社保公积金截止月份
  • 竞业限制延续条款(如有)
  • 变量:companyName, employeeName, idCard, resignDate, lastWorkDay, socialInsEndMonth, housingFundEndMonth, resignReason

三、实施优先级建议

Phase 1 — 紧急修复(阻塞客户使用)

序号 任务 根因 预估工作量
1 社保城市选择改为可输入 城市下拉限制了已有城市 0.5 天
2 社保上下限拆分(三险/医保) Schema 单一 baseMin/baseMax 1 天
3 预置河北省社保政策 无河北数据 0.5 天
4 公积金试算结果展示修复 公积金返回无 items 数组,复用社保表格渲染报错 0.5 天
5 花名册合同保存修复 前端提交 YYYY-MM-DD 未转 ISO datetime 0.5 天
6 薪酬批次创建失败修复 calcBatchEntry 缺城市过滤 + 错误处理不足 0.5 天
7 证据链查看修复 需确认 Tab 定义 + 空数据提示 0.5 天
8 个税计算排查 确认导入后是否触发重算 0.5 天

Phase 2 — 功能完善

序号 任务 预估工作量
9 批量导入 Bug 修复 + 导入按钮 0.5 天
10 单人发薪 UI 入口优化 0.5 天
11 工资表导入功能 1 天
12 大病险/长护险单独险种模块 1 天
13 专项附加扣除按月录入 0.5 天
14 加班费倍率读取配置(后端已有模型,改计算逻辑) 0.5 天
15 解除协议模板补充(员工提出离职版) 0.5 天

Phase 3 — 后期规划

序号 任务 说明
16 企微考勤对接 需 API 调研
17 电子签平台对接 需第三方平台选型

四、技术要点

4.1 社保上下限拆分方案

// schema.prisma — SocialInsuranceConfig 新增字段
model SocialInsuranceConfig {
  // ... 现有字段 ...
  baseMin          Float    @default(6326)  // 三险基数下限(养老/失业/工伤)
  baseMax          Float    @default(33891) // 三险基数上限
  medicalBaseMin   Float    @default(0)     // 医保基数下限(0 = fallback 到 baseMin
  medicalBaseMax   Float    @default(0)     // 医保基数上限(0 = fallback 到 baseMax
  extraInsurances  String?  // JSON: [{ name, type: 'fixed'|'rate', orgRate, empRate, orgAmount, empAmount }]
}
// payroll.service.ts — calcSocialInsurance 适配
export function calcSocialInsurance(base: number, config: any) {
  const pensionBase = Math.min(Math.max(base, config.baseMin), config.baseMax)
  const medicalBase = Math.min(
    Math.max(base, config.medicalBaseMin || config.baseMin),
    config.medicalBaseMax || config.baseMax
  )
  // 三险用 pensionBase,医保用 medicalBase
  const socialEmp = pensionBase * (config.pensionEmp + config.unemploymentEmp) / 100
    + medicalBase * (config.medicalEmp) / 100
  const socialOrg = pensionBase * (config.pensionOrg + config.unemploymentOrg + config.injuryOrg) / 100
    + medicalBase * (config.medicalOrg + config.maternityOrg) / 100
  return { actualBase: pensionBase, socialEmp, socialOrg }
}

4.2 合同日期格式修复

// ContractInfo.tsx — 提交前转换日期格式
const handleSubmit = () => {
  const data = {
    ...formData,
    signDate: form.signDate ? new Date(form.signDate).toISOString() : null,
    startDate: new Date(form.startDate).toISOString(),
    endDate: form.endDate ? new Date(form.endDate).toISOString() : null,
  }
  addContractMutation.mutate(data)
}

4.3 公积金试算结果展示修复

// SocialInsurance.tsx — 公积金试算结果单独渲染
{isHousing && r ? (
  <div className="space-y-2">
    <div className="flex justify-between text-sm">
      <span>缴费基数</span><span className="font-medium">¥{fmt(r.actualBase)}</span>
    </div>
    <div className="flex justify-between text-sm">
      <span>企业缴纳 ({r.housingOrg ? '' : ''})</span>
      <span className="text-danger">¥{fmt(r.housingOrg)}</span>
    </div>
    <div className="flex justify-between text-sm">
      <span>个人缴纳</span>
      <span className="text-warning">¥{fmt(r.housingEmp)}</span>
    </div>
    <div className="flex justify-between text-sm border-t pt-2 font-medium">
      <span>合计</span><span className="text-primary">¥{fmt(r.total)}</span>
    </div>
  </div>
) : !isHousing && r ? (
  // 社保试算结果保持原有 items 表格渲染
  ...
) : ...}

4.4 加班费倍率读取配置

// payroll.routes.ts — 保存加班费记录时读取 OvertimeConfig
router.post('/overtime', async (req, res, next) => {
  const data = overtimeSchema.parse(req.body)
  let config = await prisma.overtimeConfig.findUnique({ where: { orgId: req.user!.orgId } })
  if (!config) config = { weekdayRate: 1.5, weekendRate: 2.0, holidayRate: 3.0, monthlyDays: 21.75, dailyHours: 8 } as any
  const hourlyWage = data.monthlyWage / config.monthlyDays / config.dailyHours
  const weekdayPay = hourlyWage * config.weekdayRate * data.weekdayHours
  const weekendPay = hourlyWage * config.weekendRate * data.weekendHours
  const holidayPay = hourlyWage * config.holidayRate * data.holidayHours
  // ...
})

4.5 个税计算链路

创建/编辑批次条目 → calcBatchEntry()
  → 查社保配置(orgId + month,需加 city 过滤)
  → 查公积金配置(同上)
  → 累计预扣法:
     ytdIncome = 历史工资条 totalPay 之和 + 本月 totalPay
     ytdDeductions = 5000 * 月份 + ytdSocialEmp + ytdHousingEmp + ytdSpecialDeduction
     ytdTaxableIncome = max(0, ytdIncome - ytdDeductions)
     tax = calcCumulativeTax(ytdTaxableIncome, ytdTaxDeducted)
  → 返回 { tax, netPay, socialEmp, socialOrg, housingEmp, housingOrg }

个税为 0 的条件ytdTaxableIncome ≤ 0,即累计收入 ≤ 累计减除费用(5000×月份数 + 累计社保 + 累计公积金 + 累计专项附加扣除)

4.6 证据链 API 链路

前端 EvidenceChain.tsx
  → useQuery(['evidence-chain', employeeId])
  → api.get('/roster/${employeeId}/evidence-chain')
  → 后端 roster.routes.ts:240
     → prisma.employee.findFirst({ include: { contracts, payslips, overtimeRecords, ... } })
     → 组装 evidence[](劳动关系/薪酬发放/考勤记录/违纪处理/培训签收/绩效考核/解聘记录)
     → 风险检测 risks[](未签合同/合同到期/工资异常/无考勤记录等)
     → 返回 { employee, evidence, risks, summary: { total, signed, unsigned, riskCount } }

五、实施进度追踪(2026-07-27 更新)

Phase 1 — 紧急修复(全部完成

序号 任务 状态 修改文件 实施内容
1 社保城市选择改为可输入 已完成 SocialInsurance.tsx 城市下拉改为 <input list> + <datalist>,支持手动输入
2 社保上下限拆分(三险/医保) 已完成 schema.prisma social.routes.ts payroll.service.ts SocialInsurance.tsx Schema 新增 medicalBaseMin/medicalBaseMaxcalcSocialInsurance()calcSocialDetail() 医保使用独立基数(为 0 时 fallback);试算接口适配;前端新建版本表单增加医保上下限输入;配置展示区显示医保上下限
3 公积金试算结果展示修复 已完成 SocialInsurance.tsx 公积金试算结果单独渲染(企业/个人缴纳金额),不再复用社保 items.map()
4 花名册合同保存修复 已完成 ContractInfo.tsx 提交前将日期转为 ISO 格式 new Date(form.signDate).toISOString()
5 薪酬批次创建失败修复 已完成 payroll.service.ts payroll2.routes.ts calcBatchEntry 查社保/公积金配置加 city 过滤;单员工计算 try-catch 不阻塞整批;返回 failedEmployees 详情
6 证据链查看修复 已完成 shared.ts EmployeeProfile.tsx EvidenceChain.tsx 添加 evidence Tab 到 TAB_GROUPSEmployeeProfile 渲染 EvidenceChain 组件;空数据友好提示
7 个税计算排查修复 已完成 payroll2.routes.ts blank_employees 模式下从员工记录获取基本工资,避免 baseSalary=0 导致个税为 0
8 加班费倍率读取配置 已完成 payroll.routes.ts OvertimeConfig 读取倍率,fallback 到 1.5/2.0/3.0

Phase 2 — 功能完善(全部完成

序号 任务 状态 修改文件 实施内容
9 批量导入 Bug 修复 + 导入按钮 已完成 import.routes.ts Roster.tsx 加班/违纪/考勤导入添加 per-row try-catchRoster 页面添加「批量导入」按钮入口
10 单人发薪 UI 入口优化 已完成 Roster.tsx 操作列添加「发薪」按钮(Wallet 图标),跳转薪税管理页面
11 解除协议模板补充 已完成 template.service.ts 新增 tpl_termination_agreement_employee(员工主动提出离职版),含离职原因/无补偿金/竞业限制等条款

Phase 3 — 后期规划(部分完成)

序号 任务 状态 修改文件 实施内容
12 工资表导入功能 已完成 import.routes.ts Money.tsx 新增 POST /import/payroll 接口,解析 Excel 工资表批量更新批次条目(基本工资/加班费/津贴/扣款/奖金),自动重算税费;前端批次详情页添加「导入工资表」按钮 + 模板下载 + 结果展示
13 大病险/长护险单独险种模块 已完成 schema.prisma social.routes.ts payroll.service.ts SocialInsurance.tsx Schema 新增 extraInsurances JSON 字段;calcSocialInsurance()calcSocialDetail() 支持附加险种计算(按养老基数/医保基数/固定金额三种方式);试算接口返回附加险种明细;前端新建版本表单增加附加险种动态配置区(添加/删除险种行)
14 专项附加扣除按月录入 已完成 schema.prisma social.routes.ts SocialInsurance.tsx 新建 SpecialDeductionRecord 模型(子女教育/赡养老人/住房/继续教育/婴幼儿照护五项分项);后端 CRUD + 批量录入 API;前端新增「专项附加扣除」Tab,支持按月查看/编辑/新增,自动计算合计并同步员工便捷字段
15 预置河北省社保政策 已完成 seed.ts 添加河北省石家庄市社保配置(养老16/8、医疗8/2、失业0.7/0.3、工伤0.3、生育0.5,基数3920~19602+ 大病医疗(固定5元) + 长期护理险(0.1%);公积金配置(12%/12%

待实施

序号 任务 说明
16 企微考勤对接 需 API 调研
17 电子签平台对接 需第三方平台选型

技术备注

  • Prisma migrationmedicalBaseMin/medicalBaseMaxextraInsurancesSpecialDeductionRecord 已通过 prisma db push 同步到数据库
  • 向后兼容:医保独立上下限为 0 时自动 fallback 到 baseMin/baseMaxextraInsurances 为 null 时不影响现有计算
  • 附加险种计算方式baseType: 'pension' 按养老基数 × 比例,baseType: 'medical' 按医保基数 × 比例,baseType: 'fixed' 按固定金额
  • 专项附加扣除:录入后自动同步 employee.specialDeduction 便捷字段,个税计算时直接使用
  • 解除协议模板:原 tpl_termination_agreement 描述更新为「用人单位提出」,新增 tpl_termination_agreement_employee 为「员工主动提出离职」版