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

9.3 KiB
Raw Blame History

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 · 场景 - 预存款抵扣自动收款
预存款抵账单
自动抵扣收款
collect-via-prepaid-auto
场景-预存款自动抵扣
场景
prop-acc
账单
收款
跨子模块
业户
业务人员
财务
已发布 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(预存款账户详情页):

  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 等)。

系统流程(手动)

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] 业户预存款不够付,但快有了(等到本月底)怎么办? 业务上不应等。可:

[!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 就不收。

异常分支

相关文档