fujian_water_biz_doc/docs/superpowers/specs/2026-06-12-分账连续阶梯重算设计.md

141 lines
4.4 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.

# 分账连续阶梯重算设计
> 2026-06-12 | 将按水量分账从"比例均摊"改为"连续阶梯重算"
---
## 一、目标
按水量分账splitRuleType=COUNT的计费方式从比例均摊改为连续阶梯重算实现
- 总水费 = 子账单水费之和,与直接算总水量结果一致
- 第一笔子账单从第一阶梯开始计费,后续子账单接着前一笔的累计水量继续计算
- 原账单的其他费用(污水、垃圾等)暂按比例均摊
---
## 二、改动范围
**后端改动3 个文件):**
| 文件 | 改动 |
|------|------|
| `AccountingAdjustActionServiceImpl` | 重写 `createSplitChildren()` |
| `PriceDiffPreviewService` | 新增 `recalculate()` 重载,接受自定义水量和累计起始量 |
| `PriceTemplateService` + impl | 新增 `getPriceTemplateByCode(snapCode, templateCode)` 重载 |
**不改动的:**
- Controller、VO、数据库
- `cloneChargeDetailForSplit`(费用明细克隆保持现有比例逻辑)
- `createSplitChildrenByFeeComp`(按费用组成分账不动)
---
## 三、数据流
```
applySplitWriteBack()
├── markParentSplit() [不变]
├── createSplitChildren() [改造]
│ ├── getPriceTemplateByCode(snap, code) → 原账单当时的水价模板
│ ├── charge.getIsLadder() → 阶梯开关
│ │
│ ├── 逐笔重算 (accumulated 从 0 开始):
│ │ ├── recalculate(template, water, accumulated, isLadder, true)
│ │ │ → Map<costCode, amount>
│ │ ├── setFees(child, feeMap) → 设各费用字段
│ │ └── accumulated += water
│ │
│ └── 返回 children
└── cloneChargeDetailForSplit() [不变,比例复制]
```
---
## 四、新增方法签名
### 4.1 PriceDiffPreviewService.recalculate 重载
```java
/**
* 用指定水价模板 + 指定水量 + 累计起始量重算各项费用
*
* @param template 水价模板
* @param billWater 本次水量
* @param accumulatedWater 累计起始水量(前面子账单已用的水量)
* @param isLadder 是否阶梯计费
* @param calculateGarbageFee 是否计算垃圾费
* @return costCode → amount
*/
public Map<String, BigDecimal> recalculate(
PriceTemplateDO template,
BigDecimal billWater,
BigDecimal accumulatedWater,
Boolean isLadder,
Boolean calculateGarbageFee
)
```
实现逻辑:从现有 `recalculate(ChargeDO, PriceTemplateDO, Boolean, Boolean)` 提取核心计算段,用水量参数 `accumulatedWater + billWater` 替代 `charge.getBillWater()`,阶梯判断基于累计量而非单次水量。
### 4.2 PriceTemplateService.getPriceTemplateByCode 重载
```java
/**
* 按快照编号和模板代码查询水价模板
* @param snapCode 调价快照编号
* @param templateCode 模板代码
*/
PriceTemplateDO getPriceTemplateByCode(String snapCode, String templateCode);
```
实现:`priceTemplateMapper.selectOne(adjustmentSnapCode, snapCode, code, templateCode)`
---
## 五、createSplitChildren 改造
### 改造前(比例均摊)
```java
List<BigDecimal> ratios = allocateByWaterRatio(billWaters, charge.getBillWater());
List<BigDecimal> waterFees = allocateByRatio(charge.getWaterFee(), ratios, 2);
// ... 所有费用项按同一比例分
```
### 改造后(连续阶梯重算)
```java
PriceTemplateDO template = priceTemplateService.getPriceTemplateByCode(
charge.getAdjustmentSnapCode(), charge.getPriceTemplateCode());
Boolean isLadder = charge.getIsLadder();
BigDecimal accumulated = BigDecimal.ZERO;
for (BigDecimal water : billWaters) {
Map<String, BigDecimal> feeMap = priceDiffPreviewService.recalculate(
template, water, accumulated, isLadder, true);
ChargeDO child = cloneBase(charge, splitAdjustId);
child.setBillWater(water);
setFeesFromMap(child, feeMap); // 水费/污水/垃圾等
children.add(child);
accumulated = accumulated.add(water);
}
```
`setFeesFromMap``PriceDiffWriteBackService.setFeesFromMap` 逻辑一致。
---
## 六、边界条件
| 场景 | 处理 |
|------|------|
| 模板不存在 | 抛出 `invalidParamException("原账单水价模板已失效")` |
| billWaters 之和 ≠ charge.billWater | 不强制校验,允许浮点误差 |
| isLadder 为 null | 默认 true阶梯 |
| adjustmentSnapCode 为 null | 降级到最新快照 |