Browse Source

docs: add ETC vehicle recharge apply withdraw design

Capture approved rules for in_approval → draft withdraw on web-admin and miniprogram, aligned with reimbursement withdraw.

Co-authored-by: Cursor <cursoragent@cursor.com>
zlwu 1 month ago
parent
commit
134c541ebc

+ 159 - 0
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` 本人撤回成功;非本人 / 错误状态失败。