# 20260816 优化清单 > 来源:用户测试反馈(编号 4-13,共 10 项) > 结合 TurboHR 当期代码梳理,按模块归类,标注根因 / 修复方向 / 优先级 / 涉及文件。 > 优先级:P0 紧急(影响业务正确性/合规)|P1 高(流程阻塞或合规风险)|P2 中(体验/增强)|P3 规划(新模块) > > **✅ 全部 10 项已于 20260816 完成实现,前后端 tsc 均通过。完成明细见"五、已完成优化"章节(编号 5-15)。** --- ## 一、花名册 · 添加雇员(问题 4-8) ### 问题 4:合同状态未签署时仍显示"正常" - **现状**:`backend/src/services/contract.service.ts:74` 注释明确"有合同记录(FIXED/UNFIXED/LABOR/INTERNSHIP),即使 signDate 为 null 也按正常合同处理",`getContractStatus` 在有合同记录时直接返回 `active`/`正常`,未区分"已签署"与"待签署"。 - **修复方向**: 1. `getContractStatus` 增加"待签署"判定:有合同记录但 `signDate` 为空,或关联的电子签署记录状态为 `PENDING`/未 `COMPLETED` 时,返回 `status: 'pending_sign'`,`statusText: '待签署'`,`riskLevel: 'medium'`。 2. 仅当 `signDate` 已填且(无电子签署或电子签署已 COMPLETED)时才返回 `active`/`正常`。 3. 前端花名册列表/详情同步展示"待签署"标签。 - **优先级**:P1 高 - **涉及文件**:`backend/src/services/contract.service.ts`、`frontend/src/pages/Roster.tsx`、`frontend/src/pages/roster/ContractInfo.tsx` ### 问题 5:社保状态未办理时仍显示"正常" - **现状**:花名册添加雇员表单(`roster/modals.tsx:719`)有 `socialInsBase`/`socialInsStartMonth`/`housingFundBase`/`housingFundStartMonth`,但无"社保状态"字段;花名册列表/详情也未展示社保办理状态,默认视为"正常"。 - **修复方向**: 1. 新增社保状态字段(派生而非存储):根据 `socialInsStartMonth` 是否填写、是否已做"办理完成"操作判定。 2. 状态枚举:`待办`(未填写基数/起始月或未办理完成)、`正常`(已办理且在缴)、`停缴`(已设截止月)。 3. 后端 `employee.service.ts` 在花名册列表返回 `socialInsStatus` 派生字段。 4. 前端花名册列表/详情展示社保状态标签。 5. 提供"办理完成"操作入口(可在社保公积金模块或花名册操作栏)。 - **优先级**:P1 高 - **涉及文件**:`backend/src/services/employee.service.ts`、`backend/src/services/contract.service.ts`(或新增 `social-insurance.service.ts` 派生方法)、`frontend/src/pages/Roster.tsx`、`frontend/src/pages/roster/PayslipSocialInfo.tsx` ### 问题 6:证件号码未验证有效性 - **现状**:`roster/modals.tsx:886` `canSubmit` 仅校验 `idCardNumber.length >= 18`,未做身份证校验位(第 18 位)、出生日期合法性、月份/日期范围等有效性验证;`handleIdCardChange`(755 行)只做性别推导和查重,未做格式校验。 - **修复方向**: 1. 新增身份证有效性校验:校验位算法(前 17 位加权求和取模映射第 18 位)、出生日期合法、月份 01-12、日期合法。 2. 校验失败时 `ageWarning` 返回 `BLOCK`,阻断保存。 3. 后端 `employee.service.ts` 创建/更新员工时同步做校验(双保险)。 4. 支持非身份证类型(护照/港澳台通行证)时跳过 18 位校验(已部分实现,需确认)。 - **优先级**:P1 高 - **涉及文件**:`frontend/src/pages/roster/modals.tsx`(`handleIdCardChange`、`canSubmit`)、`backend/src/services/employee.service.ts`、`backend/src/routes/employee.routes.ts` ### 问题 7:超龄人员仍可选择劳动合同 - **现状**:`roster/modals.tsx:784` 超龄仅 `WARN` 提示"已达法定退休年龄,建议确认是否按退休处理",未阻断选择 `FIXED`/`UNFIXED` 劳动合同;超龄人员依法应签劳务协议/实习协议,不能签劳动合同。 - **修复方向**: 1. 年龄 ≥ 法定退休年龄(男 60、女干部 55、女工人 50)时,合同类型下拉移除 `FIXED`/`UNFIXED`,仅保留 `LABOR`/`INTERNSHIP`/`UNSIGNED`,并提示"超龄人员不可签订劳动合同,请选择劳务协议"。 2. 强制选 `LABOR` 时联动问题 8 的社保基数置 0 逻辑。 3. 后端 `employee.service.ts`/`work-process.service.ts` 增加校验:超龄 + 劳动合同类型组合拒绝并返回 400。 4. `femaleWorkerType` 字段用于判定女职工退休年龄(干部 55/工人 50),需在年龄计算时引用。 - **优先级**:P1 高 - **涉及文件**:`frontend/src/pages/roster/modals.tsx`(合同类型下拉、`ageWarning` 逻辑)、`backend/src/services/employee.service.ts`、`backend/src/services/work-process.service.ts` ### 问题 8:签署劳务协议员工社保基数未置 0 且仍可缴纳社保 - **现状**:`roster/modals.tsx:983` 社保缴费基数为独立输入框,选择 `LABOR` 合同类型时未联动置 0 或禁用;劳务协议人员依法不缴纳社保。 - **修复方向**: 1. 合同类型选择 `LABOR`/`INTERNSHIP` 时,社保缴费基数、公积金缴费基数自动置 0 且字段禁用,社保开始年月清空。 2. UI 提示"劳务协议/实习协议人员不缴纳社保公积金"。 3. 后端 `employee.service.ts` 保存时校验:`contractType === LABOR` 且 `socialInsBase > 0` 时拒绝或强制置 0。 4. 薪税批次生成时排除劳务协议人员,或其社保公积金项强制为 0。 - **优先级**:P1 高 - **涉及文件**:`frontend/src/pages/roster/modals.tsx`(社保公积金区联动)、`backend/src/services/employee.service.ts`、`backend/src/services/payroll.service.ts` --- ## 二、工作台(问题 9-10) ### 问题 9:待办事项无法点击跳转 - **现状**:`Dashboard.tsx:1148` 已有 ``,但部分待办的 `actionUrl` 为空、指向不存在的路由,或后端 `dashboard.service.ts` 生成待办时未填充 `actionUrl`,导致点击无反应或跳转 404。 - **修复方向**: 1. 排查后端 `dashboard.service.ts` 各类待办(CONTRACT/TERMINATION/ONBOARDING/RETIREMENT/MONTHLY/SALARY)的 `actionUrl` 生成逻辑,确保每条待办都有有效跳转目标。 2. `actionUrl` 为空时前端降级为"查看详情"按钮,弹窗展示待办内容,而非死链。 3. 校验 `actionUrl` 指向的路由真实存在(如 `/roster?employeeId=xxx`、`/termination?draftId=xxx`)。 4. 跳转后自动定位到对应员工/草稿/合同。 - **优先级**:P1 高 - **涉及文件**:`backend/src/services/dashboard.service.ts`、`frontend/src/pages/Dashboard.tsx`(`TodoIcon`/待办列表渲染) ### 问题 10:风险提醒内容不合理(含反向引导 bug) - **现状**:`Dashboard.tsx:202` 风险提醒按 `todo.type` 过滤(CONTRACT/TERMINATION/ONBOARDING/RETIREMENT),具体提醒文案、阈值、优先级、跳转目标生成逻辑在后端 `risk.service.ts`。存在两类问题: **问题 A · 反向引导(典型 bug)**:`risk.service.ts:309-337` 三期/医疗期/工伤员工的风险提醒,文案是"解聘受限/不得解除劳动合同",但 `actionUrl` 却指向 `/termination?employee=xxx`(解聘向导页面)。等于提示"该员工不能解聘,点这里去解聘她"——完全反向引导,可能诱导 HR 误操作违法解除。 ```ts // risk.service.ts:309-317(错误示例) if (emp.isPregnant) { risks.push({ type: 'TERMINATION', title: `${emp.name}处于孕期/哺乳期,解聘受限`, description: '三期女职工不得依非过错理由解除劳动合同...', actionUrl: `/termination?employee=${encodeURIComponent(emp.name)}`, // ← 反向引导 }) } // isInMedicalPeriod(319-328)、isWorkInjured(329-338)同样指向 /termination ``` **问题 B · 提醒规则/阈值/去重不合理**:部分提醒内容重复、阈值不合理或与实际业务无关(如合同到期 30 天提前量偏短,同一合同到期可能生成多条提醒)。 - **修复方向**: 1. **修正反向引导(问题 A,P0 紧急)**: - 三期/医疗期/工伤员工的"解聘受限"提醒,`actionUrl` 改为指向员工详情或特殊状态页(`/roster?employee=xxx` 或 `/special-status?type=PREGNANCY`),让 HR 确认员工状态,而非跳到解聘页面。 - 这类提醒的本质是"信息提示 + 合规警示",不是"可操作待办"。应在解聘向导 step 2 合规检查时硬阻断(Termination 已实现法定禁止情形阻断),工作台只做信息展示。 - 可考虑将这类提醒的 `type` 从 `TERMINATION` 改为 `COMPLIANCE`(合规提示),与可操作的解聘待办区分。 2. **梳理风险提醒规则清单(问题 B)**:合同到期(30/60/90 天分级)、未签合同(>30 天/>1 年)、试用期将满、退休预警、社保断缴、离职未办结等。 3. **每条提醒明确**:触发条件、提醒文案模板、优先级、跳转目标、是否可忽略、是"可操作待办"还是"信息提示"。 4. **去除重复提醒**(如同一合同到期生成多条),合并同类项。 5. **调整阈值至合理区间**(如合同到期提醒从 30 天提前到 60 天,给 HR 反应时间)。 6. **前端按优先级排序展示**,高风险置顶;信息提示类提醒不提供"去处理"按钮,仅提供"查看详情"。 - **优先级**:P0 紧急(问题 A 反向引导)/ P2 中(问题 B 规则优化) - **涉及文件**:`backend/src/services/risk.service.ts`(`detectTerminationRisks` 309-342 行、`detectSpecialStatusRisks`)、`frontend/src/pages/Dashboard.tsx`(风险提醒 Tab 渲染、按 type 区分操作按钮) --- ## 三、薪税管理 · 发放批次(问题 11、13) ### 问题 11:获取工资时自动识别试用期并填充试用期工资 - **现状**:不需要新增"获取试用期工资"按钮。已有的"获取工资"逻辑(创建批次、添加人员时自动填充 `baseSalary`)应自动识别员工是否处于试用期,是则填 `probationSalary`,否则填 `monthlySalary`。 **根因**:当前 3 处获取工资逻辑用了**错误的试用期判定**,判定的是"入职不到 1 年"而非"当前在试用期内": ```ts // payroll2.routes.ts:355-359(创建批次 copy_last 模式) // payroll2.routes.ts:610-614(添加人员到批次) // payroll.routes.ts:380-388(旧版批量生成工资条) if (emp.contracts?.[0]?.probationSalary && new Date(emp.contracts[0].startDate) > new Date(Date.now() - 365 * 24 * 60 * 60 * 1000)) { baseSalary = emp.contracts[0].probationSalary // ← 只要入职不到1年就用试用期工资,转正后仍用 } else if (emp.monthlySalary) { ... } ``` 问题:员工转正后(如试用期 3 个月,入职 6 个月),仍会被判定为试用期,`baseSalary` 错误填为 `probationSalary` 而非转正工资。 **正确逻辑**(`payroll.service.ts:326-331`、`payroll.service.ts:600-603` 已实现): ```ts const probationEnd = new Date(contract.startDate) probationEnd.setMonth(probationEnd.getMonth() + contract.probationMonths) if (probationEnd > new Date()) { ... } // 试用期未结束 ``` - **修复方向**: 1. 抽取统一的试用期判定工具函数 `isInProbation(contract, referenceDate?)`:`contract.startDate + contract.probationMonths > referenceDate`(默认当前日期,批次场景按批次月份的月末判定)。 2. 修正 3 处错误逻辑,统一调用 `isInProbation`: - `payroll2.routes.ts:355-359`(创建批次 copy_last 模式) - `payroll2.routes.ts:610-614`(添加人员到批次) - `payroll.routes.ts:380-388`(旧版批量生成工资条,如仍在用) 3. 判定为试用期 → `baseSalary = contract.probationSalary`;已转正 → `baseSalary = employee.monthlySalary`。 4. 批次场景按批次月份判定(如批次月份是 2026-08,则判定 2026-08-31 时员工是否仍在试用期),避免跨月转正时填错。 5. `probationSalary` 为 0 或未填时,即使试用期也回退用 `monthlySalary`(兼容旧数据)。 - **优先级**:P1 高(影响工资发放正确性) - **涉及文件**:`backend/src/routes/payroll2.routes.ts`(355-359、610-614)、`backend/src/routes/payroll.routes.ts`(380-388)、`backend/src/services/payroll.service.ts`(抽取 `isInProbation` 工具函数,或放 `contract.service.ts`) ### 问题 13:编辑薪资新增"获取提成奖金"功能 - **现状**:`money/BatchTab.tsx` 已有 `BONUS` 批次类型(`isBonus` 分支,617 行),但普通薪资批次编辑薪资时无"获取提成奖金"按钮,无法从提成奖金数据按月拉取填充 `bonus` 字段。 - **修复方向**: 1. 编辑薪资步骤新增"获取提成奖金"按钮,调用后端接口按批次月份从提成奖金表拉取每位员工的提成奖金。 2. 拉取的奖金填入 `entry.bonus` 字段,支持正负值(奖金/扣款)。 3. 后端新增接口 `POST /payroll2/batches/:id/fetch-bonus`,按批次月份查询提成奖金表并返回。 4. 前端展示获取结果摘要(X 人提成奖金已填充,合计 ¥Y),支持覆盖确认。 5. 依赖问题 12 的提成奖金数据表先建立。 - **优先级**:P2 中(依赖问题 12) - **涉及文件**:`frontend/src/pages/money/BatchTab.tsx`(编辑薪资步骤按钮区)、`backend/src/routes/payroll.routes.ts`、`backend/src/services/payroll.service.ts`、`backend/src/services/commission-bonus.service.ts`(新增) --- ## 四、提成奖金 · 新模块(问题 12) ### 问题 12:团队页面下新增提成奖金页面 - **现状**:`SidebarNav.tsx:44` "团队"分组下仅有花名册,无提成奖金入口;`money/BatchTab.tsx` 的 `BONUS` 批次是年终奖发放,非按月提成奖金管理。需新建独立提成奖金模块。 - **修复方向**: 1. **数据模型**:新增 `CommissionBonus` 表(`orgId`、`employeeId`、`month` YYYY-MM、`amount` 支持正负值、`remark`、`createdAt`、`createdBy`),唯一索引 `(orgId, employeeId, month)`。 2. **后端**:新增 `commission-bonus.service.ts` + `commission-bonus.routes.ts`: - `GET /commission-bonus?month=YYYY-MM` 按月列表 - `POST /commission-bonus/batch` 按月批量导入(JSON 数组或 Excel) - `PUT /commission-bonus/:id` 编辑单条 - `DELETE /commission-bonus/:id` 删除单条 - `GET /commission-bonus/template` 下载导入模板 3. **前端**:新增 `frontend/src/pages/CommissionBonus.tsx` 页面: - 月份选择器(默认当月) - 列表展示该月所有员工提成奖金(员工/部门/金额/备注) - 支持单条编辑、删除 - "批量导入"按钮(上传 Excel,字段:员工姓名/证件号码/月份/金额/备注) - 金额支持正负值(正为奖金,负为扣款) - 合计展示(总奖金/总扣款/净额) 4. **菜单**:`SidebarNav.tsx` "团队"分组新增"提成奖金"入口,路由 `/commission-bonus`。 5. **权限**:新增权限码 `PERM_COMMISSION_BONUS_VIEW`/`PERM_COMMISSION_BONUS_EDIT`/`PERM_COMMISSION_BONUS_IMPORT`。 - **优先级**:P3 规划(新模块) - **涉及文件**:`backend/prisma/schema.prisma`(新增 `CommissionBonus` 模型 + migration)、`backend/src/services/commission-bonus.service.ts`(新增)、`backend/src/routes/commission-bonus.routes.ts`(新增)、`frontend/src/pages/CommissionBonus.tsx`(新增)、`frontend/src/components/layout/SidebarNav.tsx`、`frontend/src/App.tsx`(路由注册)、`frontend/src/lib/api-services.ts`(`commissionBonusApi`) --- ## 优先级汇总 | 优先级 | 问题 | 说明 | 状态 | |--------|------|------|------| | **P0 紧急** | 10A | 风险提醒反向引导(三期/医疗期/工伤提醒跳转到解聘页面) | ✅ 已完成 | | **P1 高** | 4 | 合同状态增加待签署判定 | ✅ 已完成 | | **P1 高** | 5 | 社保状态派生字段(待办/正常/停缴) | ✅ 已完成 | | **P1 高** | 6 | 证件号码有效性校验(校验位+出生日期) | ✅ 已完成 | | **P1 高** | 7 | 超龄人员合同类型限制(禁止选劳动合同) | ✅ 已完成 | | **P1 高** | 8 | 劳务协议社保联动(选 LABOR 社保基数置 0 禁用) | ✅ 已完成 | | **P1 高** | 9 | 待办跳转 actionUrl 修正 + 空值降级 | ✅ 已完成 | | **P1 高** | 11 | 试用期工资判定修正(抽取 isInProbation 工具函数) | ✅ 已完成 | | **P2 中** | 10B | 风险提醒规则优化(到期 30→60 天分级 + 去重排序) | ✅ 已完成 | | **P2 中** | 13 | 薪资批次获取提成奖金(依赖 12) | ✅ 已完成 | | **P3 规划** | 12 | 提成奖金新模块(DB migration + 全栈实现) | ✅ 已完成 | --- ## 建议执行顺序 > ✅ 全部 10 项已于 20260816 完成实现,前后端 tsc 均通过。 1. **第一批(P0 + P1,立即)** ✅: - 问题 10A(风险提醒反向引导,改 actionUrl)← 最先,改动小风险高 - 问题 11(试用期工资判定修正)← 纯后端逻辑修正,抽取 `isInProbation` 工具函数 - 问题 6(证件号码有效性校验)→ 问题 7(超龄人员合同类型限制)→ 问题 8(劳务协议社保联动)→ 问题 4(合同状态待签署)→ 问题 5(社保状态待办)→ 问题 9(待办跳转) - 问题 6/7/8 均在 `roster/modals.tsx` 添加雇员表单,可一并修改;问题 4/5 在后端 `contract.service.ts`/`employee.service.ts` 派生字段,前后端协同。 2. **第二批(P2,本迭代)** ✅: - 问题 10B(风险提醒规则梳理)→ 问题 13(获取提成奖金,依赖问题 12) 3. **第三批(P3,规划)** ✅: - 问题 12(提成奖金新模块,含 DB migration)→ 问题 13(获取提成奖金,依赖 12 完成) --- ## 备注 - 本清单基于当期代码静态分析,部分"根因"为推断,实际修复前需运行复现确认。 - 问题 12 涉及数据库 schema 变更(新增 `CommissionBonus` 表),需走 migration + 回滚方案。 - 问题 4/5 的"待签署""待办"状态为派生字段,不新增存储字段,避免数据冗余;如需记录"办理完成"操作时间,可新增 `socialInsConfirmedAt` 等时间戳字段。 - 问题 7 的超龄判定需引用 `femaleWorkerType`(干部 55/工人 50),需确认该字段在添加雇员表单已正确填写。 - 问题 13 依赖问题 12 的提成奖金数据表,不可先行实现。 --- ## 五、已完成优化(20260816 补充) > 以下为本次会话中已完成并部署的优化项。 ### ✅ 已完成 1:员工端密码登录完整支持 - **默认密码**:创建员工时自动设置,密码 = 手机号后6位(无手机号则 `123456`) - **管理员重置密码**:花名册详情 → "重置密码"按钮,重置为手机号后6位 - **员工修改密码**:员工端导航栏 → "修改密码",需验证旧密码,新密码至少6位 - **Excel 批量导入**:导入时自动设置默认密码(手机号后6位),与新增员工一致 - **现有员工补初始化**:已通过脚本批量补设 512 名无密码员工(478 人手机号后6位,34 人无手机号用 123456) - **涉及文件**: - `backend/src/services/contract.service.ts`(createEmployee 添加 passwordHash) - `backend/src/routes/import.routes.ts`(Excel 导入添加 passwordHash) - `backend/src/routes/employee.routes.ts`(新增 `POST /employees/:id/reset-password`) - `backend/src/routes/portal.routes.ts`(新增 `POST /portal/change-password`) - `frontend/src/pages/roster/BasicInfo.tsx`(重置密码按钮 + 默认密码提示) - `frontend/src/pages/portal/PortalNav.tsx`(修改密码弹窗) - `frontend/src/lib/api-services.ts`(resetPassword / changePassword 接口) ### ✅ 已完成 2:员工端 portalAxios response interceptor 修复 - **问题**:`portalAxios` 缺少 response interceptor,导致 `unwrap` 取到的是 `{ success, data: {...} }` 而非 `{ token, employee }`,密码登录/验证码登录/扫码自动登录全部失败 - **修复**:给 `portalAxios` 添加与管理端 `api` 实例一致的 response interceptor `(response) => response.data` - **涉及文件**:`frontend/src/lib/api-services.ts` ### ✅ 已完成 3:同企业内员工身份证和手机号唯一性完整校验 - **createEmployee**:新增手机号查重硬校验(原有身份证查重) - **updateEmployee**:新增手机号 + 身份证查重(排除自身) - **Excel 批量导入**:新增身份证 + 手机号查重(重复则跳过并记录错误日志) - **portal 登录安全**:密码登录改为 `findMany` 遍历校验,避免跨组织手机号重复时登录到错误员工 - **涉及文件**: - `backend/src/services/contract.service.ts`(createEmployee / updateEmployee 查重) - `backend/src/routes/import.routes.ts`(导入查重) - `backend/src/routes/portal.routes.ts`(login / send-code / verify-code 跨组织安全) ### ✅ 已完成 4:电子签署改为「待签合同」,按员工聚合 + 催办功能 - **菜单改名**:「电子签署」→「待签合同」 - **页面重构为两个 Tab**: - **待签合同**:按员工聚合,每行一个员工,显示待签数,点击展开查看该员工名下所有待签文件(EsignRecord PENDING/SIGNING + LaborContract signDate 为空) - **签署记录**:原有全部签署记录列表(保留状态/场景筛选) - **催办功能**:点击催办 → 生成 24 小时有效的一次性自动登录链接(指向员工端签署页)→ 弹窗展示二维码 + 可复制链接 → HR 发给员工扫码直接进入签署 - **后端新增接口**: - `GET /esign/pending` — 待签合同按员工聚合列表 - `POST /esign/remind` — 催办生成自动登录链接 - **涉及文件**: - `backend/src/routes/esign.routes.ts`(pending + remind 接口) - `frontend/src/components/layout/SidebarNav.tsx`(菜单改名) - `frontend/src/pages/ESign.tsx`(Tab 切换 + PendingTab 组件 + 催办弹窗) - `frontend/src/lib/api-services.ts`(pending / remind 接口调用) ### ✅ 已完成 5:风险提醒反向引导修正(问题 10A) - **问题**:三期/医疗期/工伤员工的风险提醒文案是"解聘受限",但 `actionUrl` 却指向 `/termination`(解聘向导),等于提示"不能解聘,点这里去解聘她"——反向引导,可能诱导违法解除。 - **修复**:3 处 `actionUrl` 从 `/termination?employee=xxx` 改为 `/special-status?employee=xxx&type=PREGNANCY|MEDICAL_PERIOD|WORK_INJURY`,让 HR 确认员工状态而非跳去解聘。文案补充"此为合规提示,请勿发起解聘"。 - **涉及文件**:`backend/src/services/risk.service.ts`(`detectTerminationRisks` 309-342 行) ### ✅ 已完成 6:试用期工资判定修正(问题 11) - **问题**:3 处获取工资逻辑用错误的试用期判定 `startDate > now - 365d`(入职不到1年),转正后仍用 `probationSalary`。正确逻辑应是 `startDate + probationMonths > now`(试用期未结束)。 - **修复**: 1. 抽取统一工具函数 `isInProbation(contract, referenceDate?)` 到 `contract.service.ts`。 2. 修正 3 处错误逻辑,统一调用 `isInProbation`,按批次月份月末判定。 3. `probationSalary` 为 0 时回退用 `monthlySalary`(兼容旧数据)。 - **涉及文件**: - `backend/src/services/contract.service.ts`(新增 `isInProbation`) - `backend/src/routes/payroll2.routes.ts`(copy_last 模式 355 行、添加人员 614 行) - `backend/src/routes/payroll.routes.ts`(旧版批量生成 380 行) ### ✅ 已完成 7:证件号码有效性校验(问题 6) - **问题**:添加雇员仅校验证件号码长度 ≥18,未做校验位、出生日期合法性验证。 - **修复**: 1. 前端 `handleIdCardChange` 增加校验位算法(GB 11643-1999)、出生日期合法性(月份/日期范围、是否存在如2月30日、是否晚于今天),校验失败阻断保存。 2. 后端新增 `validateIdCard`/`getAgeFromIdCard`/`isOverageEmployee` 工具函数,`createEmployee` 增加双保险校验。 - **涉及文件**: - `frontend/src/pages/roster/modals.tsx`(`handleIdCardChange`、`canSubmit`) - `backend/src/services/contract.service.ts`(新增 3 个工具函数 + `createEmployee` 校验) ### ✅ 已完成 8:超龄人员合同类型限制(问题 7) - **问题**:超龄人员仅 WARN 提示,仍可选劳动合同(FIXED/UNFIXED),违反合规要求。 - **修复**: 1. 前端派生 `isOverage`(男≥60、女干部≥55、女工人≥50),合同类型下拉过滤掉 FIXED/UNFIXED,仅保留 LABOR/INTERNSHIP/UNSIGNED。 2. 草稿恢复时若当前合同类型非法,useEffect 自动切到 UNSIGNED。 3. 后端 `createEmployee` 增加校验:超龄 + FIXED/UNFIXED 拒绝并返回 400。 - **涉及文件**: - `frontend/src/pages/roster/modals.tsx`(`isOverage` 派生、合同类型下拉过滤、useEffect 自动修正) - `backend/src/services/contract.service.ts`(`createEmployee` 超龄校验) ### ✅ 已完成 9:劳务协议社保联动(问题 8) - **问题**:选劳务协议(LABOR)时社保基数未置 0 且仍可缴纳,违反合规要求。 - **修复**: 1. 前端选 LABOR/INTERNSHIP 时,社保/公积金基数自动置 0、起始月清空、字段禁用,提示"劳务协议/实习协议人员不缴纳社保公积金"。 2. 后端 `createEmployee` 强制:LABOR/INTERNSHIP 时 `socialInsBase`/`housingFundBase` = 0,起始月清空。 - **涉及文件**: - `frontend/src/pages/roster/modals.tsx`(合同类型 onChange 联动、社保区 disabled) - `backend/src/services/contract.service.ts`(`createEmployee` 社保强制 0) ### ✅ 已完成 10:合同状态增加待签署判定(问题 4) - **问题**:有合同记录但 `signDate` 为空时仍显示"正常",应显示"待签署"。 - **修复**:`getContractStatus` 增加判定:有合同记录但 `signDate` 为空 → `status: 'pending_sign'`,`statusText: '待签署'`,`riskLevel: 'medium'`。电子签署完成后会回写 `signDate`,故 `signDate` 为空即未签署。前端花名册列表 `statusTextMap` 增加 `pending_sign: '待签署'`。 - **涉及文件**: - `backend/src/services/contract.service.ts`(`getContractStatus` 74-91 行重写) - `frontend/src/pages/Roster.tsx`(`tagStyles`/`statusTextMap` 增加 `pending_sign`) ### ✅ 已完成 11:社保状态派生字段(问题 5) - **问题**:社保状态无"待办"判定,未办理社保的在职员工显示"—"而非"待办理"。 - **修复**:后端 `socialInsuranceStatus` 派生逻辑: - 劳务协议/实习协议 → null(不缴社保,显示"—") - 在职 + 应缴社保 + 无社保记录 → `PENDING`(待办理) - 有社保记录 + endMonth 为 null → `ACTIVE`(在保) - 有社保记录 + endMonth 不为 null → `SUSPENDED`(停保) - 前端已有 PENDING/UNINSURED 的标签映射,无需改前端。 - **涉及文件**:`backend/src/routes/roster.routes.ts`(`socialInsuranceStatus` 派生 243-249 行) ### ✅ 已完成 12:待办跳转 actionUrl 空值降级(问题 9) - **问题**:待办事项的"立刻办理"链接在 `actionUrl` 为空或 '/' 时仍渲染为 Link,点击跳首页无意义。 - **修复**:Dashboard 待办列表渲染时,`actionUrl` 为空或 '/' 时不渲染 Link 和"立刻办理"按钮,改为纯展示 div;有有效 URL 时才渲染可跳转链接。问题 10A 已修正三期/医疗期/工伤的反向引导 URL,其余 actionUrl(`/roster?employee=xxx`、`/special-status`、`/money`)均指向有效路由,Roster 已处理 `?employee` 参数自动定位。 - **涉及文件**:`frontend/src/pages/Dashboard.tsx`(待办列表渲染 1136-1233 行) ### ✅ 已完成 13:风险提醒规则优化(问题 10B) - **问题**:合同到期提醒仅 30 天预警,HR 反应时间不足;去重后的待办列表未按优先级排序。 - **修复**: 1. 合同到期预警从 30 天提前到 60 天,分级:≤30 天 HIGH(需立即处理),31-60 天 MEDIUM(提前预警)。 2. `dedupedTodos` 按优先级排序(URGENT > HIGH > MEDIUM > LOW),确保最紧急的待办排在最前。 - **涉及文件**:`backend/src/services/risk.service.ts`(`detectContractRisks` 216-248 行、`getDashboardData` dedupedTodos 排序 941-952 行) ### ✅ 已完成 14:提成奖金新模块(问题 12) - **问题**:无独立提成奖金管理模块,`BONUS` 批次是年终奖发放,非按月提成奖金管理。 - **实现**: 1. **数据模型**:新增 `CommissionBonus` 表(`orgId`、`employeeId`、`month` YYYY-MM、`amount` 支持正负值、`remark`、`createdBy`),唯一索引 `(orgId, employeeId, month)`。 2. **后端**:新增 `commission-bonus.service.ts` + `commission-bonus.routes.ts`: - `GET /commission-bonus?month=YYYY-MM` 按月列表 + 汇总(总奖金/总扣款/净额) - `POST /commission-bonus` 新增单条(upsert,同员工同月自动覆盖) - `PUT /commission-bonus/:id` 编辑单条 - `DELETE /commission-bonus/:id` 删除单条 - `POST /commission-bonus/import` 批量导入 Excel(字段:员工姓名/证件号码/金额/备注) - `GET /commission-bonus/template` 下载导入模板 3. **前端**:新增 `CommissionBonus.tsx` 页面: - 月份选择器(默认当月) - 汇总卡片(记录数/总奖金/总扣款/净额) - 列表展示(员工/部门/金额/备注),支持搜索、分页 - 单条新增/编辑/删除 - 批量导入 Excel + 模板下载 - 金额支持正负值(正=奖金,负=扣款) 4. **菜单**:`SidebarNav.tsx` "团队"分组新增"提成奖金"入口,路由 `/commission-bonus`。 5. **DB 同步**:`prisma db push` 已执行,Prisma Client 已重新生成。 - **涉及文件**: - `backend/prisma/schema.prisma`(新增 `CommissionBonus` 模型 + Employee/Organization 反向关联) - `backend/src/services/commission-bonus.service.ts`(新增) - `backend/src/routes/commission-bonus.routes.ts`(新增) - `backend/src/app.ts`(路由注册) - `frontend/src/pages/CommissionBonus.tsx`(新增) - `frontend/src/components/layout/SidebarNav.tsx`(菜单项) - `frontend/src/App.tsx`(路由注册) - `frontend/src/lib/api-services.ts`(`commissionBonusApi`) ### ✅ 已完成 15:获取提成奖金(问题 13) - **问题**:薪资批次编辑薪资时无"获取提成奖金"按钮,无法从提成奖金模块按月拉取填充 `bonus` 字段。 - **实现**: 1. 后端新增 `POST /payroll2/batches/:batchId/fetch-bonus`:按批次月份从 `CommissionBonus` 表拉取,填充到对应 `entries.bonus`,重算批次汇总。 2. 前端 `BatchTab.tsx` 编辑薪资步骤新增"获取提成奖金"按钮(在"导入加班费"旁),调用后展示结果摘要(X 人提成奖金已填充,合计 ¥Y)。 3. 已归档批次禁用按钮。 - **涉及文件**: - `backend/src/routes/payroll2.routes.ts`(`fetch-bonus` 接口 552-628 行) - `frontend/src/lib/api-services.ts`(`fetchBonusToBatch`) - `frontend/src/pages/money/BatchTab.tsx`(`fetchBonusMutation` + 按钮 784-798 行)