# 20260805 优化需求清单

> 基于用户反馈整理，对照系统代码逐一分析问题根因及优化方案。

---

## 问题1：花名册身份证号复制后粘贴为乱码

**模块**：花名册  
**优先级**：P0  
**状态**：待修复

**现状描述**：
花名册列表和员工详情页均支持点击身份证号复制，但用户反馈复制后粘贴出来是乱码。

**问题分析**：
- 列表页 `Roster.tsx:522-526`：点击脱敏身份证号时调用 `navigator.clipboard.writeText(e.idCardNumber)` 复制完整身份证号
- 详情页 `BasicInfo.tsx:200-213`：同样使用 `navigator.clipboard.writeText(profile.idCardNumber)` 复制
- `navigator.clipboard.writeText` 在非 HTTPS 环境或部分浏览器下可能静默失败，clipboard API 返回的 Promise 可能被 reject
- 当前 `.catch(() => toast.error('复制失败'))` 仅提示失败，但用户可能看到"已复制"提示后实际粘贴为空或乱码
- 可能原因：`idCardNumber` 字段经过加密存储，解密后的值可能包含不可见字符或编码问题

**涉及文件**：
- `frontend/src/pages/Roster.tsx:522-526`
- `frontend/src/pages/roster/BasicInfo.tsx:200-213`

**优化方案**：
1. 检查 `idCardNumber` 字段是否经过 `decrypt()` 解密，确认复制的是明文而非加密后的乱码
2. 增加 fallback 方案：当 `navigator.clipboard` 不可用时，使用 `document.execCommand('copy')` + 隐藏 textarea 兜底
3. 复制后增加验证：读取 clipboard 内容验证是否与原始值一致
4. 确认后端返回的 `idCardNumber` 已正确解密为明文

---

## 问题2：薪税管理筛选条件需精确到年月日，且每笔工资需有创建时间

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

**现状描述**：
薪税管理中筛选条件仅支持按月（YYYY-MM）筛选，无法精确到具体日期。同时发薪批次列表未显示创建时间，难以区分同月多笔工资。

**问题分析**：
- `BatchTab.tsx:101-103`：筛选条件为 `month`（YYYY-MM）、`monthFrom`、`monthTo`，均为月份级别
- 后端 `payroll2.routes.ts:151-168`：查询参数 `month`、`monthFrom`、`monthTo` 也只支持月份级别
- `PayrollBatch` schema 有 `createdAt` 字段（`schema.prisma:710`），但前端列表未展示
- 同月可创建多个批次（`batchNo` 区分），但用户无法直观看出创建先后顺序

**涉及文件**：
- `frontend/src/pages/money/BatchTab.tsx:101-103, 220-240`
- `backend/src/routes/payroll2.routes.ts:151-168`
- `backend/prisma/schema.prisma:691-719`（PayrollBatch model）

**优化方案**：
1. 批次列表增加「创建时间」列，显示 `createdAt`（格式：YYYY-MM-DD HH:mm）
2. 筛选条件增加日期范围选择器（`dateFrom` / `dateTo`），后端按 `createdAt` 过滤
3. 列表默认按 `createdAt desc` 排序（当前按 `month desc, batchNo asc`）
4. 批次详情中每条工资条目也可展示创建/修改时间

---

## 问题3：社保公积金无法创建和保存新的政策比例

**模块**：社保公积金  
**优先级**：P0  
**状态**：待修复

**现状描述**：
用户在社保公积金页面创建新版本政策比例时无法保存成功。

**问题分析**：
- 前端 `SocialInsurance.tsx:183-201`：`createVersionMutation` 和 `createHousingVersionMutation` 调用后端 API
- 后端 `social.routes.ts:126-168`：创建社保配置版本时，检查同一城市同一生效月份是否已有版本，如有则返回 400 错误
- 后端 `social.routes.ts:509-549`：创建公积金配置版本同样检查重复
- 可能原因：
  1. 前端 `newVersion.city` 默认为 `'北京'`，但后端 `socialConfigFields` 中 `city` 为 `optional`，若前端未传或传空可能导致 `where` 条件匹配到 `city: null` 的已有记录
  2. 后端 `prevCurrent` 查询 `where: { orgId, isCurrent: true }` 未按城市过滤（社保），可能将其他城市的当前版本也标记为失效
  3. 前端 `createVersionMutation` 的 `onSuccess` 未显示错误详情，`onError` 未定义，用户可能看不到错误信息
  4. `z.object` 校验可能因前端传入的字段类型不匹配（如 `number` 传为 `string`）而静默失败

