fujian_water_biz_doc/docs/superpowers/plans/2026-07-14-counter-topup-detail.md

383 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Counter Topup Detail 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:** 前端根据展示行的 `displayChargeType` 分流详情链路。普通账单继续查询 `/business/charge/get`;预存款直接使用支付记录快照与当前客户资料打开专用详情弹窗,不把支付记录 ID 当作账单 ID。
**Tech Stack:** Vue 3、TypeScript、Element Plus、现有 Axios API 封装、Node.js `node:test`
---
## File Structure
- Modify: `water-frontend/src/views/operatingCharges/counterCharging/index.vue`
- 扩充预存展示行字段、映射支付余额快照、详情类型分流、增加预存款专用弹窗。
- Modify: `water-frontend/tests/operatingCharges/counterChargingPersistentTopup.test.mjs`
- 增加预存款详情分流、字段展示、余额映射和普通账单链路不回归的契约测试。
- Create: `water-docs/docs/evidence/bugfix/2026-07-14-counter-topup-detail.md`
- 记录根因、RED/GREEN 结果、构建结果与未运行 `vue-tsc` 的约束。
### Task 1: Add failing topup-detail contract tests
**Files:**
- Modify: `water-frontend/tests/operatingCharges/counterChargingPersistentTopup.test.mjs`
- [ ] **Step 1: Add a failing test for detail routing**
在现有测试文件中增加源码契约断言,要求 `openChargeDetail``displayChargeType === 'topup'` 分流,并且仅普通账单调用 `getChargeById`
```js
test('counter charging routes topup rows to a dedicated payment detail', () => {
assert.match(
source,
/if \(row\.displayChargeType === 'topup'\) \{[\s\S]*openTopupDetail\(row\)[\s\S]*return[\s\S]*getChargeById\(row\.id\)/
)
})
```
- [ ] **Step 2: Add a failing test for dedicated fields and balance snapshots**
```js
test('topup detail shows payment semantics and keeps balance snapshots', () => {
for (const label of ['预存款详情', '业务类型', '支付记录ID', '预存金额', '期初余额', '期末余额', '收费时间', '收费方式', '收费状态']) {
assert.match(source, new RegExp(label))
}
assert.match(source, /lastDeposit:\s*record\.lastDeposit/)
assert.match(source, /deposit:\s*record\.deposit/)
assert.match(source, /displayTopupMoney\(topupDetailData\?\.lastDeposit\)/)
assert.match(source, /displayTopupMoney\(topupDetailData\?\.deposit\)/)
})
```
- [ ] **Step 3: Add a failing test for preserving normal bill details and zero values**
```js
test('normal bills keep their original detail request and zero topup values remain visible', () => {
assert.match(source, /chargeDetailData\.value = await getChargeById\(row\.id\)/)
assert.match(source, /value === null \|\| value === undefined \|\| value === ''/)
assert.match(source, /Number\(value\)\.toFixed\(2\)/)
})
```
- [ ] **Step 4: Run the focused test and verify RED**
Run:
```bash
node --test tests/operatingCharges/counterChargingPersistentTopup.test.mjs
```
Expected: FAIL on the new detail-routing/detail-fields assertions because no topup-specific detail path exists yet.
- [ ] **Step 5: Commit the RED test**
```bash
git add tests/operatingCharges/counterChargingPersistentTopup.test.mjs
git commit -m "test: reproduce empty counter topup detail"
```
### Task 2: Preserve complete topup display data
**Files:**
- Modify: `water-frontend/src/views/operatingCharges/counterCharging/index.vue`
- [ ] **Step 1: Extend the display-row type with payment snapshots**
```ts
type ChargeDisplayRowVO = CounterChargingChargeVO & {
displayChargeState?: 'settled' | 'history'
displayChargeType?: 'bill' | 'topup'
payTime?: string
chargeWay?: number
lastDeposit?: number
deposit?: number
}
```
- [ ] **Step 2: Extend the topup-row builder input and output**
```ts
const setSettledTopupDisplayRow = (record: {
id: number
amount: number
payTime: string
chargeWay: number
lastDeposit?: number
deposit?: number
}, displayChargeState: 'settled' | 'history' = 'settled') => {
const current = currentCustomer.value
settledChargeDisplayRows.value = [
{
id: record.id,
meterId: 0,
recordId: 0,
billMonth: '预存款',
custId: current?.id || 0,
custCode: current?.custCode || current?.code,
custName: current?.custName || current?.name,
custAddress: current?.custAddress || current?.address,
billAmount: record.amount,
lateFee: 0,
extendedAmount: record.amount,
payState: 1,
payStateName: '收讫',
meterCode: '预存',
recordNo: '预存',
payTime: record.payTime,
chargeWay: record.chargeWay,
lastDeposit: record.lastDeposit,
deposit: record.deposit,
displayChargeState,
displayChargeType: 'topup'
} as ChargeDisplayRowVO
]
}
```
客户地址使用 `current?.custAddress || current?.address`;金额继续放在 `billAmount``extendedAmount`,余额快照分别放入 `lastDeposit``deposit`
- [ ] **Step 3: Map persisted payment snapshots**
`loadLatestCounterTopupDisplay` 中传入:
```ts
setSettledTopupDisplayRow({
id: record.id,
amount: Number(record.actualMoney ?? 0),
payTime: record.payDate || record.creationTime || '',
chargeWay: Number(record.chargeWay ?? 1),
lastDeposit: record.lastDeposit,
deposit: record.deposit
}, 'history')
```
- [ ] **Step 4: Avoid overwriting the persisted row after an immediate topup**
收费成功刷新后,只有在 `loadCustomerChargeData` 未恢复任何预存行时才用成功响应建立降级行:
```ts
if (!settledChargeDisplayRows.value.length && counterTopupResult?.paymentRecordId) {
setSettledTopupDisplayRow({
id: counterTopupResult.paymentRecordId,
amount: Number(counterTopupResult.amount ?? enteredAmount.value),
payTime: counterTopupResult.payTime || payDate,
chargeWay: chargeWayKeyToValueMap[payType.value as keyof typeof chargeWayKeyToValueMap] || 1,
deposit: counterTopupResult.balanceAfter
})
}
```
- [ ] **Step 5: Run the focused test**
Run:
```bash
node --test tests/operatingCharges/counterChargingPersistentTopup.test.mjs
```
Expected: detail-routing test remains FAIL; balance-mapping assertions PASS.
- [ ] **Step 6: Commit the data mapping**
```bash
git add src/views/operatingCharges/counterCharging/index.vue tests/operatingCharges/counterChargingPersistentTopup.test.mjs
git commit -m "fix: preserve counter topup detail snapshots"
```
### Task 3: Add the dedicated topup-detail dialog and routing
**Files:**
- Modify: `water-frontend/src/views/operatingCharges/counterCharging/index.vue`
- [ ] **Step 1: Add topup-detail state and zero-safe formatting**
```ts
const showTopupDetailDialog = ref(false)
const topupDetailData = ref<ChargeDisplayRowVO | null>(null)
const displayTopupMoney = (value: unknown) => {
if (value === null || value === undefined || value === '') return '--'
const amount = Number(value)
return Number.isFinite(amount) ? amount.toFixed(2) : '--'
}
const getChargeWayLabel = (value: unknown) => {
const chargeWay = Number(value)
return chargeWayLabelMap.value[chargeWay] || '--'
}
```
- [ ] **Step 2: Add the dedicated dialog**
在账单详情弹窗后增加以下完整弹窗:
```vue
<el-dialog v-model="showTopupDetailDialog" title="预存款详情" width="760px">
<el-descriptions :column="3" border>
<el-descriptions-item label="客户编号">{{ topupDetailData?.custCode || '--' }}</el-descriptions-item>
<el-descriptions-item label="客户名称">{{ topupDetailData?.custName || '--' }}</el-descriptions-item>
<el-descriptions-item label="客户地址">{{ topupDetailData?.custAddress || '--' }}</el-descriptions-item>
<el-descriptions-item label="业务类型">预存款</el-descriptions-item>
<el-descriptions-item label="支付记录ID">{{ topupDetailData?.id || '--' }}</el-descriptions-item>
<el-descriptions-item label="预存金额">{{ displayTopupMoney(topupDetailData?.billAmount) }}</el-descriptions-item>
<el-descriptions-item label="期初余额">{{ displayTopupMoney(topupDetailData?.lastDeposit) }}</el-descriptions-item>
<el-descriptions-item label="期末余额">{{ displayTopupMoney(topupDetailData?.deposit) }}</el-descriptions-item>
<el-descriptions-item label="收费时间">{{ formatChargeDateTime(topupDetailData?.payTime) }}</el-descriptions-item>
<el-descriptions-item label="收费方式">{{ getChargeWayLabel(topupDetailData?.chargeWay) }}</el-descriptions-item>
<el-descriptions-item label="收费状态">收讫</el-descriptions-item>
</el-descriptions>
<template #footer>
<el-button @click="showTopupDetailDialog = false">关闭</el-button>
</template>
</el-dialog>
```
- [ ] **Step 3: Route topup rows before the bill request**
```ts
const openTopupDetail = (row: ChargeDisplayRowVO) => {
if (!row.id) {
ElMessage.warning('预存记录信息不完整')
return
}
topupDetailData.value = row
showTopupDetailDialog.value = true
}
const openChargeDetail = async (row: ChargeDisplayRowVO) => {
if (row.displayChargeType === 'topup') {
openTopupDetail(row)
return
}
if (!row?.id) return
chargeDetailLoading.value = true
showChargeDetailDialog.value = true
try {
chargeDetailData.value = await getChargeById(row.id)
} catch (error: any) {
ElMessage.error(error?.message || '查看账单详情失败')
showChargeDetailDialog.value = false
} finally {
chargeDetailLoading.value = false
}
}
```
- [ ] **Step 4: Clear topup-detail state with the page state**
`resetCustomerChargeState``onPageActivated` 的无恢复参数分支中加入:
```ts
showTopupDetailDialog.value = false
topupDetailData.value = null
```
- [ ] **Step 5: Run the focused test and verify GREEN**
Run:
```bash
node --test tests/operatingCharges/counterChargingPersistentTopup.test.mjs
```
Expected: PASS, including all pre-existing topup persistence tests and new detail tests.
- [ ] **Step 6: Run the existing counter-charging regression test**
Run:
```bash
pnpm test:counter-charging
```
Expected: PASS.
- [ ] **Step 7: Commit the UI fix**
```bash
git add src/views/operatingCharges/counterCharging/index.vue tests/operatingCharges/counterChargingPersistentTopup.test.mjs
git commit -m "fix: show counter topup payment details"
```
### Task 4: Verify and record evidence
**Files:**
- Create: `water-docs/docs/evidence/bugfix/2026-07-14-counter-topup-detail.md`
- [ ] **Step 1: Run focused tests together**
Run:
```bash
node --test tests/operatingCharges/counterChargingPersistentTopup.test.mjs tests/operatingCharges/counterChargingInventory.test.mjs
```
Expected: all focused tests PASS.
- [ ] **Step 2: Run the frontend build with sufficient heap**
Run:
```bash
NODE_OPTIONS=--max-old-space-size=8192 pnpm build:dev
```
Expected: `Build successful. Please see dist directory`.
Do not run `vue-tsc`, `pnpm ts:check`, or any command that invokes `vue-tsc`.
- [ ] **Step 3: Check the diff and worktree**
Run:
```bash
git diff --check
git status --short
```
Expected: no whitespace errors; only intentional files are present before the final commit.
- [ ] **Step 4: Write verification evidence**
全部验证达到本计划预期后,创建以下证据;若实际结果不同,停止提交证据并先处理差异:
```markdown
# 柜台收费预存款详情修复验证
## 根因
预存款展示行使用支付记录 ID但详情按钮统一调用账单详情接口 `/business/charge/get`,导致支付记录 ID 被误作账单 ID详情字段全部显示为 `--`
## 修复
-`displayChargeType` 分流普通账单与预存款详情。
- 预存款使用专用弹窗展示支付及余额快照。
- 普通账单继续使用原账单详情接口。
## TDD 证据
- RED`node --test tests/operatingCharges/counterChargingPersistentTopup.test.mjs`
- 结果7 个测试中原有 4 个通过,新增 3 个按预期失败,失败原因分别为缺少详情分流、专用字段和零值格式化。
- GREEN`node --test tests/operatingCharges/counterChargingPersistentTopup.test.mjs`
- 结果7/7 通过。
## 回归验证
- `pnpm test:counter-charging`:退出码 04/4 通过。
- `node --test tests/operatingCharges/counterChargingPersistentTopup.test.mjs tests/operatingCharges/counterChargingInventory.test.mjs`:退出码 011/11 通过。
- `NODE_OPTIONS=--max-old-space-size=8192 pnpm build:dev`:退出码 0输出 `Build successful. Please see dist directory`
## 约束
按用户要求,未运行 `vue-tsc``pnpm ts:check` 或任何会调用 `vue-tsc` 的命令。
```
- [ ] **Step 5: Commit evidence**
```bash
git add docs/evidence/bugfix/2026-07-14-counter-topup-detail.md
git commit -m "docs: record counter topup detail verification"
```