# 干部任免上会系统 · 审计遗留问题清单

- 对应原报告：`docs/审计报告_20261020.md`（共 27 条：P0 7 + P1 13 + P2 7）
- 本清单核对时间：2026-10-26
- 核对方式：逐条对照当前源码 / 接口 / 数据库实测，不沿用旧结论

---

## 一、总览

| 状态 | 条数 | 说明 |
|---|---|---|
| ✅ 已修复 | 26 | P0-1～P0-7、P1-1～P1-13、P2-1、P2-2、P2-4、P2-5、P2-6、P2-7 |
| ◐ 部分缓解 | 1 | P2-3（人数列仍需横向滚动，属布局取舍）|
| ❌ 仍未处理 | **0** | — |

**27 条审计项全部处置完毕**（26 条已修，1 条部分缓解并说明原因）。

**批次 A 已完成（10-26 17:30）**：P0-4、P0-5、P1-1、P1-2、P1-3、P1-8、P1-11、P2-6。
**批次 B 已完成（10-26 18:05）**：P1-6、P1-7、P1-9、P1-10。

> 审计后这段时间做的工作（PAD 14 项改版、隐藏汇报/说明、数据包端口与打包、处室维护真实化）里，**只有处室维护这一条命中了原审计清单**，其余都是新需求。

---

## 二、已修复（3 条，供对照）

| 编号 | 原问题 | 当前状态 |
|---|---|---|
| P0-6 | 处室维护整模块纯前端，改完刷新还原 | ✅ 已接真实接口：新增/编辑（含名称查重）/启停/删除（有在册干部时拒绝，引导停用），全部落库并写审计 |
| P1-12 | PAD 首页与水印日期取会议日期，不随当天 | ✅ 已改为 `fmtCN(new Date())`，显示当天 |
| P1-13 | APK 与 wwwroot 两份 pad.html 不同步 | ✅ 已同步（`未列入文件`、`fitAfterShow`、批注移除等标记两份一致）|
| P2-3 | 人数标注在文件名称尾部被横向滚动挤掉 | ◐ 部分：文件名称后已加「已关联 N 人 / 未关联干部」；但「干部人数」列仍需横向滚动才能看到 |

---

## 三、仍未处理 —— P0 级（6 条，直接影响能不能上会用）

| 编号 | 问题 | 现场证据（当前代码/数据） | 影响 |
|---|---|---|---|
| **P0-1** | PAD 的考察材料页是**写死的模板文字**，含编造的随机数 | `renderExam()` 内固定段落，`18+(c.id%6)*2` 名谈话对象、`30+(c.id%5)*8` 名测评、95.6% | 领导在 PAD 上看到的“考察材料”与后台上传件不一致，决策依据失真 |
| **P0-2** | 数据包**只带材料元数据，不含文件本体** | `PackageExporter` 只读照片文件；包 108KB（60 份材料不可能这么小） | PAD 物理上拿不到原件，是 P0-1 的根因 |
| **P0-3** | PAD 附件页是**硬编码 4 条假条目** | `renderAtt()` 里写死 `姓名_干部任免审批表(3.0).docx · Word·46KB` 等 | 附件材料等于没下发 |
| **P0-4** | 会议列表「上会人数 / 材料完整度」全用**当前选中会议**的数据 | `var cnt = m.type==='buwu' ? CADRES.length : CADRES.length;`（两个分支还写成一样）、`lack` 也取全局 `CADRES` | 一眼看不出哪场会材料没备齐，误导上会安排 |
| **P0-5** | 删除干部 / 删除任免文件是**假删除** | `doDelCadre` / `doDelDoc` 无任何 `apiCall`，只改本地数组 + 写假日志，提示“软删除可恢复” | 以为删了，刷新全回来；日志也没入库 |
| **P0-7** | 「用户与登录管理」整模块纯前端 | `saveUser` 中 `apiCall` 出现 **0 次**；新增账号/改角色/重置口令/停用/删除都不落库 | 开不了户、授不了权、改不了密 |

