文件
wangsli和Claude Sonnet 5 7ad022ce4b
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s
补齐验收记录并归档非参与人报销只读投影任务 [08-28-reimbursement-nonparticipant-readonly-projection]
发现并修正文档滞后:业务提交 e8d6766 已确认合入并随后续发布上线(是当前
生产 b1d25d3c 的祖先提交),但 PRD 的 9 条 AC 一直保持未勾选、task.json
备注仍写"未推送、未部署"。implement.md 的服务端/前端验证清单与独立只读
复核记录逐条对应 PRD 各项,现据实补勾 AC 并更正 notes,再归档。

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-25 20:52:12 +08:00

103 行
13 KiB
Markdown
原始文件 Blame 文件历史

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
# 非参与人报销详情只读投影修复
## Goal
把报销详情按对象参与资格收敛为两种明确模式:owner/submitter 在允许编辑的状态维护自己的报销;财务、审批人、管理员和其他因职责或 `expense.view_all` 获得访问权的非参与人只查看、核验材料和执行职责内的独立业务动作,不得复用报销人的维护入口或直接变更他人报销数据。
## Background
- `ReimbursementDetailPage.tsx:725` 和 `MobileReimbursementDetailPage.tsx:206` 只按 `draft/rejected` 判断可编辑状态。PC 的新增/编辑/删除明细、项目关联、附件上传/删除和主要提交动作,以及移动端对应动作,没有全部叠加 owner/submitter 资格。
- PC 与移动端的 `canUnbindInvoices` 都把 `expense.view_all` 当成他人报销的解绑资格;`invoicePool.js:1082` 的服务端解绑合同也允许该权限直接解除他人报销与发票的关联,因此不是单纯的前端误显。
- `ensureApplicantOrAdmin` 允许管理员修改、删除、提交他人草稿,新增/修改/删除他人明细和附件;`link-project` 与发票 bind 也把全量查看权限当成写入资格。全量查看、系统管理或当前审批职责不等于报销人自助维护资格。
- 发票卡片在所有视角共用 `resolveInvoiceNextStepText`,向非参与人显示“草稿或驳回状态可移除”;`linkedItemId` 为空时又显示技术性兜底“自动生成或匹配明细”。这些文案不是只读核验事实。
- 现有事实源要求页面按当前任务角色收敛,审批人和财务只看到职责内动作;报销材料仍允许授权查看原件和合并预览。Task A 已修复审批中撤回入口,但没有覆盖更广的草稿/驳回维护动作和服务端对象资格。
## Defect Severity
- **S1 真实写入能力**:reimbursement 发票 unbind/bind 把 `expense.view_all` 当成对象写资格;`ensureApplicantOrAdmin` 允许 admin 修改、删除、提交他人报销头、明细和附件。`link-project` 的授权设计同样错误(仅认证、复用读守卫且没有状态门),但原实现还调用了未定义的 `parseBody`,因此 feature flag 打开后会在写入前 500,历史上该写入洞不可实际利用;本轮仍须同时修复可用性和授权边界。测试必须断言 403/409 及零副作用,不能只检查按钮不可见。
- **S2 仅显示或前后端不一致**:PC 多处明细/附件动作和移动端对应动作只按 editable status 渲染,普通财务可能看到按钮但被部分服务端路由拒绝。测试必须断言 PC/移动 DOM 中不存在无资格动作,并保留直接 API 负向回归。
- **S3 已有正确边界**:PC/移动报销头编辑器已经同时检查 editable status 和参与人资格;实施只统一资格事实并证明基线,不重复改写已经正确的组件合同。
## Scope
- 统一 PC、移动端的报销参与人和可维护资格计算,修正所有报销人自助维护动作的投影。
- 收紧报销与发票路由中把 admin 或 `expense.view_all` 当成他人报销维护资格的服务端分支。
- 为报销 precheck 补对象级读取授权,避免普通员工横向读取他人预算、费用标准和风险结果。
- 为非参与人提供明确的只读查看语义和事实型票据文案,同时保留授权材料查看、打印、审批和财务专属流程。
- 使用合成身份和数据覆盖 owner/submitter、财务、管理员及普通非参与查看者,不读取或修改真实报销。
## Requirements
### R1 服务端对象资格必须 fail closed
- 报销 owner 或 submitter 统称参与人。只有参与人可通过报销人自助接口修改报销头、删除草稿、上传/删除附件、新增/修改/删除明细、提交报销、关联项目,以及为该报销绑定或解绑发票;状态和对应权限码仍需同时满足。
- `POST /reimbursements/:id/link-project` 必须补 `expense.edit_own` 权限门,只允许参与人在 `draft/rejected` 状态写入;非参与人返回 403,已通过、已支付等非编辑状态返回 409。不得继续使用 `ensureOwnOrCanViewAll` 读守卫授权写入。
- admin、财务、审批人及任何仅持 `expense.view_all` 的非参与人调用上述接口时必须稳定返回 `403 PERM_001`,并在读取目标对象后、任何业务写入、文件持久化、审计或通知副作用前失败。
- `expense.view_all` 继续允许授权查看报销详情、发票/附件原件和材料合并预览;不得把读权限降级为无权查看。
- `POST /reimbursements/:id/precheck` 虽不写业务数据,但会返回预算、费用标准、审批规则和风险结果,必须先读取目标报销并通过 owner/submitter 或 `expense.view_all` 对象级读守卫;普通非参与人返回 403,且不得调用 precheck 服务。审批页既有 `standardViolations` 快照继续保留。
- 显式职责动作保持独立:workflow 审批/退回、财务付款及付款批次接口不改;差旅订单 bind/unbind 分支不改。若未来需要财务纠正票据归属,必须在 Task B 设计专用、可审计的业务动作,不能继续借用报销人解绑接口。
- 不改变状态机、权限码集合、角色定义、数据库 schema 或迁移。
### R2 PC 与移动端统一维护资格
- 页面使用“参与人 AND 状态允许编辑”计算统一的 `canMaintainReimbursement`,不得再把 `draft/rejected` 或 `expense.view_all` 单独当成维护资格。
- 非参与人不得看到或触发:新增/编辑/删除明细、修改报销日期/事由/差旅摘要、关联项目下拉框、添加/移除发票、上传/删除附件、提交报销、删除草稿、撤回、催办等报销人自助动作。
- owner/submitter 在 draft/rejected 状态继续看到并可使用既有维护动作;owner/submitter 在 reviewing 状态继续使用 Task A 已修复的撤回和催办合同,其他状态行为不变。
- 只读查看者可继续查看报销概要、明细事实、处理进度、材料原件/合并预览、风险与材料对应情况,以及权限允许的打印入口;页面不通过禁用按钮暗示不可用动作,而是完全不渲染无资格动作。
- PC 和移动端必须使用同一资格语义,不能只修截图对应的 PC 页面。
### R3 非参与人使用事实型查看文案
- 同一详情路由可以复用,但非参与人应显示明确的“只读查看”上下文,不伪装成报销人维护页,也不新建一套重复详情页面。
- 非参与人的“下一步”按当前业务状态表达观察事实:草稿为“等待报销人完善”,驳回为“等待报销人修改后重提”,审批中显示当前处理事实,已通过为“等待财务处理”,已支付为“查看付款结果”。不得提示其新增明细、添加发票或提交审批。
- 发票存在真实 `linkedItemId` 时显示 `关联明细:<费用类型 · 明细说明>`;为空时显示 `关联明细:未关联`,关联对象无法解析时显示 `关联明细:无法识别`。不得再显示“自动生成或匹配明细”。
- bound 发票在当前不可维护视角(包括非参与人,以及 owner/submitter 查看 approved/paid 等非编辑状态)使用事实状态“已关联”,且不显示“草稿或驳回状态可移除”等操作提示。只有参与人的可维护视角可保留必要的移除说明。
- 材料对应、票面金额、日期和风险提示继续作为财务/审批核验信息展示;本轮不重构为 Task B 的一页式审批工作台。
### R4 回归与可部署基线
- 前端回归至少覆盖 PC 和移动端的非参与管理员/财务查看他人 draft、rejected、reviewing 报销,断言全部维护动作不存在、只读文案正确、原件和合并预览入口仍可用。
- owner/submitter 合成场景继续覆盖 draft/rejected 维护动作和 reviewing 撤回,防止把页面整体改成只读。
- 服务端对每类维护接口至少覆盖一个非参与高权限身份的 `403` 和零副作用,并保留参与人成功基线;发票 bind/unbind 同时保留差旅分支现有合同。
- 权限与审批规则改动实施后必须完成独立只读复核;未经复核和新工作提交,不更新部署候选、不推送、不部署。
## Acceptance Criteria
2026-09-25 核对:本节 9 条勾选此前未随实施同步更新,任务文档一直显示未完成;业务提交 `e8d6766` 已确认合入并随后续发布上线(是当前生产 `b1d25d3c` 的祖先提交),implement.md 的服务端/前端验证清单与独立只读复核记录逐条对应下列各项,现据实补勾。
- [x] 非参与管理员或财务查看他人 draft/rejected 报销时,PC 和移动端不存在明细、项目、票据、附件、提交、删除、撤回和催办等报销人维护动作。
- [x] 同一非参与人仍能查看报销概要、明细事实、处理进度、材料原件和合并预览;reviewing 审批/财务职责入口仍在原工作流页面正常使用。
- [x] 非参与人看到“只读查看”上下文;未关联票据显示“关联明细:未关联”,bound 状态显示“已关联”,页面不存在“自动生成或匹配明细”和“草稿或驳回状态可移除”。
- [x] 非参与人直接调用报销头修改/删除、附件增删、明细增改删、提交、项目关联、发票 bind/unbind 均返回 `403 PERM_001`,且对应数据、文件、审计、workflow 和通知零副作用。
- [x] 普通非参与人调用 precheck 返回 `403 PERM_001` 且 precheck 服务零调用;owner/submitter 与 `expense.view_all` 查看者保持成功读取基线。
- [x] owner/submitter 在 approved/paid 等非编辑状态调用 `link-project` 返回 409 且项目归属不变;draft/rejected 且具备 `expense.edit_own` 时保持成功基线。
- [x] owner/submitter 在既有允许状态仍可维护、提交、撤回和催办自己的报销;现有状态校验、材料查看、打印、财务付款、workflow 审批和差旅票据分支不变。
- [x] 无 schema、迁移、权限码或角色定义变更;前后端聚焦测试、前端 typecheck/build、相关 Playwright、Trellis 治理、敏感检查和 `git diff --check` 通过。
- [x] 只读权限复核结论为 PASS 后才形成新工作提交;部署子任务随后重新冻结包含 Task A 与本任务的候选 SHA,原 `174cb7a` 不再作为最终部署基线。
## Out of Scope
- Task B 的财务一页式审批、费用口径、`requires_invoice`、票据—明细强制关联、金额差异门禁和多票拆分。
- 为财务或管理员新增“代员工修改”“纠正票据归属”或其他管理修复入口。
- 修改 workflow 审批/退回、付款批次、财务付款、材料查看授权、打印权限、发票 OCR 或材料合并算法。
- 处理 `BX-20260828-0017` 的真实单据、票面差额、退回、付款或附件重传。
- 修复 r102 差旅夹具测试债;该项仍按独立任务处理。
- 推送、部署、公网验证、生产配置、生产数据写入和任务归档。
## Risks and Constraints
- 服务端移除 admin/`expense.view_all` 写入豁免属于真实权限收紧,必须用直接接口测试证明读取能力仍在、写入统一拒绝。
- 不能只隐藏前端动作;否则旧客户端或直接 API 仍可变更他人报销。
- 不能把所有非参与人操作一刀切:workflow 审批、财务付款和授权材料查看是职责动作,必须与报销人维护动作区分。
- 移除 admin 通用写入豁免后,员工离职或长期缺席造成的卡住草稿暂时没有代维护入口;若真实发生,应在 Task B 的专用可审计纠错动作或独立运营修复任务中重新规划,本任务不以保留通用后门兜底。
- 同一页面角色化比复制新页面更低风险,但 PC/移动端必须共享资格事实和文案合同,避免再次漂移。
## Test Plan
- 服务端语法与基础:`cd server && node --check src/routes/reimbursements.js && node --check src/routes/invoicePool.js && npm run prisma:validate`。
- 服务端聚焦:新增或扩展报销参与人资格矩阵测试,覆盖 form/items/attachments/submit/project/invoice bind-unbind 的非参与高权限 403、零副作用和参与人成功基线。
- 前端:`cd client && npm run typecheck && npm run build`。
- Playwright:扩展 `r102-reimbursement-main-flow.spec.ts` 或新增聚焦 spec,使用阻断 Service Worker 的合成 PC/移动身份覆盖 participant 与 nonparticipant 的 draft/rejected/reviewing 投影及只读票据文案。
- 全局回归:运行相关报销、材料预览和系统动作确认用例;完整 r102 的既有差旅夹具失败必须如实单列,不得把它当成本任务新失败或默认绿色基线。
- 治理:`node scripts/qa/check-process-contract.mjs --baseline --json`、`node scripts/qa/check-trellis-governance.mjs`、`node scripts/qa/check-sensitive-content.mjs`、`git diff --check`。