收紧报销草稿读取边界 [09-04-reimbursement-state-gap-finance-view][R2]

这个提交包含在:
2026-09-05 09:13:53 +08:00
父节点 1b120e0829
当前提交 8e0af8235e
共修改 14 个文件,包含 704 行新增和 120 行删除
@@ -0,0 +1,91 @@
# 报销状态空隙与财务处理视角:实施计划
## 0. 启动门
- 用户批准本版 `prd.md`、`design.md`、`implement.md` 后,才运行 `task.py start`。
- 实施前使用 `trellis-before-dev` 读取 frontend、backend、approval/workflow 和测试规约。
- 保持 inline 模式;不派 implement/check sub-agent。涉及权限和 workflow,编码完成后主会话执行只读 review。
## 1. R1 独立实现单元:待组批与付款阶段
1. 为付款页增加 URL 驱动的 `pending/batches/detail` 视图投影;锁定 manage/review 角色默认视图和直达边界。
2. 把现有候选搜索、选择、分页和最小创建表单提升为唯一“待组批”主体,删除旧的重复入口/休眠 DOM。
3. 候选标签展示服务端 `total`;实现加载、错误、空态、搜索、分页和选择后的组建动作。
4. 报销详情 approved/no-batch 下一步改为“等待财务组建付款批次”;仅 manage 权限注入带编号搜索的深链。
5. 调整报销处理进度组装,加入 `payment_batch_created` 的报销专属“组建付款批次”投影;批次存在后继续完全复用共享付款语义。
6. 创建成功刷新候选及批次查询,不做乐观业务状态写入。
7. 完成 R1 focused 测试并形成第一个独立提交;不回填最终 task commit,直到整个任务候选提交完成。
R1 重点文件预计包括:
- `client/src/pages/PaymentBatchesPage.tsx`
- `client/src/pages/ReimbursementDetailPage.tsx`
- `client/src/api/paymentBatches.ts`
- `server/src/routes/reimbursements.js`
- `server/src/services/paymentProcessingProgress.js`(仅在共享阶段接口确需调整时)
- 对应 server unit 与 client Playwright specs
## 2. R2 独立实现单元:草稿读取授权
1. 提取“既有角色范围 + 报销业务事实”的服务端条件构建器,表驱动验证 global/team/own 边界。
2. 应用于 `/reimbursements` 列表并保证 `items/total` 同源。
3. 应用于全局搜索、报销 CSV/XLSX 导出、报销统计/看板;原始 SQL 使用等价 `EXISTS` 条件并验证不放大聚合。
4. 收紧 `ensureOwnCanViewAllOrWorkflowReviewer` / `ensureOwnOrCanViewAll` 或合并为等价统一 helper,使详情、附件文件、preview URL、完整材料 preview 和 `precheck` 对他人未提交 draft fail closed。
5. 审计 helper 的所有调用方,确认写接口不因读取授权重构而放宽;保持 workflow reviewer 和本人路径,并增加审批人读取自己待办报销的负向保护。
6. 增加 returned 与 withdrawn/cancelled workflow fixture,证明两类历史 draft 仍可见、可由申请人按既有规则继续处理。
7. 完成 R2 focused 测试并形成第二个独立提交。
R2 重点文件预计包括:
- `server/src/routes/reimbursements.js`
- `server/src/routes/search.js`
- `server/src/routes/exports.js`
- `server/src/routes/dashboard.js`
- 可复用的报销 scope helper(优先放现有模块,不为单一条件新建过度抽象)
- 对应权限、详情、附件、搜索、导出与统计测试
## 3. Focused 验证
按实际测试文件补齐精确命令,最低包括:
```bash
cd server && node --check src/routes/reimbursements.js
cd server && node --check src/routes/paymentBatches.js
cd server && node --check src/routes/search.js
cd server && node --check src/routes/exports.js
cd server && node --check src/routes/dashboard.js
cd server && npm run prisma:validate
cd server && npx vitest run tests/unit/routes/reimbursements.detail.test.js tests/unit/routes/reimbursements.withdraw.test.js tests/unit/routes/paymentBatches.reimbursement.test.js
cd client && npm run typecheck
cd client && npm run build
node scripts/qa/check-ui-design-rules.mjs --strict
node scripts/qa/check-ui-design-rules.self-test.mjs
node scripts/qa/check-trellis-governance.mjs
git diff --check
```
还必须新增并执行:
- R1:候选 eligibility、manage/review 权限、视图 URL、搜索/分页/空态、创建后刷新、无批次/有批次三阶段进度矩阵。
- R2:角色 × owner/submitter/team × status × workflow-exists 表驱动矩阵;列表/详情/材料/precheck/搜索/导出/统计旁路一致性;workflow reviewer 待办读取不受误伤。
- 回归:付款批次 `draft/reviewed/executed/failed/cancelled`、maker-checker、报销退回/撤回/重提、B1/B2 和非报销 workflow 动作投影不变。
## 4. 只读 Review 门
提交前逐项只读复核:
1. 候选资格是否仍只有服务端一处来源,前端没有复制 `approved/unpaid/unbatched/gate` 判断。
2. 无 batch 与 batch draft 的阶段是否分别为“待组批”和“待付款复核”,没有同义混用。
3. 未提交他人 draft 是否在所有 record-level 旁路 fail closed,而 returned/withdrawn draft 仍按原范围可见。
4. R2 是否没有放宽任何写能力、没有改变角色授权、没有引入 schema 或历史数据修正。
5. R1/R2 是否可分别 revert 并各自构建、测试,不存在交叉 import 或数据前置。
## 5. 提交、部署与回滚
- Commit 1:中文提交信息引用 `[09-04-reimbursement-state-gap-finance-view][R1]`。
- Commit 2:中文提交信息引用 `[09-04-reimbursement-state-gap-finance-view][R2]`。
- 最终元数据提交把 `task.json.commit` 指向最后一个业务提交;push、部署继续分别获得授权。
- 部署前紧邻执行 `git status --porcelain --untracked-files=all`、UI strict 和 provenance 核验。
- 部署后检查 PRD AC11;真实创建批次仍由财务执行。
- R1 回滚:revert R1 提交;已创建批次继续按原付款状态机处理。
- R2 回滚:revert R2 提交;不做数据库恢复。若只发现某一旁路遗漏,R2 整体不得宣称完成。