Files
uniprop-manual/prop-acc/scenarios/billing/collect-via-prepaid-auto.md
2026-05-26 01:03:15 +08:00

268 lines
9.3 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 · billing · 场景 - 预存款抵扣自动收款
aliases:
- 预存款抵账单
- 自动抵扣收款
- collect-via-prepaid-auto
- 场景-预存款自动抵扣
tags:
- 场景
- prop-acc
- 账单
- 收款
- 跨子模块
audience:
- 业户
- 业务人员
- 财务
status: 已发布
sub_feature: billing
last_review: 2026-05-26
code_version: 2026-05-22
---
# 场景:预存款抵扣自动收款
业户**预存款余额够付账单**,系统(理想是自动 job,当前是业务人员手动触发)走 `ConsumeFromPrepaidAccountAction` 抵扣账单。这是 billing × prepaid **两子模块联动**的核心场景。
> [!info] 本场景跨两个模块
> - **billing 视角**:Bill 从 Unpaid → Paid,看似与普通收款一样
> - **prepaid 视角**:PrepaidAccount.balance 减,走 consume 流水
>
> 详见 [[../prepaid/consume-monthly-property-bill]] 完整流程。本场景从 billing 角度补充。
## 典型情境
> [!example] 真实情境
> 张阿姨预存款账户余额 ¥3,400,5 月物业费 ¥800。
>
> 手动模式:王主管在张阿姨预存款账户上点 `ConsumeAction`,选 5 月物业费 → 抵扣完成 → 账单 Paid。
>
> 自动模式(待补 [[../prepaid/auto-deduction-design]]):月初 1 日凌晨 job 自动跑 → 张阿姨账单当月自动扣 → 早晨业户收到推送"5 月物业费 ¥800 已抵扣,余额 ¥2,600"。
## billing 视角
### Bill 的变化
| 字段 | 变化前 | 变化后 |
|---|---|---|
| `status` | Unpaid | Paid |
| `paid_amount` | 0 | 800 |
| 关联的 CollectionOrderBill | 0 个 | 1 个(allocated=800)|
| 关联的 CollectionOrder | 0 个 | 1 个(type=Bill, meta.fund_source=prepaid)|
| 关联的 Receipt | 0 个 | 1 个("物业费 ¥800") |
**与普通收款的唯一差异**:CollectionOrder 的 `meta.fund_source = 'prepaid'`(而非默认 `external`)。
### CollectionOrder 的特殊性
```yaml
id: 67890
collection_type: Bill # 仍是 Bill 视角(不是 Prepaid)
actual_amount: +800 # 正数
payment_channel: null # 不走外部支付渠道
status: Completed
meta:
fund_source: prepaid # 标资金来源
prepaid_account_id: 123
prepaid_transaction_id: 456
```
详见 [[../prepaid/consume-via-bill-collection-type]]"CollectionOrder.type=Bill 设计"段。
### Receipt 文案
与现金 / 微信付的收据**长一样**:
```
物业费(5月)¥800
```
业户**感知不到**资金来源差异。这是有意设计 —— "业户付清账单"的统一感受。
## prepaid 视角(简述)
详见 [[../prepaid/consume-monthly-property-bill]] 完整流程。
- 校验账户 `canOperate()`(Active)
- 校验余额够付(`balance >= 800`)
- 校验跨社区(Bill 与 Account 同 community)
-`PrepaidTransaction(type=consume, amount=800, related_bill_id, balance 3400→2600)`
- 更新 `PrepaidAccount.balance = 2600`
- 同步 Bill 状态翻 Paid(通过 CollectionOrder + CollectionOrderBill)
## 业户视角
### 您会感受到什么
| 模式 | 感知 |
|---|---|
| **自动(待补)** | 月初推送"5 月物业费 ¥800 已自动从预存款扣,余额 ¥2,600" |
| **手动** | 同上,只是触发时间不固定(看业务人员何时操作)|
### 与现金付的差异
| 维度 | 现金付 | **预存款抵** |
|---|---|---|
| 业户操作 | 到前台 + 给钱 | **无操作**(自动) |
| 业户感知 | "我付了" | **"已抵扣"**(被动) |
| Receipt | "物业费 ¥800" | **"物业费 ¥800"**(相同) |
| 业务人员介入 | 多(收钱 + 录入) | **无**(自动 job)/ 中(手动) |
| 时长 | 业户上门 + 5 分钟办理 | 0 秒(自动)/ 5 分钟(手动) |
## 业务人员视角
### 手动模式
业务人员在 `ViewPrepaidAccount`(预存款账户详情页):
1. 看到张阿姨账户余额 ¥3,400
2. 点击 `ConsumeAction`(预存款上的)
3. Modal 选 Bill = "5 月物业费 ¥800"
4. 提交 → 系统自动跑完整链路
> [!info] 这个 Action 不在 billing 模块
> `ConsumeAction` 是 prepaid 模块的 Filament Action,在 `PrepaidAccounts/Actions/`。billing 模块的 `CollectPaymentAction` 是普通收款用的(走外部 PaymentChannel)。两者**协作完成**预存款抵扣场景。
>
> 详见 [[../prepaid/consume-monthly-property-bill]]"业务人员视角"。
### 自动模式(待补)
业务人员**几乎不操作**(产品价值的最大体现)。月初看 dashboard 报告:
```
2026 年 6 月 1 日 PrepaidAutoDeductionJob 报告
- 候选账户:500
- 全抵成功:380(76%)
- 部分抵 / 跳过:80(16%)
- 账户冻结跳过:8(2%)
- 失败:0
```
逐个跟进失败 / 跳过的(走 [[../prepaid/audit-low-balance-and-overdue|低余额预警]] 等)。
## 系统流程(手动)
```mermaid
sequenceDiagram
participant 业务
participant Filament
participant ConsumeAction[Prepaid 的 ConsumeAction]
participant ConsumeFromPrepaid[ConsumeFromPrepaidAccountAction]
participant Bill
participant PrepaidAccount
participant DB
participant Listener
业务->>Filament: ViewPrepaidAccount → ConsumeAction(选 Bill, 800)
Filament->>ConsumeFromPrepaid: handle(account, bill, 800)
ConsumeFromPrepaid->>PrepaidAccount: canOperate() ? Active=true
ConsumeFromPrepaid->>PrepaidAccount: community_id match? yes
ConsumeFromPrepaid->>PrepaidAccount: balance >= 800? yes
ConsumeFromPrepaid->>DB: 开启事务
ConsumeFromPrepaid->>DB: 1. 建 CollectionOrder(type=Bill, +800, meta.fund_source=prepaid)
ConsumeFromPrepaid->>DB: 2. 建 CollectionOrderBill(allocated=800)
ConsumeFromPrepaid->>PrepaidAccount: 3. consume(bill, 800)
PrepaidAccount->>DB: 建 PrepaidTransaction(consume, 3400→2600, related_bill_id)
PrepaidAccount->>DB: 更新 balance=2600
ConsumeFromPrepaid->>Bill: 4. recordPayment(800)
Bill->>DB: paid_amount=800, status=Paid
ConsumeFromPrepaid->>Listener: 5. 触发 CollectionOrderCompleted
Listener->>DB: 6. 建 Receipt("物业费 ¥800")
ConsumeFromPrepaid->>DB: 提交事务
Filament-->>业务: 成功
```
## 自动 job 流程(待实现)
```mermaid
sequenceDiagram
participant Scheduler
participant Job[PrepaidAutoDeductionJob]
participant Bills
participant Action[ConsumeFromPrepaidAccountAction]
Note over Scheduler: 2026-06-01 00:30
Scheduler->>Job: dispatch
Job->>Bills: SELECT 未付账单(community_id, resident_id 匹配预存款)
loop 每户
Job->>Bills: 按 due_at 升序查未付账单
loop 每张账单
alt 余额够
Job->>Action: handle(account, bill, bill.amount)
Note over Action: 同手动模式的链路
else 余额不够
Job->>Job: 跳过
end
end
end
Job-->>Scheduler: 完成 + 报告
```
详见 [[../prepaid/auto-deduction-design]] + [[../prepaid/consume-batch-auto-monthly]]。
## 与单张 / 批量收款的对比
| 维度 | [[collect-payment-single|单张]] | [[collect-payment-batch|批量]] | **预存款抵(本)** |
|---|---|---|---|
| 业务人员操作 | 单张点 CollectPayment | 多选 + BatchCollect | 手动 ConsumeAction / 自动 job |
| 触发位置 | ViewBill | BillsList | **ViewPrepaidAccount** / 定时任务 |
| 资金来源 | 现金 / 微信 / POS / 转账 | 同 | **预存款余额** |
| CollectionOrder.meta.fund_source | external | external | **prepaid** |
| 业户感知 | 主动付 | 主动付 | **被动收到通知** |
| 频率 | 高 | 中 | **未来最高(自动 job)** |
## 常见问题
> [!question] 业户预存款不够付,但快有了(等到本月底)怎么办?
> 业务上不应等。可:
>
> - 提示业户立即充值预存款([[../prepaid/deposit-additional-topup]])
> - 业户用其他方式付(现金 / 微信)
> - 部分抵(如果 Bill 支持部分付,见 [[exception-partial-payment]])
> [!question] 业户希望某些账单不要从预存款扣(例如不接受被自动扣电费)?
> 当前自动 job 待实现,实施时**可加白名单 / 黑名单机制**:
> - 业户可设置"只允许物业费自动扣"
> - 其他费用(水电气)留给业户主动付
>
> issue.md 未明确需求,看业务方反馈。
> [!question] 预存款抵扣的 Bill 后续要作废怎么办?
> 走 [[void-paid-bill|作废已付账单]] 流程,但**退款方向不同**:
> - 不退现金 / 微信
> - **退回预存款**(走 PrepaidAccount::deposit 反向充值)
> - 详见 [[void-paid-bill]]"已付作废 + 预存款退还"段
> [!question] 业户跨社区,A 社区有预存款 ¥5000,B 社区欠物业费 ¥800,能跨社区抵吗?
> **不能**。详见 [[../prepaid/exception-cross-community-consume]]"跨社区消费防御"段。
> [!question] 自动 job 跑的时候,业户同时去前台付现金,会重复收款吗?
> 看时序 + 锁机制:
> - 若 job 已锁 Bill(乐观锁)→ 前台收款失败(Bill 状态可能已变 Paid)
> - 若 job 没锁 → 可能并发问题(罕见,需排查具体实现)
>
> **预防**:业务人员收款前看 Bill 当前状态,Paid 就不收。
## 异常分支
- 业户预存款不够 → 业务推 [[../prepaid/deposit-additional-topup]] 或走现金
- 业户预存款冻结 → [[../prepaid/exception-refund-on-frozen|冻结无法抵]]
- 跨社区抵企图 → [[../prepaid/exception-cross-community-consume|系统拦截]]
- 抵后想撤 → [[void-paid-bill]] + 预存款回填
## 相关文档
- [[bill-vs-collection-order]]
- [[../prepaid/consume-via-bill-collection-type]]
- [[../prepaid/consume-monthly-property-bill]]
- [[../prepaid/auto-deduction-design]]
- [[../prepaid/consume-batch-auto-monthly]]
- [[collect-payment-single]]
- [[void-paid-bill]]