**涉及文件**：
- `frontend/src/pages/SocialInsurance.tsx:183-201, 786-789`
- `backend/src/routes/social.routes.ts:11-26, 120-168, 503-549`
- `backend/prisma/schema.prisma:430-445`（SocialInsuranceConfig model）

**优化方案**：
1. 后端 `prevCurrent` 查询增加 `city` 过滤条件，避免误将其他城市的版本标记失效
2. 前端 `createVersionMutation` 和 `createHousingVersionMutation` 增加 `onError` 回调，显示后端返回的错误信息
3. 前端提交前校验必填字段（城市、生效月份、各比例），确保类型正确
4. 后端 `createVersionSchema` 的 `city` 字段改为 `z.string().min(1)` 必填，避免 null 匹配问题
5. 增加 try-catch 日志输出，方便排查具体失败原因

---

## 问题4：证据链无法导出，导出证据链显示导出失败

**模块**：证据链  
**优先级**：P0  
**状态**：待修复

**现状描述**：
员工档案 → 证据链页面，点击「导出证据链」按钮提示"导出失败"。

**问题分析**：
- 前端 `EvidenceChain.tsx:47-63`：`handleExport` 使用 `fetch` 请求 `/api/v1/roster/${employeeId}/evidence-chain/export`，获取 blob 后下载
- 后端 `roster.routes.ts:582-747`：使用 `ExcelJS` 生成 xlsx 文件并返回
- 可能原因：
  1. 后端 `ExcelJS` 依赖未在服务器安装（`package.json` 中有 `exceljs: ^4.4.0`，但服务器可能未执行 `npm install`）
  2. `workbook.xlsx.write(res)` 写入流可能因 res 已设置 header 但写入失败而报错
  3. 前端 `fetch` 请求未携带 `Content-Type: application/json`，但后端返回的是二进制流，`res.blob()` 可能解析失败
  4. 服务器内存不足导致 ExcelJS 生成大文件失败
  5. Nginx 代理可能对大响应体有超时或大小限制

**涉及文件**：
- `frontend/src/pages/roster/EvidenceChain.tsx:47-63`
- `backend/src/routes/roster.routes.ts:582-747`
- `backend/package.json:22`（exceljs 依赖）

**优化方案**：
1. 确认服务器已安装 exceljs 依赖（`npm ls exceljs`）
2. 后端增加错误日志：`catch (err) { console.error('证据链导出失败:', err); next(err) }`
3. 前端 `handleExport` 增加详细错误处理：读取 `res.text()` 获取后端错误信息
4. 后端 `workbook.xlsx.write(res)` 改为 `workbook.xlsx.writeBuffer()` 然后 `res.send(buffer)`，避免流写入问题
5. 检查 Nginx `proxy_buffer_size` 和 `proxy_read_timeout` 配置

---

## 问题5：用工办理中离职证明无法自主选择模板，导出为txt格式且格式混乱

**模块**：用工办理  
**优先级**：P0  
**状态**：待优化

**现状描述**：
用工办理中开具离职证明时只能使用系统默认模板，导出的证明是 txt 文档格式混乱，希望能自主选择模板且能直接电子签章后提供给员工。

**问题分析**：
- 前端 `WorkProcess.tsx:129-135`：`LEAVING_CERT` 表单已有 `enterpriseTemplateId` 字段（`enterprise-template` 类型），支持选择企业自定义模板
- 后端 `work-process.service.ts:246-261`：`generateDocument` 函数已支持企业模板渲染（`formData.enterpriseTemplateId`）
- 但生成文件扩展名为 `.doc`（`work-process.service.ts:259, 289`），实际内容为纯文本，非真正的 Word 文档
- `EnterpriseTemplateSelect` 组件（`WorkProcess.tsx:722-744`）已实现模板选择下拉框，但用户可能未创建企业模板
- 导出的文书存储在 `workProcess.documents` 字段（JSON 数组），未关联电子签章流程

