Files
uniprop-manual/prop-acc/scenarios/meter/generate-bill-min-max-cap.md
2026-05-26 00:28:09 +08:00

274 lines
8.8 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.
---
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]]