242 lines
8.0 KiB
Markdown
242 lines
8.0 KiB
Markdown
|
|
---
|
||
|
|
title: prop-acc · billing · 场景 - 挂起账单(业户失联/纠纷)
|
||
|
|
aliases:
|
||
|
|
- 挂起账单
|
||
|
|
- SuspendBillAction
|
||
|
|
- 暂停收款
|
||
|
|
- suspend-bill
|
||
|
|
- 场景-挂起账单
|
||
|
|
tags:
|
||
|
|
- 场景
|
||
|
|
- prop-acc
|
||
|
|
- 账单
|
||
|
|
- 调整
|
||
|
|
audience:
|
||
|
|
- 业务人员
|
||
|
|
- 财务
|
||
|
|
status: 已发布
|
||
|
|
sub_feature: billing
|
||
|
|
last_review: 2026-05-26
|
||
|
|
code_version: 2026-05-22
|
||
|
|
---
|
||
|
|
|
||
|
|
# 场景:挂起账单(业户失联 / 纠纷)
|
||
|
|
|
||
|
|
业户**与物业有纠纷**或**长期失联**,该账单暂时**不应被收款 / 不应被催收**,但又不能直接作废(纠纷可能解决,业户可能出现)。走 `SuspendBillAction` 把账单挂起,状态 Unpaid → Suspended,后续可 [[resume-bill|恢复]]。
|
||
|
|
|
||
|
|
## 典型情境
|
||
|
|
|
||
|
|
> [!example] 真实情境(一):业户失联
|
||
|
|
> 王先生(15-7-203)三个月没缴物业费(累计 3 张账单 ¥2,400 Unpaid),物业多次联系电话不通、上门无人。物业认为业户可能搬走 / 失联 / 出国,**先把这 3 张账单挂起**,避免:
|
||
|
|
>
|
||
|
|
> - 月度报表"应收账款"虚高(挂着收不回来)
|
||
|
|
> - 催收资源浪费(联系不上的还反复发短信)
|
||
|
|
> - 业户突然回来时账单仍在(不会变作废)
|
||
|
|
|
||
|
|
> [!example] 真实情境(二):纠纷期间
|
||
|
|
> 陈先生认为 5 月物业费 ¥800 不合理(物业服务质量纠纷),拒绝付。物业 / 业主委员会调解中,**挂起该账单**,等调解结果:
|
||
|
|
> - 调解物业胜诉 → [[resume-bill|恢复]] → 业户付
|
||
|
|
> - 调解业户胜诉 → [[void-paid-bill|作废]] → 不收
|
||
|
|
> - 调解妥协 → 走拆账单 / 重新算金额
|
||
|
|
|
||
|
|
## 业务人员视角
|
||
|
|
|
||
|
|
### 第 1 步:确认挂起场景
|
||
|
|
|
||
|
|
| 场景 | 是否走挂起 |
|
||
|
|
|---|---|
|
||
|
|
| 业户失联 1-3 个月 | ✅ 挂起(等业户出现) |
|
||
|
|
| 业户失联 >6 个月 | 可考虑作废(看物业政策)|
|
||
|
|
| 业户纠纷中 | ✅ 挂起 |
|
||
|
|
| 业户拒不付 | 不挂起,走逾期催收([[exception-overdue-bills]]) |
|
||
|
|
| 业户真的搬走永久不再来 | 走作废 / 法律手段 |
|
||
|
|
|
||
|
|
### 第 2 步:打开账单
|
||
|
|
|
||
|
|
后台 → 账单 → 找到 Unpaid Bill → 进 `ViewBill`。
|
||
|
|
|
||
|
|
### 第 3 步:点击 `SuspendBillAction`(标签"挂起")
|
||
|
|
|
||
|
|
> [!warning] 按钮可见性
|
||
|
|
> 守护:`bill.status === Unpaid || Partial` + Policy `->authorize('suspend')`。Paid / Void / Suspended 状态灰化。
|
||
|
|
|
||
|
|
Modal 表单:
|
||
|
|
|
||
|
|
| 字段 | 填什么 |
|
||
|
|
|---|---|
|
||
|
|
| **挂起原因(reason)** | **必填且详细**,如 "业户失联 3 个月,电话不通 + 上门无人 + 微信无回应" |
|
||
|
|
|
||
|
|
### 第 4 步:提交
|
||
|
|
|
||
|
|
`SuspendBillAction` 业务层逻辑:
|
||
|
|
|
||
|
|
```php
|
||
|
|
class SuspendBillAction
|
||
|
|
{
|
||
|
|
public function handle(Bill $bill, string $reason, User $user): void
|
||
|
|
{
|
||
|
|
if (! in_array($bill->status, [BillStatus::Unpaid, BillStatus::Partial])) {
|
||
|
|
throw new RuntimeException("账单状态不可挂起");
|
||
|
|
}
|
||
|
|
|
||
|
|
$bill->update([
|
||
|
|
'status' => BillStatus::Suspended,
|
||
|
|
'meta' => array_merge($bill->meta ?? [], [
|
||
|
|
'suspend_reason' => $reason,
|
||
|
|
'suspended_at' => now(),
|
||
|
|
'suspended_by' => $user->id,
|
||
|
|
]),
|
||
|
|
]);
|
||
|
|
|
||
|
|
activity()
|
||
|
|
->performedOn($bill)
|
||
|
|
->causedBy($user)
|
||
|
|
->withProperties([
|
||
|
|
'reason' => $reason,
|
||
|
|
'from_status' => $bill->getOriginal('status'),
|
||
|
|
'to_status' => BillStatus::Suspended->value,
|
||
|
|
'bill_no' => $bill->bill_no,
|
||
|
|
])
|
||
|
|
->event('suspended')
|
||
|
|
->log('账单已挂起');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### 第 5 步:通知业户(可选)
|
||
|
|
|
||
|
|
- 失联场景:不通知(联系不上,无意义)
|
||
|
|
- 纠纷场景:通知"您的账单已挂起,等调解结果"
|
||
|
|
|
||
|
|
## 系统流程
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
sequenceDiagram
|
||
|
|
participant 业务
|
||
|
|
participant Filament
|
||
|
|
participant Action[SuspendBillAction]
|
||
|
|
participant DB
|
||
|
|
participant Activity
|
||
|
|
|
||
|
|
业务->>Filament: ViewBill → SuspendBillAction(modal)
|
||
|
|
Filament->>Action: handle(bill, reason, user)
|
||
|
|
Action->>Action: 校验 status Unpaid/Partial
|
||
|
|
Action->>DB: 开启事务
|
||
|
|
Action->>DB: 1. bill.status=Suspended
|
||
|
|
Action->>DB: 2. bill.meta.suspend_reason / suspended_at / suspended_by
|
||
|
|
Action->>Activity: 3. log(event=suspended)
|
||
|
|
Action->>DB: 提交
|
||
|
|
|
||
|
|
Filament-->>业务: 成功
|
||
|
|
```
|
||
|
|
|
||
|
|
## 挂起后的能力对照
|
||
|
|
|
||
|
|
| 操作 | Unpaid / Partial | **Suspended** |
|
||
|
|
|---|---|---|
|
||
|
|
| `CollectPaymentAction`(收款)| ✅ | ❌(`canBePaid=false`)|
|
||
|
|
| `CollectPaymentAction`(预存款抵)| ✅ | ❌ |
|
||
|
|
| `SuspendBillAction`(再挂起)| ✅ | ❌(已是 Suspended)|
|
||
|
|
| `ResumeBillAction`(恢复)| ❌ | ✅ |
|
||
|
|
| `VoidBillAction`(作废)| ✅(canBeVoided=true)| ✅ |
|
||
|
|
| 看账单 / 看历史 | ✅ | ✅(只读)|
|
||
|
|
| 出现在 `OverdueBillsListWidget` | ✅(若到期)| ❌(挂起不算逾期)|
|
||
|
|
| 出现在月度账单清单 | ✅ | ✅(标 Suspended)|
|
||
|
|
|
||
|
|
> [!info] Suspended 与 Overdue 的关系
|
||
|
|
> 挂起的账单**不算逾期**(catch up 不会出现在催收清单)。但**期次仍在历史**(归属本月报表)。
|
||
|
|
>
|
||
|
|
> 这是**有意设计**:挂起的目的是停掉催收 + 暂时退出收款流程,但不让账单凭空消失。
|
||
|
|
|
||
|
|
## 业户视角
|
||
|
|
|
||
|
|
### 失联场景(无感)
|
||
|
|
|
||
|
|
业户失联本来就联系不上,业务方决定挂起 → 业户**完全不知道**。等业户出现时:
|
||
|
|
|
||
|
|
- 业户来电话 / 上门
|
||
|
|
- 业务人员说"您有挂起的账单 ¥XXX,我现在帮您恢复 + 收款"
|
||
|
|
- 走 [[resume-bill|恢复]] → 收款
|
||
|
|
|
||
|
|
### 纠纷场景
|
||
|
|
|
||
|
|
业户**可能收到通知**(看物业政策):
|
||
|
|
|
||
|
|
> 陈先生您好,您的 5 月物业费账单已挂起(暂停收款),等纠纷调解结果。预计 X 月 Y 日前出结果。
|
||
|
|
|
||
|
|
业户:
|
||
|
|
|
||
|
|
- 知情 + 等待
|
||
|
|
- 期间不会被催收
|
||
|
|
- 调解后走 [[resume-bill|恢复]] 或 [[void-paid-bill|作废]]
|
||
|
|
|
||
|
|
## 与"作废"的对比
|
||
|
|
|
||
|
|
| 维度 | **挂起(Suspended,本场景)** | [[void-paid-bill|作废 Void]] |
|
||
|
|
|---|---|---|
|
||
|
|
| 是否可恢复 | ✅ 走 [[resume-bill]]| ❌ 终态 |
|
||
|
|
| 业务场景 | 临时暂停(纠纷 / 失联)| 永久消除 |
|
||
|
|
| 报表归属 | 仍在本期(标 Suspended)| 标 Void(可过滤) |
|
||
|
|
| 后续是否能收款 | 恢复后能 | 不能(已 Void)|
|
||
|
|
|
||
|
|
**简单判断**:**不确定后续怎么办 → 挂起**;**确定不再收 → 作废**。
|
||
|
|
|
||
|
|
## 长期 Suspended 的处理
|
||
|
|
|
||
|
|
挂起后**长期没恢复 / 没作废**的账单,业务上需要定期 review:
|
||
|
|
|
||
|
|
| 挂起时长 | 推荐处置 |
|
||
|
|
|---|---|
|
||
|
|
| < 1 月 | 正常等待 |
|
||
|
|
| 1-3 月 | 评估业户情况(再次联系)|
|
||
|
|
| 3-6 月 | 决定:恢复(若有进展)/ 作废(若无希望)|
|
||
|
|
| > 6 月 | 通常作废(或走法律手段)|
|
||
|
|
|
||
|
|
可加 audit 场景:**"长期挂起账单清单"**(类似 [[../deposit/audit-long-pending-accounts]])。当前 issue.md 未明确实施,可作为未来扩展。
|
||
|
|
|
||
|
|
## 常见问题
|
||
|
|
|
||
|
|
> [!question] 挂起原因填得详细到什么程度?
|
||
|
|
> **越详细越好**。审计要求 + 业户事后查询 + 业务复盘都需要。推荐结构:
|
||
|
|
>
|
||
|
|
> - 触发事件(纠纷 / 失联 / 其他)
|
||
|
|
> - 已采取的联系措施(电话 / 上门 / 微信)
|
||
|
|
> - 业务上的预期(等调解 / 等业户回来 / 等司法)
|
||
|
|
|
||
|
|
> [!question] 挂起的账单已经有部分付(Partial)?
|
||
|
|
> 看实施。SuspendBillAction 可能允许(从 Partial → Suspended)。已付的部分**不退**(只是暂停后续收款)。
|
||
|
|
|
||
|
|
> [!question] 同一业户多张挂起,能批量挂吗?
|
||
|
|
> 当前**无批量挂起 UI**。逐张走 SuspendBillAction,效率低但可控。
|
||
|
|
>
|
||
|
|
> 可加 `BulkSuspendBillsAction`(类似批删的设计),业务方提需求时实施。
|
||
|
|
|
||
|
|
> [!question] 挂起影响 prepaid 自动抵扣 job?
|
||
|
|
> 是。job 应**跳过 Suspended 状态**的账单。设计上看 [[../prepaid/auto-deduction-design]]。
|
||
|
|
|
||
|
|
> [!question] activitylog 怎么查挂起记录?
|
||
|
|
> ```sql
|
||
|
|
> SELECT * FROM activity_log
|
||
|
|
> WHERE event = 'suspended'
|
||
|
|
> AND created_at BETWEEN ? AND ?
|
||
|
|
> ORDER BY created_at DESC;
|
||
|
|
> ```
|
||
|
|
>
|
||
|
|
> 详见 [[audit-activitylog-trace]]。
|
||
|
|
|
||
|
|
## 异常分支
|
||
|
|
|
||
|
|
- 业户出现/纠纷解决 → [[resume-bill]]
|
||
|
|
- 确定不再收 → [[void-paid-bill]]
|
||
|
|
- 业务上误挂起 → [[resume-bill]] 撤回(reason 改"误操作")
|
||
|
|
- 长期挂起无解 → 作废 / 法律手段(待业务方明确)
|
||
|
|
|
||
|
|
## 相关文档
|
||
|
|
|
||
|
|
- [[bill-six-state-machine]]
|
||
|
|
- [[resume-bill]]
|
||
|
|
- [[void-paid-bill]]
|
||
|
|
- [[exception-overdue-bills]]
|
||
|
|
- [[audit-activitylog-trace]]
|
||
|
|
- [[../deposit/freeze-during-dispute]](类似的"暂停"模式对比)
|