---

## 四、仍未处理 —— P1 级（11 条）

| 编号 | 问题 | 现场证据 | 建议 |
|---|---|---|---|
| P1-1 | 届次号标了必填却不校验 | `sessionNo: /^[0-9]+$/.test(noStr) ? parseInt(noStr,10) : null` → 可传 null | 前端必填 + 后端拒空 |
| P1-2 | 届次号 / 会议名不查重 | 后端 `POST /meetings` 中查重语句数 **0**；实测能建出两条“第192次” | 建会前查重，重复时提示 |
| P1-3 | 状态机被「编辑会议」绕过 | `PUT /meetings/{id}` 里 `if (!string.IsNullOrWhiteSpace(req.status)) m.Status = req.status!;` 直接赋值 | 状态改走 `/status` 接口（已有相邻推进校验），编辑表单只读 |
| P1-4 | 提示让用「撤回归档」，但系统没有 | 全站 `撤回归档` 出现 **0** 次 | 要么加撤回归档（管理员+留痕），要么改文案 |
| P1-5 | 「回收站」不存在，但 3 处删除提示都说可恢复 | 只有提示文案提到“回收站”，无 restore 接口 | 见下方决策② |
| P1-6 | 同一人被建成两条档案（**仍在**） | `陈晓东 Id=4（1968.09，干部一处）`、`Id=16（1968.06，无处分室）`；重名档案 1 组 | 提供“合并档案”，并把匹配规则改为命中多条时不自动新建 |
| P1-7 | 上传任免文件不采集文号 / 页数 | 上传弹窗只有选文件；`sub` 文案写着“含组织部文头与文号”，但没有输入框 | 上传后可在列表补填文号/页数 |
| P1-8 | 缺文件的材料仍显示「下载」 | `if(f) ops.push(['下载', ...])` —— 只看有没有材料记录，不看 `FilePath` | 无文件时改为「补传」或置灰 |
| P1-9 | 排序模式不保存 | `bindSort` 中 `apiCall` **0** 次，拖拽只重排本地数组；页面却提示“该顺序即 PAD 展示顺序” | 加保存接口（写 `MeetingCadres.SortNo`） |
| P1-10 | 附件类材料后台**无页签** | 材料维护页签只剩 任免文件 / 任免表 / 考察材料；`att` 类型 12 条无入口 | 补「附件」页签，或在任免表行内挂附件 |
| P1-11 | 「导出清单」是假按钮 | 全站 `演示环境` 提示 **7 处**（会议清单、处室清单等） | 做实为 CSV 导出 |

---

## 五、仍未处理 —— P2 级（6 条）

| 编号 | 问题 | 说明 |
|---|---|---|
| P2-1 | 删除任免文件后旧版文件成磁盘孤儿 | 依赖 P0-5 先做真删除；做的时候一并清理历史版本文件 |
| P2-2 | 删除会议不级联 | `DELETE /meetings/{id}` 只置 `IsDeleted=1`；材料/名单/文件/数据包仍指向它，数据包仍出现在列表 |
| P2-4 | 会议状态 6 个选项平铺 | 筹备中/材料已审核/已下发/会议召开中/结果已登记/已归档，无流程引导（与 P1-3 一起改） |
| P2-5 | 已阅回执需“导出→后台导入”才看得到 | 设计取舍（PAD 全离线），可保留，但要给经办人明确操作路径 |
| P2-6 | 9 处前端假日志 | `LOGS.unshift` 里硬编码「王科长 / PC-GY-021」，未入库 |
| P2-7 | 上传弹窗「下载核对」用原始文件名 | 未按「姓名_材料类型.ext」规范 |

---

## 六、需要你拍板的 3 件事（其余都能直接改）

