114 lines
6.9 KiB
Markdown
114 lines
6.9 KiB
Markdown
# REV-003 营业收费 P0 审计记录
|
||
|
||
## 1. 记录信息
|
||
|
||
- 审计日期:2026-07-14 至 2026-07-15
|
||
- 后端基线:`5fa11d5392f759be8560b5c696ace2bc814b2faf`
|
||
- 前端基线:`bd515cc3ade9a95a5ccb884f87d5625ebe3131b9`
|
||
- 文档基线:`8617af66fd45e6889bc02bebb4ba5fa95fc93cde`
|
||
- 审计范围:柜台收费、预存充值与抵扣、支付主明细、柜员结账、红冲、银行冲正、预存审批回调、前端收费和结账页面
|
||
- 执行约束:未运行 `vue-tsc`
|
||
|
||
## 2. 总体结论
|
||
|
||
基线审计时,营业收费虽已有客户查询、欠费查询、柜台收费、预存充值、支付记录、柜员结账和红冲骨架,但不能按财务级闭环验收通过。问题集中在金额口径、跨账单事务、结账范围、渠道确认、余额并发、幂等和反向流水。
|
||
|
||
截至 2026-07-15,本记录列出的 9 项 P0 已完成代码整改并通过定向测试、后端编译和前端 Vite 构建。新的柜台收费主链路达到“服务端原子提交、请求幂等、账户与账单加锁、金额拆分可对账、显式记录结账、账单与预存可红冲”的首批上线条件;非现金渠道、打印/补打和柜员现金盘点仍不在本轮 P0 范围内。
|
||
|
||
## 3. P0 缺陷清单
|
||
|
||
### P0-01 应收金额口径断裂
|
||
|
||
`biz_charge.extended_amount` 的实体定义是不含违约金的本金金额,`late_fee` 独立保存;现有柜台支付却以 `extended_amount` 作为支付总额,再在支付明细中执行 `extended_amount - late_fee`。真实开账后产生违约金时,存在漏收违约金并错误压缩本金的风险。
|
||
|
||
统一口径:
|
||
|
||
```text
|
||
应收总额 = 本金 + 违约金
|
||
渠道实收 + 预存抵扣 = 应收总额 + 多缴转预存
|
||
```
|
||
|
||
### P0-02 多账单收费与多缴转预存不原子
|
||
|
||
前端逐账单调用 `PUT /business/charge/update`,全部成功后才单独调用预存充值。任一中间请求失败会形成部分账单已缴、部分账单未缴,或者账单已销但多缴未进入预存的状态。
|
||
|
||
### P0-03 通用营业账更新可作为支付入口
|
||
|
||
通用 `ChargeSaveReqVO` 暴露支付状态、收费员、收费时间、收费途径和金额字段。后端对零付、负数、少付和绕过前端的直接请求缺少完整金额覆盖校验,且客户端可指定收费员和收费时间。
|
||
|
||
### P0-04 柜员结账确认范围与实际范围不一致
|
||
|
||
前端确认框按当前分页和当前筛选行计算金额,请求只传 `cashierId`;后端收到后结清该收费员全部未结支付记录。页面确认金额不能约束真实落账范围。
|
||
|
||
### P0-05 预存抵扣无法与柜员实交资金对账
|
||
|
||
支付主单的 `payment_amount` 记录整张账单金额,柜员结账直接汇总该字段,会把预存抵扣也计入柜员应交资金。支付事实未分别保存渠道实收、预存抵扣和多缴转预存。
|
||
|
||
### P0-06 余额和支付记录缺少并发、幂等保护
|
||
|
||
账户余额采用查询后计算再全行更新,没有行锁、版本号或条件原子更新。柜台充值没有业务请求号;同一请求重试可能重复充值。同一账单并发收费也缺少数据库唯一约束。
|
||
|
||
### P0-07 已结账预存红冲无法成功
|
||
|
||
已结账预存红冲调用只匹配 `CHARGE_PAYMENT` 的 Mapper 方法;`DEPOSIT_TOPUP` 记录更新数必为零。现有单元测试通过 mock 返回成功,未覆盖真实 SQL 条件。
|
||
|
||
### P0-08 银行冲正和业务支付状态可能分裂
|
||
|
||
银行冲正以非空营业账对象作为成功条件。营业账不是可冲状态时可能没有发生业务反向处理,但银行交易仍被标记为已冲正。银行交易状态和业务支付入账也没有可重放的统一状态机。
|
||
|
||
### P0-09 预存审批回调可重放
|
||
|
||
审批成功回调缺少完成状态门禁,余额修改发生在支付退款去重之前,并存在绕过账户流水服务直接写余额的路径。重复回调可能重复扣款或重复转账。
|
||
|
||
## 4. 当前相对正确的链路
|
||
|
||
- 单线程柜台预存正常路径处于同一事务,并写支付记录、余额和账户流水。
|
||
- 单线程单账单收费可以写支付主明细并投影营业账状态。
|
||
- 柜台账单收费红冲已有反向支付、预存恢复和账单状态恢复结构。
|
||
- 柜员结账已有结账主明细、条件更新和重复结账防护基础。
|
||
- 缴费历史主要从 `PaymentRecord` / `PaymentRecordDetail` 读取,而不是完全依赖营业账结果字段。
|
||
|
||
这些正确性只在合法金额、单线程、合法入口和数据库约束完整部署的条件下成立。
|
||
|
||
## 5. 基线验证
|
||
|
||
前端在隔离工作树执行以下相关源码契约测试:
|
||
|
||
```bash
|
||
node --test \
|
||
src/views/operatingCharges/counterCharging/counterTopup.contract.test.mjs \
|
||
tests/revenue-bugs/counterChargeAndCheckoutDisplay.contract.test.mjs \
|
||
tests/rev006/counterCheckoutOldPageInventory.test.mjs
|
||
```
|
||
|
||
结果:30 项,22 通过、8 失败。失败属于整改前基线,其中既有旧测试与当前代码漂移,也有结账聚合和预存展示契约未对齐。
|
||
|
||
后端聚合定向测试首次执行超过等待窗口后被中止,未形成通过结论;实施阶段必须按单测试类执行并记录明确退出码。
|
||
|
||
## 6. 数据排查建议
|
||
|
||
若当前环境已经存在真实收费数据,实施上线前至少核对:
|
||
|
||
1. 已收账单的 `extended_amount + late_fee` 是否等于支付分配金额。
|
||
2. 支付主单、支付明细与账单投影是否一一对应。
|
||
3. 柜员结账金额是否剔除了预存抵扣并包含真实渠道实收。
|
||
4. 同客户、同金额、相近时间的预存充值是否存在重复请求。
|
||
5. 账户当前余额是否等于期初余额加全部有效账户流水净额。
|
||
6. 已冲正银行交易是否都有对应业务反向支付及账单状态恢复。
|
||
|
||
## 7. P0 整改结果
|
||
|
||
| 缺陷 | 整改结果 |
|
||
|------|----------|
|
||
| P0-01 金额口径 | 服务端和前端统一按本金 + 违约金计算;支付主单显式保存渠道实收、预存抵扣和多缴转预存 |
|
||
| P0-02 非原子收费 | 新增 `POST /business/charge/counter-charge/submit`,多账单、预存抵扣和多缴转预存在单事务内完成 |
|
||
| P0-03 通用支付旁路 | 通用营业账更新禁止财务状态迁移,通用账户更新禁止直接改余额 |
|
||
| P0-04 结账范围漂移 | 前端真实维护选择状态,后端只结清请求中的 `paymentRecordIds` |
|
||
| P0-05 柜员实交失真 | 结账与汇总优先使用 `channel_amount`,预存抵扣单独展示 |
|
||
| P0-06 并发与幂等 | 支付记录增加请求号/批次/唯一约束;账单与账户按序加锁并条件更新 |
|
||
| P0-07 预存红冲 | 已结和未结 `DEPOSIT_TOPUP` 分别进入正确状态路径并写反向账户流水 |
|
||
| P0-08 银行冲正分裂 | 业务反向未成功时银行交易不再标记已冲正 |
|
||
| P0-09 回调重放 | 预存审批完成回调增加状态门禁、正式记录锁和账户服务幂等变更 |
|
||
|
||
详细命令、退出结果、提交清单和剩余风险见 `docs/evidence/rev003-charging/2026-07-15-p0-verification.md`。
|