文件
hyb-oa/.trellis/tasks/09-04-reimbursement-state-gap-finance-view/implement.md
T

11 KiB
原始文件 Blame 文件历史

报销状态空隙与财务处理视角:实施计划

2026-09-10 退回删除接续

  • 用户要求继续剩余任务,先退回删除、后付款体验;沿既有 task,不新增管理任务。独立副本 artifacts/isolated-worktrees/20260910-rejected-delete 基线 e5685f1,官方 current 已对齐,无新分支,未混入其他工作树未提交内容。
  • 已核对最新 PRD 追加范围及付款 task 综合方案。现有 DELETE 会级联删除 workflow 历史;draft 本身不是可物理删除的充分条件。当前模型没有专用删除标记,不能只放开 rejected。
  • design 顶部补充 deleted_at/deleted_by 最小迁移、审计与材料引用、参与人授权、查询排除、锁顺序及旧应用回滚风险;行业调研后用户确认实施,本轮已编写迁移并仅在一次性合成数据库执行,未运行生产写入。
  • 当前实现与历史级联风险核查。
  • 用户在行业调研后确认本地实施,生产操作与提交仍另批。
  • 实施服务端删除/材料历史/查询边界和双端入口,补充针对性单测及真实隔离场景;没有声称新增测试先在旧代码上完整跑红。
  • 完成真实隔离流程、并发/权限反例与前端回归,保留独立副本作为只读复核入口。
  • 独立只读复核(当前 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 验证

按实际测试文件补齐精确命令,最低包括:

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 整体不得宣称完成。