| # | 决策 | 选项 | 我的建议 |
|---|---|---|---|
| ① | PAD 材料展示（决定 P0-1/2/3 怎么改） | A：材料转图片/PDF 塞进数据包（所见即所传，包 15~25MB）<br>B：只显示材料清单，正文回后台看 | **A（逐页图片）** |
| ② | 回收站（决定 P1-5、P2-1/2） | 做：软删可恢复 + 30 天清理<br>不做：把“可恢复”文案全改成“不可恢复”+强确认 | **做**（软删标记已有，补列表与恢复接口） |
| ③ | 用户与登录管理（P0-7） | 本轮做实<br>先隐藏按钮避免演示翻车 | **做实**（登录、改密已是真接口，只差账号 CRUD） |

---

## 七、建议动手顺序

- **批次 A（不依赖决策，风险低，约半天）**：P0-4、P0-5、P1-1、P1-2、P1-3、P1-8、P1-11、P2-6
- **批次 B（数据质量，约半天）**：P1-6（合并档案）、P1-7、P1-9、P1-10
- **批次 C（PAD 材料，需决策①）**：P0-1、P0-2、P0-3（含包格式升级，需重打 APK + 重新出包）
- **批次 D（按决策②③）**：P0-7、P1-4、P1-5、P2-2、P2-4

---

*本清单只列仍未处理的项；已修 3 条见第二节。所有“现场证据”均取自当前源码与数据库，未沿用旧结论。*


---

## 八、批次 A 完成记录（2026-10-26）

| 编号 | 改法 | 实测证据 |
|---|---|---|
| P0-4 | 后端 `bootstrap` 增 `cadres`/`lack`，按 `meetingcadres` + `materials` 逐会统计；前端不再用当前会议数据 | 会议列表实测：194次 3 人/齐全、193次 0 人、58次 12 人/2 人待补件、192次 12 人/2 人待补件、书记专题会 12 人/2 人待补件（原来五行全是 12 人/2 人）|
| P0-5 | 材料删除改调 `DELETE /api/admin/materials/{id}`（软删：置未上传 + 文件移入 `App_Data/_trash/日期/`）；任免文件新增 `DELETE /api/admin/docs/{id}`（`IsDeleted=1`，文件保留）；两处都写审计 | 上传临时→删除：记录置 missing、文件进回收目录、审计“删除材料”；任免文件软删后重复删除返回“不存在或已删除”；界面按钮与文案同步 |
| P1-1 | 前端校验 + 后端 `MeetingType.HasSessionNo()`：部务会/常委会必填，书记专题会可空 | 部务会不填 → “请填写届次号（如 192 表示第192次）”；书记专题会不填 → 创建成功 |
| P1-2 | 建会/改名时查重：名称全量唯一、同类型届次号唯一 | 重名 → “会议名称已存在，请勿重复创建”；重复 192 次 → “该类型下已有第 192 次会议” |
| P1-3 | `PUT /meetings/{id}` 的状态变更走 `MeetingGuard.CanTransition`，非法跳转返回 409 | 筹备中→已归档 → 409“不能从「筹备中」直接变更为「已归档」（允许的下一步：材料已审核）”；筹备中→材料已审核 → 200 保存成功 |
| P1-8 | 材料投影增 `hasFile`（按 `App_Data/<FilePath>` 实判）；无文件只给「补传」，不给下载/删除；有记录无文件时文件名显示“未上传” | 郑海燕（缺件）→「预览/补传」，文件名“未上传”；张建国（有件）→「预览/上传/下载/删除」|
| P1-11 | 新增 `downloadCSV()`，会议/处室/账号/登录日志四个“导出清单”全部做实（UTF-8 BOM，Excel 直接打开不乱码） | 实测导出：会议清单 6 行、处室清单 4 行、账号清单 10 行、登录日志 73 行，表头与首行内容正确 |
| P2-6 | 删除前端 9 处 `LOGS.unshift` 假日志，日志页只显示后端审计 | 全站 `LOGS.unshift` 计数 **0**；日志页显示 50 条真实审计记录 |

