# 分账连续阶梯重算设计 > 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 | 降级到最新快照 |