1. AGENTS.md 更新 - water-docs: 新增 specs/ 与 docs/design/ 生命周期规则章节 - water-backend: 更新协作引用(建设期/建成后、evidence 模块化) 2. specs/ 重复合并 - 006-reminder-event-design 合并入 003-rev006-reminder-event-design - 001-rev004-accounting 删除冗余 data-model.md + contracts/ - 002-rev005-invoice-flow 删除冗余 data-model.md + contracts/ 3. evidence 按模块归档 - 35 个 REV-004 文件归入 evidence/rev004-accounting/ - 7 个通用 bugfix 文件归入 evidence/bugfix/ 和 bugfix/frontend/ - 新建 rev005-invoice/、rev006-reminder/、rev007-statistics/ 目录 4. guides/ 清理 - 14 个 REV004_*.md 移入 evidence/rev004-accounting/ 5. 遗留文件处理 - docs/research/ 归档到 Archive/06_Migration_Plans/ - backend-check detached worktrees 清理 6. 交叉引用修复 - 006-reminder-event-design → 003-rev006-reminder-event-design - docs/guides/REV004_ → docs/evidence/rev004-accounting/REV004_ 7. DB 设计文档修正(01_Database_Design.md) - biz_invoice 明确为开票配置表,非发票记录表 - 新增 biz_invoice_record 为发票申请/结果主表 - 新增 biz_charge_invoice_rel 账单-发票关联说明 - REV-005 承接口径表名全部修正 8. 发票审计证据 - 新增 evidence/rev005-invoice/2026-06-16-invoice-document-audit.md
235 lines
6.0 KiB
Markdown
235 lines
6.0 KiB
Markdown
# REV004 / 按水量分账最小闭环设计(2026-04-14)
|
||
|
||
## 1. 目标
|
||
本设计只覆盖 **第一阶段:按水量分账最小闭环**。
|
||
|
||
目标是:
|
||
- 审批通过后,能够把一笔未收费原账单按多笔水量拆分,真正生成多张目标子账单;
|
||
- 先不做“按费用组成分账”;
|
||
- 先把最小可运行闭环打通,再作为后续扩展基础。
|
||
|
||
---
|
||
|
||
## 2. 为什么第一阶段先做按水量分账
|
||
原因:
|
||
1. **规则最清晰**:拆分水量之和必须等于原水量。
|
||
2. **业务校验最直接**:原账单 -> 多笔子账单,按水量重算金额,容易理解。
|
||
3. **比按费用组成更容易守恒**:先处理总水量、总金额守恒,再逐步扩到费用项粒度。
|
||
4. **更适合先做执行态样板**:审批通过、执行、回看、失败回滚等流程都能先沉淀下来。
|
||
|
||
---
|
||
|
||
## 3. 最小闭环边界
|
||
### 本阶段要做到
|
||
- 提交按水量分账申请
|
||
- 保存拆分明细(每笔分账水量)
|
||
- 审批通过后执行拆账
|
||
- 生成多张目标账单
|
||
- 原账单标记为已拆分/失效
|
||
- 日志与查询可回看
|
||
|
||
### 本阶段不做
|
||
- 按费用组成分账
|
||
- 目标客户跨户分账(如无强需求,可先默认同客户)
|
||
- 更复杂规则模板
|
||
- 开票联动优化
|
||
- 自动撤销分账执行结果
|
||
|
||
---
|
||
|
||
## 4. 输入输出定义
|
||
|
||
### 4.1 前端最少输入
|
||
按水量分账弹窗至少提交:
|
||
- `sourceChargeId`
|
||
- `splitItems[]`
|
||
- `seqNo`
|
||
- `splitWater`
|
||
- `reasonCode`
|
||
- `remark`
|
||
- `applicant`
|
||
- `contactMobile`
|
||
|
||
### 4.2 关键校验
|
||
- 原账单必须是未收费
|
||
- `splitItems.length >= 2`
|
||
- 每笔 `splitWater > 0`
|
||
- `sum(splitWater) = source.billWater`
|
||
- 必须填写原因
|
||
|
||
### 4.3 输出结果
|
||
申请提交成功后:
|
||
- `adjustmentNo`
|
||
- `resultStatus = PENDING_APPROVAL`
|
||
|
||
执行成功后应可回看:
|
||
- 原账单摘要
|
||
- 目标子账单列表
|
||
- 每张子账单的:
|
||
- 水量
|
||
- 账单金额
|
||
- 应收金额
|
||
- 违约金(若适用)
|
||
|
||
---
|
||
|
||
## 5. 数据模型(MVP)
|
||
|
||
### 5.1 主表:`biz_split_adjust`
|
||
建议 MVP 字段:
|
||
- `id`
|
||
- `split_adjust_no`
|
||
- `source_charge_id`
|
||
- `split_rule_type` = `BY_WATER`
|
||
- `reason_code`
|
||
- `remark`
|
||
- `approval_status`
|
||
- `execute_status`
|
||
- `execute_message`
|
||
- `executed_at`
|
||
- `creator / create_time / updater / update_time`
|
||
|
||
### 5.2 明细表:`biz_split_adjust_detail`
|
||
建议 MVP 字段:
|
||
- `id`
|
||
- `split_adjust_id`
|
||
- `seq_no`
|
||
- `split_water`
|
||
- `target_charge_id`(执行后回填)
|
||
- `split_amount`(执行后回填)
|
||
- `status`
|
||
- `creator / create_time / updater / update_time`
|
||
|
||
### 5.3 账单表最小扩展建议
|
||
在 `biz_charge` 里增加追溯字段(二选一或组合):
|
||
- `source_charge_id`
|
||
- `split_adjust_id`
|
||
- `charge_origin_type`(如:NORMAL / SPLIT_CHILD)
|
||
|
||
原账单增加状态表达(二选一):
|
||
- 新字段标记 `split_status`
|
||
- 或现有字段中增加来源/失效标识
|
||
|
||
MVP 阶段关键不是状态字段优雅,而是:
|
||
> 后续能稳定区分“原账单”和“拆分生成的子账单”。
|
||
|
||
---
|
||
|
||
## 6. 执行流程(MVP)
|
||
|
||
### Step 1:申请提交
|
||
接口:`POST /unsold-split-submit`
|
||
- 校验基本参数
|
||
- 保存 `biz_split_adjust`
|
||
- 保存 `biz_split_adjust_detail`(只保存水量)
|
||
- 返回待审批
|
||
|
||
### Step 2:审批通过
|
||
- 触发内部执行器
|
||
- 对 `biz_split_adjust` 加幂等校验:只允许执行一次
|
||
- 锁定原账单 `source_charge_id`
|
||
- 开事务
|
||
|
||
### Step 3:生成目标账单
|
||
对每个 `split_adjust_detail`:
|
||
1. 复制原账单基础信息
|
||
2. 将 `bill_water` 改成当前 `split_water`
|
||
3. 调用现有计价逻辑重新计算:
|
||
- `bill_amount`
|
||
- `extended_amount`
|
||
- 若适用则重算 `late_fee`
|
||
4. 插入新的 `biz_charge` 记录
|
||
5. 回填 `target_charge_id / split_amount`
|
||
|
||
### Step 4:处理原账单
|
||
- 原账单置为“已拆分,不可继续收费”
|
||
- 不直接删除原账单
|
||
- 保证历史可追溯
|
||
|
||
### Step 5:收尾
|
||
- `biz_split_adjust.execute_status = SUCCESS`
|
||
- 写 operat_log / log detail
|
||
- 查询面可见结果
|
||
|
||
### Step 6:失败回滚
|
||
- 任一子账单生成失败:事务回滚
|
||
- 主单置 `FAIL`
|
||
- 记录失败原因
|
||
- 不允许出现半拆分状态
|
||
|
||
---
|
||
|
||
## 7. 重算策略建议
|
||
MVP 不建议自己重新写一套计价逻辑,建议:
|
||
- 复用已有账单计算能力
|
||
- 输入拆分后的水量
|
||
- 走现有价格模板/费用计算规则
|
||
|
||
原因:
|
||
- 这样能减少“分账执行”和“普通开账计费”之间的口径偏差
|
||
- 也更容易保证后续账单金额解释一致
|
||
|
||
关键原则:
|
||
> 分账只是“改变水量输入并生成多张账单”,不要重造一套新的计费引擎。
|
||
|
||
---
|
||
|
||
## 8. 查询回看(MVP)
|
||
建议至少补两个查询面:
|
||
|
||
### 8.1 分账详情查询
|
||
- 原账单摘要
|
||
- 分账规则类型(BY_WATER)
|
||
- 每笔拆分水量
|
||
- 每笔生成的目标账单 ID / 金额
|
||
- 执行状态 / 错误原因
|
||
|
||
### 8.2 日志页增强
|
||
在现有 `log-detail / log-process` 中,增加:
|
||
- 原账单 -> 子账单映射摘要
|
||
- 执行结果摘要
|
||
|
||
---
|
||
|
||
## 9. 测试建议(MVP)
|
||
|
||
### 9.1 单测
|
||
- 水量守恒校验
|
||
- 拆分笔数边界(<2, >12)
|
||
- 非未收费账单禁止发起
|
||
- 执行幂等
|
||
|
||
### 9.2 集成测试
|
||
- 提交按水量分账申请 -> 待审批
|
||
- 审批通过 -> 生成 N 张子账单
|
||
- 子账单水量之和 = 原账单水量
|
||
- 原账单不可继续收费
|
||
- 日志页可回看执行结果
|
||
- 执行失败整体回滚
|
||
|
||
### 9.3 验收测试
|
||
- 一张账单拆成 2 笔
|
||
- 一张账单拆成多笔
|
||
- 子账单后续可分别收费/开票(若本期不做全链,可先验证可查询/可识别)
|
||
|
||
---
|
||
|
||
## 10. MVP 成功标准
|
||
MVP 完成时,应满足:
|
||
1. 用户可提交按水量分账申请
|
||
2. 审批通过后,系统真正生成多张目标账单
|
||
3. 子账单水量之和与原账单守恒
|
||
4. 原账单被正确标记,不再继续作为普通未收费账单使用
|
||
5. 查询与日志能回看这次分账执行结果
|
||
|
||
---
|
||
|
||
## 11. 建议实施顺序
|
||
1. 先补 `biz_split_adjust` / `biz_split_adjust_detail`
|
||
2. 再补审批通过后的执行器
|
||
3. 再补查询回看
|
||
4. 最后补前端执行态结果页
|
||
|
||
一句话:
|
||
> 先把“按水量分账”做成审批后真正能拆出子账单的最小闭环,再考虑按费用组成扩展。
|