--- title: prop-acc · billing · 场景 - 单张账单收款 aliases: - 单张收款 - 收款 - CollectPaymentAction - collect-payment-single - 场景-单张账单收款 tags: - 场景 - prop-acc - 账单 - 收款 audience: - 业户 - 业务人员 status: 已发布 sub_feature: billing last_review: 2026-05-26 code_version: 2026-05-22 --- # 场景:单张账单收款 业户**单张账单付款**(物业费 / 水费 / 电费的某一张),业务人员后台触发 `CollectPaymentAction`。最基础高频的收款场景。 ## 典型情境 > [!example] 真实情境 > 张阿姨 5 月物业费 ¥800 账单已生成,她下午到物业前台: > > - "我交 5 月物业费" > - 业务人员小李打开张阿姨账户 → 找到 5 月物业费账单 → 收款 ## 业户视角 ### 第 1 步:到前台 / 小程序 带: - 钱(现金 / 微信 / POS 卡) - 房号 / 姓名(身份证) ### 第 2 步:告诉业务人员要付哪张账单 > "我交 5 月物业费" 业务人员从系统找到对应账单。 ### 第 3 步:确认金额 + 付款方式 业务人员告诉张阿姨: > "您 5 月物业费 ¥800,请选付款方式" | 付款方式 | 操作 | |---|---| | 现金 | 给钱 → 找零 | | 微信扫码 | 业务人员出示物业收款码 → 业户扫 | | POS 刷卡 | 业户给银行卡 → POS 机刷 | | 银行转账 | 业户给凭证(线下转账已到账)| ### 第 4 步:拿收据 - 纸质:**当场打印** - 电子:发到业户微信 / 邮箱 > [!success] 完成 > 账单状态从 Unpaid → Paid。业户带收据离开,5 分钟内搞定。 ## 业务人员视角 ### 第 1 步:找账单 后台 → 账单 → 列表 → 按业户姓名 / 房号 / 期次过滤 → 找到张阿姨的 5 月物业费(Unpaid,¥800)→ 进 `ViewBill`。 或者: - 后台 → 业户 → 找到张阿姨 → "她的账单"标签 → 看 Unpaid 列表 → 选 ### 第 2 步:点击 `CollectPaymentAction`(标签"收款") 右上角状态管理组。 > [!warning] 按钮可见性 > `CollectPaymentAction` 守护:`canBePaid()`(Unpaid / Partial)+ `->authorize('collect')`。Paid / Void / Suspended / Processing 灰化。 Modal 表单: | 字段 | 填什么 | |---|---| | **收款金额** | ¥800(默认全额,可改为部分)| | **支付方式(`payment_channel_id`)** | 现金 / 微信 / POS / 银行转账 | | **收款银行账户(`bank_account_id`)** | 微信/POS/转账选对应银行账户;现金可空 | | **收款备注** | 选填,如"业户现场付款" | ### 第 3 步:提交 系统在**一个事务**内: 1. 校验 Bill 可付(`canBePaid` = Unpaid / Partial) 2. 校验金额 ≤ Bill 剩余应付(`amount - paid_amount`) 3. 建 `CollectionOrder`(`type=Bill`,`actual_amount=+800`,`status=Completed`,`payment_channel`,`meta.fund_source=external`) 4. 建 `CollectionOrderBill`(`bill_id`,`collection_order_id`,`allocated_amount=800`) 5. 更新 `Bill.paid_amount += 800` 6. 更新 `Bill.status`:若 paid_amount = amount → `Paid`;若 < amount → `Partial` 7. 触发 `CollectionOrderCompleted` 事件 8. Listener 自动建 `Receipt` + `ReceiptItem`(文案"物业费 ¥800") 9. 写 activitylog(可选,具体看实现) ### 第 4 步:给业户收据 后台找到新生成 Receipt → 打印 / 微信发。 ## 系统流程 ```mermaid sequenceDiagram participant 业户 participant 业务[业务人员] participant Filament participant Action[CollectPaymentAction] participant DB participant Listener 业户->>业务: 付物业费 800 业务->>Filament: ViewBill → CollectPaymentAction(modal) Filament->>Action: handle(bill, 800, channel, bank) Action->>Action: canBePaid()? Unpaid=true Action->>Action: 800 ≤ remaining(800)? yes Action->>DB: 开启事务 Action->>DB: 1. 建 CollectionOrder(type=Bill, +800, Completed) Action->>DB: 2. 建 CollectionOrderBill(allocated=800) Action->>DB: 3. Bill.paid_amount=800 Action->>DB: 4. Bill.status=Paid(800=800) Action->>Listener: 5. 触发 CollectionOrderCompleted Listener->>DB: 6. 建 Receipt("物业费 ¥800") Action->>DB: 提交事务 Filament-->>业务: 成功通知 业务-->>业户: 收据 ``` ## 部分付场景(business 上常见) 业户只想付 ¥300(全额 ¥800): ``` Modal 表单: - 收款金额:300(手动改) - 支付方式:现金 - 备注:"暂时只能付一部分" ``` 提交后: - Bill.paid_amount = 300 - Bill.status: Unpaid → **Partial** - 建 CollectionOrder(+300)+ CollectionOrderBill(allocated=300) 业户后续补付 ¥500 → 同样走 `CollectPaymentAction` → 第 2 笔 CollectionOrderBill → Bill 收齐 → Paid。 详见 [[exception-partial-payment]]。 ## 数据示例(完整流水) 业户付 ¥800 后: ### Bill 表 | 字段 | 值 | |---|---| | `id` | 12345 | | `amount` | 800 | | `paid_amount` | 800 | | `status` | Paid | ### CollectionOrderBill 表 | 字段 | 值 | |---|---| | `bill_id` | 12345 | | `collection_order_id` | 67890 | | `allocated_amount` | 800 | ### CollectionOrder 表 | 字段 | 值 | |---|---| | `id` | 67890 | | `collection_type` | Bill | | `actual_amount` | +800 | | `payment_channel_id` | 微信 | | `status` | Completed | | `meta.fund_source` | external | ### Receipt 表 | 字段 | 值 | |---|---| | `collection_order_id` | 67890 | | `amount` | +800 | | line_items | [{ 物业费(5月), 800 }] | ## 常见问题 > [!question] 业户付的钱与账单金额不一致(多付 / 少付)? > | 情况 | 处置 | > |---|---| > | 少付 | 走部分付 Partial 状态;后续补付 | > | **多付** | **不应该**(Modal 守护 `amount ≤ remaining`)。如果业务上业户给的钱多,**找零给业户**(系统层面只收账单的金额) | > [!question] 业户付错账单(本想付物业费,付到电费)? > 业务人员可: > - 立即作废这笔 CollectionOrder(走 [[void-paid-bill]] 类似流程)+ 重新对正确账单收款 > - 或者**留这笔不动**(资金确实到账)+ 业务人员手工调整 CollectionOrderBill 的 allocated_amount(危险,会破坏审计) > > **推荐第一种**(走作废 + 重收)。 > [!question] Frozen Bill 能收款吗? > 不能。`canBePaid()` 只允许 Unpaid / Partial。Suspended 状态需先 [[resume-bill|恢复]]。 > [!question] 已付的 Bill 能再收款吗? > 不能。`canBePaid()` Paid 时返 false。理论上 Bill 已经付清。 > [!question] 业户预存款够付,业务人员怎么操作? > 看自动 / 手动: > - 自动(待补的 [[../prepaid/auto-deduction-design|预存款自动抵扣 job]]):业务人员不操作,系统自动 > - 手动:业务人员在 `ViewBill` → 走 `CollectPaymentAction` 选"预存款抵扣"(或专用 Action)→ 详见 [[collect-via-prepaid-auto]] > [!question] 收款时 PaymentChannel 写错(选了微信实际是现金)? > CollectionOrder 一经创建**通常不可改**字段。错了: > - 走作废(详见 [[void-paid-bill]])+ 重新建 > - 或 tinker 改字段(运维 + 留审计) > > 预防:Modal 提交前再三确认。 > [!question] activitylog 记什么? > 详见 [[smart-bulk-delete-design]] activitylog 设计。CollectPayment 通常也记一条 activitylog:event=collected,properties 含 amount / payment_channel / receipt_id。 ## 异常分支 - 部分付场景 → [[exception-partial-payment]] - 批量付(多张账单一起付)→ [[collect-payment-batch]] - 预存款抵 → [[collect-via-prepaid-auto]] - 收款错了想撤 → [[void-paid-bill]] - Bill 挂起中无法收 → [[resume-bill]] - 逾期账单催收 → [[exception-overdue-bills]] ## 相关文档 - [[bill-six-state-machine]] - [[bill-vs-collection-order]] - [[exception-partial-payment]] - [[collect-payment-batch]] - [[collect-via-prepaid-auto]] - [[../adhoc/collection-order-and-receipt]]