# 20260809 优化需求清单 > 基于用户反馈整理,共 28 项问题,按模块和优先级分类。 > > **代码审查更新**:2026-08-09 完成全量代码核查,补充实际代码定位和确认结果。 --- ## 一、员工福利模块 ### 问题1:福利方案创建后无法添加享受人员,批量参保无人员数据 **模块**:员工福利 **优先级**:P0 **状态**:待验证 **现状描述**: 创建好福利方案后,无法增加享受福利的人员,批量参保时无人员数据可选。 **代码核查结果**: 功能实际已实现。`EmployeeBenefits.tsx` 中有完整的批量参保功能: - 点击福利方案卡片可展开参保人员列表(`EmployeeBenefits.tsx:228`) - 「批量参保」按钮打开 Modal,加载花名册在职员工列表(`:232`) - 支持全选/勾选员工,设置生效月份,提交参保(`:395-456`) - `rosterApi.list` 查询 `pageSize: 200` 条员工数据(`:78`) **潜在问题**:`rosterData` 查询仅在 `showEnrollModal` 为 true 时启用(`enabled: showEnrollModal`),如果员工超过200人则无法全部加载。建议改用不分页的 `allLite` 接口。 **涉及文件**: - `frontend/src/pages/EmployeeBenefits.tsx` 福利方案和批量参保 - `frontend/src/lib/api-services.ts` benefitApi 定义 - `backend/src/routes/benefits.routes.ts` **优化方案**: 1. 批量参保的员工列表改用 `allLite` 接口,避免200条限制 2. 增加按部门筛选功能 3. 验证实际运行时员工列表是否正常加载 --- ## 二、全局通用问题 ### 问题2:多个模块中每页条数选择无反应 **模块**:全局(花名册、薪税、考勤等多个列表页) **优先级**:P1 **状态**:待验证 **现状描述**: 多个模块列表页底部的「每页条数」选择器点击后无反应,无法切换每页显示条数。 **代码核查结果**: `usePageSize` hook(`frontend/src/hooks/usePageSize.ts:1-18`)通过 `localStorage` 持久化,并通过 `page-size-changed` 自定义事件实现跨页面响应。`Pagination` 组件(`frontend/src/components/ui/Pagination.tsx:44-53`)在 `onPageSizeChange` 时触发回调。 **疑似问题**:多个列表页在 `onPageSizeChange` 回调中仅调用 `setPage(1)` 但未显式传递新的 `pageSize` 值。例如 `Evidence.tsx:132`: ```tsx onPageSizeChange={() => setPage(1)} ``` 由于 `usePageSize` hook 返回的 `pageSize` 是全局状态,变更后自动触发 queryKey 变化,理论上应该能工作。需实际运行验证事件监听是否在所有页面正确触发重渲染。 **涉及文件**: - `frontend/src/hooks/usePageSize.ts:1-18` 全局 pageSize 状态管理 - `frontend/src/lib/pageSize.ts:1-21` getPageSize/setPageSize 工具函数 - `frontend/src/components/ui/Pagination.tsx:44-53` 分页组件 - `frontend/src/pages/Settings.tsx:15-245` 全局设置页 - `frontend/src/pages/AuditLog.tsx:118-246` 使用示例 - `frontend/src/pages/Evidence.tsx:127-133` 疑似问题点 **优化方案**: 1. 验证 `usePageSize` 的 `page-size-changed` 事件是否在所有页面正确触发 2. 确保所有列表页 `onPageSizeChange` 回调中 `setPage(1)` 后 queryKey 包含 `pageSize` 3. 全局统一分页组件,确保所有列表页行为一致 --- ## 三、离职管理模块 ### 问题3:离职证明下载内容为乱码 **模块**:离职管理 **优先级**:P0 **状态**:待修复 **现状描述**: 离职管理中下载的离职证明文件内容是一团乱码,无法正常阅读。 **问题分析**: - `work-process.service.ts` 生成的 `.doc` 文件为纯文本格式,Word 打开时可能出现编码问题 - 文件下载时 `Content-Type` 和编码声明可能不正确 - 前端下载方式可能未正确处理二进制流 **涉及文件**: - `backend/src/services/work-process.service.ts:246-290` .doc 文件生成 - `backend/src/routes/work-process.routes.ts` 下载接口 - `frontend/src/pages/WorkProcess.tsx` 下载逻辑 - `frontend/src/pages/Termination.tsx:330-400` 离职管理页面 **优化方案**: 1. 在生成的 `.doc` 内容头部添加 BOM 标记(`\uFEFF`),确保 Word 正确识别 UTF-8 编码 2. 后端下载接口设置正确的 `Content-Type: application/msword; charset=utf-8` 3. 前端下载时使用 Blob 并指定编码 4. 考虑生成 HTML 格式的 Word 文件(带 `xmlns:o` 命名空间),确保格式正确 --- ### 问题4:离职管理导出数据缺少筛选条件 **模块**:离职管理 **优先级**:P1 **状态**:待优化 **现状描述**: 离职管理导出数据时一次性导出全部数据,无法按时间范围等条件筛选导出。 **问题分析**: - 导出接口未接收前端筛选参数,直接查询全部离职记录 - 前端导出按钮未传递当前筛选条件 **涉及文件**: - `backend/src/routes/export.routes.ts` 导出接口(含 terminations 导出) - `backend/src/routes/termination.routes.ts` 离职路由 - `frontend/src/pages/Termination.tsx:330-400` 导出按钮 - `frontend/src/lib/api-services.ts:613-696` terminationApi 定义 **优化方案**: 1. 导出接口增加 `dateFrom`、`dateTo`、`department`、`status` 等查询参数 2. 前端导出时携带当前筛选条件 3. 增加导出确认弹窗,显示筛选范围和预计条数 --- ### 问题5:已提交的离职数据无法撤回,已撤回的无用数据无法删除 **模块**:离职管理 **优先级**:P1 **状态**:待修复 **现状描述**: 离职管理中已提交的数据无法撤回操作,已撤回的无用数据无法删除清理。 **代码核查结果**: - 后端 `termination.service.ts:256-334` 有 `revokeTermination` 方法,路由 `termination.routes.ts:81-115` 有 `DELETE /:id/revoke` 端点 - 前端 `api-services.ts:613-696` 有 `revoke` 方法定义 - **但前端 `Termination.tsx` 页面未暴露撤回和删除草稿的按钮**——UI 缺少对应操作入口 - 后端有 `cancelTermination`(`termination.service.ts:729-954`)和 `getDrafts` 方法 **涉及文件**: - `backend/src/services/termination.service.ts:256-334` revokeTermination - `backend/src/services/termination.service.ts:729-954` cancelTermination, getDrafts - `backend/src/routes/termination.routes.ts:81-115` 撤回路由 - `backend/src/routes/termination.routes.ts:128-234` 草稿管理路由 - `frontend/src/lib/api-services.ts:613-696` terminationApi.revoke/cancel - `frontend/src/pages/Termination.tsx:330-400` **缺少撤回/删除按钮** **优化方案**: 1. 前端 `Termination.tsx` 为已提交但未完成的离职流程增加「撤回」按钮 2. 已撤回的草稿数据允许删除,增加二次确认 3. 已完成离职的记录保留不可删除(合规要求) --- ### 问题6:用工办理中离职/解聘与离职管理模块重复 **模块**:用工办理 / 离职管理 **优先级**:P2 **状态**:待优化 **现状描述**: 用工办理中有员工离职、解聘功能,同时还有独立的离职管理模块,功能重复,显得混乱。 **代码核查结果**: - `WorkProcess.tsx:37-40` 包含 `TERMINATE`(合同终止)、`RESCIND`(合同解除)、`LEAVING_CERT`(离职证明)等流程类型 - `Termination.tsx` 是独立的离职管理页面,含草稿管理、审批、执行等完整流程 - 两个入口功能确实重叠 **涉及文件**: - `frontend/src/pages/WorkProcess.tsx:37-40` 流程类型定义 - `frontend/src/pages/Termination.tsx:330-400` 离职管理页面 - `frontend/src/components/layout/SidebarNav.tsx` **优化方案**: 1. 用工办理中保留「入职办理」「转正」「调岗」等入职相关流程 2. 离职、解聘相关流程统一归入「离职管理」模块 3. 侧边栏菜单分组明确:用工办理(入职类)→ 离职管理(离职类) --- ## 四、考勤管理模块 ### 问题7:考勤导入模板包含无关Sheet,且加班/违纪/考勤三个Sheet需合并 **模块**:考勤管理 **优先级**:P0 **状态**:待优化 **现状描述**: 导入考勤的模板包含「员工信息」和「劳动合同」两个无关 Sheet,只录入考勤信息无法导入。加班记录、违纪记录、考勤记录三个 Sheet 录入同一人员时需重复粘贴姓名与身份证号,应合并。 **代码核查结果**: - `backend/src/routes/import.routes.ts:494-692` 模板下载接口生成包含:员工信息、劳动合同、考勤记录、加班记录、违纪记录等多个 Sheet - `gen_import_sample.py:50-73` Python 脚本也生成了包含多余 Sheet 的示例文件 - 导入接口 `import.routes.ts` 处理 `考勤记录`、`加班记录`、`违纪记录`、`薪资调整`、`社保变动`、`公积金变动` 等多个 Sheet - **确认**:模板确实包含无关的员工信息和劳动合同 Sheet **涉及文件**: - `backend/src/routes/import.routes.ts:494-692` 模板下载和导入处理 - `frontend/src/pages/Attendance.tsx:525-610` 前端导入弹窗 - `gen_import_sample.py:50-73` 示例文件生成脚本 **优化方案**: 1. 考勤导入模板只保留考勤相关 Sheet,移除员工信息和劳动合同 Sheet 2. 将考勤记录、加班记录合并为一个 Sheet,用列区分(日期、班次、签到时间、签退时间、加班时长等) 3. 违纪记录因字段差异较大,可保留独立 Sheet 或独立导入入口 4. 每项业务(考勤、加班、违纪)提供独立的专用模板下载 --- ### 问题8:补卡无法修改未打卡状态,签到签退时间显示有问题 **模块**:考勤管理 - 每日出勤 **优先级**:P0 **状态**:待修复 **现状描述**: 考勤排班中每日出勤页面,操作补卡时无法修改未打卡状态,且签到与签退的时间显示有异常。 **代码核查结果**: - `backend/src/services/attendance.service.ts:264-360` 的 `manualCorrectAttendance` 方法支持更新考勤记录,可设置签到/签退时间和状态 - `backend/src/routes/attendance.routes.ts:210-233` 有 `POST /manual-correct` 端点 - `frontend/src/pages/Attendance.tsx:970-1174` 的 DailyTab 有补卡弹窗和按钮 - `frontend/src/lib/api-services.ts:247-297` 有 `manualCorrect` API 调用 - 考勤状态常量定义在 `Attendance.tsx:25-33`:NORMAL/LATE/EARLY_LEAVE/ABSENT/LEAVE/BUSINESS_TRIP/UNREGISTERED - **需确认**:补卡弹窗是否限制了状态选项(未覆盖 UNREGISTERED→其他状态的修正),以及时间格式化是否有时区问题 **涉及文件**: - `frontend/src/pages/Attendance.tsx:25-33` 状态常量定义 - `frontend/src/pages/Attendance.tsx:970-1174` DailyTab 补卡弹窗 - `frontend/src/lib/api-services.ts:247-297` attendanceApi.manualCorrect - `backend/src/services/attendance.service.ts:264-360` manualCorrectAttendance - `backend/src/routes/attendance.routes.ts:210-233` 补卡路由 **优化方案**: 1. 补卡弹窗允许修改所有考勤状态(包括未打卡→已打卡/请假/出差等) 2. 检查时间字段的时区处理,确保显示本地时间 3. 签到签退时间统一格式化为 `HH:mm` 格式 --- ### 问题9:加班费计算与考勤不关联,需重复导入 **模块**:考勤管理 / 薪税管理 **优先级**:P1 **状态**:待优化 **现状描述**: 加班费计算时需要再导入一遍考勤数据,与考勤管理模块的数据不关联。 **问题分析**: - 加班费计算模块可能独立于考勤管理,未从已有的考勤记录中读取加班时长 - 考勤管理中的加班数据未传递到薪税计算的加班费环节 **涉及文件**: - `frontend/src/pages/money/` 加班费相关组件 - `backend/src/routes/payroll2.routes.ts` 加班费计算逻辑 - `backend/src/routes/attendance.routes.ts` 考勤数据查询 - `backend/src/routes/import.routes.ts` 考勤导入(含加班记录 Sheet) **优化方案**: 1. 加班费计算改为从考勤管理模块读取已确认的加班记录 2. 薪税批次创建时自动拉取当月考勤加班数据,无需重复导入 3. 保留手动导入作为备选方案 --- ### 问题10:个人考勤记录添加后加班汇总不显示 **模块**:考勤管理 **优先级**:P1 **状态**:待修复 **现状描述**: 个人考勤记录添加时手动填写了加班时长,但加班汇总中不显示条数,不清楚加班汇总关联的是哪里。 **问题分析**: - 加班汇总可能统计的是考勤导入的加班数据,而非手动添加的加班时长 - 加班汇总的数据源与个人考勤记录的加班字段未关联 **涉及文件**: - `frontend/src/pages/Attendance.tsx:970-1174` 加班汇总和考勤记录 - `backend/src/routes/attendance.routes.ts` 加班统计接口 **优化方案**: 1. 加班汇总统计应包含手动添加的考勤记录中的加班时长 2. 加班汇总增加数据来源标识(导入/手动添加) 3. 明确加班汇总与考勤记录的关联关系,UI 上增加说明 --- ## 五、证据链模块 ### 问题11:验证全部完整性功能简陋,无法定位异常 **模块**:证据链 **优先级**:P1 **状态**:待优化 **现状描述**: 证据链中「验证全部完整性」功能验证后显示异常,但无法告知哪部分异常,下方提醒也无法跳转操作。 **代码核查结果**: - `frontend/src/pages/Evidence.tsx:31-37` 调用 `evidenceApi.verifyAll()`,返回结果仅显示 `total`、`valid`、`invalid` 三个数字(`:60-75`) - 无详细异常项列表,无跳转操作 - `frontend/src/lib/api-services.ts:700-707` `evidenceApi` 定义了 `list` 和 `verifyAll` 方法 - `frontend/src/pages/roster/EvidenceChain.tsx:1-155` 是员工个人维度的仲裁证据链,展示证据列表、风险提醒和导出功能 **涉及文件**: - `frontend/src/pages/Evidence.tsx:31-75` 验证全部完整性功能 - `frontend/src/lib/api-services.ts:700-707` evidenceApi 定义 - `frontend/src/pages/roster/EvidenceChain.tsx:1-155` 员工个人证据链 - `backend/src/routes/roster.routes.ts` 证据链验证接口 **优化方案**: 1. 验证接口返回详细的检查项列表(每项:名称、状态、异常描述) 2. 前端展示验证结果明细,异常项高亮显示 3. 每个异常项增加「去处理」跳转按钮,跳转到对应模块 --- ## 六、规章制度管理 ### 问题12:规章制度签收缺少催办和未签收人员查看 **模块**:规章制度 **优先级**:P1 **状态**:待优化 **现状描述**: 规章制度向员工公示后,签收只显示签收人数和占比,无法查看具体未签收人员,也无法催办。 **代码核查结果**: - `frontend/src/pages/Policies.tsx:249-285` 有 `ReadStats` 组件,展示签收百分比和未签收人数 - 已签收人员列表可展开查看(`:278-285`),显示姓名、部门、签收时间 - **缺少催办通知功能**——无催办按钮 - **未签收人员列表未展示**——仅显示未签收人数(`:273-277`),未列出具体人员 - `frontend/src/lib/api-services.ts:674-696` `policiesApi.readStats` 返回 `readCount`、`total`、`unreadCount`、`records` - 员工端 `frontend/src/pages/portal/MyPolicies.tsx:36-46` 有阅读确认 mutation 和待签收数量统计 **涉及文件**: - `frontend/src/pages/Policies.tsx:110-120` 签收进度条 - `frontend/src/pages/Policies.tsx:246-285` ReadStats 组件 - `frontend/src/lib/api-services.ts:674-696` policiesApi 定义 - `frontend/src/pages/portal/MyPolicies.tsx:30-50` 员工端阅读确认 - `backend/src/routes/regulations.routes.ts` **优化方案**: 1. 签收统计增加「查看明细」按钮,展开已签收/未签收人员列表 2. 未签收人员列表支持「一键催办」,发送通知提醒员工签收 3. 显示每位员工的签收状态和时间 --- ## 七、文本模板模块 ### 问题13:新建模板不支持导入文档,现有方式易造成格式混乱 **模块**:文本模板 **优先级**:P1 **状态**:待优化 **现状描述**: 文本模板新建时只能手动输入内容,无法通过导入 Word 文档创建,现有方式容易造成格式混乱,需要保留导入文档的原始格式。 **代码核查结果**: - `frontend/src/pages/Templates.tsx:303-571` 的 `EnterpriseTemplates` 组件中,新建模板仅支持 `textarea` 手动输入内容(`:493-498`) - 模板内容使用 `{{变量名}}` 占位符,支持变量替换渲染 - 系统模板支持下载 Word(`.doc` 格式),通过 `fetch` 请求 `/templates/:id/download` - `frontend/src/lib/api-services.ts:814-839` `templatesApi` 无文档导入接口 - **确认**:无文档上传入口,不支持导入 `.docx` 文件 **涉及文件**: - `frontend/src/pages/Templates.tsx:1-571` 模板管理页面(系统模板+企业模板) - `frontend/src/lib/api-services.ts:814-839` templatesApi 定义 - `backend/src/routes/templates.routes.ts` **优化方案**: 1. 新建模板增加「导入文档」入口,支持上传 `.docx` 文件 2. 后端使用 `mammoth` 或类似库解析 Word 文档,保留段落、表格等结构 3. 导入后转为 HTML 存储模板内容,前端预览时保留格式 4. 保留现有手动创建方式作为备选 --- ## 八、花名册模块 ### 问题14:录入工资后社保基数自动取工资数,选择参保地后未自动封上下限 **模块**:花名册 **优先级**:P1 **状态**:待优化 **现状描述**: 花名册单独录入员工时,社保基数自动取工资数可以,但如果选择参保地,计算时未能自动封上下限。 **代码核查结果**: - `frontend/src/pages/roster/modals.tsx:680-681` 社保基数默认取月工资:`value={form.socialInsBase || form.monthlySalary}` - `socialInsuranceApi.cities()` 已获取城市列表(`modals.tsx:493-498`) - `socialInsuranceApi.calculate(base, city)` 可计算社保费用(`api-services.ts:510-512`) - **确认**:未根据参保城市查询基数上下限进行封顶/封底处理 - `frontend/src/pages/roster/BasicInfo.tsx:74` 显示社保基数,编辑时为普通输入框(`:344-345`) **涉及文件**: - `frontend/src/pages/roster/modals.tsx:486-767` AddEmployeeModal 社保基数填充 - `frontend/src/pages/roster/modals.tsx:235-484` RehireModal 社保基数填充 - `frontend/src/pages/roster/BasicInfo.tsx:60-120` 编辑表单 - `frontend/src/lib/api-services.ts:510-520` socialInsuranceApi **优化方案**: 1. 选择参保地后,自动查询该城市的社保基数上下限 2. 社保基数 = min(max(工资数, 下限), 上限) 3. 如果工资数在上下限范围内,直接取工资数;否则显示封顶/封底后的值并提示 --- ### 问题15:社保基数手动修改时原有数据不能直接覆盖 **模块**:花名册 **优先级**:P2 **状态**:待修复 **现状描述**: 社保基数自动取工资后实际不是社保基数时需要手动修改,但修改时原有数据不能删除,必须用鼠标点击选中后再修改,影响录入效率。 **代码核查结果**: - `frontend/src/pages/roster/modals.tsx:681` 使用 `value={form.socialInsBase || form.monthlySalary}`,当 `socialInsBase` 为空时回退到 `monthlySalary` - 用户清空输入框时 `socialInsBase` 变为空字符串,又回退到 `monthlySalary`,无法真正清空 - **缺少 `onFocus={(e) => e.target.select()}` 聚焦全选功能** - `BasicInfo.tsx:344-345` 编辑模式下的社保基数输入框为普通 `Input`,无自动回退问题 **涉及文件**: - `frontend/src/pages/roster/modals.tsx:680-681` AddEmployeeModal 社保基数输入框 - `frontend/src/pages/roster/modals.tsx:397-398` RehireModal 社保基数输入框 - `frontend/src/pages/roster/BasicInfo.tsx:344-345` 编辑表单社保基数输入框 **优化方案**: 1. 社保基数输入框改为受控组件,自动填充后用户可直接输入覆盖 2. 输入框获得焦点时自动全选当前值,方便直接覆盖 3. 增加 `onFocus={(e) => e.target.select()}` 实现聚焦全选 --- ### 问题16:录入校验失败未指明具体字段 **模块**:花名册 **优先级**:P1 **状态**:待优化 **现状描述**: 录入员工时可能是手机号录入有问题,但系统只提示「校验失败」,不指出哪个字段校验失败。 **代码核查结果**: - `backend/src/schemas/contract.schema.ts:3-27` `createEmployeeSchema` 定义了字段级 Zod 校验规则,如 `phone: z.string().regex(/^1[3-9]\d{9}$/)` - 前端 `modals.tsx:648-650` 错误处理仅显示通用消息:`{error.response?.data?.error?.message || '操作失败'}` - **未解析 Zod 返回的字段级错误信息并在对应字段下方显示** - `backend/src/middleware/errorHandler.ts:27-31` P2002 唯一约束错误返回通用"数据已存在,请勿重复操作" **涉及文件**: - `backend/src/schemas/contract.schema.ts:1-72` Zod 校验 schema 定义 - `backend/src/middleware/errorHandler.ts:27-31` 错误处理中间件 - `backend/src/routes/employee.routes.ts:99-126` 创建/更新员工路由 - `frontend/src/pages/roster/modals.tsx:648-650` AddEmployeeModal 错误提示 - `frontend/src/pages/roster/BasicInfo.tsx:82-119` 编辑表单错误处理 **优化方案**: 1. 后端校验失败时返回具体字段名和错误原因(如 `{"field": "phone", "message": "手机号格式不正确"}`) 2. 前端解析错误信息,在对应字段下方显示红色提示 3. toast 提示中包含具体字段名 --- ### 问题17:花名册员工详情中薪税入口意义不明 **模块**:花名册 **优先级**:P2 **状态**:待优化 **现状描述**: 花名册员工个人详情中的小标识第二个点进去直接进入薪税模块(批次发薪),不理解放在员工个人这里的意义,应该是与此员工有关的个人薪资关联。 **代码核查结果**: - `frontend/src/pages/roster/EmployeeProfile.tsx:66` 中 `payslip` tab 展示 `PayslipSocialInfo`,显示该员工的工资条和社保记录 - `EmployeeProfileShell.tsx:12-17` 员工 profile 类型定义包含 `position` 字段 - 需确认是否有跳转到薪税批次列表页的入口 **涉及文件**: - `frontend/src/pages/roster/EmployeeProfile.tsx:60-71` tab 定义 - `frontend/src/pages/roster/EmployeeProfileShell.tsx:12-17` profile 类型 - `frontend/src/pages/roster/BasicInfo.tsx` 快捷入口 **优化方案**: 1. 改为跳转到该员工的个人薪资历史记录页面 2. 或在员工详情中增加「薪资历史」标签页,展示该员工所有批次的工资条 --- ### 问题18:花名册列表有职务列,但录入时无职务字段 **模块**:花名册 **优先级**:P1 **状态**:待修复 **现状描述**: 花名册主页显示有职务这一栏,但单独录入员工时却没有职务这一项。 **代码核查结果(确认)**: - 花名册列表 `Roster.tsx:29` 有 `position` 列(职务),`:500` 有表头,`:560` 有数据渲染 - `AddEmployeeModal`(`modals.tsx:486-767`)表单中**无 `position` 字段** - `BasicInfo.tsx` 编辑表单中也**无 `position` 字段** - `createEmployeeSchema`(`contract.schema.ts:3-27`)中**无 `position` 字段** - `updateEmployeeSchema`(`contract.schema.ts:29-51`)中也**无 `position` 字段** - 后端 `createEmployee`(`contract.service.ts:193-272`)中也**未设置 `position` 字段** - **但后端查询时 select 包含 `position`**(`employee.routes.ts:56,82`),说明数据库有此字段 - `EmployeeProfileShell.tsx:15` 类型定义包含 `position`,`:147-149` 显示 position **涉及文件**: - `frontend/src/pages/Roster.tsx:29,500,560` 列表显示职务列 - `frontend/src/pages/roster/modals.tsx:486-767` AddEmployeeModal **缺少 position 字段** - `frontend/src/pages/roster/BasicInfo.tsx:60-120` 编辑表单 **缺少 position 字段** - `backend/src/schemas/contract.schema.ts:3-51` **缺少 position 字段** - `backend/src/services/contract.service.ts:193-272` createEmployee **未设置 position** - `backend/src/routes/employee.routes.ts:56,82` 查询时 select 包含 position - `frontend/src/pages/roster/EmployeeProfileShell.tsx:15,147-149` profile 显示 position **优化方案**: 1. `AddEmployeeModal` 和 `BasicInfo` 编辑表单增加「职务」字段 2. `createEmployeeSchema` 和 `updateEmployeeSchema` 增加 `position: z.string().max(50).optional()` 3. `createEmployee` 和 `updateEmployee` 服务中设置 `position` 字段 --- ### 问题19:花名册中社保费用计算与社保模块不一致 **模块**:花名册 / 社保管理 **优先级**:P1 **状态**:待修复 **现状描述**: 花名册里员工个人计算的社保费用与社保模块中不一致。社保模块里已修改了养老医保基数不一致,但花名册里计算还是保持一致。 **代码核查结果**: - `BasicInfo.tsx:325-330` 显示社保缴费基数和公积金缴费基数,使用统一基数 - `BasicInfo.tsx:364-368` 未设置基数时显示警告提示 - `contract.service.ts:205-206` 创建员工时 `socialInsBase` 和 `housingFundBase` 均默认取 `salaryNum` - `api-services.ts:510-512` `socialInsuranceApi.calculate(base, city)` 使用统一 base 计算 - **确认**:花名册使用统一基数,未读取社保模块中按险种分别配置的基数 **涉及文件**: - `frontend/src/pages/roster/BasicInfo.tsx:320-370` 社保费用显示和编辑 - `backend/src/services/contract.service.ts:205-206` 创建员工时社保基数设置 - `frontend/src/lib/api-services.ts:510-520` socialInsuranceApi - `backend/src/routes/social.routes.ts` 社保配置查询 **优化方案**: 1. 花名册社保费用计算改为读取社保模块中各险种的独立基数和比例 2. 养老保险用养老基数、医疗保险用医疗基数,分别计算后汇总 3. 确保两个模块的计算逻辑统一 --- ### 问题20:合同附件PDF/Word不支持在线查看,且无法删除传错的附件 **模块**:花名册 - 劳动合同 **优先级**:P0 **状态**:待修复 **现状描述**: 劳务合同附件上传了 PDF 后不可以查看,显示没有插件;Word 也不支持在线查看,只有图片格式可以查看。且附件上传之后传错了无法删除,没有删除按钮。 **代码核查结果**: - `ContractInfo.tsx:391-455` 附件预览弹窗实现: - **图片**:`` 在线预览 ✅(`:432`) - **PDF**:`` 在线预览 ✅(`:434`)——已支持,非完全缺失 - **Word/其他**:显示"此文件格式不支持在线预览",提供下载 ❌(`:436-449`) - 附件上传支持格式:`.pdf, .jpg, .jpeg, .png, .heic, .gif, .bmp, .webp, .doc, .docx, .xls, .xlsx, .tiff, .tif`(`:36,106`) - **新建合同时**的附件可删除(`:258`)✅ - **已保存合同的附件无删除按钮**——只有下载按钮(`:335-358`)和补充上传按钮(`:363`)❌ - 附件以 base64 data URL 存储在 `attachmentUrl` 字段中,预览时转为 blob URL **涉及文件**: - `frontend/src/pages/roster/ContractInfo.tsx:16-70` 附件上传逻辑 - `frontend/src/pages/roster/ContractInfo.tsx:258` 新建时删除附件按钮 - `frontend/src/pages/roster/ContractInfo.tsx:310-370` 已保存合同附件展示(无删除) - `frontend/src/pages/roster/ContractInfo.tsx:391-455` 附件预览弹窗 **优化方案**: 1. Word 预览:使用 `mammoth.js` 转换为 HTML 在线预览,或提示下载查看 2. 已保存合同的附件增加删除按钮,删除时二次确认 3. 后端增加附件删除接口,更新 `attachmentUrl` 字段 --- ### 问题21:用工办理与花名册添加员工功能重复 **模块**:花名册 / 用工办理 **优先级**:P2 **状态**:待优化 **现状描述**: 花名册可以添加员工,用工办理也可以录入员工,两个模块添加员工有什么区别不清楚。如果都可以添加没有必要,最好固定在一个模块。 **代码核查结果**: - `Roster.tsx` 有 `AddEmployeeModal`(`modals.tsx:486-767`)直接创建员工 - `WorkProcess.tsx:56-66` 的 `HIRE` 流程类型也创建员工,字段为 `name`、`department`、`idCardNumber` 等 text 输入 - `WorkProcess.tsx:67-70` 的 `ONBOARD` 流程使用 `employee-select` 选择已有员工 - 两个入口都调用 `createEmployee`,写入同一张表 **涉及文件**: - `frontend/src/pages/Roster.tsx:70-90` 花名册状态和模态框 - `frontend/src/pages/roster/modals.tsx:486-767` AddEmployeeModal - `frontend/src/pages/WorkProcess.tsx:56-70` HIRE/ONBOARD 流程定义 - `backend/src/routes/employee.routes.ts:99-126` 创建员工路由 - `backend/src/services/contract.service.ts:193-272` createEmployee **优化方案**: 1. 统一员工添加入口为「用工办理 → 入职办理」,包含完整入职流程 2. 花名册保留「查看」和「编辑」功能,移除独立添加入口 3. 或在花名册添加员工时引导跳转到用工办理的入职流程 --- ### 问题22:用工办理录入中途切换窗口丢失已填信息 **模块**:用工办理 **优先级**:P1 **状态**:待修复 **现状描述**: 在用工办理里录入员工,录到身份证号处,点开别的文件想粘贴一下,再回去,刚才录入的页面就退出了,需要重新打开重新录前面的信息。 **代码核查结果**: - `frontend/src/components/ui/Modal.tsx:36` 遮罩层 `onClick={onClose}`——**点击遮罩层会关闭弹窗** - 无 `closeOnOverlayClick={false}` 配置选项 - 表单数据未持久化到 `sessionStorage` - `AddEmployeeModal`(`modals.tsx:641`)使用了 `useUnsavedChanges(isDirty)` 但仅提示,不阻止关闭 - `WorkProcess.tsx:204-205` 录入弹窗也使用 `div` + `onClick={onClose}` 模式 **涉及文件**: - `frontend/src/components/ui/Modal.tsx:33-56` Modal 组件(遮罩层 onClick={onClose}) - `frontend/src/pages/roster/modals.tsx:641-643` AddEmployeeModal useUnsavedChanges - `frontend/src/pages/WorkProcess.tsx:204-205` 录入弹窗 **优化方案**: 1. 弹窗设置为 `closeOnOverlayClick={false}`,禁止点击遮罩层关闭 2. 表单数据持久化到 `sessionStorage`,重新打开时恢复 3. 关闭前增加「确认关闭?未保存的数据将丢失」提示 --- ### 问题23:用工办理未按身份证号查重 **模块**:用工办理 **优先级**:P1 **状态**:待修复 **现状描述**: 在花名册录入一个人,在用工办理里录入了一个人但没录入身份证号,不显示重复,不知道是否用身份证查重。 **代码核查结果**: - `createEmployee`(`contract.service.ts:193-272`)**无查重逻辑**——直接创建 - 数据库依赖 `idCardHash` 唯一约束,重复时抛出 P2002 错误 - `errorHandler.ts:27-31` P2002 错误返回通用"数据已存在,请勿重复操作"消息 - 前端 `WorkProcess.tsx` 的 `HIRE` 流程类型使用 `text` 类型字段(`name`、`department` 等),**非 `employee-select`** - `INCOME_CERT` 和 `LEAVING_CERT` 也使用 `text` 类型手动输入员工信息(`:107-114, :129-134`) - `import.routes.ts:330-333` 导入时有身份证号查重,返回字段级错误信息 **涉及文件**: - `backend/src/services/contract.service.ts:193-272` createEmployee(无查重) - `backend/src/middleware/errorHandler.ts:27-31` P2002 错误处理 - `frontend/src/pages/WorkProcess.tsx:56-66` HIRE 流程字段定义 - `frontend/src/pages/WorkProcess.tsx:107-114,129-134` 证明开具字段(手动输入) - `backend/src/routes/import.routes.ts:330-333` 导入查重(有字段级错误) **优化方案**: 1. 用工办理录入时根据姓名+手机号或身份证号查重 2. 身份证号为空时用姓名+手机号组合查重 3. 发现重复时提示「该员工已存在,是否查看/跳转」 --- ## 九、证明开具模块 ### 问题24:收入证明等应支持员工下拉选择,直接拉取数据 **模块**:用工办理 - 证明开具 **优先级**:P1 **状态**:待优化 **现状描述**: 开具收入证明或其他证明时,需要手动粘贴员工信息,应该有员工下拉选项直接拉取数据,避免开具非本公司员工的证明。 **代码核查结果**: - `INCOME_CERT`(`WorkProcess.tsx:107-114`)字段为手动输入:`employeeName`(text)、`idCardNumber`(text)、`position`(text)、`monthlyIncome`(text) - `LEAVING_CERT`(`:129-134`)同样为手动输入 - **未使用 `employee-select` 类型**,不关联花名册 - **但批量开具证明弹窗(`:537-590`)已有员工多选列表**——单条开具时却无下拉选择 - `WorkProcess.tsx:746-809` 有 `EmployeeSelect` 组件实现,支持搜索和选择员工 - `WorkProcess.tsx:298-306` 批量提交时从员工数据自动填充 `employeeName`、`idCardNumber`、`position` **涉及文件**: - `frontend/src/pages/WorkProcess.tsx:107-114` INCOME_CERT 字段定义(手动输入) - `frontend/src/pages/WorkProcess.tsx:129-134` LEAVING_CERT 字段定义(手动输入) - `frontend/src/pages/WorkProcess.tsx:537-590` 批量开具证明弹窗(有员工选择) - `frontend/src/pages/WorkProcess.tsx:746-809` EmployeeSelect 组件 - `frontend/src/pages/WorkProcess.tsx:298-306` 批量提交自动填充字段 **优化方案**: 1. 证明开具表单增加员工下拉选择器,支持姓名/手机号搜索 2. 选择员工后自动填充身份证号、入职日期、职务、月收入等字段 3. 只允许选择本公司在职员工 --- ## 十、培训记录模块 ### 问题25:培训记录只能选择单个员工,不支持批量/按部门 **模块**:培训记录 **优先级**:P1 **状态**:待优化 **现状描述**: 添加培训记录只能选择一个员工,但实际培训可能是好几个员工一起,也可能是一个部门甚至整个公司。 **代码核查结果**: - `TrainingRecords.tsx:211-220` 员工选择为 `