268 lines
9.3 KiB
Markdown
268 lines
9.3 KiB
Markdown
|
|
---
|
|||
|
|
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]]
|