diff --git a/.obsidian/workspace.json b/.obsidian/workspace.json
index 15f6671..bd8ef56 100644
--- a/.obsidian/workspace.json
+++ b/.obsidian/workspace.json
@@ -197,6 +197,9 @@
},
"active": "849c5ff8936a2b67",
"lastOpenFiles": [
+ "prop-acc/scenarios/billing/create-meter-bill-auto.md",
+ "prop-acc/scenarios/billing/create-periodic-property-fee.md",
+ "prop-acc/scenarios/billing",
"prop-acc/maps/billing-knowledge-map.md",
"prop-acc/concepts/billing/smart-bulk-delete-design.md",
"prop-acc/concepts/billing/delete-vs-void-dual-track.md",
@@ -223,15 +226,12 @@
"prop-acc/maps/meter-knowledge-map.md",
"prop-acc/concepts/meter/decommission-and-locking.md",
"prop-acc/concepts/meter/reading-source-and-photo-proof.md",
- "prop-acc/concepts/meter/bill-generation-pipeline.md",
- "prop-acc/concepts/meter/multiplier-and-tiered-pricing.md",
"prop-acc/concepts/meter",
"prop-acc/scenarios/prepaid",
"prop-acc/concepts/prepaid",
"prop-acc/scenarios/deposit",
"prop-acc/concepts/deposit",
"prop-acc/scenarios/adhoc",
- "prop-acc/concepts/adhoc",
- "resident-portal/scenarios"
+ "prop-acc/concepts/adhoc"
]
}
\ No newline at end of file
diff --git a/prop-acc/scenarios/billing/create-meter-bill-auto.md b/prop-acc/scenarios/billing/create-meter-bill-auto.md
new file mode 100644
index 0000000..9d61c0f
--- /dev/null
+++ b/prop-acc/scenarios/billing/create-meter-bill-auto.md
@@ -0,0 +1,253 @@
+---
+title: prop-acc · billing · 场景 - 抄表自动生成计量账单
+aliases:
+ - 计量账单生成
+ - 抄表后建账单
+ - create-meter-bill-auto
+ - 场景-计量账单自动生成
+tags:
+ - 场景
+ - prop-acc
+ - 账单
+ - 创建
+ - 计量账单
+audience:
+ - 业务人员
+ - 财务
+ - 抄表员
+status: 已发布
+sub_feature: billing
+last_review: 2026-05-26
+code_version: 2026-05-22
+---
+
+# 场景:抄表自动生成计量账单
+
+抄表数据进入系统后(`MeterReading`),触发 `GenerateBillsFromMeterReadingsAction` 自动建账单。**核心机制在 meter 模块的[bill-generation-pipeline](../concepts/meter/bill-generation-pipeline.md)**,本场景描述 billing 模块的**对接视角**。
+
+## 典型情境
+
+> [!example] 真实情境
+> 嘉禾花园 5 月底:
+>
+> - 1,200 张表的本月抄表全部完成
+> - 1,160 张走集抄([[../meter/read-via-iot-remote-source]])
+> - 40 张走手抄([[../meter/read-single-meter-manual]] + [[../meter/read-batch-via-excel-import]])
+> - 系统在抄表完成后**自动触发**:`GenerateBillsFromMeterReadingsAction`(批量,1,200 张 reading → 1,200 张 Bill)
+> - 王主管看到的:**1,200 张计量账单已就绪**,无需手工建
+
+## 业务人员视角
+
+### 自动模式(默认)
+
+业务人员**几乎不操作**:
+
+1. 抄表完成 → 抄表 Action / Importer 内部触发 `GenerateBillsFromMeterReadingsAction`
+2. 系统逐张 reading 算金额(走 [[../meter/multiplier-and-tiered-pricing|倍率+阶梯+min/max]] 三层算法)
+3. 建 Bill,sourceable=MeterReading,bill_type=Meter
+4. reading.bill_id 回写
+5. 报告"已生成 1,200 张计量账单"
+
+### 手动模式(罕见)
+
+某些情况下业务人员需要**手动触发**:
+
+- 抄表数据补录后(集抄掉线 + 后续补抄)
+- 部分 reading 之前生成 Bill 失败后重试
+- 测试 / 验证
+
+后台 → 账单 → 列表 → 顶部 "从 reading 生成账单" 按钮(若有 UI)→ 选 reading 范围 → 提交。
+
+### 第 1 步:核对 reading 完成
+
+抄表完成后(月底):
+
+- 看 `MetersNeedingReadingListWidget`:本月待抄数 = 0
+- 看 `MeterReadingsList`:本月 reading 数 = 1,200
+
+### 第 2 步:确认账单生成
+
+抄表 Action 自动触发后:
+
+- 看 `BillsList`,过滤 `bill_type=Meter` + 本月期次 → 应有 1,200 张
+- 看每张账单的 `sourceable_id` 指向对应 reading
+
+### 第 3 步:异常处理
+
+| 异常 | 处置 |
+|---|---|
+| 某 reading 没生成 Bill(状态:`reading.bill_id=null`)| 排查原因(RatePlan 缺失?业户没绑定?)→ 修复 → 重新触发 |
+| 某 Bill 金额异常(高出预期 10 倍)| 排查 multiplier / 阶梯配置,可能要 [[exception-readings-locked-after-bill|修正流程]] |
+| 大面积失败(>10%)| 系统级问题,联系运维 |
+
+## 业户视角
+
+业户**月底/月初**收到水电气账单:
+
+> 陈先生您好,您的 2026 年 5 月费用账单已生成:
+>
+> - 水费:¥54(用水 12 吨)
+> - 电费:¥168(用电 280 度)
+> - 燃气费:¥35(用气 15 立方)
+>
+> 合计 ¥257,请于 6 月 15 日前付清。
+
+业户**不知道**这些账单是抄表自动生成的(后端透明)。他看到的就是"几张需要付的账单"。
+
+## 系统流程(完整链路)
+
+```mermaid
+sequenceDiagram
+ participant 集抄/抄表员
+ participant MeterAction[抄表 Action / Importer]
+ participant GenBills[GenerateBillsFromMeterReadingsAction]
+ participant Calc[MeterBillCalculator]
+ participant Service[MeterBillGenerationService]
+ participant DB
+
+ Note over 集抄/抄表员: 本月抄表完成
+
+ 集抄/抄表员->>MeterAction: 推送 / 录入 reading 数据
+ MeterAction->>DB: 建 MeterReading(bill_id=null)
+ MeterAction->>GenBills: 触发(刚建的 reading 列表)
+
+ loop 每个 reading
+ GenBills->>Service: generateBillForReading(reading)
+ Service->>Calc: calculate(consumption, ratePlan)
+ Calc-->>Service: amount
+ Service->>DB: 建 Bill(bill_type=Meter, sourceable=reading, status=Unpaid)
+ Service->>DB: 更新 reading.bill_id = bill.id
+ end
+
+ GenBills-->>MeterAction: 报告(N 张生成,M 张失败)
+```
+
+详细分层见 [[../meter/bill-generation-pipeline]]"Calculator + Service + Action"段。
+
+## 与 meter 模块的关系
+
+| 维度 | meter 模块 | **billing 模块(本场景)** |
+|---|---|---|
+| 主对象 | Meter + MeterReading | **Bill** |
+| 责任 | 抄表 + 算用量 | **建账单 + 后续收款** |
+| 触发 | 抄表 Action / Importer | 由 meter 模块触发 |
+| 共享 | sourceable_type='MeterReading' | sourceable_id 指向 reading |
+
+billing 模块对计量账单**没有**自己的创建 Action UI(由 meter 模块的 `GenerateBillsFromMeterReadingsAction` 完全负责)。billing 模块只**接收** sourceable 标记 + 提供后续 [[collect-payment-single|收款]] / [[exception-overdue-bills|催收]] 流程。
+
+## 计量账单的特殊处理
+
+### 1. sourceable 关联
+
+```php
+// Bill 字段
+sourceable_type = 'App\Models\MeterReading'
+sourceable_id = 12345
+```
+
+可双向反查:
+
+```php
+// 从 Bill 看 reading
+$bill->sourceable; // → MeterReading 实例
+
+// 从 reading 看 Bill
+$reading->bill; // → Bill 实例(通过 reading.bill_id)
+```
+
+### 2. bill_type=Meter
+
+| 字段 | 值 |
+|---|---|
+| `bill_type` | `Meter`(BillType 枚举) |
+| 业务分类 | 水费 / 电费 / 燃气费(由 fee_type_id 决定具体)|
+| 报表分类 | 进"水电气收入"科目 |
+
+### 3. 期次
+
+计量账单的 `billing_period_start / end` 通常对应**抄表期次**(例如:本期抄表是 5/26 - 上期 4/28 = 期次 4/29 - 5/26)。具体看实现。
+
+## 异常分支
+
+### 异常 1:reading 已生成 Bill,重复触发
+
+```php
+// GenerateBillsFromMeterReadingsAction
+foreach ($readings as $reading) {
+ if ($reading->bill_id !== null) {
+ $skipped[] = ['reading' => $reading, 'reason' => 'already_billed'];
+ continue;
+ }
+ // ...
+}
+```
+
+已有 Bill 的 reading 直接 skip。不会重复建。
+
+### 异常 2:RatePlan 不存在
+
+某 fee_type 没配 RatePlan:
+
+```php
+$ratePlan = $feeType->currentRatePlan;
+if (! $ratePlan) {
+ throw new RatePlanNotFoundException();
+}
+```
+
+Service 抛错,Action 捕获后 skip 该 reading + 报告。业务人员需先配 RatePlan 再重试。
+
+### 异常 3:asset 没绑业户(`community_asset_users` 缺失)
+
+```php
+$resident = $this->findCurrentResident($asset);
+if (! $resident) {
+ // 视设计:抛 / 建匿名 Bill(无 resident_id)/ 跳过
+}
+```
+
+通常**跳过**(看具体实现),业务人员先绑业户再重试。
+
+### 异常 4:已落账的 reading 数据错
+
+走 [[../meter/exception-readings-locked-after-bill|修正流程]]:作废 Bill → 改 reading → 重生成 Bill。复杂,需运维介入。
+
+## 常见问题
+
+> [!question] 触发时机:抄表 Action 内部 / 单独定时?
+> 当前实现:**抄表 Action 完成后立即触发**(同事务或紧接事务)。
+>
+> 优点:数据立即一致(reading + Bill 同步出现)。
+> 缺点:抄表 Action 性能 = 抄表 + 建账单两段时间。
+>
+> 替代方案:定时任务(每月固定时间扫所有未生成账单的 reading)。当前未采用。
+
+> [!question] 业户对计量账单金额有疑问怎么办?
+> 业务人员可:
+>
+> - 给业户看抄表照片(`reading.photo_url`)
+> - 给业户看 RatePlan(单价配置)
+> - 重算给业户看(用 Calculator 算法)
+>
+> 若证实数据错 → 走 [[../meter/exception-readings-locked-after-bill|修正流程]]。
+
+> [!question] 与周期账单(物业费)冲突吗?
+> 不冲突。两者 BillType 不同(`Periodic` vs `Meter`),fee_type 不同(物业费 vs 水电气),sourceable 不同(null vs MeterReading)。各走各的生成路径,各自独立账单。
+
+> [!question] 业户预存款抵这种账单的优先级?
+> 看 [[../prepaid/consume-batch-auto-monthly|预存款自动抵扣 job]] 的策略:
+>
+> - 按 `due_at` 升序(早到期的先抵)
+> - 计量账单与物业费的 due_at 通常相近(都是月底 + 宽限期)
+> - 哪个早抵哪个
+
+## 相关文档
+
+- [[bill-types-and-sources]]
+- [[bill-vs-collection-order]]
+- [[../meter/bill-generation-pipeline]]
+- [[../meter/multiplier-and-tiered-pricing]]
+- [[../meter/exception-readings-locked-after-bill]]
+- [[create-periodic-property-fee]]
+- [[create-single-bill-manual]]
+- [[collect-payment-batch]](业户可能水电气一起付)
diff --git a/prop-acc/scenarios/billing/create-periodic-property-fee.md b/prop-acc/scenarios/billing/create-periodic-property-fee.md
new file mode 100644
index 0000000..b50f749
--- /dev/null
+++ b/prop-acc/scenarios/billing/create-periodic-property-fee.md
@@ -0,0 +1,230 @@
+---
+title: prop-acc · billing · 场景 - 月度物业费 300 户批量生成
+aliases:
+ - 批量生成物业费
+ - 月度物业费生成
+ - create-periodic-property-fee
+ - GeneratePeriodicBillsAction 实战
+ - 场景-月度物业费生成
+tags:
+ - 场景
+ - prop-acc
+ - 账单
+ - 创建
+ - 周期账单
+audience:
+ - 业务人员
+ - 财务
+status: 已发布
+sub_feature: billing
+last_review: 2026-05-26
+code_version: 2026-05-22
+---
+
+# 场景:月度物业费 300 户批量生成
+
+每月 1 日,物业为社区**所有业户**批量生成本月物业费账单。走 `GeneratePeriodicBillsAction`,默认 `SkipExisting` 策略。
+
+## 典型情境
+
+> [!example] 真实情境
+> 5 月 1 日上午 9 点,嘉禾花园物业财务王主管打开后台:
+>
+> - 300 户业户(住宅 + 商铺)
+> - 物业费 RatePlan:住宅 ¥3 / m²(平均面积 100 m² → ¥300),商铺 ¥8 / m²
+> - 王主管:**1 次点击 + 1 个 Modal**,300 张账单全部建好
+
+## 业务人员视角
+
+### 第 1 步:打开 BillsList
+
+后台 → 账单 → 列表 → 顶部 **"批量生成周期账单"** 按钮(`GeneratePeriodicBillsAction`)。
+
+### 第 2 步:Modal 填参数
+
+| 参数 | 填什么 |
+|---|---|
+| **期次(billing_period)** | 2026 年 5 月(start: 5/1, end: 5/31)|
+| **费用类型(fee_type_id)** | 物业费(下拉选)|
+| **社区范围(community_id)** | 嘉禾花园 |
+| **业户范围** | 全社区业户(默认)|
+| **合并策略(merge_strategy)** | `SkipExisting`(默认,详见 [[periodic-bill-generation]])|
+| 备注 | 选填,如 "5 月例行物业费生成" |
+| **到期日(due_at)** | 6 月 15 日(本月 + 15 天宽限期)|
+
+### 第 3 步:提交
+
+> [!warning] Policy 守护
+> 按钮 `->authorize('create', Bill::class)`,需要 `bill.create` 权限。
+
+系统执行(后台逻辑):
+
+```mermaid
+flowchart TD
+ A[扫描候选业户清单
SELECT * FROM community_user_profiles
WHERE community_id=? AND status=active] --> B[300 业户]
+
+ B --> C[每户:]
+ C --> D{该户该期次
已有 Bill?}
+ D -->|是| E[Skip(默认策略)]
+ D -->|否| F[查 RatePlan 算金额
3 * 100 = 300]
+ F --> G[建 Bill]
+ G --> H[活动日志]
+
+ E --> I[报告]
+ H --> I
+```
+
+### 第 4 步:看结果报告
+
+```
+2026 年 5 月物业费生成完成
+
+✅ 已生成:298 张
+⏭️ 已跳过:2 张(本月已存在,SkipExisting 策略)
+❌ 失败:0 张
+
+总金额:¥120,400
+平均每户:¥401
+```
+
+### 第 5 步:抽样核对
+
+业务人员抽 2-3 张账单看金额是否对(尤其 RatePlan 改过后)。
+
+### 第 6 步:推送给业户
+
+视物业策略:
+
+- 自动推送(若集成微信公众号 / 小程序)
+- 或单独触发"通知本月账单"动作
+
+## 系统流程
+
+```mermaid
+sequenceDiagram
+ participant 王主管
+ participant Filament
+ participant Action[GeneratePeriodicBillsAction]
+ participant DB
+ participant Notify[通知服务]
+
+ 王主管->>Filament: 批量生成周期账单 → Modal 填参数 → 提交
+ Filament->>Action: handle(community, feeType, period, strategy)
+ Action->>DB: 扫描候选业户清单
+
+ loop 每户
+ Action->>DB: 查该户该期次是否已有 Bill
+ alt 已有
+ Action->>Action: 按策略处理(默认 Skip)
+ else 无
+ Action->>DB: 查 RatePlan + 算金额
+ Action->>DB: 建 Bill (status=Unpaid, period, due_at)
+ end
+ end
+
+ Action->>Notify: 触发"批量账单已生成"通知(可选)
+ Action-->>Filament: 报告(生成 / 跳过 / 失败统计)
+ Filament-->>王主管: Modal 显示统计
+```
+
+## 业户视角
+
+业户 5 月 1 日 / 2 日陆续收到推送:
+
+> 张阿姨您好,您的 2026 年 5 月物业费 ¥300 已生成,请于 6 月 15 日前付清。
+>
+> 可选付款方式:
+> - 微信小程序付
+> - 到前台付现金 / 微信 / POS
+> - 您预存款余额 ¥1,200 足够,可自动抵扣(若开通自动抵扣)
+
+业户可选:
+
+- 立即付([[collect-payment-single]] / [[collect-payment-batch]])
+- 等到期日前付
+- 不付 → 到期日后变逾期([[exception-overdue-bills]])
+
+## 异常处理
+
+### 部分业户失败(RatePlan 缺失)
+
+| 异常 | 处置 |
+|---|---|
+| 某户没分配 RatePlan(新业户)| Action 跳过该户 + 报告标记 → 业务人员手动配 RatePlan 后单独生成 |
+| 某户面积字段缺失 | 同上 |
+| 系统级故障(DB / 内存)| Action 抛错 + 部分生成的 Bill 在事务内回滚 |
+
+### Merge 策略案例(进阶)
+
+如果业务人员想"同业户的物业费 + 电视费 + 网络费**合并到一张账单**":
+
+- Modal 选 `merge_strategy = Merge`
+- 系统找到已生成的物业费 Bill,把电视费 / 网络费**追加进同一张 Bill**(amount 累加)
+- 业户看到的是一张合并账单 ¥XX(明细几项)
+
+详见 [[periodic-bill-generation]]"策略 2:Merge"段。
+
+### Replace 策略案例(罕见)
+
+业务人员发现 5 月物业费 RatePlan 配错了 → 选 `merge_strategy = Replace`:
+
+- 找到已有 Bill → **作废**(VoidBillAction)
+- 按新 RatePlan 重新生成
+
+> [!warning] Replace 风险
+> Replace 对**已付的 Bill** 慎用,会让业户已付的钱"卡在 Void 状态",需配套退款流程。
+
+## 常见问题
+
+> [!question] 同一户能否生成两次同期次同费用类型?
+> 业务上不应(违反一户一月一物业费规则)。系统层面通常有 unique 索引拦截。SkipExisting 策略也避免重复。
+
+> [!question] 一次生成失败一半怎么办?
+> Action 内部对单户失败容错(跳过失败户,继续其他),不会因一户失败回滚全部。报告会列出失败户,业务人员单独处理。
+
+> [!question] 周期账单生成后想推迟到期日?
+> Bill 创建后 `due_at` 字段**通常可编辑**(若 status=Unpaid)。批量改可能需要专门的"批量推迟到期日" Action(当前没,可手工逐张改或运维 tinker)。
+
+> [!question] 业务人员忘了月初生成怎么办?
+> 月中或月底再触发,系统正常生成(due_at 仍按原计划,可能立即逾期)。业户体验不好(账单晚到),需提前通知。
+
+> [!question] 自动化(Scheduled Job)有吗?
+> issue.md Q6 未明确(meter 和 prepaid 也都有"自动化待补"的待办)。当前**手动触发**。未来可加:
+>
+> - 月初 1 日 00:30 自动触发(类似 prepaid 自动抵扣)
+> - 触发后立即触发预存款抵扣(无缝)
+> - 业户次日早收到推送
+
+> [!question] 业户拒绝接收推送怎么办?
+> 系统层面不影响(账单仍在,可在小程序 / 后台查)。推送只是触达手段。业户拒收推送 → 物业改用电话 / 短信 / 上门通知。
+
+> [!question] 不同费用类型(物业费 + 电视费)能一次性都生成吗?
+> 当前 `GeneratePeriodicBillsAction` 一次只生成**一个费用类型**。批量需触发多次(物业费 1 次 + 电视费 1 次 + ...)。或者业务方反馈后增加"多费用类型批量"功能。
+
+## 与计量账单生成的对比
+
+| 维度 | 本场景(周期账单)| [[create-meter-bill-auto|计量账单]] |
+|---|---|---|
+| 触发 | 业务人员手动 | 抄表完成后自动 / 批量 |
+| 金额 | 固定(RatePlan)| 浮动(用量计算)|
+| 数量 | 每户每期 1 张 | 每抄表 1 张 |
+| sourceable | null(或周期任务 ID)| MeterReading |
+| 频率 | 月度 1 次 | 看抄表频率 |
+
+## 异常分支
+
+- 计量账单生成 → [[create-meter-bill-auto]]
+- 临时手动建单 → [[create-single-bill-manual]]
+- 业务人员要批量删错的 → [[bulk-delete-batch-mistake]]
+- 业户收到账单要付款 → [[collect-payment-single]]
+
+## 相关文档
+
+- [[periodic-bill-generation]]
+- [[bill-six-state-machine]]
+- [[bill-types-and-sources]]
+- [[create-meter-bill-auto]]
+- [[collect-payment-single]]
+- [[bulk-delete-batch-mistake]]
+- [[../meter/bill-generation-pipeline]]
+- [[../prepaid/auto-deduction-design]](账单生成下游消费)