From dc30e99b2d5e6b937beef20887f0d48e170792b3 Mon Sep 17 00:00:00 2001 From: tangweijie <877588133@qq.com> Date: Tue, 14 Jul 2026 14:39:23 +0800 Subject: [PATCH] docs: plan counter topup detail fix --- .../plans/2026-07-14-counter-topup-detail.md | 382 ++++++++++++++++++ 1 file changed, 382 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-14-counter-topup-detail.md diff --git a/docs/superpowers/plans/2026-07-14-counter-topup-detail.md b/docs/superpowers/plans/2026-07-14-counter-topup-detail.md new file mode 100644 index 0000000..9ced6bb --- /dev/null +++ b/docs/superpowers/plans/2026-07-14-counter-topup-detail.md @@ -0,0 +1,382 @@ +# 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(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 + + + {{ topupDetailData?.custCode || '--' }} + {{ topupDetailData?.custName || '--' }} + {{ topupDetailData?.custAddress || '--' }} + 预存款 + {{ topupDetailData?.id || '--' }} + {{ displayTopupMoney(topupDetailData?.billAmount) }} + {{ displayTopupMoney(topupDetailData?.lastDeposit) }} + {{ displayTopupMoney(topupDetailData?.deposit) }} + {{ formatChargeDateTime(topupDetailData?.payTime) }} + {{ getChargeWayLabel(topupDetailData?.chargeWay) }} + 收讫 + + + +``` + +- [ ] **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`:退出码 0,4/4 通过。 +- `node --test tests/operatingCharges/counterChargingPersistentTopup.test.mjs tests/operatingCharges/counterChargingInventory.test.mjs`:退出码 0,11/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" +```