diff --git a/docs/superpowers/specs/2026-06-12-分账连续阶梯重算设计.md b/docs/superpowers/specs/2026-06-12-分账连续阶梯重算设计.md new file mode 100644 index 0000000..ced3768 --- /dev/null +++ b/docs/superpowers/specs/2026-06-12-分账连续阶梯重算设计.md @@ -0,0 +1,140 @@ +# 分账连续阶梯重算设计 + +> 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 + │ │ ├── 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 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 ratios = allocateByWaterRatio(billWaters, charge.getBillWater()); +List 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 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 | 降级到最新快照 |