# 报销状态空隙与财务处理视角:实施计划 ## 2026-09-10 退回删除接续 - 用户要求继续剩余任务,先退回删除、后付款体验;沿既有 task,不新增管理任务。独立副本 `artifacts/isolated-worktrees/20260910-rejected-delete` 基线 e5685f1,官方 current 已对齐,无新分支,未混入其他工作树未提交内容。 - 已核对最新 PRD 追加范围及付款 task 综合方案。现有 DELETE 会级联删除 workflow 历史;draft 本身不是可物理删除的充分条件。当前模型没有专用删除标记,不能只放开 rejected。 - design 顶部补充 deleted_at/deleted_by 最小迁移、审计与材料引用、参与人授权、查询排除、锁顺序及旧应用回滚风险;行业调研后用户确认实施,本轮已编写迁移并仅在一次性合成数据库执行,未运行生产写入。 - [x] 当前实现与历史级联风险核查。 - [x] 用户在行业调研后确认本地实施,生产操作与提交仍另批。 - [x] 实施服务端删除/材料历史/查询边界和双端入口,补充针对性单测及真实隔离场景;没有声称新增测试先在旧代码上完整跑红。 - [x] 完成真实隔离流程、并发/权限反例与前端回归,保留独立副本作为只读复核入口。 - [ ] 独立只读复核(当前 inline,仅主会话自审,不冒充独立 reviewer)。 - [ ] 通过后再按授权提交发布;付款体验在独立副本与原 task 承接,不一并提交。 ### 实现与证据 - 运行时:仅 draft/rejected + 删除权限 + owner/submitter 可删;拒绝活动流程/待办、非取消付款占用、paid_at、异常关联票据。重复留痕删除先授权,再幂等返回。 - 无流程历史的 draft 原样物理删除;历史 draft/rejected 保留 form/items/workflow/attachments,写 cancelled + deleted_at/deleted_by,并保留票据 PDF 引用/摘要;删除审计与释放同事务。 - 查询:共享报销可读条件、财务台账、原始 SQL 统计、门户计数及项目汇总排除删除标记。详情、材料按既有权限只读,进度不再要求继续处理。 - 并发补口:附件上传落库时重取单锁校验状态;项目关联使用条件 updateMany,防止读到可编辑状态后越过删除继续写入。 - 双端详情/列表允许退回删除,确认不填理由;手机删除位于“更多”,删除成功提示使用“报销单”而非“草稿”。历史详情及打印有删除标识。 - Prisma validate、相关 node --check、前端 typecheck/build 已通过。 - 后端最终 focused:22 文件、279/279,通过记录在 artifacts/qa-flow/20260910-rejected-delete/unit-final.log。运行目录为本副本 server;命令为 PAYMENT_ORDER_PYTHON_BIN=<既有 BIFF8 虚拟环境解释器> npx vitest run tests/unit/routes/reimbursements*.test.js tests/unit/routes/reimbursement.submit-side-effects.test.js tests/unit/routes/paymentBatches.reimbursement.test.js tests/unit/services/reimbursementVisibility.test.js tests/unit/services/financeLedger.test.js tests/unit/routes/exports*.test.js tests/unit/routes/search.test.js tests/unit/routes/portal.test.js tests/unit/routes/projects.test.js。 - 页面回归:本地构建产物 preview,system-action-confirmation.spec.ts --grep '报销',6/6;ui-preview.log。与真实隔离 HTTP/browser 分开计,不把 mock 页面回归当真实服务验收。 - 隔离:RUN_ISOLATED_DB_TEST=1 RUN_RETAINED_DELETE_TEST=1 RUN_BROWSER_TEST=1 node scripts/qa/test-invoice-first-http.cjs;真实 PostgreSQL、密码登录/JWT、HTTP、桌面/手机;合成票据,OCR 为手动调度真实 worker + mock provider。涵盖退回/驳回/撤回→删除→原历史/凭证可读→票据重用→新单提交;删除与 submit/bind/delete 锁屏障;审计异常全事务回滚。 - 增补原始统计/台账排除的最终复跑记录:final-isolated.log,PASS_REAL_DB_HTTP,退出 0;临时容器、合成文件和浏览器已清理。不以之前只输出 PASS 但退出 143 的管道运行替代完整退出结果。 - UI strict/self-test、错误解释 strict、process contract baseline、Trellis governance、git diff --check 通过;文档 registry 为 errors=0、warnings=1(既有 22 项未注册文件),不扩修。 ### 调试与证据边界 - 首轮 BIFF8 回归 1 项失败为系统 python 缺 xlwt,使用既有专用解释器后 76 项付款相关测试通过;未修改付款产品逻辑。 - 隔离 fixture 最初复用了同一文件摘要,被真实重复发票门禁正确拒绝;改为每个场景独立合成 PDF,不通过假摘要/放宽去重绕过测试。 - 一轮新手机“更多”定位与旧构建产物不一致而超时;重建后真实双端通过。原日志留存,不把测试器问题写成业务通过。 - Vite dev 冷启动首项停在登录控件加载,终止该 mock 运行后改用构建产物 preview,6 项重跑通过;没有要求真实用户代测,也没有修改生产登录。 - 本轮保留删除时实际存在的流程、明细和凭证,不回填历史已释放票据、不补造过去提交版本。不可承诺完整重建原本未留存的历史快照。 - 当前状态:代码已实现、本地验证完成;发布未执行;真实业务删除未执行;等待独立复核及提交/部署授权。数据库 nullable 增量不等于旧应用可无损回滚:旧应用可能重新显示删除记录,需前向修复或经过审查的兼容回退。 - 主会话已只读检查任务 diff 的状态/参与人守卫、释放锁顺序、材料授权、查询旁路、迁移和回滚边界;独立 reviewer 尚未执行。复核包 artifacts/qa-flow/20260910-rejected-delete/changes.diff 包含本副本全部任务改动及未跟踪的迁移/测试文件,不含其他副本或生产数据。 下文是原 R1/R2 阶段清单,不能继承其无 migration 前提或勾选为本追加阶段的验收结果。 ## 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 整体不得宣称完成。