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

8.9 KiB
原始文件 Blame 文件历史

报销状态空隙与财务处理视角:设计

1. 设计边界

本任务包含两个独立变更单元:

  • R1 是付款运营入口与只读进度投影调整,复用现有付款候选及创建合同。
  • R2 是报销读取授权收紧,使用已有 workflow 关系判断是否形成过业务事实。

两者不共享新数据库字段、feature flag 或写状态机。实现、测试和提交顺序允许分开回滚;部署候选可同时包含两项,但不得形成互相调用或互为前置的数据依赖。

2. R1:待组批运营队列

2.1 服务端合同

继续以 GET /payment-batches/candidates 为唯一候选来源:approved、paid_at=null、不属于任何非取消报销付款批次,并通过付款部门门禁。接口已经提供搜索、页码、页大小和 total,无需新增第二个候选接口或在客户端复制 eligibility。

候选查询仍要求 payment.batch.manage。付款批次列表继续查询已经创建的 PaymentBatch;“待组批”与“付款批次”是同一付款工作台的两个数据视图,不把候选伪装成批次记录。

2.2 页面状态与路由

付款页引入显式 URL 视图,例如:

  • /payment-batches?view=pending:待组批。
  • /payment-batches?view=batches:付款批次。
  • /payment-batches?id=<batch-id>:既有批次详情,优先于 view。

报销详情深链使用 view=pending&q=<form-number>。页面从 URL 初始化搜索并保持返回来源;不得把内部 UUID 放入用户文案。组批管理员无 detail 参数时默认 pending,复核只读角色固定 batches。

现有“新建批次”面板中的候选搜索/选择提升为 pending 视图的主体,创建所需的最小字段和主动作沿用当前 KISS 合同。实现应只有一个候选 map 和一份选择状态;不能保留旧弹层或折叠区中的第二份候选 DOM。

候选行使用稳定 entity ID 作为 key,展示服务端权威字段。查询超过 300ms 显示加载反馈;请求失败就地显示可重试错误;空态只写“当前没有待组批报销”。选择后才显示批量创建主动作,避免未选择时铺设高风险动作。

2.3 进度语义

共享 PAYMENT_PROCESSING_STAGES 已有 payment_batch_created / 批次创建,但报销详情当前故意丢弃该阶段并在无批次时把 payment_batch_review 提前标 current。本任务调整报销组装层:

  • 将 created 阶段对报销用户重命名/投影为“组建付款批次”,不改变付款批次详情使用的“批次创建”。
  • 无 batch:created=current,result=待组批,detail=待财务组建付款批次;review=pending;payment=pending。
  • 有 batch:三个付款阶段全部来自共享 buildPaymentProcessingSemantics,created 的时间和创建人按现有 actorSource 解析,review/payment 语义不变。

报销 DTO 阶段 key 增加 payment_batch_created 属向后兼容的数组扩展;旧客户端按通用 stage 渲染或忽略,不需要 schema/migration。结构测试锁定共享语义仍只有一套,禁止在客户端根据 batch status 再写矩阵。

2.4 查询刷新

创建批次成功后同时失效:候选列表、候选总数、付款批次列表和当前批次详情所需查询。候选资格由服务端自然消失;客户端不得乐观伪造 approved -> batched 状态。失败时保留选择并显示现有自然中文错误。

3. R2:曾进入流程的草稿可见性

3.1 权威判据

定义可复用的 Prisma 条件:

own-or-submitted-draft =
  owner_id = actor
  OR submitter_id = actor
  OR workflow_instances.some(entity_type = reimbursement)

关系本身已限定为报销实体;若 Prisma relation 不需要额外 entity type 条件,以 relation some: {} 为准,并由结构测试证明关联字段正确。判据只在记录 status=draft 时附加。非 draft 沿用原范围条件。

撤回 workflow 的 current_status=cancelled、退回 workflow 的 current_status=terminated,两者都满足“曾进入流程”;不得使用 pending/latest/outcome 过滤。

3.2 列表、搜索、导出和统计

提取或共享一个“当前人员可读取的报销事实”条件构建器,组合顺序为:

  1. 既有角色范围:全局角色 {};经理为本人/提交人/直属下属;普通 own 为本人/提交人。
  2. 业务事实条件:非 draft,或本人/提交人,或存在 workflow。
  3. 页面自己的状态、时间、部门、搜索等条件。