**涉及文件**：
- `frontend/src/pages/WorkProcess.tsx:129-135, 722-744`
- `backend/src/services/work-process.service.ts:245-290`
- `backend/src/routes/work-process.routes.ts:136-192`
- `backend/src/routes/enterprise-template.routes.ts`

**优化方案**：
1. **导出格式优化**：将纯文本 `.doc` 改为生成真正的 Word 文档（使用 `docx` 库）或 PDF 格式
2. **模板选择增强**：在离职证明表单中增加模板预览功能，选择模板后可实时预览渲染效果
3. **电子签章集成**：审批通过后自动创建电子签署记录（类似入职流程 `work-process.routes.ts:168-186`），场景为 `RESIGNATION`
4. **文书下载优化**：前端增加文书下载按钮，支持直接下载 PDF/Word 格式
5. **模板提示**：当无企业模板时，增加快捷跳转链接到「模板库 → 企业文本库」创建

---

## 问题6：用工办理中多个模块功能重复

**模块**：用工办理  
**优先级**：P2  
**状态**：待优化

**现状描述**：
用工办理中多个流程类型功能重复，都是录入员工信息和合同时间，希望合并精简。

**问题分析**：
- `work-process.service.ts:8-22`：共定义 13 类流程
- 功能重复的流程：
  - `HIRE`（员工录用）和 `ONBOARD`（员工入职）：都涉及录入员工信息和创建合同
  - `CUSTOM_CONTRACT`（自定义合同签署）和 `CHANGE`（合同变更）和 `RENEW`（合同续签）：都是合同相关操作
  - `TERMINATE`（合同终止）和 `RESCIND`（合同解除）：都是结束劳动关系
  - `INCOME_CERT`（收入证明）和 `LEAVING_CERT`（离职证明）：都是开具证明文书
- 前端 `WorkProcess.tsx` 的 `FORM_FIELDS` 配置中多个流程字段高度重叠（employeeName、idCardNumber、startDate、endDate 等）

**涉及文件**：
- `backend/src/services/work-process.service.ts:8-22`
- `frontend/src/pages/WorkProcess.tsx`（FORM_FIELDS 配置）

**优化方案**：
1. **合并入离职类**：将 `HIRE` 和 `ONBOARD` 合并为「入职办理」，区分"新员工入职"和"录用+入职一步完成"两种模式
2. **合并合同类**：将 `CUSTOM_CONTRACT`、`CHANGE`、`RENEW` 合并为「合同签署/变更」，通过子类型区分
3. **合并解聘类**：将 `TERMINATE` 和 `RESCIND` 合并为「解除/终止合同」，通过原因字段区分
4. **合并证明类**：将 `INCOME_CERT` 和 `LEAVING_CERT` 合并为「开具证明」，通过证明类型切换模板
5. **保留独立流程**：`CONFIRM`（转正）、`SUSPEND`（中止）、`FLEXIBLE`（灵活用工）、`INFO_SUBMIT`（信息变更）保持独立
6. 合并后流程类型从 13 个精简为约 8 个，减少用户选择困难

---

## 问题7：违纪记录员工签字确认后企业端需可下载违纪确认证明

**模块**：违纪记录  
**优先级**：P0  
**状态**：待新增

**现状描述**：
员工在员工端签字确认违纪记录后，企业端没有可下载的违纪确认证明文件。

**问题分析**：
- 前端 `DisciplinaryInfo.tsx`：仅展示违纪记录列表和新增表单，无下载/导出功能
- 后端 `roster.routes.ts:478-490`：证据链中包含违纪记录信息，但无单独的违纪确认证明导出接口
- `DisciplinaryRecord` schema（`schema.prisma:565-577`）有 `employeeAck`、`ackDate`、`ackMethod`、`witness`、`attachmentUrl` 字段，但无独立的证明生成功能
- 培训记录已有签收单导出的先例可参考

**涉及文件**：
- `frontend/src/pages/roster/DisciplinaryInfo.tsx`
- `frontend/src/pages/roster/PerformanceRecords.tsx`（同样需要下载功能）
- `backend/src/routes/roster.routes.ts`（需新增导出接口）
- `backend/prisma/schema.prisma:565-577`（DisciplinaryRecord model）

