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

4.4 KiB
Raw Blame History

分账连续阶梯重算设计

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 重载

/**
 * 用指定水价模板 + 指定水量 + 累计起始量重算各项费用
 * 
 * @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 重载

/**
 * 按快照编号和模板代码查询水价模板
 * @param snapCode 调价快照编号
 * @param templateCode 模板代码
 */
PriceTemplateDO getPriceTemplateByCode(String snapCode, String templateCode);

实现:priceTemplateMapper.selectOne(adjustmentSnapCode, snapCode, code, templateCode)


五、createSplitChildren 改造

改造前(比例均摊)

List<BigDecimal> ratios = allocateByWaterRatio(billWaters, charge.getBillWater());
List<BigDecimal> waterFees = allocateByRatio(charge.getWaterFee(), ratios, 2);
// ... 所有费用项按同一比例分

改造后(连续阶梯重算)

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);
}

setFeesFromMapPriceDiffWriteBackService.setFeesFromMap 逻辑一致。


六、边界条件

场景 处理
模板不存在 抛出 invalidParamException("原账单水价模板已失效")
billWaters 之和 ≠ charge.billWater 不强制校验,允许浮点误差
isLadder 为 null 默认 true阶梯
adjustmentSnapCode 为 null 降级到最新快照