9.3 KiB
title, aliases, tags, audience, status, sub_feature, last_review, code_version
| title | aliases | tags | audience | status | sub_feature | last_review | code_version | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| prop-acc · billing · 场景 - 预存款抵扣自动收款 |
|
|
|
已发布 | billing | 2026-05-26 | 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 的特殊性
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(预存款账户详情页):
- 看到张阿姨账户余额 ¥3,400
- 点击
ConsumeAction(预存款上的) - Modal 选 Bill = "5 月物业费 ¥800"
- 提交 → 系统自动跑完整链路
[!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 等)。
系统流程(手动)
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 流程(待实现)
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 + 预存款回填