fujian_water_biz_doc/docs/superpowers/specs/2026-06-30-counter-unsettled-red-flush-design.md

143 lines
6.6 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.

# 柜台未结账正式红冲设计
## 背景
当前 `water-backend` 已实现柜台已结账红冲:`POST /admin-api/business/charge/counter-settle/red-flush` 先查 `biz_settle_record_detail`,再校验支付主单 `settleStatus=SETTLED`,因此只能处理已结清记录。历史操作手册要求柜台结账模块的“未结账”和“已结账”页签均可红冲。
本设计补齐“未结账正式红冲”能力。用户已确认业务口径:未结账红冲也按正式红冲处理;红冲后原收费记录不再参与后续柜台结账汇总,只能通过红冲记录查询和导出追溯。
## 目标
- 支持对未结账柜台收费记录执行正式红冲。
- 生成正式反向支付流水,保留原收费主单与反向流水关联。
- 红冲后原收费主单从待结清列表消失,后续结账确认不再统计。
- 红冲记录查询和导出能同时覆盖已结账红冲与未结账红冲。
- 保留红冲原因、经办人、红冲时间、客户、金额、原收费单号等审计字段。
## 非目标
- 不把未结账红冲伪造成普通结账单。
- 不改变已结账红冲的现有业务语义。
- 不在本次设计中扩展发票红冲、渠道退款、银行对账红冲等其他场景。
- 不修改 `.specify/` 流程工件。
## 推荐方案
采用“新增未结账正式红冲路径 + 独立红冲投影”的方案。
已结账红冲继续复用现有 `biz_settle_record` / `biz_settle_record_detail` 路径。未结账红冲不创建普通结账单,而是在支付主单和账单完成红冲状态流转后,写入一条未结账红冲投影记录。红冲记录查询将已结账明细和未结账投影合并返回。
不推荐先生成“红冲专用结清单”,因为这会让未结账业务产生结账单号,污染结账汇总、打印和财务日结语义。
## 接口设计
继续使用现有接口:
- `POST /admin-api/business/charge/counter-settle/red-flush`
请求对象 `CounterRedFlushReqVO` 保持兼容:
- `paymentRecordIds`:待红冲支付主单 ID 列表。
- `reason`:红冲原因。
- `operatorId`:兼容字段,实际操作人以当前登录用户为准。
接口行为按支付主单状态分流:
- `SETTLED` 且存在 `biz_settle_record_detail`:走现有已结账红冲逻辑。
- `UNSETTLED``settleId=null`:走新增未结账正式红冲逻辑。
- 同一批次中不建议混合已结账和未结账记录;若混合会增加事务回滚和结果解释复杂度,推荐直接拒绝并提示分批操作。
## 数据设计
新增未结账红冲投影表,建议命名为 `biz_counter_unsettled_red_flush_record`
核心字段:
- `id`
- `payment_record_id`:原收费支付主单 ID唯一约束。
- `payment_no`:原收费单号。
- `reverse_payment_record_id`:反向支付主单 ID。
- `reverse_payment_no`:反向支付单号。
- `charge_id`
- `source_cust_id`
- `source_cust_code`
- `source_cust_name`
- `cashier_id`
- `reversed_amount`
- `reversed_time`
- `reverse_reason`
- `proc_person`
- `proc_type`:固定为 `COUNTER_UNSETTLED_RED_FLUSH`
- `tenant_id``creator``create_time``updater``update_time``deleted`
约束:
- `payment_record_id + tenant_id` 唯一,防止重复红冲。
-`cashier_id + reversed_time``source_cust_code + reversed_time` 建普通索引,支撑红冲记录查询。
## 流程设计
未结账红冲流程:
1. 根据 `paymentRecordIds` 查询支付主单。
2. 校验每条记录均为柜台收费来源、收入方向、收费业务场景、`settleStatus=UNSETTLED``settleId=null`
3. 校验当前登录收费员只能红冲自己的收费记录;具备管理权限的后台角色可按既有权限模型扩展。
4. 查询关联账单 `ChargeDO`,要求账单仍能与该收费主单匹配。
5. 调用现有 `PaymentCommandApplicationService.reverseChargePayment(charge)` 生成反向流水。
6. 将原支付主单更新为 `settleStatus=REVERSED`,并要求更新条件仍是 `UNSETTLED``settleId=null`
7. 将账单恢复到可再次收费状态,状态规则与现有收费红冲保持一致。
8. 写入 `biz_counter_unsettled_red_flush_record`
9. 返回红冲笔数、现金退回金额、预存抵扣退回金额等汇总结果。
待结清列表继续只查询 `settleStatus=UNSETTLED``settleId=null` 的收入主单。原收费主单改为 `REVERSED` 后自然不再出现,也不会被结清确认统计。
## 红冲记录查询
现有红冲记录查询从 `biz_settle_record_detail.detail_status=REVERSED` 读取已结账红冲记录。新增后查询逻辑改为合并两个来源:
- 已结账红冲:沿用现有 `biz_settle_record` + `biz_settle_record_detail`
- 未结账红冲:读取 `biz_counter_unsettled_red_flush_record`
响应 VO 可保持兼容:
- 未结账红冲的 `settleId``settleNo` 为空。
- `paymentRecordId``paymentNo`、客户、收费员、红冲金额、红冲时间、红冲原因正常返回。
导出逻辑使用同一合并查询,确保页面查询和 Excel 导出一致。
## 异常处理
- 支付主单不存在:拒绝。
- 已红冲:拒绝重复处理。
- 已结账与未结账记录混批:拒绝,提示分批红冲。
- 未结账记录已被其他事务结清:更新原支付主单为 `REVERSED` 时影响行数为 0拒绝并回滚。
- 未结账记录已被其他事务红冲:唯一约束或状态更新失败,拒绝并回滚。
- 反向流水生成失败:拒绝并回滚,不写红冲投影。
## 测试设计
后端单测覆盖:
- 未结账柜台收费红冲成功:生成反向流水、原主单改为 `REVERSED`、写红冲投影、待结清不再出现。
- 未结账红冲记录查询和导出可查到记录,且 `settleNo` 为空。
- 已结账红冲现有行为不回归。
- 已结账和未结账混批被拒绝。
- 重复红冲被拒绝。
- 当前收费员红冲他人未结账收费被拒绝。
- 并发结账或并发红冲导致状态更新失败时回滚。
最小验证建议:
- `mvn -pl sw-business/sw-business-server -Dtest=CounterSettleApplicationServiceImplTest test`
- 如涉及 mapper SQL 或集成数据,补充受环境变量控制的柜台结账接口集成测试。
## 文档与证据
实现后需要回写:
- `docs/design/02_Detailed_Design/12_REV_Detailed.md`:明确未结账红冲与已结账红冲的状态边界。
- `docs/design/03_Technical_Design/01_Database_Design.md`:补充未结账红冲投影表。
- `docs/design/03_Technical_Design/03_Interface_Design.md`:补充 `counter-settle/red-flush` 对未结账记录的处理约束。
- `docs/evidence/bugfix/` 或对应模块 evidence记录编译、单测、最小 smoke 结果。