**优化方案**：
1. 后端新增 `GET /roster/:employeeId/disciplinary/:recordId/certificate` 接口，生成违纪确认证明 PDF
2. 证明内容包含：企业名称、员工姓名、身份证号、违纪事实、处理结果、签字确认状态、确认日期、见证人
3. 前端 `DisciplinaryInfo.tsx` 在已签字的记录上增加「下载确认证明」按钮
4. 同步为绩效考核记录增加类似的确认证明下载功能
5. 证明格式使用 PDF（使用 `pdfkit` 或 `puppeteer` 生成）

---

## 问题8：医疗期计算只有全国和上海两个地区政策

**模块**：医疗期计算器  
**优先级**：P2  
**状态**：待优化

**现状描述**：
医疗期计算器仅支持"全国（通用规定）"和"上海（特殊规定）"两个地区选项，其他有特殊政策的地区无法选择。

**问题分析**：
- 前端 `MedicalPeriodCalculator.tsx:42-86`：`calculateMedicalPeriod` 函数硬编码了 `region: 'shanghai' | 'national'` 两种逻辑
- 地区选择为固定下拉框（`MedicalPeriodCalculator.tsx:146-153`），只有两个选项
- 后端 `special-status.service.ts:68-76`：`calculateMedicalMonths` 函数也仅按全国通用标准计算，未区分地区
- 各地特殊政策举例：
  - 广东：按实际工作年限和本单位工作年限分档
  - 北京：与全国规定一致但有补充细则
  - 江苏、浙江等省份有各自的地方规定

**涉及文件**：
- `frontend/src/pages/tools/MedicalPeriodCalculator.tsx:29-107, 146-153`
- `backend/src/services/special-status.service.ts:68-76`

**优化方案**：
1. 将地区政策配置改为数据驱动，支持动态添加地区规则
2. 新增 `medicalPeriodPolicy` 配置表或 JSON 配置，存储各地政策分档规则
3. 前端地区选择改为可搜索下拉框，支持从配置中动态加载
4. 管理员可在系统设置中添加自定义地区政策（工龄分档 → 医疗期月数 → 累计周期月数）
5. 预置全国通用、上海、广东、北京等常见地区政策
6. 后端 `calculateMedicalMonths` 函数同步支持按地区查询配置

---

## 问题9：绩效考核需区分月度/年度考核，得分与等级应关联

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

**现状描述**：
1. 绩效考核无法区分月度考核与年度考核
2. 录入的得分和等级二者无关联，应按得分自动分等级

**问题分析**：
- `PerformanceRecord` schema（`schema.prisma:624-643`）：`period` 字段为自由文本（`YYYY-MM` 或 `YYYY-Q1`），无考核类型字段
- `score`（Float）和 `grade`（String，A/B/C/D）是独立字段，前端表单分别输入，无联动逻辑
- `result`（EXCELLENT/QUALIFIED/NEED_IMPROVE/UNQUALIFIED）也与 `score` 和 `grade` 无关联
- 前端 `PerformanceInfo.tsx:38-48`：考核周期为自由输入框，得分和等级分别独立选择
- 前端 `PerformanceRecords.tsx:212-228`：考核周期使用 `type="month"` 选择器，仅支持月度

**涉及文件**：
- `frontend/src/pages/roster/PerformanceInfo.tsx:14, 38-48`
- `frontend/src/pages/roster/PerformanceRecords.tsx:181-270`
- `backend/prisma/schema.prisma:624-643`（PerformanceRecord model）
- `backend/src/routes/roster.routes.ts:1064-1097`

**优化方案**：
1. **新增考核类型字段**：`PerformanceRecord` 增加 `periodType` 字段（`MONTHLY`/`QUARTERLY`/`YEARLY`），前端表单增加类型选择
2. **考核周期选择优化**：根据 `periodType` 动态切换输入方式（月度→ month 选择器，季度→ Q1/Q2/Q3/Q4 选择，年度→ year 选择器）
3. **得分等级自动关联**：
   - 前端输入得分后自动计算等级和结果：
     - 90-100 → A（优秀 EXCELLENT）
     - 80-89 → B（合格 QUALIFIED）
     - 60-79 → C（需改进 NEED_IMPROVE）
     - 0-59 → D（不胜任 UNQUALIFIED）
   - 等级和结果字段变为只读，由得分自动填充（可手动覆盖，覆盖后标记为"手动调整"）