### 过程中修掉的两个自身缺陷
1. `hasFile` 起初拼路径漏了 `App_Data` 段，导致所有有文件的材料被判成“未上传”（已修）。
2. 补传按钮一度与上传按钮重复（同一行两个按钮都调 uploadFile），已改为无文件时上传按钮直接显示「补传」。

### 测试痕迹处理
测试产生的临时会议、临时材料、临时任免文件、临时处室与 15 条测试审计记录均已清理；误删的张建国《考察材料》已从 `_trash` 恢复（记录回归 `passed`、文件 37,582 字节）。处室名称按用户 17:05 的改名（干部一科/干部二科/人事科）保持不动。


---

## 九、批次 B 完成记录（2026-10-26）

| 编号 | 改法 | 实测证据 |
|---|---|---|
| P1-6 | 新增 `POST /api/admin/cadres/{id}/merge`（把一条并入另一条：名单去重/改挂，材料、批注、简历、家庭成员、其他关系、已阅回执、任免文件关联全部转挂，简历/成员/关系按内容去重，冲突字段按提交值覆盖，最后删源档案并写审计）；`bootstrap` 新增 `dupCadres`（全局跨会议查同名）；材料页顶部出现提示条与「合并」入口 | 陈晓东 #16[1968.06] 并入 #4[1968.09]：简历6、家庭成员2、材料1、任免文件关联1、名单改挂1，合并后 12 名干部、`dupCadres` 为空、审计写明两条出生年月 |
| P1-7 | 选人弹窗里加「文件文号」「页数」两个输入，回填已有值，随上传一并提交（后端本就支持 docNo/pages）| 干部二科弹窗实测：文号回填 `海组干〔2026〕88号`、页数回填 `3` |
| P1-9 | `saveOrder()` 改为调 `PUT /api/admin/meetings/{id}/cadre-order`（写 `MeetingCadres.SortNo`），成功后重新拉取；后端校验人数一致、无重复、干部在本场名单内 | 把第 1 行拖到末尾：库内 SortNo 变为 2..12,1，审计“调整上会顺序 12 人”；再次提交原顺序即还原 |
| P1-10 | 材料维护新增「附件」页签（与考察材料同一套渲染，支持预览/上传/补传/下载/删除）| 页签为 任免文件(2)/任免表(12)/考察材料(11)/附件(12)，附件页 12 行（干部档案审核表），操作按钮齐全 |

### 过程中修掉的两个自身缺陷
1. 合并后 #4 出现**重复简历 6 条、重复家庭成员 2 条**（合并未去重）——已清理数据，并把去重逻辑写进合并接口。
2. `hasFile`（批次 A）与本次 `CadreRelation` 字段名（`RelatedName` 而非 `Name`）都踩过字段名坑，编译期已发现并修正。

### 数据说明
- 陈晓东合并保留 **#4「1968.09 / 干部一科」**（原文 5 份材料，是主档案）；#16 的 1968.06 已在审计里留痕，如认定应以 1968.06 为准，改档案出生年月即可。
- 测试用的同名档案、临时材料、顺序调整均已还原，未留残留。


---

## 十、批次 C/D 完成记录（2026-10-26 决策 ②③）

### 决策② 回收站（含撤回归档）

| 编号 | 改法 | 实测证据 |
|---|---|---|
| P1-5 | 新增「回收站」页（会议／任免文件／材料三类）：可恢复、可彻底删除、可「清理超期」（30 天）；`Materials` 加 `IsDeleted/PrevStatus/TrashPath/DeletedAt`（迁移 `AddMaterialSoftDelete` 已应用）；材料删除改为软删并把文件移到 `_trash/日期/` | 删张建国考察材料 → 回收站出现「张建国_考察材料.docx／exam·passed／删除时间」，文件在 `_trash/20261027/exam.docx`、原位置已无；恢复后 `Status=passed`、文件回到 `uploads/1/exam.docx`（37,582 字节）、页面显示「齐全」 |
| P1-4 | 新增 `POST /meetings/{id}/unarchive`（已归档 → 结果已登记，唯一逆向出口），会议行对已归档会议显示「撤回归档」 | 书记专题会撤回归档 → 「结果已登记」；重新归档回「已归档」；对非归档会议调用被拒（提示当前是「材料已审核」）|
| P2-1 | 删除即把文件移入 `_trash`，彻底删除时连文件一并删除；不再留下磁盘孤儿 | 彻底删除测试会议后 `_trash` 与库内均无残留 |
| P2-2 | 已删除会议的**数据包不再出现在列表**；彻底删除会议时级联清理名单、材料、任免文件、数据包、回执 | 回收站会议 1 条（用户 10-20 删掉的第194次旧件），可恢复/可彻底删除 |

