vault backup: 2026-05-26 00:28:09

This commit is contained in:
Willie
2026-05-26 00:28:09 +08:00
parent 495caa2780
commit 6bdaa31017
4 changed files with 764 additions and 3 deletions

View File

@@ -0,0 +1,273 @@
---
title: prop-acc · meter · 场景 - 单笔账单上下限封顶(防异常用量爆账)
aliases:
- min max 封顶
- 账单封顶
- generate-bill-min-max-cap
- 场景-账单上下限封顶
tags:
- 场景
- prop-acc
- 计量表
- 账单生成
- 异常防御
audience:
- 业务人员
- 财务
- 业户
status: 已发布
sub_feature: meter
last_review: 2026-05-26
code_version: 2026-05-22
---
# 场景:单笔账单上下限封顶(防异常用量爆账)
`RatePlan` 上的 `min_amount` / `max_amount` 字段为单笔账单设置**上下限**:
- **`max_amount`** 防止极端用量(漏水 / 设备故障)导致离谱账单 → 业户友好,但物业承担差额
- **`min_amount`** 防止零用量 / 极低用量逃避基础服务费 → 物业兜底,但业户可能不爽
本场景演示三种触发情境。
## 典型情境
### 情境 1:漏水触发 max 封顶
> [!example] 真实情境
> 张阿姨家**水管漏水**(藏在墙里没发现),5 月用水 **800 吨**(平时 12 吨)。按阶梯计价:
>
> ```
> 0-20 吨段:20 × 3.0 = 60
> 21-30 吨段:10 × 4.5 = 45
> 31-800 吨段:770 × 6.0 = 4,620
> 算出金额:4,725 元
> ```
>
> RatePlan 配置 `max_amount = 1,500`,触发封顶:
>
> ```
> final_amount = min(4725, 1500) = 1500
> ```
>
> 账单 ¥1,500,差额 ¥3,225 物业承担(走维修保险 / 业务减免)。
### 情境 2:零用量触发 min 兜底
> [!example] 真实情境
> 王先生整月**出差不在家**,水表读数无变化(consumption=0)。按阶梯算 = 0 元。但物业配置 `min_amount = 20`,兜底:
>
> ```
> final_amount = max(0, 20) = 20
> ```
>
> 账单 ¥20,理由"基础服务费 / 管网维护费"。王先生抱怨"我没用水为什么收钱?",物业解释:小区水管 / 表的维护是公共成本,按户分摊。
### 情境 3:正常范围,无封顶
> [!example] 真实情境
> 陈先生 5 月用水 35 吨,按阶梯算 135 元。RatePlan 配置 `min_amount=20`, `max_amount=1500`:
>
> ```
> final_amount = max(20, min(135, 1500)) = 135
> ```
>
> 账单 ¥135,封顶规则**不触发**(在合理范围内)。
## min / max 算法
```
if (max_amount !== null && calculated > max_amount) calculated = max_amount;
if (min_amount !== null && calculated < min_amount) calculated = min_amount;
final_amount = calculated;
```
或等价:`final_amount = max(min_amount ?? 0, min(calculated, max_amount ?? INF))`
详见 [[multiplier-and-tiered-pricing|倍率与阶梯计价]]"第 3 层 min/max 封顶"段。
## 系统流程
```mermaid
sequenceDiagram
participant Calc[MeterBillCalculator]
participant Service[MeterBillGenerationService]
participant DB
Calc->>Calc: 阶梯算法 → calculated_amount
Note over Calc: 假设漏水算出 4725
Calc->>Calc: max_amount 检查
alt calculated > max_amount(4725 > 1500)
Calc->>Calc: final = max_amount = 1500
else 在范围内
Calc->>Calc: 通过
end
Calc->>Calc: min_amount 检查
alt final < min_amount
Calc->>Calc: final = min_amount
else 在范围内
Calc->>Calc: 通过
end
Calc-->>Service: 1500
Service->>DB: 建 Bill(amount=1500, sourceable=reading)
Note over DB: 业务上是否记录"封顶减免"?目前 Bill 表无此字段,留作业务备注
```
## 业户视角
### Max 封顶(漏水)
业户收到的账单:
```
2026 年 5 月水费账单
用水量:800 吨 ⚠️ 用量异常高
按阶梯算应付:¥4,725.00
封顶后实付:¥1,500.00
差额减免:¥3,225.00(由物业 / 维修保险承担)
应付:¥1,500.00
```
> [!info] 账单展示封顶信息
> **强烈推荐**账单展示"按阶梯算 X,封顶 Y,差额 Z 由物业承担"。让业户明白封顶的存在 + 物业的友好。
业户会**感激物业封顶**,但更会**自查漏水 / 设备故障**。
### Min 兜底(零用量)
业户收到的账单:
```
2026 年 5 月水费账单
用水量:0 吨
按阶梯算应付:¥0.00
基础费(min):¥20.00
应付:¥20.00
```
业户可能**不接受**:"我没用水为什么收钱?"。物业要解释:
- 水管 / 公共部位维护成本
- 物业服务费的"基础保障"性质(签合同时已告知)
- 法律 / 政策依据(若有)
## 业务人员视角
### 配置 min / max
后台 → 费率管理 → RatePlan → 编辑 → 填字段:
| 字段 | 推荐值(参考)|
|---|---|
| `min_amount` | 10-30 元(看物业 + 费用类型)|
| `max_amount` | 1000-5000 元(看业户类型,商铺可以更高)|
> [!warning] 配置要谨慎
>
> **max 配低**:正常用量也被封顶 → 物业损失收入。例如 max=300,商铺正常月费 ¥500 → 物业每月被减免 ¥200。**严重 bug**。
>
> **max 配高**(或不配):无封顶 → 极端用量爆账户,业户投诉 + 法律风险。
>
> **min 配高**:业户不满 + 投诉。
>
> **配置后用极端值算例验证**(0 度 / 极少 / 正常 / 极高 各算一遍看是否合理)。
### 触发封顶后的业务流程
| 触发 | 业务人员动作 |
|---|---|
| max 触发(异常高用量)| 联系业户排查([[exception-high-consumption]]) → 减免数额可能要审批 |
| min 触发(零 / 极低用量)| 通常无需介入,业户接受 min 即可 |
| 频繁 max 触发 | 评估是否表 / 设备有问题(漏水 / 故障)|
### 封顶减免的会计处理
封顶差额(`max_amount` 触发时,实际应付 vs 物业承担)的会计处理:
| 选项 | 实现 |
|---|---|
| **物业直接承担**(本系统当前简化)| Bill.amount 直接是封顶后金额。账面收入 ¥1,500(实际应是 ¥4,725)→ 物业少收 ¥3,225 |
| **走维修保险**(高大上)| 物业向保险公司报销 ¥3,225,账面通过应收 / 已收 走完整流程 |
| **业户与物业分摊**(罕见)| 部分协议:超过封顶部分 50% 业户 50% 物业 |
当前**最简单实现**:物业直接承担。其他方案需要业务方提需求。
## 财务视角
### 月度报表统计封顶情况
```sql
-- 本月触发 max 封顶的 reading(假设 Bill 不存"封顶前 amount",我们用 reading 算)
SELECT
r.id AS reading_id,
r.consumption,
-- 重算应付(简化,实际要走 Calculator 逻辑)
-- calculate(consumption, ratePlan) AS expected_amount
b.amount AS billed_amount,
-- (expected - billed) AS reduction
rp.max_amount
FROM acc_meter_readings r
JOIN acc_meters m ON r.meter_id = m.id
JOIN fee_types ft ON m.fee_type_id = ft.id
JOIN rate_plans rp ON ft.current_rate_plan_id = rp.id
JOIN acc_bills b ON r.bill_id = b.id
WHERE b.amount = rp.max_amount -- 简化判断:Bill 金额刚好等于封顶值 = 大概率封顶触发了
AND b.created_at BETWEEN '2026-05-01' AND '2026-05-31';
```
业务用途:看月度物业因封顶减免多少收入。若太多 → 调整 max 或排查根因(频繁漏水 / 设备故障)。
## 常见问题
> [!question] min_amount 是 0 / null 时不兜底?
> 是的。若 `min_amount=null`,系统不兜底,零用量账单 = 0 元(可能不开账单)。物业政策决定是否兜底。
> [!question] max 触发后业户反悔说"我自查没漏水,你怎么算出我用 800 吨的?"
> 业务人员排查:
> - 看 reading 数据(读数对吗?抄表照片有吗?)
> - 派人现场核对物理表
> - 找漏水点(物业派维修人员)
> - 若证实表故障 → 走 [[replace-broken-meter|换表]],并重算账单(走 [[exception-readings-locked-after-bill|修正流程]])
>
> 若所有证据都指向"确实用了 800 吨"(且业户家有漏水迹象)→ 业户认账,封顶后金额是优惠了。
> [!question] min 触发后业户拒付怎么办?
> 物业说服 + 法律协议层面要求(物业合同里通常有"基础服务费"条款)。坚决拒付 → 进入逾期催收。
> [!question] 不同业户(住宅 vs 商铺)封顶不同可以吗?
> 看 RatePlan 设计。当前可能"每个 FeeType 一份 RatePlan",所以同 FeeType 共用 min/max。如果要区分,可:
>
> - 给商铺单独建 FeeType + RatePlan
> - 或扩展 RatePlan 支持多档 min/max(改 schema)
> [!question] 跨月用量(忘了抄一个月,两个月用量算一笔)会触发 max 吗?
> 可能会(双月用量翻倍)。**预防**:不要漏抄(走 [[audit-meters-needing-reading|审计]] 监控)。漏抄了发现:
> - 把这笔大账单**人工拆**成两个月(系统不直接支持,业务流程做)
> - 或当作正常账单收 + 与业户沟通
> [!question] 封顶后差额怎么入账?
> 当前最简单实现:Bill.amount 直接是封顶后金额,差额不入账(物业默默承担)。
>
> 严格会计:差额应记入"管理费用 / 维修保险报销 / 服务减免"科目。需扩展 schema 才能精确处理。
## 异常分支
- 阶梯计价(本场景叠加)→ [[generate-bill-tiered-pricing]]
- 工业表倍率叠加 → [[generate-bill-with-multiplier]]
- 异常高用量(可能触发 max)→ [[exception-high-consumption]]
- 读数错误导致离谱算账 → [[exception-readings-locked-after-bill]] 修正
## 相关文档
- [[multiplier-and-tiered-pricing]]
- [[bill-generation-pipeline]]
- [[generate-bill-tiered-pricing]]
- [[generate-bill-with-multiplier]]
- [[exception-high-consumption]]