Files
uniprop-manual/prop-acc/scenarios/meter/generate-bill-tiered-pricing.md
2026-05-26 00:23:07 +08:00

247 lines
7.8 KiB
Markdown
Raw Permalink 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 · 场景 - 阶梯水电价生成账单(progressive 累进)
aliases:
- 阶梯计价账单
- tiered pricing 算例
- generate-bill-tiered-pricing
- 场景-阶梯计价生成账单
tags:
- 场景
- prop-acc
- 计量表
- 账单生成
audience:
- 业务人员
- 财务
- 业户
status: 已发布
sub_feature: meter
last_review: 2026-05-26
code_version: 2026-05-22
---
# 场景:阶梯水电价生成账单(progressive 累进)
业户**用量超过低阶梯**进入更高阶梯时,系统按 **progressive 累进** 算账单(每段用量按各自单价,**不是**整月按最高阶梯)。本场景完整演示算例。
## 典型情境
> [!example] 真实情境
> 张阿姨 5 月用水 35 吨(平时 12-15 吨,本月浇花 + 装修),嘉禾花园的水费阶梯如下:
>
> | 阶梯 | 月用水(吨)| 单价(元/吨)|
> |---|---|---|
> | 第一阶梯 | 0-20 | 3.0 |
> | 第二阶梯 | 21-30 | 4.5 |
> | 第三阶梯 | 30+ | 6.0 |
>
> 张阿姨 5 月用 35 吨,本月水费应是多少?
**Progressive 累进算法**(本系统采用):
```
段 1(0-20 吨):20 × 3.0 = 60 元
段 2(21-30 吨):10 × 4.5 = 45 元
段 3(31-35 吨): 5 × 6.0 = 30 元
合计: 135 元 ✅
```
**Full-tier 简陋实现**(错!本系统未采用):
```
35 吨 × 6.0(整月按最高阶梯) = 210 元 ❌
```
差额 ¥75。简陋实现对业户极不公平,**是市场上劣质系统的常见 bug**。本系统 `MeterBillCalculator::calculateTiered()` 实现的是 progressive,业户收到的账单准确。
## 系统流程
```mermaid
sequenceDiagram
participant 抄表完成
participant Action[GenerateBillsFromMeterReadingsAction]
participant Service[MeterBillGenerationService]
participant Calc[MeterBillCalculator]
participant 数据库
抄表完成->>Action: handle([5月 reading,consumption=35])
Action->>Service: generateBillForReading(reading)
Service->>数据库: 查 meter.fee_type=水费 → 查 RatePlan + RateTier(3 个 tier)
Service->>Calc: calculate(consumption=35, ratePlan, min, max)
Calc->>Calc: calculateTiered(35, [tier1, tier2, tier3])
Note over Calc: 段 1:min(35,20)-0=20 → 20*3=60
Note over Calc: 段 2:min(35,30)-20=10 → 10*4.5=45
Note over Calc: 段 3:35-30=5 → 5*6=30
Note over Calc: 总和 135
Calc-->>Service: 135
Service->>Service: clamp(135, min, max) = 135
Service->>数据库: 建 Bill(amount=135, sourceable=reading)
Service->>数据库: 更新 reading.bill_id
Service-->>Action: ok
```
## `calculateTiered()` 算法(伪代码)
```php
function calculateTiered(float $consumption, Collection $tiers): float
{
$amount = 0.0;
$remaining = $consumption;
foreach ($tiers as $tier) {
if ($remaining <= 0) break;
$tierRange = $tier->upper ? $tier->upper - $tier->lower : INF;
$consumedInTier = min($remaining, $tierRange);
$amount += $consumedInTier * $tier->unit_price;
$remaining -= $consumedInTier;
}
return $amount;
}
```
详见 [[multiplier-and-tiered-pricing|倍率与阶梯计价]] 概念。
## 业户视角
### 您收到的账单(简化版)
```
5 月水费账单
用水量:35 吨
明细:
0-20 吨段:20 × 3.0 = 60 元
21-30 吨段:10 × 4.5 = 45 元
31+ 吨段: 5 × 6.0 = 30 元
合计:135 元
应付:¥135.00
```
> [!info] 账单明细的展示
> 当前 Bill / Receipt 是否展示阶梯明细取决于模板设计。**强烈推荐展示**:
>
> - 业户能看清"为什么收 135 不是 105"
> - 政策合规(国家阶梯水电价要求公开透明)
> - 减少业户疑问
### 用得越多越贵的教育意义
阶梯计价**鼓励节约**:
| 用量 | 总水费 | 平均单价 |
|---|---|---|
| 15 吨(低用)| 45 | 3.0 |
| 35 吨(本场景)| 135 | 3.86 |
| 60 吨(浪费)| 270 | 4.5 |
业户**用越多平均单价越高**,符合"超额消费多付费"的政策导向。
## 业务人员视角
### 配置阶梯
阶梯定义在 `RatePlan` + `RateTier`(不在 meter 子模块,通常运营 / 财务总监配):
- 后台 → 费率管理 → 选水费 → 编辑 RatePlan
- 加 RateTier:`tier=1, lower=0, upper=20, unit_price=3.0`
- 加 RateTier:`tier=2, lower=20, upper=30, unit_price=4.5`
- 加 RateTier:`tier=3, lower=30, upper=null, unit_price=6.0`(upper=null 表示无上限)
> [!warning] 阶梯配置要严谨
> 配置错的常见症状:
> - 段不连续(`tier1 upper=20`, `tier2 lower=22`)→ 21 吨用量无法分配
> - 段重叠(`tier1 upper=20`, `tier2 lower=18`)→ 18-20 吨段算两次
> - 缺最高段(没有 `tier_max`)→ 超过最高阶梯的用量无单价
>
> 业务人员配置完后**用极端值算例**验证(0 / 1 / 20 / 21 / 30 / 31 / 100 / 1000 吨各算一遍看是否合理)。
### 月度账单生成
抄表完成 → 业务人员触发 `GenerateBillsFromMeterReadingsAction`(或自动)→ 系统调 `MeterBillCalculator` → 每张表算金额 → 建 Bill。
### 异常处理
阶梯计价的常见异常:
| 异常 | 处置 |
|---|---|
| 业户用量极高(> 100 吨) | [[exception-high-consumption|高用量预警]] → 排查是否漏水 / 设备故障 |
| 业户用量极低(0 吨)| `min_amount` 兜底,详见 [[generate-bill-min-max-cap]] |
| 业户用量倒走(reading 错)| 走 [[exception-readings-locked-after-bill|修正流程]] |
## 财务视角
### 账面会计
阶梯账单的**总金额**仍归"水费收入"科目。无需按阶梯拆分入账。
阶梯只影响**业户感知**(单价不同)和**业户行为引导**(鼓励节约),不影响会计核算。
### 报表统计
业务可能想看"本月各阶梯段用量分布"(政策报告 / 节约成效),需要单独的报表 SQL:
```sql
-- 本月各阶梯段用量分布(简化版,真实算法更复杂)
SELECT
SUM(LEAST(consumption, 20)) AS tier1_volume,
SUM(GREATEST(LEAST(consumption, 30) - 20, 0)) AS tier2_volume,
SUM(GREATEST(consumption - 30, 0)) AS tier3_volume
FROM acc_meter_readings
WHERE meter_id IN (SELECT id FROM acc_meters WHERE community_id=? AND fee_type_id=)
AND read_at BETWEEN '2026-05-01' AND '2026-05-31';
```
## 常见问题
> [!question] 阶梯按月 / 按年?
> 看物业政策:
> - **按月**(常见):每月 reset,从段 1 开始算
> - **按年**(部分地区):全年累计,跨段更慢
>
> 当前系统**按月**(单次抄表 = 单段计价)。按年的话需要不同算法(累加去年 12 月以来的用量,再分阶梯)。
> [!question] 不同物业 / 不同社区可以有不同阶梯吗?
> 可以。`RatePlan` 按 `community_id` + `fee_type_id` 隔离。每个社区独立配置。
> [!question] 阶梯改了,历史 Bill 怎么办?
> 历史 Bill 不变(`Bill.amount` 是当时算出的,不动态查 RatePlan)。新 Bill 按新阶梯算。
>
> 这是正确做法(已发账单不应因配置变化追溯改金额)。
> [!question] 业户对算法有疑问怎么解释?
> 给业户看明细(段 1 + 段 2 + 段 3)+ 阶梯单价表。绝大多数业户看懂后接受。
> [!question] progressive 算法的边界用量(如 20 吨整)算哪段?
> 看实现细节:
> - `consumption=20`:段 1 全部(20 × 3 = 60),段 2 / 3 不进入
> - `consumption=20.01`:段 1(20 × 3 = 60)+ 段 2(0.01 × 4.5 ≈ 0.045)
> - 边界是 inclusive 还是 exclusive 看 RateTier 配置(`lower`/`upper` 字段语义)
> [!question] 阶梯计价对工业表(multiplier > 1)的影响?
> 倍率 + 阶梯叠加:`consumption = (current - previous) × multiplier`,然后这个 consumption 走阶梯。例如三相工业表 multiplier=10:
>
> - 物理表头读数差 28 度 → consumption = 280 度
> - 280 度走阶梯计算
>
> 详见 [[generate-bill-with-multiplier]]。
## 异常分支
- 工业表倍率参与 → [[generate-bill-with-multiplier]]
- 异常用量触发 min/max → [[generate-bill-min-max-cap]]
- 用量异常高(漏水)→ [[exception-high-consumption]]
## 相关文档
- [[multiplier-and-tiered-pricing]]
- [[bill-generation-pipeline]]
- [[meter-vs-meter-reading]]
- [[generate-bill-with-multiplier]]
- [[generate-bill-min-max-cap]]