# 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` 附件预览弹窗实现：
  - **图片**：`<img>` 在线预览 ✅（`:432`）
  - **PDF**：`<embed>` 在线预览 ✅（`: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` 员工选择为 `<Select>` 单选下拉框，`employees.map` 渲染选项
- `AttendanceOvertimeInfo.tsx:79-81` 中的培训记录新增也为单选
- **不支持多选或按部门批量选择**
- 表单字段：`employeeId`、`trainingDate`、`topic`、`content`、`trainer`、`duration`、`remark`

**涉及文件**：
- `frontend/src/pages/roster/TrainingRecords.tsx:200-267` 培训记录表单（单选员工）
- `frontend/src/pages/roster/AttendanceOvertimeInfo.tsx:10,79-81` 考勤/培训合并组件
- `backend/src/routes/employee.routes.ts` 培训记录接口

**优化方案**：
1. 员工选择改为多选模式，支持按部门筛选勾选
2. 增加「按部门添加」和「全公司添加」快捷选项
3. 批量创建培训记录，每人选一条，共享培训主题/日期/讲师等信息

---

## 十一、绩效考核模块

### 问题26：绩效考核模块过于片面，应支持导入公司自定义考核表

**模块**：绩效考核  
**优先级**：P2  
**状态**：待优化

**现状描述**：
绩效考核模块只有简单的得分/等级/评语，每个公司考核类别、评分等差别比较大，现有功能几乎没法用。应支持导入本公司绩效考核表，再进行个人绩效考核统计。

**代码核查结果**：
- `PerformanceInfo.tsx:14` 表单仅包含：`period`、`periodType`（月度/季度/年度）、`score`、`grade`（A/B/C/D）、`result`（优秀/合格/需改进/不胜任）、`summary`、`improvementPlan`、`reviewer`、`employeeAck`
- 得分自动计算等级和结果（`:29-39`）
- **无自定义考核维度、权重、指标**
- 不支持导入 Excel 考核表

**涉及文件**：
- `frontend/src/pages/roster/PerformanceInfo.tsx:1-118` 绩效考核完整组件
- `backend/src/routes/employee.routes.ts` 绩效记录接口
- `backend/prisma/schema.prisma` PerformanceRecord 模型

**优化方案**：
1. 增加「绩效模板」管理，支持定义考核维度、权重、评分标准
2. 支持导入 Excel 考核表作为模板
3. 绩效考核时按模板填写各维度得分，系统按权重计算总分
4. 保留现有简单模式作为默认，自定义模板作为高级功能

---

## 十二、考勤导入流程

### 问题27：考勤模板 Sheet 过多，导入后无法确认数据

**模块**：考勤管理  
**优先级**：P0  
**状态**：待优化

**现状描述**：
考勤管理下载模板时模板包含太多无关 Sheet，需要都删除后再导入。而且导入后提示导入成功，但找不到从哪里确认数据。

**代码核查结果**：
- 与问题7相关，`import.routes.ts:494-692` 模板包含多个无关 Sheet
- `Attendance.tsx:525-610` 导入弹窗显示导入结果（成功数、跳过数、错误），**但无跳转到考勤确认页面的链接**
- 导入成功后仅 toast 提示，无自动跳转

**涉及文件**：
- `backend/src/routes/import.routes.ts:494-692` 模板下载
- `frontend/src/pages/Attendance.tsx:525-610` 前端导入弹窗
- `frontend/src/pages/attendance/AttendanceConfirm.tsx` 考勤确认页面

**优化方案**：
1. 模板精简为单个考勤 Sheet（与问题7统一处理）
2. 导入成功后 toast 提示中增加「点击查看」跳转链接
3. 导入成功后自动跳转到考勤确认页面

---

## 十三、加班费计算

### 问题28：加班费计算需重复导入考勤数据

**模块**：薪税管理 / 考勤管理  
**优先级**：P1  
**状态**：待优化

**现状描述**：
加班费计算跟考勤不关联，到加班费计算时还需要再导入一遍考勤。

**问题分析**：
- 与问题9相同，加班费计算模块独立于考勤管理
- 考勤管理中已确认的加班数据未传递到薪税计算

**涉及文件**：
- `frontend/src/pages/money/` 加班费相关
- `backend/src/routes/payroll2.routes.ts`
- `backend/src/routes/import.routes.ts` 考勤导入（含加班记录 Sheet）
- `backend/src/routes/attendance.routes.ts` 考勤数据查询

**优化方案**：
1. 与问题9统一处理：加班费从考勤管理读取已确认的加班记录
2. 薪税批次创建时自动拉取当月加班数据

---

## 优先级汇总

| 优先级 | 编号 | 问题 |
|--------|------|------|
| P0 | 1 | 福利方案无法添加人员 | 功能已实现，pageSize 200 条限制待优化 |
| P0 | 3 | 离职证明下载乱码 | 待修复 |
| P0 | 7 | 考勤导入模板无关Sheet+合并 | 确认：模板含员工信息/劳动合同等无关Sheet |
| P0 | 8 | 补卡无法修改状态+时间显示异常 | 后端支持，需确认前端弹窗状态限制 |
| P0 | 20 | 合同附件PDF/Word不支持查看+无法删除 | PDF已支持预览，Word不支持，已保存附件无法删除 |
| P0 | 27 | 考勤模板Sheet过多+导入后无法确认 | 确认：无导入后跳转引导 |
| P1 | 2 | 每页条数选择无反应 | 需验证usePageSize事件触发 |
| P1 | 4 | 离职导出缺少筛选条件 | 确认：导出未传筛选参数 |
| P1 | 5 | 已提交离职无法撤回+已撤回无法删除 | 后端有revoke接口，前端UI未暴露 |
| P1 | 9 | 加班费与考勤不关联 | 确认：独立模块 |
| P1 | 10 | 加班汇总不显示手动添加的加班 | 确认：数据源未关联 |
| P1 | 11 | 证据链验证无法定位异常 | 确认：仅显示汇总数字 |
| P1 | 12 | 规章制度签收缺少催办和明细 | 确认：无催办按钮，未签收人员未列出 |
| P1 | 13 | 文本模板不支持导入文档 | 确认：仅textarea输入 |
| P1 | 14 | 社保基数选择参保地后未封上下限 | 确认：未查询城市上下限 |
| P1 | 16 | 校验失败未指明具体字段 | 确认：仅显示通用错误 |
| P1 | 17 | 员工详情薪税入口意义不明 | 需进一步确认 |
| P1 | 18 | 录入时缺少职务字段 | **确认：前后端均缺失position字段** |
| P1 | 19 | 花名册社保计算与社保模块不一致 | 确认：使用统一基数 |
| P1 | 22 | 用工办理录入中途切换窗口丢失数据 | 确认：遮罩层点击关闭，无持久化 |
| P1 | 23 | 用工办理未按身份证号查重 | 确认：无查重逻辑，仅依赖DB唯一约束 |
| P1 | 24 | 证明开具不支持员工下拉选择 | 确认：手动输入，批量开具有选择器 |
| P1 | 25 | 培训记录不支持批量选择员工 | 确认：单选下拉框 |
| P1 | 28 | 加班费需重复导入考勤 | 与问题9相同 |
| P2 | 6 | 用工办理与离职管理功能重复 | 确认 |
| P2 | 15 | 社保基数修改不能直接覆盖 | 确认：value回退问题 |
| P2 | 21 | 花名册与用工办理添加员工重复 | 确认 |
| P2 | 26 | 绩效考核模块过于片面 | 确认：固定字段，无自定义 |
