|
|
@@ -0,0 +1,397 @@
|
|
|
+# 车辆充值模块收窄为仅 ETC Implementation Plan
|
|
|
+
|
|
|
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
+
|
|
|
+**Goal:** 将「ETC加油申请 / ETC加油发放登记」收窄为仅 ETC:菜单改名、UI 只留 ETC、列表/选项/导出强制只出 etc,历史 fuel 留库不删。
|
|
|
+
|
|
|
+**Architecture:** Schema 写路径只允许 `etc`;Service 读路径(列表/看板/导出/pending/按 id)统一 `recharge_type == 'etc'`;SQL migration 改菜单名并软停用字典「加油」;前端去掉加油选项并同步路由标题。
|
|
|
+
|
|
|
+**Tech Stack:** FastAPI + Pydantic v2、SQLAlchemy、Vue 3 + Element Plus、MySQL migration
|
|
|
+
|
|
|
+**Spec:** `docs/superpowers/specs/2026-09-12-pl-vehicle-recharge-etc-only-design.md`
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## File map
|
|
|
+
|
|
|
+| File | Responsibility |
|
|
|
+|------|----------------|
|
|
|
+| `docs/sql/migrations/2026-09-12-pl-vehicle-recharge-etc-only.sql` | 菜单改名 + 字典 fuel 软停用 |
|
|
|
+| `backend/app/schemas/pl/vehicle_recharge.py` | `RECHARGE_TYPES = {"etc"}` |
|
|
|
+| `backend/app/services/pl_vehicle_recharge_service.py` | 读强制 etc;get 对 fuel 等同不存在 |
|
|
|
+| `backend/tests/test_vehicle_recharge_etc_only.py` | Schema + 常量断言单测 |
|
|
|
+| `web-admin/src/router/modules/pl.ts` | `meta.title` |
|
|
|
+| `web-admin/src/views/pl/vehicle-recharge-apply/index.vue` | 文案 + 下拉 |
|
|
|
+| `web-admin/src/views/pl/vehicle-recharge-record/index.vue` | 文案 + 下拉 |
|
|
|
+| `web-admin/src/views/pl/vehicle-recharge-apply/columnFilterDefs.ts` | 列头枚举只留 ETC |
|
|
|
+
|
|
|
+**Do not change:** API path、perms、表结构、`getPlRechargeTypeLabel` 对 `fuel` 的映射(历史标签仍可解析,列表已不可见)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 1: SQL migration(菜单 + 字典)
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Create: `docs/sql/migrations/2026-09-12-pl-vehicle-recharge-etc-only.sql`
|
|
|
+
|
|
|
+- [ ] **Step 1: 写 migration**
|
|
|
+
|
|
|
+```sql
|
|
|
+-- 车辆充值模块收窄为仅 ETC:菜单改名 + 加油字典软停用(不删业务数据)
|
|
|
+-- Spec: docs/superpowers/specs/2026-09-12-pl-vehicle-recharge-etc-only-design.md
|
|
|
+
|
|
|
+UPDATE sys_menu
|
|
|
+SET menu_name = 'ETC充值申请',
|
|
|
+ update_time = NOW()
|
|
|
+WHERE id = 'PL_VEH_RECHARGE_APPLY'
|
|
|
+ AND del_flag = 0;
|
|
|
+
|
|
|
+UPDATE sys_menu
|
|
|
+SET menu_name = 'ETC充值发放登记',
|
|
|
+ update_time = NOW()
|
|
|
+WHERE id = 'PL_VEH_RECHARGE_RECORD'
|
|
|
+ AND del_flag = 0;
|
|
|
+
|
|
|
+UPDATE sys_dict_data
|
|
|
+SET del_flag = 1,
|
|
|
+ remark = 'ETC模块收窄:加油选项停用,业务数据保留待新加油模块',
|
|
|
+ update_time = NOW()
|
|
|
+WHERE id = 'PL_RECHARGE_TYPE_FUEL'
|
|
|
+ AND del_flag = 0;
|
|
|
+
|
|
|
+-- 可选知情统计(只读,不写入变更):
|
|
|
+-- SELECT COUNT(*) FROM pl_vehicle_recharge_apply
|
|
|
+-- WHERE del_flag = 0 AND recharge_type = 'fuel' AND status = 'pending';
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add docs/sql/migrations/2026-09-12-pl-vehicle-recharge-etc-only.sql
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+docs(sql): narrow vehicle recharge menus to ETC-only labels
|
|
|
+
|
|
|
+Rename ETC/fuel menus and soft-disable fuel dict for the ETC-only cutover.
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 2: Schema 只允许 etc + 单测
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `backend/app/schemas/pl/vehicle_recharge.py`
|
|
|
+- Create: `backend/tests/test_vehicle_recharge_etc_only.py`
|
|
|
+
|
|
|
+- [ ] **Step 1: 写失败单测(当前仍接受 fuel)**
|
|
|
+
|
|
|
+```python
|
|
|
+"""车辆充值模块收窄为仅 ETC。"""
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+import pytest
|
|
|
+from pydantic import ValidationError
|
|
|
+
|
|
|
+from app.schemas.pl.vehicle_recharge import (
|
|
|
+ RECHARGE_TYPES,
|
|
|
+ VehicleRechargeApplyCreate,
|
|
|
+ VehicleRechargeRecordCreate,
|
|
|
+)
|
|
|
+
|
|
|
+
|
|
|
+def test_recharge_types_only_etc():
|
|
|
+ assert RECHARGE_TYPES == {"etc"}
|
|
|
+
|
|
|
+
|
|
|
+def test_apply_create_rejects_fuel():
|
|
|
+ with pytest.raises(ValidationError):
|
|
|
+ VehicleRechargeApplyCreate.model_validate(
|
|
|
+ {
|
|
|
+ "licensePlate": "鄂A12345",
|
|
|
+ "rechargeType": "fuel",
|
|
|
+ "applyDate": "2026-09-12",
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+def test_apply_create_accepts_etc():
|
|
|
+ obj = VehicleRechargeApplyCreate.model_validate(
|
|
|
+ {
|
|
|
+ "licensePlate": "鄂A12345",
|
|
|
+ "rechargeType": "etc",
|
|
|
+ "applyDate": "2026-09-12",
|
|
|
+ }
|
|
|
+ )
|
|
|
+ assert obj.recharge_type == "etc"
|
|
|
+
|
|
|
+
|
|
|
+def test_record_create_rejects_fuel():
|
|
|
+ with pytest.raises(ValidationError):
|
|
|
+ VehicleRechargeRecordCreate.model_validate(
|
|
|
+ {
|
|
|
+ "rechargeDate": "2026-09-12",
|
|
|
+ "rechargeType": "fuel",
|
|
|
+ "amount": "100.00",
|
|
|
+ "licensePlate": "鄂A12345",
|
|
|
+ }
|
|
|
+ )
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 跑测确认失败**
|
|
|
+
|
|
|
+Run:
|
|
|
+
|
|
|
+```bash
|
|
|
+cd backend && source venv/bin/activate && pytest tests/test_vehicle_recharge_etc_only.py -v
|
|
|
+```
|
|
|
+
|
|
|
+Expected: `test_recharge_types_only_etc` FAIL(当前 `RECHARGE_TYPES == {"etc", "fuel"}`)。
|
|
|
+
|
|
|
+- [ ] **Step 3: 改 Schema**
|
|
|
+
|
|
|
+In `backend/app/schemas/pl/vehicle_recharge.py`:
|
|
|
+
|
|
|
+```python
|
|
|
+RECHARGE_TYPES = {"etc"}
|
|
|
+```
|
|
|
+
|
|
|
+Update both validators' error message to:
|
|
|
+
|
|
|
+```python
|
|
|
+if v not in RECHARGE_TYPES:
|
|
|
+ raise ValueError("本期仅支持 ETC")
|
|
|
+return v
|
|
|
+```
|
|
|
+
|
|
|
+(`VehicleRechargeApplyCreate.validate_recharge_type` 与 `VehicleRechargeRecordCreate.validate_recharge_type` 两处。)
|
|
|
+
|
|
|
+- [ ] **Step 4: 跑测确认通过**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd backend && source venv/bin/activate && pytest tests/test_vehicle_recharge_etc_only.py -v
|
|
|
+```
|
|
|
+
|
|
|
+Expected: 全部 PASS。
|
|
|
+
|
|
|
+- [ ] **Step 5: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add backend/app/schemas/pl/vehicle_recharge.py backend/tests/test_vehicle_recharge_etc_only.py
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+fix(pl): allow only etc recharge type in vehicle recharge schemas
|
|
|
+
|
|
|
+Reject fuel writes so the shared module can be narrowed to ETC-only.
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 3: Service 读路径强制 etc + fuel 按 id 不可见
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `backend/app/services/pl_vehicle_recharge_service.py`
|
|
|
+
|
|
|
+- [ ] **Step 1: 在 service 顶部增加模块常量**
|
|
|
+
|
|
|
+```python
|
|
|
+# 本期本模块仅服务 ETC;历史 fuel 行保留但不暴露
|
|
|
+MODULE_RECHARGE_TYPE = "etc"
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: `_get_apply_row` / `_get_record_row` 增加类型条件**
|
|
|
+
|
|
|
+In `_get_apply_row` filter add:
|
|
|
+
|
|
|
+```python
|
|
|
+PlVehicleRechargeApply.recharge_type == MODULE_RECHARGE_TYPE,
|
|
|
+```
|
|
|
+
|
|
|
+In `_get_record_row` filter add:
|
|
|
+
|
|
|
+```python
|
|
|
+PlVehicleRechargeRecord.recharge_type == MODULE_RECHARGE_TYPE,
|
|
|
+```
|
|
|
+
|
|
|
+保留原有 404 文案「申请单不存在」/「发放登记不存在」(与 spec:等同不存在)。
|
|
|
+
|
|
|
+- [ ] **Step 3: `list_applies` 基查询强制 etc**
|
|
|
+
|
|
|
+在初始 `.filter(tenant_no, del_flag == 0)` 中增加:
|
|
|
+
|
|
|
+```python
|
|
|
+PlVehicleRechargeApply.recharge_type == MODULE_RECHARGE_TYPE,
|
|
|
+```
|
|
|
+
|
|
|
+(与列头 `recharge_type` / `recharge_type_in` AND 叠加:客户端传 fuel 时结果为空。)
|
|
|
+
|
|
|
+- [ ] **Step 4: `list_pending_apply_options` 强制 etc**
|
|
|
+
|
|
|
+在其 `.filter(...)` 增加:
|
|
|
+
|
|
|
+```python
|
|
|
+PlVehicleRechargeApply.recharge_type == MODULE_RECHARGE_TYPE,
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 5: `list_records` 强制 etc**
|
|
|
+
|
|
|
+将可选:
|
|
|
+
|
|
|
+```python
|
|
|
+if recharge_type:
|
|
|
+ query = query.filter(PlVehicleRechargeRecord.recharge_type == recharge_type)
|
|
|
+```
|
|
|
+
|
|
|
+改为始终:
|
|
|
+
|
|
|
+```python
|
|
|
+query = query.filter(PlVehicleRechargeRecord.recharge_type == MODULE_RECHARGE_TYPE)
|
|
|
+```
|
|
|
+
|
|
|
+(忽略请求里的 `recharge_type` 参数,避免绕过。)
|
|
|
+
|
|
|
+- [ ] **Step 6: `list_disburse_board` 强制 etc**
|
|
|
+
|
|
|
+在申请基查询 `.filter(...)` 增加:
|
|
|
+
|
|
|
+```python
|
|
|
+PlVehicleRechargeApply.recharge_type == MODULE_RECHARGE_TYPE,
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 7: 跑既有 + 新单测**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd backend && source venv/bin/activate && pytest tests/test_vehicle_recharge_etc_only.py tests/test_pl_vehicle_ops_list_filters.py -v
|
|
|
+```
|
|
|
+
|
|
|
+Expected: PASS。
|
|
|
+
|
|
|
+- [ ] **Step 8: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add backend/app/services/pl_vehicle_recharge_service.py
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+fix(pl): hide non-ETC vehicle recharge rows in list and get APIs
|
|
|
+
|
|
|
+Force etc on reads so historical fuel data stays in DB but out of this module.
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 4: 前端菜单标题 + 只留 ETC 选项
|
|
|
+
|
|
|
+**Files:**
|
|
|
+- Modify: `web-admin/src/router/modules/pl.ts`
|
|
|
+- Modify: `web-admin/src/views/pl/vehicle-recharge-apply/columnFilterDefs.ts`
|
|
|
+- Modify: `web-admin/src/views/pl/vehicle-recharge-apply/index.vue`
|
|
|
+- Modify: `web-admin/src/views/pl/vehicle-recharge-record/index.vue`
|
|
|
+
|
|
|
+- [ ] **Step 1: 路由标题**
|
|
|
+
|
|
|
+In `web-admin/src/router/modules/pl.ts`,四处替换:
|
|
|
+
|
|
|
+- `'ETC加油申请'` → `'ETC充值申请'`
|
|
|
+- `'ETC加油发放登记'` → `'ETC充值发放登记'`
|
|
|
+
|
|
|
+- [ ] **Step 2: 列头枚举**
|
|
|
+
|
|
|
+In `columnFilterDefs.ts`:
|
|
|
+
|
|
|
+```typescript
|
|
|
+const RECHARGE_TYPE_OPTIONS = [{ value: 'etc', label: 'ETC' }]
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 3: 申请页**
|
|
|
+
|
|
|
+1. File comment: `<!-- ETC充值申请 -->`
|
|
|
+2. Form select — 删除加油 option,只保留:
|
|
|
+
|
|
|
+```vue
|
|
|
+<el-option label="ETC" value="etc" />
|
|
|
+```
|
|
|
+
|
|
|
+3. 类型收窄:`rechargeType: 'etc' as 'etc'`(去掉 `| 'fuel'`)
|
|
|
+
|
|
|
+- [ ] **Step 4: 发放页**
|
|
|
+
|
|
|
+1. File comment: `<!-- ETC充值发放登记 -->`
|
|
|
+2. 同样删除 `<el-option label="加油" value="fuel" />`
|
|
|
+3. 收窄 `rechargeType` 类型同上
|
|
|
+4. Optional:placeholder「ETC卡号/油卡号」→「ETC卡号」
|
|
|
+
|
|
|
+- [ ] **Step 5: 本地目视 / 类型检查**
|
|
|
+
|
|
|
+```bash
|
|
|
+cd web-admin && pnpm exec vue-tsc --noEmit --pretty false 2>&1 | head -n 40
|
|
|
+```
|
|
|
+
|
|
|
+浏览器:侧栏标题、新建下拉、列头漏斗仅 ETC。
|
|
|
+
|
|
|
+- [ ] **Step 6: Commit**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add web-admin/src/router/modules/pl.ts \
|
|
|
+ web-admin/src/views/pl/vehicle-recharge-apply/columnFilterDefs.ts \
|
|
|
+ web-admin/src/views/pl/vehicle-recharge-apply/index.vue \
|
|
|
+ web-admin/src/views/pl/vehicle-recharge-record/index.vue
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+fix(web-admin): rename ETC recharge menus and drop fuel type option
|
|
|
+
|
|
|
+Align UI with ETC-only scope; list filtering is enforced by the backend.
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### Task 5: 图谱更新 + 手工验收清单
|
|
|
+
|
|
|
+- [ ] **Step 1: 更新知识图谱**
|
|
|
+
|
|
|
+```bash
|
|
|
+./scripts/update-graph.sh all
|
|
|
+```
|
|
|
+
|
|
|
+- [ ] **Step 2: 手工验收(对照 spec)**
|
|
|
+
|
|
|
+| # | 检查项 | 期望 |
|
|
|
+|---|--------|------|
|
|
|
+| 1 | 执行 migration 后侧栏 | ETC充值申请 / ETC充值发放登记 |
|
|
|
+| 2 | 新建申请/补录发放 | 只能选 ETC,落库 etc |
|
|
|
+| 3 | POST/PUT `rechargeType=fuel` | 422 / 校验失败 |
|
|
|
+| 4 | 库中已有 fuel 行 | 列表、看板、导出、pending-options 均不可见 |
|
|
|
+| 5 | GET fuel 的 id | 与「不存在」相同 |
|
|
|
+| 6 | etc 全流程 | 申请→待发放→发放正常 |
|
|
|
+| 7 | 字典管理 | 「加油」不再出现在有效选项 |
|
|
|
+
|
|
|
+- [ ] **Step 3: Commit 图谱(若有 diff)**
|
|
|
+
|
|
|
+```bash
|
|
|
+git add graphify-out backend/graphify-out web-admin/graphify-out
|
|
|
+git commit -m "$(cat <<'EOF'
|
|
|
+chore: update graphify after ETC-only vehicle recharge cutover
|
|
|
+EOF
|
|
|
+)"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Spec coverage self-check
|
|
|
+
|
|
|
+| Spec 项 | Task |
|
|
|
+|---------|------|
|
|
|
+| D1 列表只出 etc | Task 3 |
|
|
|
+| D2 菜单名 | Task 1 + Task 4 |
|
|
|
+| D3 待发放加油可隐去 | Task 3(pending + board) |
|
|
|
+| D4 UI 只留 ETC | Task 4 |
|
|
|
+| D5 不删数据 / 不改 path·perms | 全任务均未 DELETE 业务行、未改 path |
|
|
|
+| D6 写拒 fuel + 字典软停用 | Task 1 + Task 2 |
|
|
|
+| get fuel ≡ 不存在 | Task 3 Step 2 |
|
|
|
+| 导出只出 etc | Task 3(list/board 被 export 复用) |
|
|
|
+
|
|
|
+## Out of scope(勿做)
|
|
|
+
|
|
|
+- 新加油模块、数据迁移、改 API path、改权限码、删 `fuel` 历史行
|