diff --git a/docs/superpowers/specs/2026-09-29-pl-vehicle-recharge-apply-withdraw-design.md b/docs/superpowers/specs/2026-09-29-pl-vehicle-recharge-apply-withdraw-design.md new file mode 100644 index 000000000..f3bfec883 --- /dev/null +++ b/docs/superpowers/specs/2026-09-29-pl-vehicle-recharge-apply-withdraw-design.md @@ -0,0 +1,159 @@ +# ETC/加油充值申请 · 撤回 — 设计文档 + +> **日期**: 2026-09-29 +> **状态**: 已确认待实现 +> **仓库**: shanxia2.0(`backend/` + `web-admin/` + `miniprogram/`) +> **对齐**: 报销申请撤回(`ReimburseService.withdraw`:审批中 → 草稿,记录保留) +> **前置**: +> - `docs/superpowers/specs/2026-06-29-pl-vehicle-recharge-design.md` +> - `docs/superpowers/specs/2026-09-26-pl-vehicle-recharge-workflow-design.md` +> - `docs/superpowers/specs/2026-09-27-mini-pl-vehicle-recharge-apply-design.md` + +## 背景与问题 + +【报销申请】在审批中支持申请人**撤回** → 变为待提交/草稿,可修改后再提交。 + +【ETC/加油充值申请】现状: + +| 能力 | 状态 | +|------|------| +| 草稿 / 已驳回:编辑、提交、作废 | 已有 | +| 审批中 `in_approval`:编辑 | 无 | +| 审批中:撤回 → 草稿 | **缺失** | +| 作废 `cancel` | 仅草稿/已驳回 → `cancelled`(与撤回不同) | + +申请人发现写错内容时,只能等驳回或让审批人处理,无法主动撤回修改。 + +## 已确认决策 + +| # | 决策 | 说明 | +|---|------|------| +| D1 | 撤回结果 | 审批中 → **草稿 `draft`**,可再编辑后重新提交(与报销一致) | +| D2 | 端范围 | **Web 管理端 + 小程序** | +| D3 | 审批记录 | **保留**,不强制切轮次、不写「撤回」分界(与报销撤回现状对齐) | +| D4 | 可撤回状态 | 仅 **`in_approval`(审批中)**;不含待发放 `pending`、已发放、已作废、草稿、已驳回 | +| D5 | 操作人 | 仅 **申请人本人**(`applicant_id === 当前用户`) | +| D6 | 实现方案 | 新增独立 `withdraw` API,与 `cancel`(作废)分离 | + +## 1. 目标与范围 + +### 目标 + +1. 申请人可在审批中撤回充值申请,单据回到草稿。 +2. Web 与小程序均可操作,规则一致。 +3. 撤回后沿用现有草稿编辑 / 提交能力。 + +### 范围内 + +- 后端:`withdraw_apply` + `POST /transport/vehicle-recharge-apply/{id}/withdraw` +- Web:`views/pl/vehicle-recharge-apply/index.vue` 列表工具栏 + 查看弹窗 +- 小程序:`pages/pl/vehicle-recharge-apply/list.vue`(及详情/表单页若有操作区则对齐) +- API 封装:`web-admin/src/api/pl/index.ts`、`miniprogram/api/pl.js` + +### 范围外(明确不做) + +- 审批轮次过滤改造、写入 `withdrawn` 审批记录 +- 待发放 `pending` 撤回 +- 改作废语义或合并 cancel/withdraw +- 备用金、维修、尿素等其它业务的撤回 +- 同步改造报销撤回的轮次逻辑 + +--- + +## 2. 后端行为 + +### 2.1 接口 + +`POST /api/transport/vehicle-recharge-apply/{id}/withdraw` + +Query(与现有 apply 写接口一致): + +- `mineVehicles`(可选,小程序列表作用域时传 `true`) + +成功:返回更新后的 `VehicleRechargeApplyRead`。 + +### 2.2 前置校验 + +1. 申请存在且未删除、租户匹配、数据权限可读。 +2. `applicant_id` 必须等于当前用户,否则 403。 +3. `status == "in_approval"`,否则 400(文案如:仅审批中的申请可撤回)。 +4. `mineVehicles=true` 时继续沿用现有本人车辆/本人写校验。 + +### 2.3 成功副作用 + +| 字段 | 处理 | +|------|------| +| `status` | → `draft` | +| `current_node` | → `null` / 清空 | +| `approval_round` | **保持不变**(下次 `submit` 仍会 `+1`,当前进度按新轮展示) | +| `wf_id` | 保持不变 | +| `pl_vehicle_recharge_approval` 行 | **不删、不改** | +| 其它业务字段 | 不变 | + +无发票额度等资源需退回(与报销不同)。 + +### 2.4 与重新提交的关系 + +现有 `submit_apply` 已执行 `approval_round = (approval_round or 0) + 1` 并重置 `current_node` 为首节点。 +因此:撤回 → 改稿 → 再提交后,**当前审批进度**自然只看新轮;旧轮记录仍可通过既有 **审批历史** 接口/UI 查看。 +这与「不强制切轮次」决策兼容:不新增撤回分界记录,依赖已有 `approval_round`。 + +### 2.5 错误语义(建议) + +| 情况 | HTTP | detail | +|------|------|--------| +| 非申请人 | 403 | 仅申请人可撤回 | +| 非审批中 | 400 | 仅审批中的申请可撤回 | +| 不存在 | 404 | 申请单不存在 | + +--- + +## 3. 前端行为 + +### 3.1 公共规则 + +`canWithdraw(row)`: + +- `row.status === 'in_approval'` +- `String(row.applicantId) === 当前用户 id` + +确认文案建议: + +> 确定撤回该充值申请?撤回后变为草稿,可修改后再提交。 + +成功 Toast:`已撤回`。 + +### 3.2 Web 管理端 + +对齐报销申请页交互: + +- 列表工具栏:在「作废」附近增加「撤回」按钮;单选且 `canWithdraw` 时可用。 +- 查看弹窗 `#hero-actions`:审批中且本人时展示「撤回」(替代或并列于仅查看态;**不**开放编辑/作废)。 +- 图标:与报销一致优先 `RefreshLeft`(或项目已有等价图标)。 + +编辑 / 提交 / 作废条件不变:仍仅 `draft` / `rejected`。 + +### 3.3 小程序 + +- 列表卡片:当 `canWithdraw(item)` 时展示「撤回」;审批中他人单据仅「查看」+「复制发群」等现有能力。 +- 若表单页以只读打开审批中本人单,可在底部增加「撤回」(可选,与列表能力二选一即可;**至少列表具备**)。 +- 调用 `vehicleRechargeApplyApi.withdraw(id)`,小程序请求带现有 `mineVehicles` 约定。 + +--- + +## 4. 验收要点 + +1. 本人 + 审批中:两端均可撤回 → 状态变草稿 → 可编辑 → 可再提交进入新一轮审批。 +2. 非本人 / 非审批中:无撤回入口;直调 API 被拒绝。 +3. 作废仍仅草稿/已驳回;撤回不会变成 `cancelled`。 +4. 撤回后审批历史中仍可见撤回前已产生的审批动作;再提交后当前进度为新轮。 +5. 待发放、已发放、已作废不可撤回。 + +--- + +## 5. 实现备注 + +- Service 放在 `pl_vehicle_recharge_service.py`(与 `submit_apply` / `cancel_apply` 同层)。 +- 路由挂在 `vehicle_recharge.py` 的 `apply_router`,放在 `submit` / `cancel` 附近。 +- 无需改表结构 / SQL。 +- 建议补一条后端单测:`in_approval` 本人撤回成功;非本人 / 错误状态失败。