Files

258 lines
7.8 KiB
Markdown
Raw Permalink Normal View History

2026-05-26 01:03:15 +08:00
---
title: prop-acc · billing · 场景 - 同业户多账单批量收款
aliases:
- 批量收款
- BatchCollectPaymentAction
- 一次付多张
- collect-payment-batch
- 场景-同业户批量收款
tags:
- 场景
- prop-acc
- 账单
- 收款
audience:
- 业户
- 业务人员
status: 已发布
sub_feature: billing
last_review: 2026-05-26
code_version: 2026-05-22
---
# 场景:同业户多账单批量收款
业户**本月有多张账单**(物业费 + 水电气费 + 其他),想**一次性付清**。业务人员走 `BatchCollectPaymentAction`,**一笔 CollectionOrder 关联多张 Bill**(走 CollectionOrderBill 多对多)。
## 典型情境
> [!example] 真实情境
> 张阿姨本月有 4 张账单:
>
> | 账单 | 金额 |
> |---|---|
> | 5 月物业费 | ¥800 |
> | 5 月水费 | ¥54 |
> | 5 月电费 | ¥168 |
> | 5 月燃气 | ¥30 |
> | **合计** | **¥1,052** |
>
> 张阿姨到前台:"4 张账单我一次性付清,微信扫码"。业务人员**1 笔操作**完成 4 张账单收款。
## 业户视角
### 第 1 步:告诉业务人员要付哪些
> "我把本月 4 张账单都付了,微信扫码"
### 第 2 步:确认金额
业务人员说:"4 张账单合计 ¥1,052,微信扫这个码"。
### 第 3 步:微信付
业户扫码 → 输密码 / 指纹 → 付 ¥1,052。
### 第 4 步:拿收据
可能是:
- 一张 Receipt 含 4 行明细(物业费 ¥800 / 水费 ¥54 / ...)
- 或 4 张独立 Receipt(每张账单一张)
具体看实现。**业户体验上前者更好**(一张收据看全部)。
## 业务人员视角
### 第 1 步:找业户
后台 → 业户 → 找到张阿姨 → "她的账单"标签 → 看到 4 张 Unpaid。
或:后台 → 账单 → 列表 → 过滤业户=张阿姨 + 状态=Unpaid。
### 第 2 步:选中多张账单
Table 上勾选 4 张账单 → 顶部 **"批量收款"** 按钮(`BatchCollectPaymentAction`)。
或:后台 → 业户视图 → "批量收款"(若 UI 支持单业户聚合)。
### 第 3 步:Modal 表单
| 字段 | 填什么 |
|---|---|
| **选中账单数** | 4 张(显示)|
| **合计金额** | ¥1,052(自动算)|
| **支付方式** | 微信 |
| **收款银行账户** | 物业微信账户 |
| **备注** | 选填,如 "业户本月全套缴" |
### 第 4 步:提交
系统在**一个事务**内:
1. 校验每张 Bill 可付(`canBePaid()`)
2.**1 个 CollectionOrder**(`type=Bill`,`actual_amount=+1052`,`status=Completed`)
3.**4 个 CollectionOrderBill**(每张账单一个,各自 `allocated_amount` = 该账单金额)
4. 4 张 Bill 各自 `paid_amount = amount`,`status = Paid`
5. 触发 `CollectionOrderCompleted` 事件
6. Listener 建 Receipt(可能含 4 个 line_items)
### 第 5 步:给收据
后台找到 Receipt → 打印 / 微信发。
## 系统流程
```mermaid
sequenceDiagram
participant 业户
participant 业务
participant Filament
participant Action[BatchCollectPaymentAction]
participant DB
业务->>Filament: BillsList → 选 4 张 → 批量收款
Filament->>Action: handle(bills, channel, bank)
Action->>Action: 每张 Bill 校验 canBePaid
Action->>DB: 开启事务
Action->>DB: 1. 建 CollectionOrder(+1052, Completed)
loop 每张 Bill
Action->>DB: 2. 建 CollectionOrderBill(allocated=bill.amount)
Action->>DB: 3. Bill.paid_amount = amount, status=Paid
end
Action->>Listener: 4. 触发 CollectionOrderCompleted
Listener->>DB: 5. 建 Receipt(line_items × 4)
Action->>DB: 提交事务
Filament-->>业务: 成功通知
业务-->>业户: 收据(含 4 项明细)
```
## 数据示例
收款后:
### CollectionOrder(1 条)
```
id: 67890
collection_type: Bill
actual_amount: +1052
payment_channel: 微信
status: Completed
meta.fund_source: external
```
### CollectionOrderBill(4 条)
| collection_order_id | bill_id | allocated_amount |
|---|---|---|
| 67890 | 物业费 Bill | 800 |
| 67890 | 水费 Bill | 54 |
| 67890 | 电费 Bill | 168 |
| 67890 | 燃气 Bill | 30 |
### Bill(4 条更新)
每张 paid_amount = amount,status = Paid。
### Receipt(1 条,4 行明细)
```
collection_order_id: 67890
amount: +1052
line_items: [
{ 物业费(5月), 800 },
{ 水费(5月), 54 },
{ 电费(5月), 168 },
{ 燃气(5月), 30 },
]
```
## 与单张收款的对比
| 维度 | [[collect-payment-single|单张]] | **批量(本场景)** |
|---|---|---|
| Modal 选账单 | 1 张(从 ViewBill 进入)| **多张**(Table 勾选)|
| CollectionOrder | 1 个 | 1 个(共用)|
| CollectionOrderBill | 1 个 | **N 个**(每张一个)|
| 业务人员操作 | 单笔 | 一笔 |
| 业户体验 | 一张一张付(慢)| 一次付清(快)|
**批量收款的好处**:业户体验更好(一次付),业务人员工作量更小。**唯一前提**:业户愿意一次付清。
## 不同支付方式的分摊
业户支付的钱**自动按账单原金额比例分摊** 到各 Bill。不需要业务人员手动分配。
例:¥1,052 微信付 → 4 个 CollectionOrderBill 各自 allocated_amount = 该账单 amount(全额分配)。
### 如果业户付不够(部分批量付)
业户只想付 ¥600(不够 ¥1,052)→ 业务人员有几种选择:
| 策略 | 操作 |
|---|---|
| **优先付物业费**(默认?)| ¥600 全部分配给物业费 Bill(部分付)|
| **按比例分摊** | 600 × 800/1052 = 456 给物业费,600 × 54/1052 = 31 给水费, ... |
| **业务人员手动决定** | 给业务人员选哪张账单付多少 |
当前实现的具体策略看代码。**业务上建议优先付到期早的**(避免逾期)。
> [!info] 批量收款的"部分付"复杂度
> 上述场景比单张部分付更复杂。当前 `BatchCollectPaymentAction` 可能**只支持全额批量**(若金额够付所有选中账单 → 全付;不够 → 走单张部分付逐张操作)。看实现。
## 业务人员视角:Modal 的预检查
类似 [[smart-bulk-delete-design|智能批删]] 的预检查思路:
- 选中 4 张账单 → Modal 显示"总计 ¥1,052"
- 选中包含已 Paid 的账单 → Modal 显示"4 选 + 1 已付跳过"
- 选中包含 Suspended → Modal 提示 "该账单挂起,无法收款"
> [!info] 当前实现的成熟度
> `BatchCollectPaymentAction` 的智能 Modal 程度看实现。可能比 `BulkDeleteBillsAction` 简单(批删有 issue.md Q6 详细设计,批收款未单独描述)。
## 常见问题
> [!question] 选中跨业户的多张账单能批量收款吗?
> 业务上**不应该**(不同业户付的钱不能混)。系统层面应**校验同业户**,否则拒绝。
>
> 例外:**家属代付**场景(儿子来付父母账单)→ 业务上算同业户的钱。
> [!question] 批量收款失败一半怎么办?
> 事务内**全成功或全失败**。任一 Bill 校验失败(例如某张已 Paid)→ 整笔回滚 → 业户的钱不被收。
>
> 实施上可能优化为"部分成功"(只失败的跳过),但破坏事务原子性,通常不推荐。
> [!question] 业户支付的钱比账单合计**多**?
> Modal 守护应限制金额 ≤ 合计。如果业务上业户故意多给:
>
> - 找零给业户(系统层面只收账单的金额)
> - **或转入业户预存款账户**(若有此自动逻辑)
> [!question] 不同期次的账单能一起付吗?
> 可以(账单状态都是 Unpaid 即可)。例如付 4 月物业费 + 5 月物业费 + 5 月水电气。
> [!question] 批量收款的 activitylog 怎么记?
> 一条 CollectionOrder 的 activitylog(event=created)+ 每张 Bill 状态变化的 log。可在 SQL 反查 affected_bill_ids。
## 异常分支
- 单张付 → [[collect-payment-single]]
- 部分付 → [[exception-partial-payment]]
- 预存款抵 → [[collect-via-prepaid-auto]]
- 业户全付不起,挑某张付 → 走 [[collect-payment-single]] 逐张
- 收错了想撤 → [[void-paid-bill]](已付作废 + 退款)
## 相关文档
- [[bill-vs-collection-order]]
- [[collect-payment-single]]
- [[collect-via-prepaid-auto]]
- [[exception-partial-payment]]
- [[../prepaid/auto-deduction-design]]