vault backup: 2026-05-26 01:08:16
This commit is contained in:
8
.obsidian/workspace.json
vendored
8
.obsidian/workspace.json
vendored
@@ -197,6 +197,10 @@
|
|||||||
},
|
},
|
||||||
"active": "849c5ff8936a2b67",
|
"active": "849c5ff8936a2b67",
|
||||||
"lastOpenFiles": [
|
"lastOpenFiles": [
|
||||||
|
"prop-acc/scenarios/billing/delete-bill-unpaid.md",
|
||||||
|
"prop-acc/scenarios/billing/resume-bill.md",
|
||||||
|
"prop-acc/scenarios/billing/suspend-bill.md",
|
||||||
|
"prop-acc/scenarios/billing/split-bill.md",
|
||||||
"prop-acc/scenarios/billing/collect-via-prepaid-auto.md",
|
"prop-acc/scenarios/billing/collect-via-prepaid-auto.md",
|
||||||
"prop-acc/scenarios/billing/collect-payment-batch.md",
|
"prop-acc/scenarios/billing/collect-payment-batch.md",
|
||||||
"prop-acc/scenarios/billing/collect-payment-single.md",
|
"prop-acc/scenarios/billing/collect-payment-single.md",
|
||||||
@@ -221,10 +225,6 @@
|
|||||||
"prop-acc/scenarios/meter/read-with-photo-proof.md",
|
"prop-acc/scenarios/meter/read-with-photo-proof.md",
|
||||||
"prop-acc/scenarios/meter/read-via-iot-remote-source.md",
|
"prop-acc/scenarios/meter/read-via-iot-remote-source.md",
|
||||||
"prop-acc/scenarios/meter/read-batch-via-excel-import.md",
|
"prop-acc/scenarios/meter/read-batch-via-excel-import.md",
|
||||||
"prop-acc/scenarios/meter/read-single-meter-manual.md",
|
|
||||||
"prop-acc/scenarios/meter/decommission-without-replacement.md",
|
|
||||||
"prop-acc/scenarios/meter/replace-broken-meter.md",
|
|
||||||
"prop-acc/scenarios/meter/register-single-meter.md",
|
|
||||||
"prop-acc/scenarios/meter",
|
"prop-acc/scenarios/meter",
|
||||||
"prop-acc/concepts/meter",
|
"prop-acc/concepts/meter",
|
||||||
"prop-acc/scenarios/prepaid",
|
"prop-acc/scenarios/prepaid",
|
||||||
|
|||||||
223
prop-acc/scenarios/billing/delete-bill-unpaid.md
Normal file
223
prop-acc/scenarios/billing/delete-bill-unpaid.md
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
---
|
||||||
|
title: prop-acc · billing · 场景 - 物理删除未付账单(误建立刻删)
|
||||||
|
aliases:
|
||||||
|
- 物理删除账单
|
||||||
|
- 删除未付账单
|
||||||
|
- delete-bill-unpaid
|
||||||
|
- 场景-删除未付账单
|
||||||
|
tags:
|
||||||
|
- 场景
|
||||||
|
- prop-acc
|
||||||
|
- 账单
|
||||||
|
- 删除
|
||||||
|
audience:
|
||||||
|
- 业务人员
|
||||||
|
status: 已发布
|
||||||
|
sub_feature: billing
|
||||||
|
last_review: 2026-05-26
|
||||||
|
code_version: 2026-05-22
|
||||||
|
---
|
||||||
|
|
||||||
|
# 场景:物理删除未付账单(误建立刻删)
|
||||||
|
|
||||||
|
业务人员**误建**了账单(选错业户 / 写错金额 / 误触发批量生成),**立即发现**且**业户还没付**任何钱 → 走**物理删除**。`canBeDeleted()` 守护:Unpaid + 无付款关联。
|
||||||
|
|
||||||
|
## 典型情境
|
||||||
|
|
||||||
|
> [!example] 真实情境
|
||||||
|
> 王主管月初手工建账单"5 月物业费 ¥800",**填错业户 ID**(应该是张阿姨,选成了陈先生)。提交后立刻发现错误。
|
||||||
|
>
|
||||||
|
> - 账单状态:Unpaid
|
||||||
|
> - 业户没付过任何钱
|
||||||
|
> - **可走物理删除**(走 [[delete-vs-void-dual-track|双轨制]] 的 delete 路径)
|
||||||
|
> - 再重新建一张正确的(选张阿姨)
|
||||||
|
|
||||||
|
## 业务人员视角
|
||||||
|
|
||||||
|
### 第 1 步:发现错误
|
||||||
|
|
||||||
|
| 发现时机 | 处置 |
|
||||||
|
|---|---|
|
||||||
|
| **立即**(几秒钟内)| 物理删(本场景)|
|
||||||
|
| **几小时**(可能业户已收到推送)| 仍可删(若 Unpaid + 无付款)|
|
||||||
|
| **几天**(可能业户已付)| **不可删**,走 [[void-paid-bill|作废]] |
|
||||||
|
|
||||||
|
### 第 2 步:打开账单
|
||||||
|
|
||||||
|
后台 → 账单 → 找到误建账单 → 进 `EditBill`(或 `ViewBill`)。
|
||||||
|
|
||||||
|
### 第 3 步:点击 `DeleteAction`(标签"删除")
|
||||||
|
|
||||||
|
> [!warning] 按钮可见性
|
||||||
|
> 守护:`canBeDeleted()` = Unpaid + 无付款 + `->authorize('delete')`(`bill.delete` 权限)。
|
||||||
|
>
|
||||||
|
> 任何其他状态(Partial / Paid / Suspended / Void)或有任何付款关联 → 按钮**灰化**。
|
||||||
|
|
||||||
|
### 第 4 步:Modal 确认
|
||||||
|
|
||||||
|
```
|
||||||
|
确认删除账单 #B-202605-501-001?
|
||||||
|
|
||||||
|
⚠️ 此账单状态为 Unpaid 且无付款关联,可物理删除。
|
||||||
|
删除后无法恢复(但 activitylog 会保留记录)。
|
||||||
|
|
||||||
|
[取消] [确认删除]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第 5 步:提交
|
||||||
|
|
||||||
|
系统:
|
||||||
|
|
||||||
|
1. 再次校验 `canBeDeleted()`(防 UI 缓存过期)
|
||||||
|
2. 物理删 Bill(从数据库消失)
|
||||||
|
3. 写 activitylog(event=deleted,记 bill_no / amount / 操作员 / 时间)
|
||||||
|
|
||||||
|
> [!info] activitylog 留什么
|
||||||
|
> 即使 Bill 物理删了,activitylog 表还有完整记录:
|
||||||
|
>
|
||||||
|
> ```sql
|
||||||
|
> SELECT * FROM activity_log
|
||||||
|
> WHERE event = 'deleted'
|
||||||
|
> AND properties->>'$.bill_no' = 'B-202605-501-001';
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> 可看到"谁在什么时候删了什么账单"。详见 [[smart-bulk-delete-design]]"activitylog 设计"。
|
||||||
|
|
||||||
|
### 第 6 步:重新建正确账单(若需要)
|
||||||
|
|
||||||
|
走 [[create-single-bill-manual|手动建单]] 流程,选正确业户。
|
||||||
|
|
||||||
|
## 系统流程
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant 业务
|
||||||
|
participant Filament
|
||||||
|
participant Action[DeleteAction]
|
||||||
|
participant Bill
|
||||||
|
participant Activity
|
||||||
|
participant DB
|
||||||
|
|
||||||
|
业务->>Filament: EditBill → DeleteAction
|
||||||
|
Filament->>Action: handle(bill)
|
||||||
|
Action->>Bill: canBeDeleted()? Unpaid + 无付款 = true
|
||||||
|
Action->>Activity: log(event=deleted, properties={bill_no, amount, ...})
|
||||||
|
Action->>DB: 物理删 Bill
|
||||||
|
Action-->>Filament: 跳回 ListBills
|
||||||
|
```
|
||||||
|
|
||||||
|
## 业户视角
|
||||||
|
|
||||||
|
业户**无感**:
|
||||||
|
|
||||||
|
- 误建的账单(尤其几秒内删的)业户**根本不知道**它存在过
|
||||||
|
- 即使业户已经收到推送,删除后再次刷新看不到了
|
||||||
|
- 物业可选择给业户发"前述账单作废,系统误建"(可选,看业务策略)
|
||||||
|
|
||||||
|
**最理想场景**:业务人员发现 → 立即删 → 业户从未感知。
|
||||||
|
|
||||||
|
## `canBeDeleted` 详解
|
||||||
|
|
||||||
|
`Bill::canBeDeleted()`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function canBeDeleted(): bool
|
||||||
|
{
|
||||||
|
return $this->status === BillStatus::Unpaid
|
||||||
|
&& ! $this->hasAnyPayment();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`hasAnyPayment()`:
|
||||||
|
|
||||||
|
```php
|
||||||
|
public function hasAnyPayment(): bool
|
||||||
|
{
|
||||||
|
return $this->collectionOrderBills()->exists()
|
||||||
|
|| $this->prepaidTransactionsRelatedToBill()->exists();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
两条都为真(Unpaid + 无付款关联) → 可删。
|
||||||
|
|
||||||
|
> [!info] 为什么 Partial 不能删?
|
||||||
|
> Partial 意味着业户已经付了一部分 → 有 CollectionOrderBill 关联 → `hasAnyPayment=true` → 拒绝物理删。
|
||||||
|
>
|
||||||
|
> 业户付的钱不能"凭空消失"。Partial 状态要么继续收款 → Paid;要么走 [[void-paid-bill|作废 + 退款]] 流程。
|
||||||
|
|
||||||
|
## 与作废的对比
|
||||||
|
|
||||||
|
| 维度 | **删除(本场景)** | [[void-paid-bill|作废]] |
|
||||||
|
|---|---|---|
|
||||||
|
| 适用 | Unpaid + 无付款 | 非 Paid 非 Void(基本是 Partial / Suspended) |
|
||||||
|
| 数据库 | **从数据库消失** | 留状态 = Void |
|
||||||
|
| activitylog | 留(event=deleted)| 留(event=voided)|
|
||||||
|
| 业户感知 | 通常无感(未付过)| 有(看到状态 Void) |
|
||||||
|
| 可恢复 | ❌ 不可(数据真没了)| ❌ 不可(Void 终态)|
|
||||||
|
| 适用场景 | 误建立刻删 | 任何已付 / 有付款 / 业户已知的 |
|
||||||
|
|
||||||
|
详见 [[delete-vs-void-dual-track]] 双轨制设计哲学。
|
||||||
|
|
||||||
|
## 批量删除(误建一批)
|
||||||
|
|
||||||
|
如果不是单张误建,而是**误触发批量生成**(例如点了一次"生成 5 月物业费"后忘了又点了一次):
|
||||||
|
|
||||||
|
→ 走 [[bulk-delete-batch-mistake|智能批量删除]],走 `BulkDeleteBillsAction` 的预检查 + 三档分类。
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
> [!question] 删了后悔了能恢复吗?
|
||||||
|
> 不能(数据真的没了)。**只能重新建**(走 [[create-single-bill-manual]] 或 [[create-periodic-property-fee]])。
|
||||||
|
>
|
||||||
|
> 但**所有信息可从 activitylog 还原**(原 bill_no / amount / 业户 等),重建相对简单。
|
||||||
|
|
||||||
|
> [!question] 业务人员误删大量账单怎么办?
|
||||||
|
> 单删一次一张,影响有限。**批删**才容易误删大量(详见 [[smart-bulk-delete-design]] 的预检查防护)。
|
||||||
|
>
|
||||||
|
> 单张误删的预防:
|
||||||
|
> - Modal 确认(默认有)
|
||||||
|
> - 思考"是否真要删"再点
|
||||||
|
|
||||||
|
> [!question] 删除有审批吗?
|
||||||
|
> 当前**无审批流**(单签批操作)。但**权限层有控制**:`bill.delete` 是普通业务人员权限;`bill.bulkDelete` 是高敏独立权限(详见 [[smart-bulk-delete-design]])。
|
||||||
|
|
||||||
|
> [!question] activitylog 表会爆掉吗?
|
||||||
|
> 长期看会(每次单删 + 批删 + 作废 + 收款都加几条)。需归档策略(详见 [[smart-bulk-delete-design]]"常见问题"段)。
|
||||||
|
|
||||||
|
> [!question] 删除影响 reading.bill_id 吗(若是计量账单)?
|
||||||
|
> 看 cascade 设计:
|
||||||
|
> - 若 reading 的 `bill_id` 是 FK + cascade SET NULL → 自动 nullify
|
||||||
|
> - 若没 cascade → reading.bill_id 仍指向已删 Bill(孤儿 FK,需修复)
|
||||||
|
>
|
||||||
|
> 当前实施看代码。**预防**:计量账单不轻易物理删,走作废更稳妥。
|
||||||
|
|
||||||
|
> [!question] 已删账单的 activitylog 怎么用?
|
||||||
|
> ```sql
|
||||||
|
> -- 某员工某天的全部 delete
|
||||||
|
> SELECT
|
||||||
|
> id, causer_id,
|
||||||
|
> properties->>'$.bill_no' AS bill_no,
|
||||||
|
> properties->>'$.amount' AS amount,
|
||||||
|
> created_at
|
||||||
|
> FROM activity_log
|
||||||
|
> WHERE event = 'deleted'
|
||||||
|
> AND causer_id = ?
|
||||||
|
> AND created_at BETWEEN ? AND ?;
|
||||||
|
> ```
|
||||||
|
|
||||||
|
## 异常分支
|
||||||
|
|
||||||
|
- 误删后想重建 → [[create-single-bill-manual]]
|
||||||
|
- 误删一批 → 走批删的反向(无,只能 1 张张重建)
|
||||||
|
- 不能删(有付款)→ [[void-paid-bill]]
|
||||||
|
- 批量误建 → [[bulk-delete-batch-mistake]]
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [[delete-vs-void-dual-track]]
|
||||||
|
- [[smart-bulk-delete-design]]
|
||||||
|
- [[bill-six-state-machine]]
|
||||||
|
- [[void-paid-bill]]
|
||||||
|
- [[bulk-delete-batch-mistake]]
|
||||||
|
- [[audit-activitylog-trace]]
|
||||||
|
- [[create-single-bill-manual]]
|
||||||
253
prop-acc/scenarios/billing/resume-bill.md
Normal file
253
prop-acc/scenarios/billing/resume-bill.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
---
|
||||||
|
title: prop-acc · billing · 场景 - 恢复挂起的账单
|
||||||
|
aliases:
|
||||||
|
- 恢复账单
|
||||||
|
- ResumeBillAction
|
||||||
|
- 解除挂起
|
||||||
|
- resume-bill
|
||||||
|
- 场景-恢复挂起账单
|
||||||
|
tags:
|
||||||
|
- 场景
|
||||||
|
- prop-acc
|
||||||
|
- 账单
|
||||||
|
- 调整
|
||||||
|
audience:
|
||||||
|
- 业务人员
|
||||||
|
- 财务
|
||||||
|
status: 已发布
|
||||||
|
sub_feature: billing
|
||||||
|
last_review: 2026-05-26
|
||||||
|
code_version: 2026-05-22
|
||||||
|
---
|
||||||
|
|
||||||
|
# 场景:恢复挂起的账单
|
||||||
|
|
||||||
|
[[suspend-bill|挂起]] 状态的账单,在**纠纷解决 / 业户回来**后恢复到 Unpaid / Partial,后续可正常收款。`ResumeBillAction` 对称于 SuspendBillAction。
|
||||||
|
|
||||||
|
## 典型情境
|
||||||
|
|
||||||
|
> [!example] 真实情境(一):业户回来了
|
||||||
|
> 王先生(15-7-203)出国 3 个月后回来,到物业前台:"我去美国出差 3 个月,没顾上缴物业费,现在补"。
|
||||||
|
>
|
||||||
|
> - 王主管查看王先生账户 → 3 张挂起的物业费(Suspended)
|
||||||
|
> - 走 `ResumeBillAction` 逐张恢复 → 状态 Suspended → Unpaid
|
||||||
|
> - 然后走 [[collect-payment-batch|批量收款]] 一次性 ¥2,400 付清
|
||||||
|
|
||||||
|
> [!example] 真实情境(二):纠纷解决
|
||||||
|
> 陈先生与物业 5 月物业费纠纷调解结果:物业有部分过错,**协议金额 ¥600**(而不是原 ¥800)。
|
||||||
|
>
|
||||||
|
> - 物业要做:
|
||||||
|
> - 走 ResumeBill(挂起 → Unpaid)
|
||||||
|
> - 改账单金额(Edit Bill,若 Policy 允许 update 字段)/ 或作废原账单 + 重建 ¥600 账单
|
||||||
|
> - 业户付 ¥600 → 走 [[collect-payment-single]]
|
||||||
|
|
||||||
|
## 业务人员视角
|
||||||
|
|
||||||
|
### 第 1 步:确认恢复场景
|
||||||
|
|
||||||
|
| 场景 | 后续 |
|
||||||
|
|---|---|
|
||||||
|
| 业户失联回来要付 | 恢复 → 收款 |
|
||||||
|
| 纠纷解决(物业胜)| 恢复 → 收原金额 |
|
||||||
|
| 纠纷解决(妥协)| 恢复 → 改金额(或作废+重建) |
|
||||||
|
| 误挂起 | 恢复(reason = "误操作解除") |
|
||||||
|
|
||||||
|
### 第 2 步:打开账单
|
||||||
|
|
||||||
|
后台 → 账单 → 过滤"状态=Suspended" → 找到目标账单 → 进 `ViewBill`。
|
||||||
|
|
||||||
|
状态显示 "🧊 Suspended",右上角只有 `ResumeBillAction` 和 `VoidBillAction` 可点。
|
||||||
|
|
||||||
|
### 第 3 步:点击 `ResumeBillAction`(标签"恢复")
|
||||||
|
|
||||||
|
> [!warning] 按钮可见性
|
||||||
|
> 守护:`bill.status === Suspended` + Policy `->authorize('resume')`。
|
||||||
|
|
||||||
|
Modal 表单:
|
||||||
|
|
||||||
|
| 字段 | 填什么 |
|
||||||
|
|---|---|
|
||||||
|
| **恢复原因(reason)** | 必填,如 "业户出差回来,主动付清"|
|
||||||
|
|
||||||
|
### 第 4 步:提交
|
||||||
|
|
||||||
|
`ResumeBillAction` 业务层逻辑:
|
||||||
|
|
||||||
|
```php
|
||||||
|
class ResumeBillAction
|
||||||
|
{
|
||||||
|
public function handle(Bill $bill, string $reason, User $user): void
|
||||||
|
{
|
||||||
|
if ($bill->status !== BillStatus::Suspended) {
|
||||||
|
throw new RuntimeException("账单非 Suspended 状态,不可恢复");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 智能恢复:有部分付 → Partial;无付款 → Unpaid
|
||||||
|
$newStatus = $bill->paid_amount > 0
|
||||||
|
? BillStatus::Partial
|
||||||
|
: BillStatus::Unpaid;
|
||||||
|
|
||||||
|
$bill->update([
|
||||||
|
'status' => $newStatus,
|
||||||
|
'meta' => array_merge($bill->meta ?? [], [
|
||||||
|
'resume_reason' => $reason,
|
||||||
|
'resumed_at' => now(),
|
||||||
|
'resumed_by' => $user->id,
|
||||||
|
// 可选:把这次"挂起-恢复"完整记录追加到 suspend_history 数组
|
||||||
|
]),
|
||||||
|
]);
|
||||||
|
|
||||||
|
activity()
|
||||||
|
->performedOn($bill)
|
||||||
|
->causedBy($user)
|
||||||
|
->withProperties([
|
||||||
|
'reason' => $reason,
|
||||||
|
'from_status' => BillStatus::Suspended->value,
|
||||||
|
'to_status' => $newStatus->value,
|
||||||
|
'bill_no' => $bill->bill_no,
|
||||||
|
])
|
||||||
|
->event('resumed')
|
||||||
|
->log('账单已恢复');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 第 5 步:通知业户(可选)
|
||||||
|
|
||||||
|
恢复后立即提示业户付款:
|
||||||
|
|
||||||
|
> 王先生,您的 3 张挂起账单已恢复(合计 ¥2,400),现在可以付清。
|
||||||
|
|
||||||
|
### 第 6 步:走收款
|
||||||
|
|
||||||
|
恢复后走 [[collect-payment-single]] 或 [[collect-payment-batch]]。
|
||||||
|
|
||||||
|
## 系统流程
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant 业户
|
||||||
|
participant 业务
|
||||||
|
participant Filament
|
||||||
|
participant Action[ResumeBillAction]
|
||||||
|
participant DB
|
||||||
|
|
||||||
|
业户->>业务: 我要付挂起的账单
|
||||||
|
业务->>Filament: ViewBill(Suspended)→ ResumeBillAction(modal, reason)
|
||||||
|
Filament->>Action: handle(bill, reason, user)
|
||||||
|
Action->>Action: 校验 status === Suspended
|
||||||
|
Action->>Action: 判定恢复后状态(Unpaid 或 Partial)
|
||||||
|
Action->>DB: 1. bill.status = Unpaid / Partial
|
||||||
|
Action->>DB: 2. bill.meta.resume_reason / resumed_at / resumed_by
|
||||||
|
Action->>Activity: 3. log(event=resumed)
|
||||||
|
Filament-->>业务: 成功
|
||||||
|
|
||||||
|
业务->>Filament: 继续走收款流程
|
||||||
|
```
|
||||||
|
|
||||||
|
## 智能恢复:Partial vs Unpaid
|
||||||
|
|
||||||
|
`ResumeBillAction` 判断恢复到哪个状态:
|
||||||
|
|
||||||
|
| 挂起前的状态 | 恢复后的状态 |
|
||||||
|
|---|---|
|
||||||
|
| Unpaid(无付款)→ Suspended | **Unpaid** |
|
||||||
|
| Partial(部分付)→ Suspended | **Partial** |
|
||||||
|
|
||||||
|
代码层用 `paid_amount > 0` 判断。这样恢复后业户的"已付部分"还在账户上。
|
||||||
|
|
||||||
|
## 多次"挂起-恢复"的历史记录
|
||||||
|
|
||||||
|
如果同一账单被多次挂起 / 恢复:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Bill.meta 推荐结构(看实现是否如此)
|
||||||
|
{
|
||||||
|
"suspend_reason": "最近一次挂起原因",
|
||||||
|
"suspended_at": "最近一次挂起时间",
|
||||||
|
"resume_reason": "最近一次恢复原因",
|
||||||
|
"resumed_at": "最近一次恢复时间",
|
||||||
|
"suspend_history": [
|
||||||
|
{
|
||||||
|
"suspended_at": "...",
|
||||||
|
"suspended_by": "...",
|
||||||
|
"suspend_reason": "...",
|
||||||
|
"resumed_at": "...",
|
||||||
|
"resumed_by": "...",
|
||||||
|
"resume_reason": "..."
|
||||||
|
},
|
||||||
|
{ ...第二次... },
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
第一次"挂-恢复"完整存进 `suspend_history` 数组,新一次的"挂"覆盖 `suspend_reason`/`suspended_at`。完整审计可追溯。
|
||||||
|
|
||||||
|
> [!info] 实施细节
|
||||||
|
> 当前 `SuspendBillAction` / `ResumeBillAction` 是否实现 `suspend_history` 数组看代码。简版实现可能只覆盖最近一次(无 history)。
|
||||||
|
|
||||||
|
## 业户视角
|
||||||
|
|
||||||
|
### 您会感受到什么
|
||||||
|
|
||||||
|
- 收到通知"您的账单已恢复,请尽快付款"
|
||||||
|
- 小程序"我的账单"看到状态:Suspended → Unpaid
|
||||||
|
- 后续付款流程同正常
|
||||||
|
|
||||||
|
### 业户配合
|
||||||
|
|
||||||
|
业户应:
|
||||||
|
|
||||||
|
- 立即付款(避免再次进入逾期)
|
||||||
|
- 若有付款困难,提前告诉物业(可能再次挂起 + 协商)
|
||||||
|
|
||||||
|
## 与其他模块的对比
|
||||||
|
|
||||||
|
| 模块 | 类似 Suspend / Resume |
|
||||||
|
|---|---|
|
||||||
|
| **billing(本)** | SuspendBillAction / ResumeBillAction |
|
||||||
|
| deposit | freeze / unfreeze(账户级,详见 [[../deposit/unfreeze-after-mediation]]) |
|
||||||
|
| prepaid | FreezeAccountAction / ReactivateAccountAction([[../prepaid/unfreeze-after-verification]]) |
|
||||||
|
| meter | 无(meter 用 decommission,不可恢复)|
|
||||||
|
|
||||||
|
**billing / deposit / prepaid 都有"挂起 / 恢复"的对偶设计** —— 这是金融类业务的通用模式。
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
> [!question] 恢复后业户又找不到了怎么办?
|
||||||
|
> 走 [[exception-overdue-bills|逾期催收]] → 多次催不到 → 再 [[suspend-bill|挂起]] 一次(reason 改"再次失联")。多次"挂-恢复"循环说明业户有问题,需法律 / 走绕监管路径。
|
||||||
|
|
||||||
|
> [!question] 恢复时账单期次已经过去很久(例如 5 月账单 11 月才恢复),还按原 due_at 算逾期吗?
|
||||||
|
> 看业务策略:
|
||||||
|
>
|
||||||
|
> - 严格:仍按原 due_at(5 月底 + 宽限期)→ 一恢复就是逾期(可能加滞纳金)
|
||||||
|
> - 宽松:挂起期间不算逾期 → 恢复时给新的宽限期(例如恢复后 + 7 天)
|
||||||
|
>
|
||||||
|
> 当前实施看 `OverdueBillsListWidget` 的判断逻辑。
|
||||||
|
|
||||||
|
> [!question] 误挂起立即恢复,activitylog 显示两条(suspended + resumed)对吗?
|
||||||
|
> 对。每次状态变化各一条 log。可在 reason 备注"误操作 + 立即恢复"。
|
||||||
|
|
||||||
|
> [!question] 恢复后能直接改金额吗?
|
||||||
|
> 看 Policy / EditBill。Unpaid 状态可能允许 Edit(改 amount)。Partial 状态(已付款部分)改金额复杂(原 paid_amount 怎么算)→ 不推荐改,改用"作废 + 重建"。
|
||||||
|
|
||||||
|
> [!question] 恢复操作需要审批吗?
|
||||||
|
> 当前**无审批流**(单签批操作)。业务上若需要审批(例如金额大),靠人员制度保障(高权限人员才操作)。
|
||||||
|
|
||||||
|
## 异常分支
|
||||||
|
|
||||||
|
- 误恢复(应作废)→ [[void-paid-bill]] 走作废路径
|
||||||
|
- 恢复后改金额 → 复杂,走作废 + 重建([[create-single-bill-manual]])
|
||||||
|
- 长期挂起最终决定作废 → [[void-paid-bill]]
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [[suspend-bill]]
|
||||||
|
- [[bill-six-state-machine]]
|
||||||
|
- [[void-paid-bill]]
|
||||||
|
- [[collect-payment-single]]
|
||||||
|
- [[exception-overdue-bills]]
|
||||||
|
- [[audit-activitylog-trace]]
|
||||||
|
- [[../deposit/unfreeze-after-mediation]](deposit 同类对比)
|
||||||
|
- [[../prepaid/unfreeze-after-verification]](prepaid 同类对比)
|
||||||
220
prop-acc/scenarios/billing/split-bill.md
Normal file
220
prop-acc/scenarios/billing/split-bill.md
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
---
|
||||||
|
title: prop-acc · billing · 场景 - 拆账单(SplitBillAction)
|
||||||
|
aliases:
|
||||||
|
- 拆账单
|
||||||
|
- 账单分摊
|
||||||
|
- SplitBillAction
|
||||||
|
- split-bill
|
||||||
|
- 场景-拆账单
|
||||||
|
tags:
|
||||||
|
- 场景
|
||||||
|
- prop-acc
|
||||||
|
- 账单
|
||||||
|
- 调整
|
||||||
|
audience:
|
||||||
|
- 业务人员
|
||||||
|
- 财务
|
||||||
|
status: 已发布
|
||||||
|
sub_feature: billing
|
||||||
|
last_review: 2026-05-26
|
||||||
|
code_version: 2026-05-22
|
||||||
|
---
|
||||||
|
|
||||||
|
# 场景:拆账单(SplitBillAction)
|
||||||
|
|
||||||
|
业户**多人共住一户**(房东 + 租户 / 合租),物业费 / 水电气**按比例分摊**到各自账户,业务人员走 `SplitBillAction` 把一张账单**拆成多张**。
|
||||||
|
|
||||||
|
## 典型情境
|
||||||
|
|
||||||
|
> [!example] 真实情境
|
||||||
|
> 12-3-501 房屋:
|
||||||
|
>
|
||||||
|
> - 房东陈先生(产权人)
|
||||||
|
> - 租户李先生(2026 年 1 月起租)
|
||||||
|
>
|
||||||
|
> 合同约定:**物业费房东付,水电气租户付**。但系统月初按"业户=陈先生"批量生成的:
|
||||||
|
>
|
||||||
|
> - 5 月物业费 ¥800(应给陈先生)
|
||||||
|
> - 5 月水费 ¥54(实际应租户付)
|
||||||
|
> - 5 月电费 ¥168(实际应租户付)
|
||||||
|
> - 5 月燃气 ¥30(实际应租户付)
|
||||||
|
>
|
||||||
|
> 业务人员王主管要把后 3 张账单**重新分给租户李先生**(从陈先生的账户拆出去)。
|
||||||
|
|
||||||
|
## 业务人员视角
|
||||||
|
|
||||||
|
### 第 1 步:确认拆单需求
|
||||||
|
|
||||||
|
业户来电话 / 合同约定:
|
||||||
|
|
||||||
|
- 房东本人来报备"水电气以后租户付"
|
||||||
|
- 看合同 / 租赁备案
|
||||||
|
- 确认租户身份(可能已在 `community_user_profiles` 注册)
|
||||||
|
|
||||||
|
### 第 2 步:逐张拆单
|
||||||
|
|
||||||
|
**(本场景默认逐张拆,不是一次 SplitAll)**
|
||||||
|
|
||||||
|
后台 → 账单 → 找到 5 月水费 Bill → 进 `ViewBill` → 点 `SplitBillAction`(标签"拆账单")。
|
||||||
|
|
||||||
|
> [!warning] 按钮可见性
|
||||||
|
> 看 `SplitBillAction` 守护(应该是 Unpaid + 业务权限)。Paid / Void 状态可能不允许拆(已付的钱难撤)。
|
||||||
|
|
||||||
|
### 第 3 步:Modal 填参数
|
||||||
|
|
||||||
|
| 字段 | 填什么 |
|
||||||
|
|---|---|
|
||||||
|
| **原账单** | 5 月水费 ¥54(陈先生)|
|
||||||
|
| **拆分方式** | 按比例 / 按金额 / 按目标业户 |
|
||||||
|
| **新业户** | 李先生(下拉选)|
|
||||||
|
| **拆给新业户的金额** | ¥54(全部 → 全转给李先生 = 业户更换;或部分 → 拆成两张)|
|
||||||
|
| **拆分原因** | 必填,如 "房东与租户合同约定:水费由租户付"|
|
||||||
|
|
||||||
|
### 第 4 步:提交
|
||||||
|
|
||||||
|
`SplitBillAction` 业务逻辑(看实现):
|
||||||
|
|
||||||
|
**模式 1:全转给新业户**(本场景常见)
|
||||||
|
|
||||||
|
```
|
||||||
|
- 原 Bill #X(陈先生):状态翻 Void(原账单作废)+ 标 split_to=新 Bill ID
|
||||||
|
- 新建 Bill #Y(李先生):amount=54, status=Unpaid
|
||||||
|
- 关联(meta 记拆分来源)
|
||||||
|
```
|
||||||
|
|
||||||
|
**模式 2:部分拆分**(罕见)
|
||||||
|
|
||||||
|
```
|
||||||
|
- 原 Bill #X(陈先生):amount 改为 30(原 54 - 拆出 24)
|
||||||
|
- 新建 Bill #Y(李先生):amount=24
|
||||||
|
- 两张同存
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!info] 实施细节看代码
|
||||||
|
> `SplitBillAction` 的具体行为(全转 vs 部分拆 vs 按比例)看 `packages/prop-acc/src/Actions/Bills/SplitBillAction.php`。本文按业务场景描述。
|
||||||
|
|
||||||
|
### 第 5 步:通知双方
|
||||||
|
|
||||||
|
- 陈先生:"5 月水费已转给租户李先生付,您不必付了"
|
||||||
|
- 李先生:"您 5 月水费 ¥54 新账单已生成,请于 6 月 15 日前付清"
|
||||||
|
|
||||||
|
### 第 6 步:重复对电费 / 燃气
|
||||||
|
|
||||||
|
逐张拆。3 张全拆完后:
|
||||||
|
|
||||||
|
- 陈先生只欠 5 月物业费 ¥800
|
||||||
|
- 李先生欠 5 月水费 + 电费 + 燃气 ¥252
|
||||||
|
|
||||||
|
## 系统流程(全转模式)
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant 业务
|
||||||
|
participant Filament
|
||||||
|
participant Action[SplitBillAction]
|
||||||
|
participant DB
|
||||||
|
participant Activity[activitylog]
|
||||||
|
|
||||||
|
业务->>Filament: ViewBill(陈先生水费)→ SplitBillAction
|
||||||
|
Filament->>Action: handle(bill=陈先生水费, mode=Full, target=李先生, reason)
|
||||||
|
Action->>Action: 校验 bill.status (Unpaid)
|
||||||
|
Action->>Action: 校验 target_resident 在同 community
|
||||||
|
|
||||||
|
Action->>DB: 开启事务
|
||||||
|
Action->>DB: 1. 原 Bill 翻 Void + meta.split_to=新 ID
|
||||||
|
Action->>DB: 2. 新建 Bill(target=李先生, amount=54, status=Unpaid, meta.split_from=原 ID)
|
||||||
|
Action->>Activity: log(event=split, properties)
|
||||||
|
Action->>DB: 提交
|
||||||
|
|
||||||
|
Filament-->>业务: 跳转新 Bill
|
||||||
|
```
|
||||||
|
|
||||||
|
## 业户视角
|
||||||
|
|
||||||
|
### 陈先生(原账单业户)
|
||||||
|
|
||||||
|
收到通知:
|
||||||
|
|
||||||
|
> 陈先生您好,您的 5 月水费 ¥54 已根据合同拆给租户李先生付。您原账单已作废,无需付款。
|
||||||
|
|
||||||
|
### 李先生(新账单业户)
|
||||||
|
|
||||||
|
收到通知:
|
||||||
|
|
||||||
|
> 李先生您好,您 5 月水费 ¥54 账单已生成(由 12-3-501 房屋的合同拆分而来),请于 6 月 15 日前付清。
|
||||||
|
|
||||||
|
## 拆单的几种业务场景
|
||||||
|
|
||||||
|
| 场景 | 频率 | 拆法 |
|
||||||
|
|---|---|---|
|
||||||
|
| **房东 / 租户合同分摊** | 中(本场景)| 按费用类型分,水电气给租户 |
|
||||||
|
| **多业主分摊**(共有产权)| 罕见 | 按比例 |
|
||||||
|
| **公摊费用追溯** | 中(本月才发现某费用应分摊)| 按户数 |
|
||||||
|
| **企业内部子公司分摊**(商铺)| 罕见 | 按合同 |
|
||||||
|
|
||||||
|
## 与"作废 + 重建"的对比
|
||||||
|
|
||||||
|
| 维度 | 拆账单(SplitBillAction)| 作废 + 手动重建 |
|
||||||
|
|---|---|---|
|
||||||
|
| 单一操作 | ✅(一个 Action 完成)| ❌(走 2-3 个 Action)|
|
||||||
|
| 关联追溯 | ✅(meta 标 split_from/to)| ❌(无系统关联)|
|
||||||
|
| 审计 | activitylog event=split | 多条 activitylog(void + create)|
|
||||||
|
| 灵活性 | 受 SplitBillAction 限制 | 更灵活(可改任意字段)|
|
||||||
|
| 推荐场景 | 简单拆分(全转 / 部分按金额)| 复杂拆分 / 拆完还要改其他字段 |
|
||||||
|
|
||||||
|
## 拆单后已付款的复杂情况
|
||||||
|
|
||||||
|
> [!warning] 已付款 Bill 拆单的复杂度
|
||||||
|
>
|
||||||
|
> 如果原账单**已付一部分**(Partial 状态):
|
||||||
|
>
|
||||||
|
> - 原账单作废 → 已付的部分怎么办?
|
||||||
|
> - 退还给原业户 → 走作废 + 退款([[void-paid-bill]] 类似)
|
||||||
|
> - 新账单上记 paid_amount? 不行(原业户付的钱不该算到新业户)
|
||||||
|
>
|
||||||
|
> 实施上 `SplitBillAction` 可能**不允许 Partial 状态拆**(只允许 Unpaid)。看具体实现。
|
||||||
|
>
|
||||||
|
> **推荐**:拆单前先处理已付款(作废 + 退款 / 退现金后)再拆。
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
> [!question] 拆给的业户不存在(系统里没注册)怎么办?
|
||||||
|
> 先建业户档案(community 模块的 `community_user_profiles`)→ 然后拆。
|
||||||
|
|
||||||
|
> [!question] 拆错了(应该拆给李先生,选成王先生)?
|
||||||
|
> 看实施:
|
||||||
|
> - 若新 Bill 仍 Unpaid → 走 [[delete-bill-unpaid|删除]] + 重拆
|
||||||
|
> - 若已 Paid → 麻烦,需走作废 + 退款 + 重拆
|
||||||
|
|
||||||
|
> [!question] 按比例自动拆所有账单(房东 50% / 租户 50% 物业费)?
|
||||||
|
> 当前 `SplitBillAction` 应是**逐张操作**。按比例批量拆需要业务方提需求 + 加 `BulkSplitBillsAction`(待实现)。
|
||||||
|
|
||||||
|
> [!question] 拆单的合规性?
|
||||||
|
> 物业要确保:
|
||||||
|
> - 业户合同明确分摊条款
|
||||||
|
> - 双方书面同意拆分
|
||||||
|
> - 物业内部审批留底
|
||||||
|
>
|
||||||
|
> 系统层面只管账单数据,合规由物业流程保障。
|
||||||
|
|
||||||
|
> [!question] 一张账单能拆成 3+ 份吗(多人合租 3 人均摊)?
|
||||||
|
> 看 SplitBillAction 设计。简单实现是**一次拆 2 份**(原账单 + 新账单)。要拆 3 份需 2 次操作:
|
||||||
|
>
|
||||||
|
> 1. 第 1 次:原账单(¥54)→ 拆出 ¥18(给租户 A),原账单剩 ¥36
|
||||||
|
> 2. 第 2 次:原账单(¥36)→ 拆出 ¥18(给租户 B),原账单剩 ¥18(给房东自己)
|
||||||
|
>
|
||||||
|
> 累加得到三份 ¥18 各自归一人。
|
||||||
|
|
||||||
|
## 异常分支
|
||||||
|
|
||||||
|
- 拆错了 → 删除 / 作废新 Bill,原 Bill 状态可能要恢复(看实施)
|
||||||
|
- 已付的拆 → 走 [[void-paid-bill]] 流程,复杂
|
||||||
|
- 简单"换业户"(不拆,只改账单的 resident_id)→ 可能用 Edit Bill 直接改(若 Policy 允许),但**强烈不推荐**(破坏追溯)→ 用拆单更标准
|
||||||
|
|
||||||
|
## 相关文档
|
||||||
|
|
||||||
|
- [[bill-six-state-machine]]
|
||||||
|
- [[delete-bill-unpaid]]
|
||||||
|
- [[void-paid-bill]]
|
||||||
|
- [[suspend-bill]]
|
||||||
|
- [[bill-vs-collection-order]]
|
||||||
241
prop-acc/scenarios/billing/suspend-bill.md
Normal file
241
prop-acc/scenarios/billing/suspend-bill.md
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
---
|
||||||
|
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]](类似的"暂停"模式对比)
|
||||||
Reference in New Issue
Block a user