4. **后端校验**：保存时校验得分与等级的匹配性，若不一致记录日志
5. **列表展示**：绩效考核列表页增加考核类型筛选（月度/季度/年度）

---

## 问题10：花名册劳动合同无法下载，且不应能删除

**模块**：花名册 → 劳动合同  
**优先级**：P0  
**状态**：待修复

**现状描述**：
1. 员工花名册中的劳动合同附件无法下载，点击附件和下载按钮都无反应
2. 劳动合同作为重要资料可以修改或覆盖，但不应该能删除

**问题分析**：
- 前端 `ContractInfo.tsx:258-289`：合同附件展示区域尝试解析 `c.attachmentUrl`（JSON 或 data URL），使用 `<a href={att.url} download={att.name}>` 下载
- 附件以 base64 data URL 形式存储在数据库中，`<a>` 标签的 `download` 属性对 data URL 在某些浏览器下不生效
- 下载无反应的可能原因：
  1. data URL 过长，浏览器阻止下载
  2. `attachmentUrl` 字段存储的是 JSON 字符串，解析失败时回退逻辑可能未正确处理
  3. `<a>` 标签点击事件被外层 `<button>` 或其他事件拦截
- 删除问题：
  - 前端 `ContractInfo.tsx:300-306`：有删除按钮，调用 `deleteContractMutation`
  - 后端 `employee.routes.ts:279-294`：`DELETE /contracts/:contractId` 直接物理删除合同记录
  - 合同作为重要法律文件，应禁止删除，仅允许新增或修改（覆盖）

**涉及文件**：
- `frontend/src/pages/roster/ContractInfo.tsx:258-289, 300-306`
- `backend/src/routes/employee.routes.ts:279-294`
- `backend/src/services/contract.service.ts:713-764`

**优化方案**：
1. **下载修复**：
   - 将 data URL 转为 Blob URL 后再触发下载（已有 `dataToBlobUrl` 函数用于预览，下载也应用相同逻辑）
   - 下载按钮改为 `onClick` 事件主动创建 `<a>` 元素并 click，而非依赖 `<a>` 标签的 `download` 属性
   - 或改为调用后端接口下载（后端返回文件流），避免前端处理大 data URL
2. **禁止删除**：
   - 移除前端删除按钮，改为「作废」按钮（将合同标记为 `VOID` 状态而非物理删除）
   - 后端 `DELETE /contracts/:contractId` 改为 `PATCH /contracts/:contractId/void`，仅更新状态
   - schema 中 `LaborContract` 增加 `status` 字段（`ACTIVE`/`VOID`），作废后不在正常列表展示但保留记录
   - 证据链中保留作废合同记录，标注"已作废"
3. **允许覆盖**：新增合同时若日期完全相同则提示"已存在相同日期合同，确认覆盖？"（当前是直接报错拒绝）

---

## 优先级汇总

| 编号 | 问题 | 优先级 | 模块 |
|------|------|--------|------|
| 1 | 花名册身份证号复制乱码 | P0 | 花名册 |
| 2 | 薪税管理筛选精确到日+创建时间 | P1 | 薪税管理 |
| 3 | 社保公积金无法创建保存新政策 | P0 | 社保公积金 |
| 4 | 证据链导出失败 | P0 | 证据链 |
| 5 | 离职证明模板选择+格式+电子签章 | P0 | 用工办理 |
| 6 | 用工办理模块功能重复 | P2 | 用工办理 |
| 7 | 违纪记录签字后下载确认证明 | P0 | 违纪记录 |
| 8 | 医疗期计算增加其他地区政策 | P2 | 医疗期计算器 |
| 9 | 绩效考核月度/年度区分+得分等级关联 | P0 | 绩效考核 |
| 10 | 劳动合同无法下载+不应能删除 | P0 | 花名册 |

---

## 已确认无需修改

（暂无）