列表、全局搜索、报销 CSV/XLSX 导出、报销统计/看板使用同一事实条件。原始 SQL 统计使用 EXISTS 子查询表达相同合同,不能因为实现技术不同而遗漏。审批瓶颈查询本身 inner join workflow 且只查 reviewing,天然满足,但测试应证明无需额外分支。

付款候选固定只查 approved,不复用这个授权条件;项目关联、AP 等非全范围草稿入口若本就不展示 draft,保持不变。实现前逐个确认所有报销 record-level 导出入口,防止只修一条 CSV 路径。

3.3 详情与材料防旁路

把当前 ensureOwnCanViewAllOrWorkflowReviewer 的无条件 canViewAll 快速放行改为异步事实判断:

  • 本人/提交人:按原权限放行。
  • 非本人且持有 view_all:记录非 draft 直接放行;draft 必须查询至少一个 workflow instance。
  • 历史/当前 workflow reviewer:继续按既有 task 关系放行;从未提交草稿不可能存在 reviewer task。
  • 其他人员:拒绝。

详情、附件文件、附件 preview URL、完整材料 preview 和 POST /reimbursements/:id/precheck 都调用同一事实授权 helper。材料与 precheck 的现有轻量查询没有同时选择 status/workflow,可由 helper 使用 reimbursement ID 做最小 findFirst/count,避免为每个调用方复制状态和 workflow 查询。拒绝统一返回既有 PERM_001,不区分不存在、未提交或越权原因。

写接口的 owner/submitter、状态和 permission 门禁不因本任务放宽。precheck 虽是 POST,但只读返回预算、费用标准、审批规则与风险事实,必须使用同一读取边界;它不得因重用 helper 让 view_all 用户获得修改他人草稿的能力。当前或历史 workflow reviewer 分支继续按 task 关系独立放行,focused 测试必须证明新草稿过滤不会阻断审批人读取自己的待办报销。

3.4 性能与兼容

优先使用现有 relation filter 与 workflow_instances(entity_type, reimbursement_id) 查询路径,不新增 submitted_at 或 migration。分页先过滤后计数,保证 items 和 total 一致。对原始 SQL 使用 EXISTS,避免 join 放大金额或记录数。

生产当前仅 1 张从未提交草稿、0 张有 workflow 的草稿,发布不需要数据修复。历史 returned/withdrawn/rejected 数据不改写;列表状态中文映射、审批历史和再次提交保持兼容。

4. 权限与状态矩阵

访问者/记录 自己的未提交 draft 范围内他人的未提交 draft 范围内他人的退回/撤回 draft 范围内他人的非 draft
owner/submitter 可见、可按原规则编辑 不适用 可见、可按原规则编辑 可见
全局 view_all 角色 可见 不可见 只读可见 只读可见
manager 可见 不可见 仅直属团队只读可见 仅直属团队只读可见
workflow reviewer(无 view_all) 按本人规则 不存在 reviewer task,拒绝 有历史任务时只读可见 有任务时只读可见
无关员工 按本人规则 不可见 不可见 不可见

5. UI/UX 约束

本任务使用现有 React/Tailwind 工作台设计,不引入新色板、字体或动画体系。ui-ux-pro-max 的适用结论落实为:异步候选查询必须有加载/错误反馈;列表使用稳定 key;导航和搜索状态写入 URL;危险批量动作仅在选择后出现;移动触控目标维持至少 44px;1280px 不产生横向滚动。

“待组批”是可执行运营队列,不是解释卡:首屏优先数量、搜索、候选和组批动作,不重复说明系统内部状态。付款批次仍是历史与后续处理对象,两者用清晰标签区分。

6. 发布、回滚与真实验收

  • 无 migration、schema、seed、flag、生产配置或数据修正。
  • R1 与 R2 使用独立提交和 focused gate。R1 回滚只恢复付款视图/进度投影;R2 回滚只恢复读取条件。任何一项回滚都不撤销已由财务人工创建的批次。
  • 部署前跑 UI strict、权限/workflow focused 回归和标准 provenance 门禁。
  • 部署后只读确认目标单据在待组批可搜到、未被自动修改;由财务人工点击创建后,才能形成真实付款复核对象。
  • R2 部署后使用受控测试身份或既有非敏感 fixture 核验未提交他人草稿不可见、有 workflow 草稿可见;不在生产临时创建真实员工草稿来凑证据。