6.9 KiB
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。真实开账后产生违约金时,存在漏收违约金并错误压缩本金的风险。
统一口径:
应收总额 = 本金 + 违约金
渠道实收 + 预存抵扣 = 应收总额 + 多缴转预存
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. 基线验证
前端在隔离工作树执行以下相关源码契约测试:
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. 数据排查建议
若当前环境已经存在真实收费数据,实施上线前至少核对:
- 已收账单的
extended_amount + late_fee是否等于支付分配金额。 - 支付主单、支付明细与账单投影是否一一对应。
- 柜员结账金额是否剔除了预存抵扣并包含真实渠道实收。
- 同客户、同金额、相近时间的预存充值是否存在重复请求。
- 账户当前余额是否等于期初余额加全部有效账户流水净额。
- 已冲正银行交易是否都有对应业务反向支付及账单状态恢复。
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。