### 决策③ 用户与登录管理做实（P0-7）

| 项 | 改法 | 实测证据 |
|---|---|---|
| 开户 | `POST /api/admin/users`：账号规则校验、唯一性、角色解析、初始口令（默认 Hd@2026）PBKDF2 入库 | 新建 `test01`（处室业务员/干部一科）→ 用 `Test@123` **登录成功** |
| 改资料 | `PUT /api/admin/users/{acc}`：姓名、角色、处室、数据范围、启用状态 | 改名+换角色为干部科科长 → 库内与页面同步 |
| 重置口令 | `POST /api/admin/users/{acc}/reset-password`：换 hash、清失败计数、`TokenVersion+1` | 重置后**旧令牌访问 bootstrap 返回 401**、旧口令登录失败、新口令登录成功 |
| 停用/启用 | 同上 PUT：停用即 `TokenVersion+1` 强制下线 | 停用后登录返回「账号已停用，请联系管理员」 |
| 删除 | `DELETE /api/admin/users/{acc}`，连带清理角色映射 | 删除 `test01` 成功，库内恢复 9 个账号 |
| 守卫 | 不能删当前登录账号；不能停用/改角色/删除**最后一个启用的管理员** | 删自己 → 「不能删除当前登录的账号」；停用 admin → 「这是最后一个启用的系统管理员，不能停用」 |
| 审计 | 新增/编辑/重置/删除全部写审计（含是否强制下线） | 审计可见 5 条测试操作 |

### ⚠ 口径变更（需要你知道）

材料「缺件」判定改为**以有没有实体文件为准**（原来只看有没有 `status=missing` 的记录）。影响：
- 删除材料进回收站后，该干部会立即回到「缺件」状态（符合直觉）；
- 会议列表的「材料完整度」数字因此有变化，例如第 58 次从「2 人待补件」变为「1 人待补件」、第 194 次从「齐全」变为「3 人待补件」（它有 3 名干部但确实没有任何材料文件）。这是**更真实**的口径，不是数据丢失。

### 迁移记录
- `20261027012515_AddMaterialSoftDelete`：`materials` 新增 `IsDeleted / PrevStatus / TrashPath / DeletedAt`（已在正式库执行）。


---

## 十一、决策① 落地（PAD 显示材料原件，2026-10-26）

**方案：①D** —— 后台照常传 Word/PDF；出包时服务器把 Word 转 PDF 塞进包；PAD 用**系统原生 PdfRenderer** 逐页渲染成图显示（不引第三方库、离线可用）。

| 编号 | 改法 | 实测证据 |
|---|---|---|
| P0-1 | 删除 PAD 中写死的考察材料模板（含 `18+(id%6)*2` 名谈话、95.6% 等编造数字），改为显示真实 PDF 原件；没有原件就明说"本场会议未下发该同志的考察材料原件" | 全站 `paras=` 计数 **0**；考察材料页显示真实阅读器（上一页/下一页/缩小/放大），浏览器预览态提示"预览环境不支持"，真机渲染 |
| P0-2 | 数据包新增 `files(matId,cadreId,type,fileName,mime,size,data)` 表，装 Word 转出的 PDF；`materials` 显式带 id 与 `files.matId` 对应 | 生成 `PK20261012-03`：**2456 KB / 57 份 PDF / 2246 KB**（原来 108KB 纯元数据），校验接口 `pdfFiles:57` |
| P0-3 | 删除 PAD 附件页 4 条硬编码假条目，改为按包内材料清单渲染；有正文的可「查看」，无正文的显示「无正文」并禁用 | 附件页实测：列出真实三条材料，按钮「查看/查看/无正文」 |

