Files
uniprop-manual/prop-acc/scenarios/billing/collect-via-prepaid-auto.md

268 lines
9.3 KiB
Markdown
Raw Permalink Normal View History

2026-05-26 01:03:15 +08:00
---
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]]