fujian_water_biz_doc/docs/superpowers/specs/2026-06-30-late-fee-start-and-daily-recalculation-design.md

279 lines
9.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-30
## 背景
本设计用于明确违约金四种起算方式、逾期天数、金额计算、每日任务重算和历史账单处理口径。
本次范围同时覆盖:
- 开账时违约金起算日计算与固化
- 每日任务按未结清账单重算当前应收违约金
- 当前未缴本金、已收违约金、已生效减免金额的扣减口径
- 小数、封顶、配置非法值处理
本设计不改变违约金减免对象本身的提交/审批流程。注意区分:
- `CALC_LATE_FEE_TYPE`:系统参数,表示违约金起算方式,取值 `1/2/3/4`
- `lateFeeType`:账务调整/违约金减免字段,表示减免方式,例如按金额、按日期
## 相关配置
核心起算配置:
- `CALC_LATE_FEE_TYPE`:违约金起算方式
- `1`:账单账务年月当月某日
- `2`:开账后若干天
- `3`:账单账务年月下月某日
- `4`:账单账务年月下下月某日
- `CALC_LATE_FEE_SETTING`:起算参数值
- `type=1/3/4` 时表示日号
- `type=2` 时表示宽限天数
金额相关配置:
- `LATE_FEE_DECIMAL`:违约金保留小数位数
- `LATE_FEE_MORE_PRINCIPAL`:违约金封顶倍数
- `CostComponentDO.penaltyCoefficient`:费用项违约金系数
账单字段:
- `ChargeDO.billMonth`:账单账务年月,格式 `YYYYMM`
- `ChargeDO.billDate`:开账时间
- `ChargeDO.lateFeeBeginDate`:实际开始计算违约金的日期
- `ChargeDO.lateFee`:当前应收违约金
## 开账起算日规则
开账时根据当时系统参数计算并固化 `ChargeDO.lateFeeBeginDate`。系统参数先计算出“宽限截止日”,宽限截止日当天不计违约金,次日才是第 1 个违约金计费日。
### 月份基准
`CALC_LATE_FEE_TYPE=1/3/4` 的月份最终以账单 `billMonth` 字段对应自然月为准。
- `type=1``billMonth` 当月某日为宽限截止日
- `type=3``billMonth` 下月某日为宽限截止日
- `type=4``billMonth` 下下月某日为宽限截止日
`BILLINGMONTH_STARTDAY` 负责账务年月生成和解释。本次违约金起算计算不在每日任务中重新按运行日期推导账务周期,而是使用账单已固化的 `billMonth`
### 四种起算方式
`type=1/3/4`
1. 读取 `CALC_LATE_FEE_SETTING` 作为目标日号。
2. 根据 `billMonth``type` 得到目标月份。
3. 若目标月份没有该日号,例如 2 月没有 30/31 号,则目标日取该月最后一天。
4. 目标日为宽限截止日。
`type=2`
1. 读取 `CALC_LATE_FEE_SETTING` 作为宽限天数 `N`
2. 开账日不算第 1 天。
3. 宽限截止日为 `billDate.toLocalDate() + N 天`
4. `N=0` 表示开账日就是宽限截止日,开账次日开始计违约金。
### 实际起算日
实际违约金起算日为:
```text
lateFeeBeginDate = max(宽限截止日 + 1 天, billDate.toLocalDate() + 1 天)
```
因此,如果配置算出的宽限截止日早于开账日,也不能在开账日前产生违约金。
### 配置非法处理
`CALC_LATE_FEE_SETTING` 的处理规则:
- `type=1/3/4`:有效日号范围为 `1-31`
- `type=2`:有效天数范围为 `>=0`
- `settingValue` 非法时回退 `settingDefault`
- `settingDefault` 仍非法时,不生成 `lateFeeBeginDate`,并记录配置异常
- `type=1/3/4` 中有效日号超过目标月份最大天数时,不视为非法,取目标月最后一天
`billDate` 为空时:
- 不计算 `lateFeeBeginDate`
- 记录数据异常
- 不使用当前日期或 `createTime` 兜底
## 每日重算规则
每日任务只处理未结清账单:
- 未缴账单:参与重算
- 部分缴费账单:按当前未缴本金参与重算
- 已结清账单:不再重算,避免覆盖历史违约金
每日任务必须幂等。每次根据当前账单状态重新计算并覆盖当前应收违约金,重复执行、失败重试、补跑都不得重复累加。
每日任务默认使用开账时固化的 `lateFeeBeginDate`。后续系统参数变化只影响新开账账单,不自动改变历史未结清账单的起算日。
历史未结清账单如果 `lateFeeBeginDate` 为空:
- 不使用当前系统参数临时推导
- 不计算违约金
- 记录异常或告警,等待数据修正
### 逾期天数
- 当前日期早于 `lateFeeBeginDate`:逾期天数为 `0`
- 当前日期达到或晚于 `lateFeeBeginDate`:逾期天数包含当前日期
公式:
```text
overdueDays = 当前日期 - lateFeeBeginDate.toLocalDate() + 1
```
## 金额计算规则
本次不判断通知状态。违约金产生的计算前提为:
- 账单未结清
- 存在当前未缴本金
- 账单存在有效 `lateFeeBeginDate`
- 当前日期达到或晚于 `lateFeeBeginDate`
- 费用项 `penaltyCoefficient > 0`
### 本金基数
违约金本金基数使用当前未缴本金:
- 已缴本金不再继续产生违约金
- 部分缴费后,剩余未缴本金继续按完整逾期天数计算
- 优先使用费用项级剩余未缴本金
- 如果系统没有费用项级剩余未缴本金,则按各费用项原始本金比例分摊账单剩余未缴本金
- 分摊后的费用项未缴本金之和必须等于账单剩余未缴本金
- 分摊尾差落到最后一个参与项或金额最大的参与项
### 逐费用项计算
继续采用逐费用项计算后汇总的口径:
```text
单项理论违约金 = 单项未缴本金 × 单项违约金系数 × 逾期天数
```
费用项违约金系数来源:
```text
ChargeDetailDO.costComponentCode -> CostComponentDO.code -> CostComponentDO.penaltyCoefficient
```
每个费用项先按 `LATE_FEE_DECIMAL` 四舍五入,再汇总到账单理论违约金。封顶在账单理论违约金汇总后执行,封顶结果再按 `LATE_FEE_DECIMAL` 规范化一次。
### 小数位
`LATE_FEE_DECIMAL` 控制违约金保留小数位数:
- 优先使用 `settingValue`
- `settingValue` 为空、非数字、负数时回退 `settingDefault`
- `settingDefault` 仍非法时按 `0`
- 取整方式固定四舍五入
- `COUNTER_DECIMAL_WAY` 不参与本次违约金计算
### 封顶
`LATE_FEE_MORE_PRINCIPAL` 控制违约金封顶:
- 空值、非数字、负数按 `1`
- `=0` 表示不设上限
- `>0` 表示理论违约金不得超过当前未缴本金乘以配置倍数
处理顺序:
1. 逐费用项计算原始违约金
2. 每个费用项先按 `LATE_FEE_DECIMAL` 四舍五入
3. 汇总得到账单理论违约金
4.`LATE_FEE_MORE_PRINCIPAL` 对账单理论违约金封顶
5. 封顶结果再按 `LATE_FEE_DECIMAL` 四舍五入后作为可扣减前理论违约金
## 已收与减免扣减
每日任务覆盖账单当前应收违约金时,必须扣减已收违约金和已审批生效的违约金减免金额。
公式:
```text
当前应收违约金 = max(0, 理论违约金 - 已收违约金 - 已生效违约金减免金额)
```
已收违约金:
- 从成功且未被撤销/红冲的收费或支付明细汇总
- 只汇总实际违约金金额,例如 `late_fee_amount`
- 不把本金支付误算为违约金支付
已生效违约金减免金额:
- 只统计审批通过且已生效的违约金减免记录
- 待审批、驳回、撤销的减免记录不参与扣减
## 主要影响范围
后端实现预计涉及:
- 开账服务:生成账单时计算并保存 `lateFeeBeginDate`
- 系统参数读取:读取 `CALC_LATE_FEE_TYPE``CALC_LATE_FEE_SETTING``LATE_FEE_DECIMAL``LATE_FEE_MORE_PRINCIPAL`
- 违约金计算器:统一封装起算日和金额计算
- 每日任务:扫描未结清账单,幂等重算并覆盖当前应收违约金
- 账单明细读取:获取费用项本金、费用项编码和违约金系数
- 支付明细汇总:汇总已收违约金
- 违约金减免汇总:汇总已审批生效减免金额
- 异常记录:配置异常、`billDate` 缺失、`lateFeeBeginDate` 缺失等
## 测试与验收点
开账起算日:
- `type=1` 当月某日起算
- `type=2` 开账后 N 天,开账日不算第 1 天
- `type=2 setting=0`
- `type=3` 下月某日起算
- `type=4` 下下月某日起算
- 配置日为 29/30/31 时遇到 2 月、4 月等短月
- 开账日晚于宽限截止日时,起算日取 `billDate + 1`
- `billDate` 为空时不生成起算日并记录异常
- `CALC_LATE_FEE_SETTING` 非法时回退默认值
- `CALC_LATE_FEE_SETTING` 默认值仍非法时不生成起算日并记录异常
每日重算:
- 当前日期早于起算日,逾期天数为 0
- 当前日期等于起算日,逾期天数为 1
- 当前日期晚于起算日,逾期天数包含当前日期
- 未缴账单参与重算
- 部分缴费账单按当前未缴本金重算
- 已结清账单不重算
- `lateFeeBeginDate` 为空的历史未结清账单不计算并记录异常
- 重复执行任务结果不变
金额计算:
- 逐费用项按不同 `penaltyCoefficient` 计算并汇总
- 没有费用项级剩余本金时按比例分摊账单剩余未缴本金
- 分摊尾差不造成汇总不平
- `LATE_FEE_DECIMAL` 控制逐项舍入和最终结果
- `LATE_FEE_DECIMAL` 非法时回退默认值,默认值非法时按 0
- `LATE_FEE_MORE_PRINCIPAL` 空值按 1
- `LATE_FEE_MORE_PRINCIPAL=0` 不封顶
- `LATE_FEE_MORE_PRINCIPAL>0` 按当前未缴本金倍数封顶
- 逐项舍入、汇总、封顶、最终舍入的顺序固定
- 已收违约金扣减
- 已审批生效违约金减免扣减
- 扣减后结果不小于 0
## 非目标
本次不覆盖:
- 违约金减免流程重构
- 通知状态判断
- 已结清账单历史金额重算
- 参数变更后自动追溯修改历史未结清账单起算日
- 查询实时计算替代落库金额