Files
TurboHR/docs/20260805-优化.md

353 lines
18 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 | 花名册 |
---
## 已确认无需修改
(暂无)