docs: add implementation plan for payment-info 缴费明细 dialog
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
34c8a1a62d
commit
5025aa09c2
1 file changed
+376
@@ -0,0 +1,376 @@
|
||||
# 缴费信息「缴费明细」列表模式转账 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:** 将缴费信息列表「缴费确认」改为「缴费明细」:打开样本全量缴费详情(应缴只读),在期内用协议单同款列表模式管理转账并提交财务确认。
|
||||
|
||||
**Architecture:** 新增独立弹窗组件 `SamplePaymentDetailDialog`(摘要 + 各期只读头 + 可编辑转账表),由 `PaymentExcelList` 挂载。转账写操作复用现有 payment-transfers API;补充软删接口供已落库草稿删除。本页移除对 `PaymentReceiptConfirmDialog` 的主路径依赖。
|
||||
|
||||
**Tech Stack:** Vue 3 + Element Plus(web-admin)、FastAPI + SQLAlchemy(backend)、现有 `protocol-sample-payment` API。
|
||||
|
||||
**Spec:** `docs/superpowers/specs/2026-10-10-payment-info-detail-list-mode-design.md`
|
||||
**Demo:** `docs/demos/2026-10-10-payment-confirm-sample-detail-demo.html`
|
||||
|
||||
---
|
||||
|
||||
## File map
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `backend/app/crud/protocol_sample_payment.py` | 新增 `soft_delete_payment_transfer`(仅 `dfj`) |
|
||||
| `backend/app/api/protocol_sample_payment.py` | `DELETE /payment-transfers/{id}` |
|
||||
| `backend/tests/test_payment_transfer_delete.py`(新建) | 软删用例 |
|
||||
| `web-admin/src/api/protocol-sample-payment.ts` | `fetchDeletePaymentTransfer` |
|
||||
| `web-admin/src/components/business/sample-payment-detail-dialog/index.vue`(新建) | 详情弹窗 + 列表模式转账 |
|
||||
| `web-admin/src/views/cell-storage/payment-info/PaymentExcelList.vue` | 按钮改名、挂载弹窗、去掉旧确认路径与单期弹窗确认按钮 |
|
||||
| `docs/sql/` | 本迭代无表结构变更 |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Backend — soft-delete payment transfer (dfj only)
|
||||
|
||||
**Files:**
|
||||
- Modify: `backend/app/crud/protocol_sample_payment.py`
|
||||
- Modify: `backend/app/api/protocol_sample_payment.py`
|
||||
- Create: `backend/tests/test_payment_transfer_delete.py`
|
||||
|
||||
- [ ] **Step 1: Write failing tests**
|
||||
|
||||
```python
|
||||
# backend/tests/test_payment_transfer_delete.py
|
||||
"""软删转账笔:仅 dfj 可删。"""
|
||||
from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
from app.crud import protocol_sample_payment as crud
|
||||
|
||||
|
||||
def test_soft_delete_dfj_ok():
|
||||
tr = MagicMock()
|
||||
tr.pay_status = "dfj"
|
||||
tr.del_flag = 0
|
||||
db = MagicMock()
|
||||
# 需要 crud 暴露 soft_delete_payment_transfer;先写期望调用形态
|
||||
# 实现后改为真实函数 + mock get
|
||||
assert hasattr(crud, "soft_delete_payment_transfer")
|
||||
|
||||
|
||||
def test_soft_delete_rejects_jfqrz():
|
||||
assert hasattr(crud, "soft_delete_payment_transfer")
|
||||
```
|
||||
|
||||
完善为:mock `get_transfer_or_raise` 返回 `jfqrz` 时抛 `ValueError`;`dfj` 时 `del_flag=1`。
|
||||
|
||||
- [ ] **Step 2: Run tests — expect fail**
|
||||
|
||||
```bash
|
||||
cd backend && source venv/bin/activate && pytest tests/test_payment_transfer_delete.py -v
|
||||
```
|
||||
|
||||
Expected: FAIL(函数不存在)
|
||||
|
||||
- [ ] **Step 3: Implement CRUD**
|
||||
|
||||
在 `protocol_sample_payment.py` 靠近 `get_transfer` / update 处新增:
|
||||
|
||||
```python
|
||||
def soft_delete_payment_transfer(
|
||||
db: Session,
|
||||
*,
|
||||
transfer_id: str,
|
||||
tenant_no: str,
|
||||
user_id: Optional[str],
|
||||
) -> None:
|
||||
tr = get_transfer_or_raise(db, transfer_id=transfer_id, tenant_no=tenant_no)
|
||||
if tr.pay_status != "dfj":
|
||||
raise ValueError("仅待缴费(已缴费草稿)转账笔可删除")
|
||||
tr.del_flag = 1
|
||||
tr.update_by = user_id
|
||||
tr.update_time = _now()
|
||||
db.add(tr)
|
||||
db.commit()
|
||||
```
|
||||
|
||||
(函数名以文件内现有 `get_*_or_raise` 为准;若不存在则复用现有 get + 校验模式。)
|
||||
|
||||
- [ ] **Step 4: Wire DELETE route**
|
||||
|
||||
在 `backend/app/api/protocol_sample_payment.py` 的 withdraw 路由附近:
|
||||
|
||||
```python
|
||||
@router.delete("/payment-transfers/{transfer_id}")
|
||||
async def delete_payment_transfer(
|
||||
transfer_id: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
# tenant_no / user_id 取法与同文件其它 transfer 路由一致
|
||||
try:
|
||||
crud.soft_delete_payment_transfer(
|
||||
db,
|
||||
transfer_id=transfer_id,
|
||||
tenant_no=tenant_no,
|
||||
user_id=user_id,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
return {"code": 0, "message": "ok"} # 或项目统一响应封装
|
||||
```
|
||||
|
||||
对齐同文件 `create_payment_transfer` 的响应与鉴权写法,勿自创响应形状。
|
||||
|
||||
- [ ] **Step 5: Run tests — expect pass**
|
||||
|
||||
```bash
|
||||
cd backend && pytest tests/test_payment_transfer_delete.py -v
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add backend/app/crud/protocol_sample_payment.py backend/app/api/protocol_sample_payment.py backend/tests/test_payment_transfer_delete.py
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(backend): allow soft-delete of dfj payment transfers
|
||||
|
||||
Supports 缴费明细 list-mode row delete for draft transfers.
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Frontend API — fetchDeletePaymentTransfer
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/api/protocol-sample-payment.ts`
|
||||
|
||||
- [ ] **Step 1: Add delete helper after `fetchWithdrawPaymentTransfer`**
|
||||
|
||||
```typescript
|
||||
export function fetchDeletePaymentTransfer(transferId: string) {
|
||||
return request.del({
|
||||
url: `/web/admin/payment-transfers/${transferId}`,
|
||||
showSuccessMessage: true
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
若项目 HTTP 封装是 `request.delete` 而非 `request.del`,与同文件其它 delete(如 `fetchDeleteProtocolRefundRecord`)保持一致。
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/api/protocol-sample-payment.ts
|
||||
git commit -m "feat(web-admin): add fetchDeletePaymentTransfer API"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: SamplePaymentDetailDialog component
|
||||
|
||||
**Files:**
|
||||
- Create: `web-admin/src/components/business/sample-payment-detail-dialog/index.vue`
|
||||
|
||||
- [ ] **Step 1: Scaffold dialog shell**
|
||||
|
||||
Props:
|
||||
|
||||
```typescript
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
samplePaymentId: string
|
||||
}>()
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', v: boolean): void
|
||||
(e: 'changed'): void // 转账保存/提交/撤回/删除后通知列表刷新
|
||||
}>()
|
||||
```
|
||||
|
||||
结构:
|
||||
|
||||
- `el-dialog`:`class="dialog-form-layout"`、`width="1080px"`、`align-center`、`append-to-body`、`modal-class="protocol-dialog-mask"`、`destroy-on-close`、`:close-on-click-modal="false"`
|
||||
- 标题:`样本缴费详情`
|
||||
- body:`v-loading`;摘要 `form-grid-2` / summary-grid;`应缴批次` 区块列表
|
||||
- footer:关闭按钮(Close 图标)
|
||||
|
||||
打开时 `watch(modelValue + samplePaymentId)` → `fetchSamplePaymentDetail`。
|
||||
|
||||
摘要字段:`protocolNo`、`motherName`/`motherPhone`、`sampleCode`、`payMode` Tag、`contractAmount`、`paidAmount`/`dueRemainAmount`、进度 Tag、`clearedScheduleCount`/`activeScheduleCount`。
|
||||
|
||||
期头只读文案:第 N 期 · 应缴 · 应缴日 · 已缴 · 欠缴 · `scheduleStatus` Tag。
|
||||
`arrearsAmount > 0` 时显示「添加转账」按钮(Plus)。
|
||||
|
||||
- [ ] **Step 2: Embed transfer table (copy interaction from SamplePaymentPanel)**
|
||||
|
||||
从 `SamplePaymentPanel.vue` 复制并裁剪转账表格相关逻辑(勿整文件引入 Panel):
|
||||
|
||||
- 列:转账方式、金额、时间、凭证上传、备注、财务备注、状态、操作
|
||||
- `isTransferLocked`:`yjf` 全锁;`jfqrz` 字段只读但可撤回
|
||||
- `addTransfer(schedule)`:本地 push `{ payStatus:'dfj', transferType:'', ... }`
|
||||
- `saveTransferRow`:`fetchCreatePaymentTransfer` / `fetchUpdatePaymentTransfer`
|
||||
- `submitTransfer`:`fetchSubmitPaymentTransferFinance`
|
||||
- `withdrawTransfer`:`fetchWithdrawPaymentTransfer` + `ElMessageBox.confirm`
|
||||
- `removeTransfer`:无 `id` 本地 splice;有 `id` 且 `dfj` → `fetchDeletePaymentTransfer` 后刷新详情
|
||||
- 附件:`fetchUploadAttachment` folder `protocol-transfer`;卡片布局对齐 UI 规范
|
||||
- 字典:`loadTransferTypeDict` / `getTransferTypeOptions`
|
||||
- Tag:`payStatusLabel` / `payStatusTagType` 等从 `@/utils/status-tag-registry` 或 `protocol-payment-labels`(与 Panel 一致)
|
||||
|
||||
每次写成功后:重新 `loadDetail()`,并 `emit('changed')`。
|
||||
|
||||
样式:可参考 Panel 的 `.schedule-block` / `.transfer-table`,scoped 到本组件;金额用 `AmountInput` + `formatMoneyDisplay`。
|
||||
|
||||
- [ ] **Step 3: Manual smoke(dev)**
|
||||
|
||||
```bash
|
||||
cd web-admin && pnpm dev
|
||||
```
|
||||
|
||||
用临时挂载或 Task 4 完成后验证:打开弹窗、添加行、保存、提交、撤回、删除草稿。
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/components/business/sample-payment-detail-dialog/index.vue
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(web-admin): add SamplePaymentDetailDialog with list-mode transfers
|
||||
|
||||
Readonly schedules; transfer CRUD aligned with SamplePaymentPanel.
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Wire PaymentExcelList
|
||||
|
||||
**Files:**
|
||||
- Modify: `web-admin/src/views/cell-storage/payment-info/PaymentExcelList.vue`
|
||||
|
||||
- [ ] **Step 1: Replace action button**
|
||||
|
||||
操作列:
|
||||
|
||||
```vue
|
||||
<el-button class="btn-op-item primary" @click="handleOpenDetail(row)">
|
||||
<el-icon><List /></el-icon>缴费明细
|
||||
</el-button>
|
||||
```
|
||||
|
||||
- 去掉 `v-if="row.canConfirm"`(始终可点;无 `row.id` 时禁用)。
|
||||
- 图标从 `@element-plus/icons-vue` 引入 `List`(或 `Tickets`);可移除仅用于旧确认的 `Check`(若单期弹窗也不再需要)。
|
||||
|
||||
```typescript
|
||||
async function handleOpenDetail(row: Record<string, any>) {
|
||||
const id = String(row.id || '')
|
||||
if (!id) {
|
||||
ElMessage.warning('缺少缴费方案,无法查看明细')
|
||||
return
|
||||
}
|
||||
detailSamplePaymentId.value = id
|
||||
detailVisible.value = true
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Mount dialog; remove old confirm path**
|
||||
|
||||
模板增加:
|
||||
|
||||
```vue
|
||||
<SamplePaymentDetailDialog
|
||||
v-model="detailVisible"
|
||||
:sample-payment-id="detailSamplePaymentId"
|
||||
@changed="loadData"
|
||||
/>
|
||||
```
|
||||
|
||||
删除或停用:
|
||||
|
||||
- `PaymentReceiptConfirmDialog` 及相关 `receiptDialogVisible` / `receiptTargetId` / `receiptPreviewRow` / `confirming` / `handleConfirm` / `openConfirmFromPeriod`
|
||||
- `fetchCreatePaymentTransfer` 若仅被确认路径使用则从 import 移除
|
||||
|
||||
单期弹窗 `openPeriodDialog` **保留**只读转账列表;footer 中:
|
||||
|
||||
```vue
|
||||
<!-- 删除 v-if="periodCanConfirm" 的「缴费确认」按钮,仅保留关闭 -->
|
||||
```
|
||||
|
||||
可删除 `periodCanConfirm` / `periodSchedule` 中仅服务于确认的状态(若仍用于它处则保留最小集)。
|
||||
|
||||
- [ ] **Step 3: Smoke on page**
|
||||
|
||||
打开 `/#/market/cell-storage/payment-info`:
|
||||
|
||||
1. 「缴费明细」打开全量详情
|
||||
2. 添加转账 → 保存 → 提交财务确认
|
||||
3. 列表进度/金额刷新
|
||||
4. 点某期实缴金额仍只读,无确认按钮
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add web-admin/src/views/cell-storage/payment-info/PaymentExcelList.vue
|
||||
git commit -m "$(cat <<'EOF'
|
||||
feat(web-admin): open 缴费明细 dialog from payment-info list
|
||||
|
||||
Rename action button; drop direct PaymentReceiptConfirmDialog path.
|
||||
EOF
|
||||
)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Graph update + verification
|
||||
|
||||
**Files:** none required beyond graph script
|
||||
|
||||
- [ ] **Step 1: Update frontend knowledge graph**
|
||||
|
||||
```bash
|
||||
./scripts/update-graph.sh frontend
|
||||
```
|
||||
|
||||
若 Task 1 改了 backend:
|
||||
|
||||
```bash
|
||||
./scripts/update-graph.sh backend
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run backend tests + frontend typecheck (scoped)**
|
||||
|
||||
```bash
|
||||
cd backend && pytest tests/test_payment_transfer_delete.py -v
|
||||
cd web-admin && pnpm exec vue-tsc --noEmit 2>&1 | head -80
|
||||
```
|
||||
|
||||
修复本改动引入的类型错误。
|
||||
|
||||
- [ ] **Step 3: Final commit if graph/files dirty**
|
||||
|
||||
```bash
|
||||
git add web-admin/graphify-out backend/graphify-out 2>/dev/null || true
|
||||
git status
|
||||
# 若有图谱变更:
|
||||
git commit -m "chore: update graphify after payment-info detail dialog"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Spec coverage checklist
|
||||
|
||||
| Spec 要求 | Task |
|
||||
|-----------|------|
|
||||
| 按钮改名「缴费明细」且始终可开 | Task 4 |
|
||||
| 样本全量明细、应缴只读 | Task 3 |
|
||||
| 列表模式转账 + 保存/提交/撤回/删除 | Task 3 + Task 1/2 |
|
||||
| 去掉操作列旧确认路径 | Task 4 |
|
||||
| 单期实缴弹窗去确认按钮 | Task 4 |
|
||||
| 变更后刷新列表 | Task 3 emit + Task 4 `@changed` |
|
||||
| 不改缴费方案 / 不改造财务确认页 | 无任务(非目标) |
|
||||
|
||||
## Out of scope (do not implement)
|
||||
|
||||
- 改造 `SamplePaymentPanel` 抽公共组件复用(可后续)
|
||||
- 改造 `PaymentBySampleTab`(旧 Tab,非当前入口)
|
||||
- 财务确认待办页
|
||||
Reference in new issue
Block a user