收敛报销退回动作与审批布局 [09-04-approval-return-reject-actions]

这个提交包含在:
2026-09-04 10:31:32 +08:00
父节点 59df0594bb
当前提交 8551601818
共修改 17 个文件,包含 520 行新增和 56 行删除
@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
@@ -0,0 +1,92 @@
# 技术设计:审批退回驳回语义与动作布局
## 1. 决策
采用按实体区分的动作模型,不全局合并状态机:
- 报销:新待办对客户端宣告 `approve / return / transfer`,不宣告 `reject`;界面文案为“通过 / 退回修改 / 转审”。
- 请假、采购、差旅:保留 `approve / reject / return`;补卡保留 `approve / reject`。
- 本任务只调整报销动作投影与报销 PC/移动布局;通用审批页仍消费其他四类实体的既有动作。
这不是把 `reject` 改名为 `return`,也不改写任何已存在的 outcome。新报销审批的修改诉求统一走原有 return 事务。
## 2. 服务端边界
在 `listAllowedActionsForTask` 中按实体产生对外 capability:
```text
reimbursement -> approve, return, [transfer]
attendance_correction -> approve, reject, [transfer]
leave/purchase/travel -> approve, reject, return, [transfer]
```
`transfer` 仍由既有权限决定。本任务不增加或删除审批权限。
兼容策略:
- `/workflow/tasks/:taskId/reject` 与 `/workflow/batch-reject` 暂不删除,避免旧页面、离线队列或内部调用在同一版本中发生破坏性变化。
- 新页面以 `allowedActions` 为可见能力来源,不为报销渲染 reject。
- 既有 `rejected` 业务单、task、instance、history 和 notification 原样保留;报销 `draft/rejected` 的编辑与重提能力不变。
- 不迁移数据库、不改 Prisma schema、不重写历史状态。
## 3. PC 报销动作区
默认态只保留一个紧凑动作组:
```text
审批决定
────────────────────────
[ 通过 ] [ 退回修改 ]
────────────────────────
转审
```
- “通过”和“退回修改”同一行、同高;主按钮使用实心主色,退回使用次级描边。
- 两列在右栏宽度内稳定铺满,不使用一个通栏按钮加若干内在宽度小按钮。
- “转审”保持现有 ghost/低频区域,不恢复更多菜单,也不改变其能力条件。
- 点击“退回修改”后才展开意见和引用清单;确认仍二次确认。
- fail-closed 原因继续紧贴动作组上方;被阻断时通过、退回修改、转审全部禁用。
为避免影响共享组件的其他消费者,优先由 `ReimbursementApprovalAdapter` 注入一个报销专用 action-group 节点;不改变 `BusinessDetailActionPanel` 其他消费者的 DOM 顺序。
## 4. 移动报销动作区
默认 sticky action bar 改为两个直接动作:
```text
[ 退回修改 ] [ 通过 ]
```
- 两个按钮均不小于 44px 高;通过保持主视觉,退回为次级样式。
- 点击退回后,在正文动作卡中渐进展开意见、异常优先引用清单和确认动作。
- 转审继续留在正文的低频处理区,不塞入 sticky 主动作组。
- 离线报销继续全动作 fail-closed,不因本次减少按钮而开放离线退回或驳回。
## 5. 数据与历史
- 新退回历史仍写 `workflow_history.action=return`、task outcome=`returned`、instance status=`terminated`、报销 status=`draft`。
- 历史 rejected 报销仍显示“已驳回”,并可按既有规则修改重提。
- 打印页、处理进度和审批历史继续同时认识 return/reject;不得因为新 UI 不再产生报销 reject 而删除历史映射。
- 通知继续使用 `reimbursement.returned`;历史 `reimbursement.rejected` 类型保留。
## 6. 测试边界
- 服务端:五类实体的 allowedActions 矩阵;报销新任务不含 reject,其他实体保持原样。
- 报销 PC:默认动作区只有通过、退回修改、转审;无驳回按钮;两项核心动作同组;退回展开/引用/确认保持。
- 报销移动:sticky 包含退回修改与通过;无驳回入口;离线全部禁用。
- 兼容:历史 rejected 报销仍可编辑重提,history/print 显示不变;旧 reject endpoint 的既有测试继续通过。
- 回归:请假、补卡、采购、差旅动作集合、确认文案和页面布局不变。
## 7. 风险与回滚
- 风险:`allowedActions` fixture 广泛硬编码,需只修改报销 fixture,不能全局删除 reject。
- 风险:移动通用审批页与报销专用工作台并存,必须验证报销路由最终只呈现一套动作。
- 风险:旧 bundle 在切换窗口仍可能调用兼容 reject endpoint;因此首轮上线后仍可能出现少量新 rejected 历史,这是兼容期预期,不做数据修正。
- 回滚:纯代码回滚即可恢复原动作投影和三按钮布局;无数据库回滚步骤。
## 8. 延后事项
- 请假/采购/差旅 return 缺少结果通知类型与发送,是独立通知治理问题。
- 是否最终退役报销 reject 与 batch-reject API,需在兼容期和调用审计后另立任务。
- 转审是否隐藏或进入“更多”仍等待真实使用证据。
@@ -0,0 +1 @@
{"_example": "Fill with {\"file\": \"<path>\", \"reason\": \"<why>\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."}
@@ -0,0 +1,64 @@
# 实施计划:审批退回驳回语义与动作布局
## 实施顺序
1. 为 `listAllowedActionsForTask` 增加实体动作矩阵,报销不再宣告 reject,其他四类保持现状。
2. 更新服务端聚焦测试,覆盖五类实体的正向与负向 allowedActions,并保留 reject/return endpoint 的兼容测试。
3. 收敛 `ReimbursementApprovalAdapter` 的 PC 动作组:通过与退回修改同组,删除报销可见驳回入口,转审不动。
4. 收敛移动报销 sticky action bar:退回修改与通过并列;退回意见、引用清单和确认仍渐进展开;删除离线时强制渲染驳回的旧分支。
5. 更新报销 PC/移动 E2E fixtures 和断言:无驳回、动作同组、退回展开、fail-closed、离线禁用、历史 rejected 兼容。
6. 回归通用审批页的请假、补卡、采购、差旅动作集合与确认流程,确认共享组件 DOM 未漂移。
7. 仅在稳定合同确有变化时更新 `docs/需求-统一审批中心规约.md` 与 `.trellis/spec/hyb-oa/approval-workflow.md`,明确报销用“退回修改”、其他实体按后果保留驳回。
## 风险文件
- `server/src/routes/workflow.js`:共享工作流动作投影,必须覆盖五类实体。
- `client/src/pages/reimbursement-workspace/ReimbursementApprovalAdapter.tsx`:PC/移动和离线分支集中,不能误删 return 引用能力或 fail-closed。
- `client/src/pages/MobileApprovalsPage.tsx`:通用移动审批回归面,原则上不改。
- `client/src/components/patterns/BusinessDetailTemplate.tsx`:共享动作面板,原则上不改。
- `client/src/lib/workflowActionConfirm.ts`:通用后果文案,原则上不改;报销仅不再进入 reject 分支。
## 验证命令
```bash
cd server && npm run prisma:validate
cd server && node --check src/routes/workflow.js
cd server && npm test -- tests/unit/routes/workflow.permissions.test.js tests/unit/routes/workflow.batch.test.js tests/unit/routes/workflow.travel.test.js
cd server && npm test -- tests/unit/routes/reimbursements.detail.test.js tests/unit/routes/reimbursements.invoice-item-link.test.js
cd client && npm run build
cd client && npx playwright test tests/e2e/reimbursement-content-consistency.spec.ts --workers=1 --reporter=list
cd client && npx playwright test tests/e2e/workflow-action-confirm-ui.spec.ts --workers=1 --reporter=list
cd client && npx playwright test tests/e2e/mobile-approvals-offline.spec.ts --grep '报销证据不进入 IndexedDB' --workers=1 --reporter=list
node scripts/qa/check-ui-design-rules.mjs --strict
node scripts/qa/check-ui-design-rules.mjs --self-test
node scripts/qa/check-process-contract.mjs --baseline --json
node scripts/qa/audit-docs.mjs --check-registry
node scripts/qa/check-trellis-governance.mjs
python3 ./.trellis/scripts/task.py validate 09-04-approval-return-reject-actions
git diff --check
```
## 验证结果
- Prisma schema 与 `workflow.js` 语法检查通过。
- 服务端动作矩阵、旧 reject 兼容及四类非报销回归共 24/24 通过;历史报销详情与 rejected 可维护证据回归 48/48 通过。
- 前端 typecheck、生产构建通过;报销静态契约 9/9、审批动作浏览器回归 11/11 通过。
- 移动报销离线专项未进入断言:本机缺 Playwright WebKit executable;保留为环境边界,不记为产品失败,也未安装新浏览器或更改测试配置。
- UI strict 扫描 155 文件、0 findings;self-test PASS。
- process contract、Trellis governance、task validate 与 `git diff --check` 通过。
- docs registry 审计为 0 error / 1 warning;warning 为仓库既有 22 个未注册 docs 文件,本任务未新增该类文件。
## 人工验收
1. PC 报销审批右栏:通过与退回修改同组、无驳回、转审位置不变;1280px 无溢出。
2. 移动报销审批:底部直接显示退回修改与通过;点击退回后才展开意见与引用清单。
3. 任一报销 fail-closed fixture:原因紧邻动作,所有可见动作均禁用。
4. 历史 rejected 报销仍显示已驳回、仍可修改重提;历史 timeline/打印无丢失。
5. 请假、补卡、采购、差旅各打开一个:动作数量、文案和确认流程无变化。
## 提交与部署边界
- commit 使用中文并引用 `09-04-approval-return-reject-actions`。
- 实施后先做只读 review;用户批准后才能提交。
- push、生产部署和生产数据检查均需分别获得明确授权。
- 无 migration/schema/seed/flag/生产数据写入;回滚到上一构建即可。
@@ -0,0 +1,118 @@
# 审批退回驳回语义与动作布局
## Goal
基于系统真实后果而不是行业常识,厘清“退回”与“驳回”的实体差异;优先收敛唯一真实使用的报销审批链路,使审批人只面对必要动作,并修复当前“通栏主按钮 + 零散小按钮”的割裂布局。
## Background
当前报销审批右栏把“通过”作为通栏主按钮,“退回”“驳回”作为下一行两个内在宽度按钮,“转审”再单独分组。该布局既失衡,也把两个对报销申请人后果近似的动作同时交给审批人判断。
报销现有“驳回”并未兑现通常意义上的终止:`rejected` 报销仍可在原单编辑并重提。因此移除报销审批端的“驳回”不损失已存在的终止能力,而是撤掉一个与真实后果不符的语义承诺。审批人若认为该笔费用根本不应申报,现阶段在退回意见中说明,申请人收到后可删除草稿;真正的审批人终止能力如有现实需求,必须另立状态机任务,不能继续借用名不副实的 reject。
只读核查确认五类实体不能全局合并:
| 实体 | 退回后 | 驳回后 | 后续能力 | 结论 |
| --- | --- | --- | --- | --- |
| 报销 | 原单回 `draft` | 原单为 `rejected` | 两者均可在原单修改、重提,并创建新 workflow instance | 新审批只保留“退回修改” |
| 请假 | 原单回 `draft` | 原单为 `rejected` | 两者均可修改、重提并重新预留假期;但只有驳回发送结果通知 | 本次不改 |
| 补卡 | 不提供退回 | 原单为 `rejected` | 无原单编辑/重提 | 本次不改 |
| 采购 | 原单回 `draft` | 原单为 `rejected` | 只有退回后能修改、重提 | 两动作有实质差异,本次不改 |
| 差旅 | 原单回 `draft` | 原单为 `rejected` | 只有退回后能修改、重提 | 两动作有实质差异,本次不改 |
服务端证据与文件锚点见 `research/return-reject-semantics.md`。国内 OA 的通行叫法仅作为参考,目标口径由本系统状态、编辑/重提、通知和释放副作用共同决定。
## Scope
- 按五类实体建立服务端动作矩阵,修改新报销待办的 `allowedActions` 投影。
- 收敛报销 PC 与移动审批动作文案、分组和渐进展开方式。
- 增加服务端矩阵测试、报销 PC/移动交互测试和四类非报销回归。
- 保留历史状态与旧接口兼容,必要时同步审批规约和稳定工程 spec。
## Requirements
### R1:按实体投影动作
- 服务端 `allowedActions` 必须按实体后果产生,不再默认给所有非补卡实体同一组动作。
- 新报销待办只宣告 `approve / return`,有既有转审权限时再宣告 `transfer`;不再向 PC 或移动客户端宣告 `reject`。
- 请假、采购、差旅继续宣告 `approve / reject / return`;补卡继续宣告 `approve / reject`;各实体的转审仍沿用现有权限。
### R2:报销只保留“退回修改”
- 报销审批界面将 return 展示为“退回修改”,继续调用既有 return 事务。
- 退回后必须保持:报销单回 `draft`、原单号保留、历史追加 returned、申请人可修改并重提、重提新建 workflow instance。
- 不得把 reject 改名后继续调用 reject,也不得把历史 rejected 重写为 returned。
### R3:PC 动作布局收敛
- 报销 PC 右栏的“通过”和“退回修改”必须在同一个两列动作组中,同高、铺满可用宽度,以颜色和样式表达主次,不再使用一枚通栏按钮加零散小按钮。
- “转审”保持现有低频区域、权限和展开行为;不恢复“更多”菜单。
- 点击“退回修改”后才展开意见、异常优先引用清单与确认入口。
### R4:移动动作布局收敛
- 报销移动端 sticky action bar 必须直接显示“退回修改”和“通过”,触控高度不小于 44px。
- 退回编辑内容在点击后渐进展开;转审继续留在低频处理区。
- 报销离线仍 fail-closed,不因减少一个按钮而开放任何远程审批动作。
### R5:安全与解释不退化
- 不增加或删除现有 `decisionReady`、B1/B2、权限、自审批或内容完整性条件。
- fail-closed 原因继续紧邻动作组上方;被阻断时所有可见动作全部禁用。
- 通过、退回修改和转审继续使用现有二次确认、必填意见与审计规则。
### R6:历史和接口兼容
- 既有 rejected 报销继续显示“已驳回”,仍按现有规则允许修改重提;打印、处理进度、审批历史和通知映射不得删除 reject。
- 本任务不迁移数据库、不修改 schema、不改写 task/instance/history/outcome。
- 旧单项 reject endpoint 和隐藏的 batch reject endpoint 作为兼容入口暂时保留,但不得新增或恢复任何报销可见入口;其最终退役另行立项。
### R7:其他实体零行为变化
- 请假、补卡、采购、差旅的动作集合、状态写入、通知、释放逻辑、确认文案和页面布局本次不变。
- 请假/采购/差旅 return 缺少结果通知属于已识别既有缺口,另记待办,不在本任务顺手修复。
## Acceptance Criteria
- [ ] AC1:服务端测试逐项证明 reimbursement=`approve,return[,transfer]`、leave/purchase/travel=`approve,reject,return[,transfer]`、attendance_correction=`approve,reject[,transfer]`;无权限或已处理任务仍返回空动作。
- [ ] AC2:报销 PC 和移动 fixture 的 `allowedActions` 及可见 UI 均不含 reject;页面上“驳回”“确认驳回”按钮数为 0。
- [ ] AC3:PC 报销右栏“通过 / 退回修改”同处一个两列动作组、等高且无横向溢出;不存在旧的一通栏加两个小按钮布局。
- [ ] AC4:移动报销 sticky action bar 默认直接包含“退回修改 / 通过”,两者触控高度至少 44px;转审不进入该主动作组。
- [ ] AC5:展开退回修改前,意见输入和引用清单不存在;展开后意见必填、异常引用项优先、确认弹窗准确说明回到申请人修改态。
- [ ] AC6:任一现有 fail-closed 条件成立时,原因紧贴动作组上方,通过、退回修改、转审全部禁用;正常报销的可见动作不被误伤。
- [ ] AC7:执行退回修改后,服务端响应和持久化仍为 returned/draft;修改后重提创建新 workflow instance 并从重新匹配的首个可执行节点开始。
- [ ] AC8:历史 rejected 报销仍可读取、显示、编辑和重提;打印/时间线仍正确显示“驳回”,不发生数据迁移或历史改写。
- [ ] AC9:旧 reject endpoint 的既有兼容测试继续通过;产品 UI 和新 `allowedActions` 不再为报销提供调用路径。
- [ ] AC10:请假、补卡、采购、差旅分别通过动作集合与确认流程回归;共享 `BusinessDetailActionPanel` 的其他消费者 DOM 顺序不变。
- [ ] AC11:typecheck、build、相关后端测试、PC/移动 E2E、UI strict/self-test、process contract、Trellis governance 与 `git diff --check` 全部通过。
- [ ] AC12:面向用户可测试前,需另行批准 push/部署,并通过公网 bundle、PC 1280px、移动端和五类审批回归。
## Test Plan
- 服务端:五类实体 `allowedActions` 矩阵、无权限/已处理负向分支、报销 return 状态与重提新实例、旧 reject endpoint 兼容。
- PC:报销两列动作组、无驳回入口、退回渐进展开、引用与确认、正常态及 fail-closed;四类通用审批动作回归。
- 移动:报销 sticky 双动作、44px 触控、离线 fail-closed、退回编辑;通用移动审批不受影响。
- 历史:rejected 报销读取、编辑、重提、打印和 timeline 映射不变。
- 门禁:前后端聚焦测试、typecheck/build、UI strict/self-test、process contract、Trellis governance、`git diff --check`。
## Out of Scope
- 删除 reject/batch-reject API、迁移或清理历史 rejected 数据。
- 调整请假、补卡、采购、差旅的动作语义或页面布局。
- 修复请假/采购/差旅 return 通知缺口。
- 新增退回上一节点、退回指定人或续接旧 workflow instance 的能力。
- B1/B2、审批链配置、权限模型、自审批规则和状态机的其他改造。
- 转审入口归属、全局按钮体系或其他页面视觉重构。
- 生产部署和真实数据写入。
## Risks And Deferred Items
- 旧 bundle 在发布切换窗口仍可能调用兼容 reject endpoint,因此短暂兼容期内仍可能产生 rejected 报销;不自动修正此类历史。
- 采购和差旅页面存在“驳回后补材料”类文案与服务端只允许 draft 编辑之间的既有漂移,需独立任务处理,不混入本次报销收敛。
- 请假/采购/差旅 return 不发送结果通知;需按各模块实际使用优先级另行治理。
- 请假/采购/差旅 return 结果通知缺口应作为本任务之后的独立高优先级任务,优先于一般视觉和代码清理债;不得在本任务中夹带实现。
- 转审是否隐藏、保留或进入更多菜单,继续等待真实使用证据。
## Planning Gate
本任务为复杂 workflow + UI 改动,`design.md`、`implement.md` 和研究记录已齐备。只有用户审批本版最终规划后,才可执行 `task.py start` 并修改产品代码。
@@ -0,0 +1,63 @@
# 退回 / 驳回语义核查
## 核查边界
只读检查服务端状态写入、业务对象编辑/重提条件、工作流实例创建、通知类型、额度/预算释放、前端动作投影和历史展示。未查询或修改生产数据。
## 总结
五类实体不能用一套“退回 / 驳回等价”结论:
| 实体 | 退回 | 驳回 | 申请人后续能力 | 通知 | 结论 |
| --- | --- | --- | --- | --- | --- |
| 报销 | 业务单回 `draft`;实例 `terminated`;task outcome=`returned` | 业务单回 `rejected`;实例/task=`rejected` | 两者均可编辑并在原单重提;重提创建新实例、重新从首个可执行节点开始 | 两者各有通知类型和不同标题 | 当前用户后果近似,审批端同时暴露两个动作没有足够价值 |
| 请假 | 业务单回 `draft`;释放已预留假期 | 业务单回 `rejected`;同样释放已预留假期 | 两者均可编辑并在原单重提;重提创建新实例并重新预留 | 驳回有通知;退回当前没有对应通知类型 | 业务能力近似但副作用不等价,不能顺手合并 |
| 补卡 | 不在 `allowedActions` 中,return handler 也无业务对象分支 | 业务单变 `rejected` | 没有原单编辑/重提入口 | 驳回有通知 | 只有驳回,语义明确 |
| 采购 | 业务单回 `draft`;释放预算 | 业务单变 `rejected`;同样释放预算 | 只有 `draft` 能编辑/提交;驳回后原单不能继续修改 | 驳回有通知;退回当前没有对应通知类型 | 两者有实质差异,必须保留 |
| 差旅 | 业务单回 `draft`;释放预算 | 业务单变 `rejected`;同样释放预算 | 只有 `draft` 能编辑/提交;驳回后原单不能继续修改 | 驳回有通知;退回当前没有对应通知类型 | 两者有实质差异,必须保留 |
## 关键证据
### 服务端动作投影
- `server/src/routes/workflow.js:684-703`:当前所有可审批实体默认获得 `approve + reject`,补卡以外再获得 `return`;差异仅按补卡特殊处理,没有按真实后果建模。
- `server/src/routes/workflow.js:1858-2013`:驳回把 task/outcome、workflow instance 和各业务对象写成 `rejected`;请假释放假期,采购/差旅释放预算。
- `server/src/routes/workflow.js:2990-3136`:退回把 task outcome 写成 `returned`、instance 写成 `terminated`,报销/请假/采购/差旅业务对象回到 `draft`;请假释放假期,采购/差旅释放预算;补卡没有业务对象更新分支。
### 编辑与重提
- 报销:提交及维护接受 `draft/rejected`;重提在 `server/src/routes/reimbursements.js:4556` 创建新 workflow instance。
- 请假:编辑、提交接受 `draft/rejected`(`server/src/routes/leave.js:805-850`、`:1009-1010`);`:1113-1130` 创建新 workflow instance;`:1169-1184` 重新预留假期。
- 采购:权限投影、编辑、提交均只接受 `draft`(`server/src/routes/purchaseRequests.js:675-678`、`:1652-1653`、`:1828-1829`);提交在 `:1959-1975` 创建新实例。
- 差旅:权限投影、编辑、提交均只接受 `draft`(`server/src/routes/travelRequests.js:493-496`、`:808-809`、`:945-946`);提交在 `:1032-1050` 创建新实例。
- 补卡:创建时直接创建 workflow instance(`server/src/routes/attendanceCorrections.js:307-322`),后续只有取消接口,没有原单编辑/重提接口。
### 通知
- `server/src/routes/workflow.js:1073-1096`:五类实体都有 rejected 通知;returned 仅映射报销。
- `server/src/services/notifications/workflowCopy.js:238-253`:退回和驳回通知文案分别使用“已退回”“已驳回”。
- 因此请假/采购/差旅执行退回时,`queueWorkflowOutcomeNotifications` 会因 notification type 为 `null` 直接返回。该缺口是既有行为,本任务不顺手扩张为通知治理。
### 现有产品合同与实现漂移
- `docs/需求-统一审批中心规约.md:205-242` 将驳回定义为终止或按模块重新发起,将退回定义为回到修改态并保留原单。
- 采购/差旅服务端与该合同一致;报销/请假允许 `rejected` 原单直接编辑重提,比文档中的默认驳回语义更宽松。
- `client/src/lib/workflowActionConfirm.ts` 仍按通用合同解释:退回原单修改,驳回通常重新发起。该文案对采购/差旅成立,对报销/请假不完全成立。
## 布局证据
- `client/src/components/patterns/BusinessDetailTemplate.tsx:83-138` 将 primary、secondary、more 拆成三个视觉分组。
- 报销在 `client/src/pages/reimbursement-workspace/ReimbursementApprovalAdapter.tsx:398-414` 将“通过”放 primary,将“退回/驳回”放 secondary,将“转审”放 more,形成当前通栏主按钮、两个内在宽度按钮和分隔后的转审。
- 移动报销在同文件 `:361-376` 把三个次动作放正文卡片,只把“通过”放 sticky action bar;信息层级与 PC 不一致。
- `ui-ux-pro-max` 的相关规则要求危险动作二次确认、错误靠近字段/动作、触控目标至少 44px;现有确认和 fail-closed 规则应保留。
## 规划结论
采用“按实体区分”,但本次产品改动只收敛 OA 主链路报销:
1. 新报销审批不再向 UI 宣告 `reject`,仅提供“通过 / 退回修改 / 转审”。
2. 报销“退回修改”继续调用既有 return 路径,保持 `draft + returned history + returned notification`。
3. 历史 `rejected` 报销继续按原状态展示和允许修改重提;不改写历史记录、不迁移数据。
4. 旧 reject endpoint 和隐藏的 batch reject endpoint暂作兼容保留,不新增入口;另行治理 API 退役,避免本次视觉改造扩大为破坏性接口变更。
5. 请假、补卡、采购、差旅的动作与布局本次不改;它们作为回归面。请假/采购/差旅 return 通知缺口另记待办。
@@ -0,0 +1,26 @@
{
"id": "approval-return-reject-actions",
"name": "approval-return-reject-actions",
"title": "审批退回驳回语义与动作布局",
"description": "核清五类审批实体的退回与驳回真实后果,并按实体收敛报销动作投影及 PC/移动布局。",
"status": "in_progress",
"dev_type": null,
"scope": "trellis,server,client,tests,docs",
"package": null,
"priority": "P2",
"creator": "shunli",
"assignee": "shunli",
"createdAt": "2026-09-04",
"completedAt": null,
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}