feat: 20260805 系统优化 - 身份证复制fallback/薪税日期筛选/社保版本修复/证据链导出/违纪证明/医疗期政策/绩效类型评级/合同作废/帮助更新

This commit is contained in:
freedakgmail
2026-08-05 20:26:16 +08:00
parent a5901d648e
commit c355a7d208
24 changed files with 1185 additions and 257 deletions
+352
View File
@@ -0,0 +1,352 @@
# 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 | 花名册 |
---
## 已确认无需修改
(暂无)