--- 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]]