### 技术链路
```
上传：Word / PDF（习惯不变）
出包：LibreOffice 转 PDF → 缓存 App_Data/_pdfcache/<材料Id>_<版本>.pdf → 写入包内 files 表（BLOB）
加密：整个 SQLite 走原有 SM4-CBC + HMAC-SM3
PAD：解密 → SQLite → pdfPages(matId) 取页数 / pdfPage(matId,序号,宽度) 取该页 JPEG → <img> 显示
```
- 首次出包 36 份材料约 2.5 分钟（之后命中缓存，只转新增/变更）
- APK 资源零增加（没引 pdf.js），只多用系统 PdfRenderer

### ⚠ 部署前提：服务器需要 Word→PDF 转换器
- 出包机上必须有 **LibreOffice（soffice）**，或设置环境变量 `RMSYS_SOFFICE` 指向 soffice 可执行文件
- 没装转换器时：数据包照常生成，但**不含 PDF 正文**，PAD 上会显示"这份材料在数据包里没有 PDF 正文"
- 安装包里**尚未内置** LibreOffice（会再加约 330MB）；是否需要内置待定

### PAD 详情页同步调整
汇报材料 / 说明材料两个页签一并从 PAD 详情页移除（后台已隐藏这两类，包里也不再下发），详情页现在只有 **任免审批表 / 考察材料 / 附件** 三个页签。


---

## 十二、收尾三条 + 内置转换器（2026-10-26）

| 编号 | 改法 | 实测证据 |
|---|---|---|
| P2-4 | `bootstrap` 给每场会议下发 `allowedNext`；编辑会议时的「会议状态」不再是 6 项平铺，而是**当前状态 + 允许的下一步**，下方给出流向提示 | 第192次（材料已审核）下拉实测为 `[材料已审核, 筹备中, 已下发]`，提示「只能逐级推进：材料已审核 → 筹备中 / 已下发」；新建会议固定「筹备中」只读 |
| P2-5 | 后台「PAD端数据设置 → PAD 设备台账」新增**「导入已阅回执」**按钮（导入函数原已存在但从未挂出），并写明回收路径；PAD「设置」页新增**「导出已阅回执」**，原生把 JSONL 复制到 `Android/data/<包名>/files/` 并回显路径与条数 | 后台按钮实测存在且带路径说明；PAD 设置页字段为 显示字号/设备编号/全屏水印/**已阅回执**，点击导出在预览环境友好提示、真机导出到外部私有目录 |
| P2-7 | 单份上传弹窗记录规范名（姓名_材料类型）；选文件后即时校验：不规范给黄色提示并给出建议名，规范则显示「命名规范 ✓」；「下载核对」副本按规范名存盘（保留原扩展名） | 实测选 `扫描件001.docx` → 提示「建议重命名为 张建国_考察材料.docx」；选 `张建国_考察材料.docx` → 「命名规范 ✓」|

### 决策 B：LibreOffice 内置进安装包

- 从清华镜像取 `LibreOffice_25.8.7_Win_x86-64.msi`（约 360MB）放入 `thirdparty/`
- 安装时用 `msiexec /a ... TARGETDIR=C:\Rmsys\lo` 做**administrative install**：只铺文件、不写注册表、不建快捷方式
- 自动定位 `soffice.exe`，写入系统环境变量 `RMSYS_SOFFICE`（后台据此调用）
- 装完做一次**真实 Word→PDF 自检**（用随包的 `converter-test.docx`），窗口显示「自检通过 / 自检失败」
- 卸载时清掉 `RMSYS_SOFFICE` 与 `C:\Rmsys\lo`
- 安装包体积：约 **650MB**（原 290MB + LibreOffice 约 360MB）
