vault backup: 2026-05-25 22:32:40
This commit is contained in:
165
prop-acc/scenarios/deposit/close-after-zero-balance.md
Normal file
165
prop-acc/scenarios/deposit/close-after-zero-balance.md
Normal file
@@ -0,0 +1,165 @@
|
||||
---
|
||||
title: prop-acc · deposit · 场景 - 余额清零自动关闭
|
||||
aliases:
|
||||
- 押金账户自动关账
|
||||
- 余额 0 自动 Closed
|
||||
- close-after-zero-balance
|
||||
- 场景-押金账户自动关闭
|
||||
tags:
|
||||
- 场景
|
||||
- prop-acc
|
||||
- 保证金
|
||||
- 结清
|
||||
audience:
|
||||
- 业户
|
||||
- 业务人员
|
||||
status: 已发布
|
||||
sub_feature: deposit
|
||||
last_review: 2026-05-25
|
||||
code_version: 2026-05-22
|
||||
---
|
||||
|
||||
# 场景:余额清零自动关闭
|
||||
|
||||
押金账户在**最后一笔操作**(退款 / 扣罚)使余额变 0 的瞬间,系统**自动**把账户状态翻为 `Closed`,无需手工再点"关账"。
|
||||
|
||||
## 典型情境
|
||||
|
||||
> [!example] 真实情境(三种触发)
|
||||
>
|
||||
> **情境 A**:张阿姨家装修无损坏,物业全额退 ¥5,000 → 余额 0 → 账户**自动 Closed**。
|
||||
>
|
||||
> **情境 B**:陈先生家装修部分损坏,先扣 ¥800,再退余下 ¥4,200 → 余额 0 → 账户**自动 Closed**。
|
||||
>
|
||||
> **情境 C**:刘先生违约严重,全部 ¥5,000 全扣无退 → 余额 0 → 账户**自动 Closed**。
|
||||
|
||||
## 业户视角
|
||||
|
||||
### 您会感受到什么
|
||||
|
||||
- 收到**最后一张红字收据**(可能是退款 / 也可能是扣罚)
|
||||
- 同时收到通知:"您的押金账户已结清,余额 0"
|
||||
- 小程序"我的押金账户"显示 "🔒 已结清"
|
||||
- 流水台账仍可查(只读),整张账户的历史可追溯
|
||||
|
||||
### 您要做什么
|
||||
|
||||
- 妥善保管所有收据(缴款蓝字 + 历次退款 / 扣罚红字)
|
||||
- **不要试图重启账户** —— 系统设计上不允许;新业务请联系物业开新账户
|
||||
- 如有任何关于过往流水的疑问 → 联系物业核对(凭收据 + 流水台账)
|
||||
|
||||
## 业务人员视角
|
||||
|
||||
### 关键认知:不需要手工关账
|
||||
|
||||
> [!info] 自动机制
|
||||
> 任何 `RefundAction` / `ForfeitureAction` 提交后,系统在事务尾部检查 `balance == 0`,**自动**把 `status` 从 `Active` 翻到 `Closed`。
|
||||
>
|
||||
> 你不需要(也不应该)在余额变 0 后还跑一次 `CloseAction` —— 那个按钮 UI 上会被守护 `canBeClosed()`(已是 Closed → false),即使强行调也会被 Policy 拦截。
|
||||
|
||||
### 触发链路
|
||||
|
||||
最常见的几条路径:
|
||||
|
||||
| 序号 | 场景 | 触发动作 | 关账方式 |
|
||||
|---|---|---|---|
|
||||
| 1 | 无损全退 | `RefundAction(全额)` | 自动 |
|
||||
| 2 | 部分扣罚 + 退余 | `RefundAction(剩余)` | 自动(退完瞬间)|
|
||||
| 3 | 全扣无退 | `ForfeitureAction(余额全)` | 自动(扣完瞬间)|
|
||||
| 4 | 多次扣罚直到清零 | 最后一笔 `ForfeitureAction` | 自动 |
|
||||
| 5 | 主动关账(罕见,见 [[close-manual-with-zero-balance]]) | `CloseAction` | 手工(账户余额本来就 0) |
|
||||
|
||||
### 实现细节(`canBeClosed` + 自动逻辑)
|
||||
|
||||
`DepositAccount` 模型上的相关方法:
|
||||
|
||||
```php
|
||||
public function canBeClosed(): bool
|
||||
{
|
||||
return $this->balance == 0 && $this->status !== DepositAccountStatus::Closed;
|
||||
}
|
||||
```
|
||||
|
||||
业务层(`RefundFromDepositAccountAction` / `ForfeitFromDepositAccountAction`)在事务最后会检查并自动调用 `close()`:
|
||||
|
||||
```php
|
||||
// 伪代码
|
||||
if ($account->canBeClosed()) {
|
||||
$account->update(['status' => DepositAccountStatus::Closed]);
|
||||
}
|
||||
```
|
||||
|
||||
### 通知
|
||||
|
||||
后台日志记录"账户已自动关账",业户侧通过最后一张 Receipt 同时收到关账信息(Receipt 内容 + 短信)。
|
||||
|
||||
## 系统流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant 财务
|
||||
participant Filament
|
||||
participant RefundFromDepositAccountAction
|
||||
participant 数据库
|
||||
|
||||
Note over 财务: 余额 4200,退最后 4200
|
||||
|
||||
财务->>Filament: RefundAction (4200)
|
||||
Filament->>RefundFromDepositAccountAction: handle(account, 4200, channel)
|
||||
RefundFromDepositAccountAction->>数据库: 开启事务
|
||||
RefundFromDepositAccountAction->>数据库: 建 CO(-4200) + Transaction(refund, 4200→0)
|
||||
RefundFromDepositAccountAction->>数据库: balance=0
|
||||
Note over RefundFromDepositAccountAction: 检查 canBeClosed() → balance==0 → true
|
||||
RefundFromDepositAccountAction->>数据库: 自动 status=Closed
|
||||
RefundFromDepositAccountAction->>数据库: 触发 CollectionOrderCompleted
|
||||
RefundFromDepositAccountAction->>数据库: 提交事务
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
> [!question] 余额 0 但 status 还是 Active 怎么办?
|
||||
> 这是 bug。可能原因:
|
||||
> - 业务 Action 没调 `canBeClosed()` 检查
|
||||
> - 事务回滚后状态不一致
|
||||
>
|
||||
> 修复:tinker 手工执行 `$account->update(['status' => DepositAccountStatus::Closed])`,然后修复 Action 代码。审计上可写一笔说明备注。
|
||||
|
||||
> [!question] 没有任何流水(从未缴款)的账户能关账吗?
|
||||
> 技术上可以(`balance == 0`)。但**业务上不正常**(开了账户没缴款),应:
|
||||
> - 查清原因(为什么开了不缴?录错?)
|
||||
> - 走 `CloseAction` 手工关([[close-manual-with-zero-balance]] 场景)
|
||||
> - 备注清楚"无缴款记录,关闭"
|
||||
|
||||
> [!question] Frozen 账户余额是 0,自动关账吗?
|
||||
> **不会**。Frozen 状态下不会自动关账,因为冻结期间任何状态变更都需要审慎处理。Frozen 且余额 0 → 先 [[unfreeze-after-mediation|解冻]] → 自动变 Active → 再走 `CloseAction` 手工关(或重启业务再继续操作)。
|
||||
|
||||
> [!question] 已关账户能"重新开张"继续用吗?
|
||||
> 不能。`canBeReopened()` 永远 false。**新业务开新账户**。详见 [[account-state-machine]] "为什么不允许 Reopen" 段。
|
||||
|
||||
> [!question] 关账时业户能查看流水吗?
|
||||
> 能。Closed 账户**只读模式**保留全部历史 —— 流水台账、所有 Receipt、状态变更记录(`meta`)都可查询。这是账户的"历史档案",随时可调出对账。
|
||||
|
||||
## 与 ForceClose 的区别
|
||||
|
||||
| 维度 | 本场景(自动关账) | [[force-close-refund]] 等 |
|
||||
|---|---|---|
|
||||
| 触发条件 | balance==0 时的最后一笔 Refund / Forfeiture | 账户 Frozen + 有余额,但要终结此账户 |
|
||||
| 状态前置 | Active(Frozen 不会自动) | Frozen + hasBalance |
|
||||
| Action | 任何减余额到 0 的 Action | 专用 `ForceCloseAction` |
|
||||
| 资金处置 | 已有的 Refund / Forfeiture 决定 | 由 disposition 选(refund / forfeit / retain) |
|
||||
| 关账理由 | "余额清零正常关账" | 强制结清,有专门 audit 字段 |
|
||||
|
||||
## 异常分支
|
||||
|
||||
- 关账时业户提出反悔 → 不可逆;走线下沟通 / 司法
|
||||
- 余额非 0 想强制关 → 走 [[force-close-refund]] / [[force-close-forfeit]] / [[force-close-retain]]
|
||||
- 主动关账(无业务往来)→ [[close-manual-with-zero-balance]]
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [[account-state-machine]]
|
||||
- [[refund-full-no-damage]]
|
||||
- [[refund-partial-after-forfeit]]
|
||||
- [[forfeit-damage-public-area]]
|
||||
- [[close-manual-with-zero-balance]]
|
||||
- [[force-close-refund]]
|
||||
142
prop-acc/scenarios/deposit/close-manual-with-zero-balance.md
Normal file
142
prop-acc/scenarios/deposit/close-manual-with-zero-balance.md
Normal file
@@ -0,0 +1,142 @@
|
||||
---
|
||||
title: prop-acc · deposit · 场景 - 主动关账(已无业务往来)
|
||||
aliases:
|
||||
- 主动关账
|
||||
- 手工关押金账户
|
||||
- close-manual-with-zero-balance
|
||||
- 场景-押金账户主动关账
|
||||
tags:
|
||||
- 场景
|
||||
- prop-acc
|
||||
- 保证金
|
||||
- 结清
|
||||
audience:
|
||||
- 业务人员
|
||||
status: 已发布
|
||||
sub_feature: deposit
|
||||
last_review: 2026-05-25
|
||||
code_version: 2026-05-22
|
||||
---
|
||||
|
||||
# 场景:主动关账(已无业务往来)
|
||||
|
||||
账户**余额本来就是 0**(从未缴款、或之前已通过其他流程退完),物业财务想把账户**主动归档**,走 `CloseAction` 手工关账。比 [[close-after-zero-balance|自动关账]] 罕见,但 UI 仍提供。
|
||||
|
||||
## 典型情境
|
||||
|
||||
> [!example] 真实情境
|
||||
> 一年前物业为某业户开了押金账户,但**业户最终没装修**(改主意了),账户始终余额 0、状态 Active。一年后清理账户列表时,物业财务想把这种"挂着但没用过"的账户关掉,避免长期占列。
|
||||
|
||||
## 业务人员视角(本场景**业户无感**)
|
||||
|
||||
> [!info] 业户视角
|
||||
> 业户从未缴款、也没动账户,**完全感受不到**这个操作。不会收到通知,也不会拿到收据(没有资金流水,Listener 不触发)。
|
||||
|
||||
### 适用前提
|
||||
|
||||
- 账户 `status = Active`
|
||||
- 账户 `balance == 0`
|
||||
- 业务上已确认**确实无后续业务**(联系业户确认或长期无动静)
|
||||
|
||||
### 第 1 步:打开账户
|
||||
|
||||
后台 → 保证金 → 账户列表 → 按"余额=0 + 状态=Active"过滤 → 找到目标账户。
|
||||
|
||||
### 第 2 步:点击 `CloseAction`(标签"关账")
|
||||
|
||||
右上角状态管理组。Modal 表单:
|
||||
|
||||
| 字段 | 填什么 |
|
||||
|---|---|
|
||||
| **关账事由(memo)** | 必填,如 "账户未缴款,业户已确认无装修计划" |
|
||||
|
||||
> [!warning] Policy 守护
|
||||
> `CloseAction` 调 `canBeClosed()`:
|
||||
> - `balance == 0` ✅
|
||||
> - `status !== Closed` ✅
|
||||
> 满足这两条即可关,**不要求 Active**(理论上 Frozen + balance==0 也行,但 Frozen 状态下通常先解冻再关)。
|
||||
|
||||
### 第 3 步:提交
|
||||
|
||||
系统调 `DepositAccount::close($memo)`,事务内:
|
||||
|
||||
1. 校验 `canBeClosed()`
|
||||
2. 更新 `status=Closed`
|
||||
3. 在 `meta.close_memo` 记关账事由
|
||||
4. 在 `meta.closed_at` 记关账时间
|
||||
|
||||
**不产生** `DepositTransaction`(没有资金流水)、**不产生** `CollectionOrder`、**不产生** `Receipt`。
|
||||
|
||||
### 第 4 步:无需通知业户
|
||||
|
||||
业务上无变化,业户无感知。如果业务上认为需要(例如告知业户"我们这边把账户归档了,以后要装修请重新申请开户"),走线下沟通,不走系统通知。
|
||||
|
||||
## 系统流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant 财务
|
||||
participant Filament
|
||||
participant 数据库
|
||||
|
||||
Note over 财务: 找到一年前开的空账户
|
||||
|
||||
财务->>Filament: ViewDepositAccount → CloseAction(modal, memo)
|
||||
Filament->>数据库: 校验 canBeClosed() (balance==0, status!=Closed) → true
|
||||
Filament->>数据库: 更新 status=Closed + meta.close_memo + meta.closed_at
|
||||
数据库-->>Filament: ok
|
||||
Filament-->>财务: 成功通知
|
||||
|
||||
Note over 数据库: 无 Transaction,无 CO,无 Receipt
|
||||
```
|
||||
|
||||
## 流水台账(本场景完整记录)
|
||||
|
||||
| 流水 | 说明 |
|
||||
|---|---|
|
||||
| (无)| 账户从开户到关账,无任何 DepositTransaction |
|
||||
|
||||
只有账户本身的 `status` 字段从 `Active` 变 `Closed`,`meta` 加几个审计字段。
|
||||
|
||||
## 常见问题
|
||||
|
||||
> [!question] 没有流水的账户也算有效记录吗?
|
||||
> 算。`DepositAccount` 本身是审计对象,记录了开户时间、缴款人信息、关账事由,具备完整的"开-关"生命周期文档,即使中间没有资金往来。
|
||||
|
||||
> [!question] 业务上常见这种"开了不缴"的账户吗?
|
||||
> 不应常见。如果一个物业项目积累了很多空账户,说明**开户流程**有问题(业户开始装修前就开账户,但有相当比例改主意)。建议改流程为"业户决定装修当天才开账户",降低空账户产生率。
|
||||
|
||||
> [!question] 主动关账后能反悔重开吗?
|
||||
> **不能**。`canBeReopened()` 永远 false。如果业户后来真的要装修,**开新账户**。
|
||||
|
||||
> [!question] 余额非 0 主动关账可以吗?
|
||||
> **不能**。`canBeClosed()` 要求 `balance==0`。如果账户有余额但要终结:
|
||||
> - Active + 有余额 → 走 [[refund-full-no-damage|退款]] 或 [[forfeit-damage-public-area|扣罚]] 清零后自动关
|
||||
> - Frozen + 有余额 → 走 [[force-close-refund]] / [[force-close-forfeit]] / [[force-close-retain]] 强制关账
|
||||
|
||||
> [!question] 主动关账和自动关账的区别?
|
||||
> 主要区别在**触发方式**和**业务背景**:
|
||||
>
|
||||
> | 维度 | 自动关账([[close-after-zero-balance]]) | 主动关账(本场景) |
|
||||
> |---|---|---|
|
||||
> | 触发 | 最后一笔 Refund / Forfeiture 使余额变 0 | 手工 `CloseAction` |
|
||||
> | 业务背景 | 有缴款也有退/扣的完整生命周期 | 通常无缴款,只是清理 |
|
||||
> | 流水台账 | 有 | 无 |
|
||||
> | 业户感知 | 收到最后一张红字收据 | 无感 |
|
||||
> | 频率 | 大量(每个正常退还都触发)| 罕见(清理用)|
|
||||
|
||||
> [!question] 批量关账(同时关多个空账户)有功能吗?
|
||||
> 当前没有。如果要清理 100+ 空账户,需要在 List 页加批量操作,或运维 tinker。
|
||||
|
||||
## 异常分支
|
||||
|
||||
- 余额非 0 想强制关 → [[force-close-refund]] / [[force-close-forfeit]] / [[force-close-retain]]
|
||||
- 已关账户想恢复使用 → 不可逆,开新账户
|
||||
- Frozen 账户余额 0 想关 → 先 [[unfreeze-after-mediation]] 再走本流程,或直接走 [[force-close-retain]]
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [[close-after-zero-balance]]
|
||||
- [[account-state-machine]]
|
||||
- [[deposit-first-time-renovation]]
|
||||
- [[audit-long-pending-accounts]]
|
||||
188
prop-acc/scenarios/deposit/force-close-refund.md
Normal file
188
prop-acc/scenarios/deposit/force-close-refund.md
Normal file
@@ -0,0 +1,188 @@
|
||||
---
|
||||
title: prop-acc · deposit · 场景 - 冻结状态强制全退并关账
|
||||
aliases:
|
||||
- 强制全退关账
|
||||
- ForceClose refund
|
||||
- force-close-refund
|
||||
- 场景-冻结强制退还关账
|
||||
tags:
|
||||
- 场景
|
||||
- prop-acc
|
||||
- 保证金
|
||||
- 强制关账
|
||||
audience:
|
||||
- 业务人员
|
||||
status: 已发布
|
||||
sub_feature: deposit
|
||||
last_review: 2026-05-25
|
||||
code_version: 2026-05-22
|
||||
---
|
||||
|
||||
# 场景:冻结状态强制全退并关账
|
||||
|
||||
账户处于 **Frozen** 状态、**还有余额**,但**纠纷结果明确利向业户**(损坏归责不在业户、调解结果全退、司法判决业户胜诉等)。物业用 `ForceCloseAction` 选 `refund` disposition,**一步完成"解冻 + 全额退还 + 关账"**。
|
||||
|
||||
## 典型情境
|
||||
|
||||
> [!example] 真实情境
|
||||
> 陈先生家押金账户因墙面损坏归责争议被冻结。1 个月后第三方机构鉴定结论 —— **损坏与陈先生装修无关**(是先前住户造成的)。物业认可结论,要把 ¥5,000 全额退给陈先生并关账户。
|
||||
>
|
||||
> 不能走普通 [[refund-full-no-damage|RefundAction]],因为账户 Frozen 状态下 `canWithdraw=false` 守护会拦截。需要 ForceClose 走 `refund` disposition。
|
||||
|
||||
## 业户视角
|
||||
|
||||
### 您会感受到什么
|
||||
|
||||
- 收到通知:"您的押金账户已结清,全额 ¥5,000 退还"
|
||||
- 收到**红字收据**:"装修保证金退还 ¥-5,000(强制关账退还,事由:鉴定结论无责)"
|
||||
- 银行 / 微信收到退款
|
||||
- 小程序"我的押金账户"显示 "🔒 已结清"
|
||||
|
||||
### 您要做什么
|
||||
|
||||
- 配合提供退款渠道
|
||||
- 保管红字收据(税务凭证)
|
||||
|
||||
## 业务人员视角
|
||||
|
||||
### 第 1 步:确认全退判定
|
||||
|
||||
- 第三方鉴定 / 司法判决 / 调解协议(书面)
|
||||
- 物业内部决定"按全退处理"
|
||||
|
||||
> [!warning] 这是不可逆操作
|
||||
> ForceClose 提交后账户立即 Closed,**无法撤销**。务必在书面凭证就位后再操作。
|
||||
|
||||
### 第 2 步:打开 Frozen 账户
|
||||
|
||||
后台 → 保证金 → 账户列表 → 找 Frozen 账户(状态显示 🧊)→ 进 `ViewDepositAccount`。
|
||||
|
||||
> 右上角状态管理组只有 `UnfreezeAction` 和 `ForceCloseAction` 可点。
|
||||
|
||||
### 第 3 步:点击 `ForceCloseAction`(标签"强制关账")
|
||||
|
||||
Modal 表单(关键是 **disposition** Radio):
|
||||
|
||||
| 字段 | 填什么 |
|
||||
|---|---|
|
||||
| **处置方式 (disposition)** | 选 ✅ **`refund`(退还)** |
|
||||
| 退款渠道(动态出现)| 选业户指定渠道 |
|
||||
| **关账事由(memo)** | 必填,如 "鉴定结论:墙面损坏与业户装修无关,全额退还" |
|
||||
|
||||
> [!info] 不复用 RefundFromDepositAccountAction
|
||||
> ForceClose 的 refund 逻辑**单独写**(`ForceCloseDepositAccountAction` ~230 行),不复用 `RefundFromDepositAccountAction`。理由:
|
||||
>
|
||||
> - 后者的 `canWithdraw` 守护刚好挡住 Frozen
|
||||
> - 如果复用,普通调用方意外绕过冻结检查,语义边界模糊
|
||||
> - 重复约 60 行核心代码换语义边界清晰,值得
|
||||
>
|
||||
> 这条权衡详见 issue.md Q3 "第二轮已落地"段。
|
||||
|
||||
> [!warning] Policy 守护
|
||||
> `DepositAccountPolicy::forceClose()`:
|
||||
> - `update` 权限
|
||||
> - `isFrozen() && hasBalance()`(只允许 Frozen + 有余额)
|
||||
>
|
||||
> Active 账户 / Closed 账户 / Frozen 但 balance=0 的账户,按钮都灰化。
|
||||
|
||||
### 第 4 步:提交
|
||||
|
||||
系统调 `ForceCloseDepositAccountAction(disposition=refund)`,事务内:
|
||||
|
||||
1. 校验 `isFrozen() && hasBalance()`
|
||||
2. 建 `CollectionOrder`(`actual_amount=-5000` 红字,`status=Completed`)
|
||||
3. 加 `DepositTransaction`(`type=refund`,`amount=5000`,`balance_before=5000`,`balance_after=0`,关联红字 CO)
|
||||
4. 更新 `balance=0`
|
||||
5. 直接 `status=Closed`(从 Frozen)
|
||||
6. 在 `meta.force_closed_disposition=refund`、`meta.force_closed_memo=...`、`meta.force_closed_at=...` 记审计字段
|
||||
7. 触发 `CollectionOrderCompleted` → Listener 建红字 Receipt
|
||||
|
||||
### 第 5 步:走线下退款 + 给收据
|
||||
|
||||
银行 / 微信退 ¥5,000;红字收据交业户。
|
||||
|
||||
## 系统流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant 业户
|
||||
participant 财务
|
||||
participant Filament
|
||||
participant ForceCloseDepositAccountAction
|
||||
participant 数据库
|
||||
participant 监听器
|
||||
|
||||
Note over 业户,财务: 账户 Frozen + balance=5000,鉴定结论全退
|
||||
|
||||
财务->>Filament: ViewDepositAccount → ForceCloseAction (disposition=refund)
|
||||
Filament->>ForceCloseDepositAccountAction: handle(account, disposition=refund, channel, memo)
|
||||
ForceCloseDepositAccountAction->>ForceCloseDepositAccountAction: isFrozen() && hasBalance()? yes
|
||||
ForceCloseDepositAccountAction->>数据库: 开启事务
|
||||
ForceCloseDepositAccountAction->>数据库: 1. 建 CO (-5000 红字, Completed)
|
||||
ForceCloseDepositAccountAction->>数据库: 2. 建 DepositTransaction (refund, 5000→0)
|
||||
ForceCloseDepositAccountAction->>数据库: 3. balance=0, status=Closed(直接 Frozen→Closed)
|
||||
ForceCloseDepositAccountAction->>数据库: 4. meta.force_closed_disposition=refund 等审计字段
|
||||
ForceCloseDepositAccountAction->>监听器: 5. 触发 CollectionOrderCompleted
|
||||
监听器->>数据库: 6. 建 Receipt (强制关账退还 ¥-5,000)
|
||||
ForceCloseDepositAccountAction->>数据库: 提交事务
|
||||
Filament-->>财务: 成功通知
|
||||
财务-->>业户: 银行/微信退 5000 + 红字收据
|
||||
```
|
||||
|
||||
## 状态转换
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Active
|
||||
Active --> Frozen : freeze() 纠纷
|
||||
Frozen --> Closed : ForceClose(refund)<br/>+建红字流水<br/>+全额退款
|
||||
Closed --> [*]
|
||||
```
|
||||
|
||||
ForceClose 是**唯一**能从 Frozen 直接到 Closed(不经过 Active)的路径。
|
||||
|
||||
## 常见问题
|
||||
|
||||
> [!question] 为什么不先解冻再退?
|
||||
> 可以走"解冻 + 普通 Refund"两步组合,效果一样。但 ForceClose 一步到位有几个好处:
|
||||
>
|
||||
> - **审计标记**:`meta.force_closed_*` 字段明确记录"这次关账是强制的、什么 disposition、什么事由",未来追溯一眼明白
|
||||
> - **避免中间态**:Active 状态下账户能被其他人误操作(再缴款 / 再扣罚),ForceClose 跳过这个窗口
|
||||
> - **业务语义清晰**:这次关账不是"正常退完关",是"特殊处置关",标志性强
|
||||
|
||||
> [!question] disposition 选错(原本应该 retain 选了 refund)能改吗?
|
||||
> 不能。`DepositTransaction` 和 `CollectionOrder` 都不可变。如果选错:
|
||||
> - 已实际退款给业户 → 找业户协商再缴回,系统记一笔 deposit;然后开新账户继续(原账户已 Closed 永久)
|
||||
> - 还没实际线下退款 → 系统记录已成事实但线下不操作,业务上需在审计备注解释"系统记退款实际未执行";然后在新账户上做正确处置
|
||||
>
|
||||
> **预防胜于补救**:Modal 表单提交前务必再三确认 disposition。
|
||||
|
||||
> [!question] 退款金额能改成部分吗?
|
||||
> **不能**。ForceClose 是"一步终结",退款金额自动 = 当前全部余额。如果需要部分退、部分扣、部分保留,**不应该用 ForceClose**,应该:
|
||||
> 1. 先 [[unfreeze-after-mediation|解冻]] 回 Active
|
||||
> 2. 按比例操作:部分 [[refund-full-no-damage|退]] + 部分 [[forfeit-damage-public-area|扣]]
|
||||
> 3. 余额清零时自动 Closed
|
||||
|
||||
> [!question] 业户拒绝接受 ForceClose refund 的结果怎么办?
|
||||
> 一旦 ForceClose 提交,账户 Closed 不可逆。**业户接受与否**只影响线下退款流程的执行:
|
||||
> - 业户接受 → 收到退款
|
||||
> - 业户拒收(罕见,觉得"应该是物业全责加罚款") → 走司法
|
||||
>
|
||||
> 系统层面的账面已平,后续纠纷与账户无关。
|
||||
|
||||
> [!question] ForceClose 后能反悔(Active 重启)吗?
|
||||
> 不能。Closed 永久。详见 [[account-state-machine]]。
|
||||
|
||||
## 异常分支
|
||||
|
||||
- 全扣不退 → [[force-close-forfeit]]
|
||||
- 资金保留待领 → [[force-close-retain]]
|
||||
- 业户没失联且配合 → 优先 [[unfreeze-after-mediation]] + 普通 refund(更标准)
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [[account-state-machine]]
|
||||
- [[freeze-during-dispute]]
|
||||
- [[force-close-forfeit]]
|
||||
- [[force-close-retain]]
|
||||
- [[red-receipt-design]]
|
||||
163
prop-acc/scenarios/deposit/freeze-during-dispute.md
Normal file
163
prop-acc/scenarios/deposit/freeze-during-dispute.md
Normal file
@@ -0,0 +1,163 @@
|
||||
---
|
||||
title: prop-acc · deposit · 场景 - 业户纠纷期间冻结账户
|
||||
aliases:
|
||||
- 冻结押金账户
|
||||
- 纠纷冻结
|
||||
- freeze-during-dispute
|
||||
- 场景-押金账户冻结
|
||||
tags:
|
||||
- 场景
|
||||
- prop-acc
|
||||
- 保证金
|
||||
- 冻结
|
||||
audience:
|
||||
- 业户
|
||||
- 业务人员
|
||||
status: 已发布
|
||||
sub_feature: deposit
|
||||
last_review: 2026-05-25
|
||||
code_version: 2026-05-22
|
||||
---
|
||||
|
||||
# 场景:业户纠纷期间冻结账户
|
||||
|
||||
业户和物业**就扣罚 / 退款 / 损坏归责发生纠纷**,沟通陷入僵局。物业先把账户**冻结**(任何资金动作禁止),等调解结果。
|
||||
|
||||
## 典型情境
|
||||
|
||||
> [!example] 真实情境
|
||||
> 陈先生家装修完了,物业验收发现公共走道墙面损坏要扣 ¥800。陈先生坚持"墙是装修前就坏的,跟我没关系,拒绝扣罚"。物业找施工方核实暂无定论,**预计要 1-2 周走调解**。这段时间不能让账户继续动 —— 不能让陈先生提退款,也不能强行扣罚。物业先冻结账户。
|
||||
|
||||
## 业户视角
|
||||
|
||||
### 您会感受到什么
|
||||
|
||||
- 想申请退押金 → 物业说"账户冻结中,等纠纷处理完才能退"
|
||||
- 微信小程序"我的押金账户"显示"⏸ 冻结"
|
||||
- 不会被强行扣钱(冻结期间扣罚也不允许)
|
||||
- 不会丢钱(余额冻结,只是不让动)
|
||||
- 收到通知:"您的押金账户已冻结,事由:XXX"
|
||||
|
||||
> [!info] 冻结不等于扣钱
|
||||
> 冻结只是**暂停所有资金动作**,余额仍是您的钱,等纠纷处理完会做正确处置(退 / 扣 / 部分扣余下退)。
|
||||
|
||||
### 您要做什么
|
||||
|
||||
- 配合物业调解(提供装修前后照片、施工合同等证据)
|
||||
- 不要单方面追讨(系统层面已挡住,纠纷不在系统层面解决)
|
||||
- 调解有结果后通知物业操作(走 [[unfreeze-after-mediation|解冻]] 或 [[force-close-refund|强制关账]] 等)
|
||||
|
||||
## 业务人员视角(物业财务 + 物业管理)
|
||||
|
||||
### 第 1 步:决定冻结
|
||||
|
||||
触发场景:
|
||||
|
||||
- 业户对扣罚归责有异议
|
||||
- 装修方与业户之间纠纷,押金归属不明
|
||||
- 涉及司法 / 仲裁,资金需保留待裁决
|
||||
- 业户失联但有未决业务(暂留 Active 不合适)
|
||||
|
||||
### 第 2 步:打开账户
|
||||
|
||||
后台 → 保证金 → 账户列表 → 找到陈先生账户(Active,balance=5000)→ 进入 `ViewDepositAccount`。
|
||||
|
||||
### 第 3 步:点击 `FreezeAction`(标签"冻结")
|
||||
|
||||
右上角状态管理组。Modal 表单:
|
||||
|
||||
| 字段 | 填什么 |
|
||||
|---|---|
|
||||
| **冻结事由(reason)** | 必填,如 "业户对墙面损坏归责有异议,等待第三方核查" |
|
||||
| **预计冻结时长** | 选填,如 "2 周" |
|
||||
|
||||
> [!warning] Policy 守护
|
||||
> `FreezeAction` 调 `DepositAccount::canBeFreezed()`:仅 Active 状态可冻。Frozen / Closed 状态下按钮灰化。
|
||||
|
||||
### 第 4 步:提交
|
||||
|
||||
系统调 `DepositAccount::freeze(reason)`,事务内:
|
||||
|
||||
1. 校验 `canBeFreezed()`(Active only)
|
||||
2. 更新 `status=Frozen`
|
||||
3. 在 `meta.freeze_reason` 记冻结事由(可选)
|
||||
4. 在 `meta.frozen_at` 记冻结时间
|
||||
|
||||
**注意:不产生 DepositTransaction**(冻结不是资金动作,只是状态变更)。
|
||||
|
||||
### 第 5 步:通知业户
|
||||
|
||||
后台 → 短信 / 微信通知陈先生"账户已冻结,事由 XXX,预计解冻时间 XXX"。
|
||||
|
||||
## 系统流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant 业户
|
||||
participant 财务
|
||||
participant Filament
|
||||
participant 数据库
|
||||
|
||||
Note over 业户,财务: 业户与物业就 800 扣罚分歧,调解中
|
||||
|
||||
财务->>Filament: ViewDepositAccount → FreezeAction(modal, reason)
|
||||
Filament->>数据库: 校验 canBeFreezed() (Active=true, ok)
|
||||
Filament->>数据库: 更新 status=Frozen + meta.freeze_reason
|
||||
数据库-->>Filament: ok
|
||||
Filament-->>财务: 成功通知
|
||||
|
||||
Note over 数据库: 冻结期间所有 deposit/refund/forfeit 调用都会被守护拦截
|
||||
```
|
||||
|
||||
## 冻结状态下的能力对照
|
||||
|
||||
| 操作 | Active 状态 | Frozen 状态 |
|
||||
|---|---|---|
|
||||
| `DepositAction`(缴款)| ✅ | ❌(`canDeposit=false`)|
|
||||
| `RefundAction`(退款)| ✅ | ❌(`canWithdraw=false`)|
|
||||
| `ForfeitureAction`(扣罚)| ✅ | ❌(`canWithdraw=false`)|
|
||||
| `FreezeAction`(冻结)| ✅ | ❌(已是 Frozen)|
|
||||
| `UnfreezeAction`(解冻)| ❌(已是 Active)| ✅ |
|
||||
| `CloseAction`(关账)| ✅(余额 0) | ❌(必须先解冻再关)|
|
||||
| `ForceCloseAction`(强制关账)| ❌ | ✅(`isFrozen && hasBalance`)|
|
||||
| 看账户 / 看流水 | ✅ | ✅(只读)|
|
||||
|
||||
> [!info] Frozen + 有余额 + 想关账 = 用 ForceClose
|
||||
> Frozen 状态下既不能退也不能扣,但万一就是要终结此账户(纠纷长期无果、业户失联多年、法律保留期满等),走 [[force-close-refund]] / [[force-close-forfeit]] / [[force-close-retain]]。详见 [[account-state-machine]]。
|
||||
|
||||
## 常见问题
|
||||
|
||||
> [!question] 冻结后能解冻吗?
|
||||
> 当然能,走 [[unfreeze-after-mediation]]。冻结是临时状态,设计上就是"暂停"。
|
||||
|
||||
> [!question] 冻结期间业户能查询余额吗?
|
||||
> 能。**只读**,业户看得到余额、流水台账、状态(Frozen)、冻结事由。
|
||||
|
||||
> [!question] 冻结期间业户能在小程序申请退款吗?
|
||||
> 看小程序设计。当前 Action 假设手工触发,业户的小程序申请只是一个**业务申请单**,系统不会自动执行退款。物业人工处理时仍会被 `canWithdraw=false` 守护拦截,自然走流程。
|
||||
|
||||
> [!question] 冻结理由必填吗?
|
||||
> 系统层面不强制(`reason` 参数可选)。**业务层面强烈推荐填**,审计追溯、调解复盘、与业户沟通都需要。
|
||||
|
||||
> [!question] 冻结时间长了系统会自动解冻吗?
|
||||
> **不会**。冻结是手工操作,解冻也是手工。如果业务上需要"30 天自动解冻"之类的策略,需要单独写定时任务,但不推荐 —— 调解周期不固定,自动解冻容易在错误的时机解开。
|
||||
|
||||
> [!question] 多次冻结会有多个冻结理由吗?
|
||||
> 当前只有 1 个 `meta.freeze_reason` 字段(后写覆盖前写)。如果想保留历史,可以在解冻时把当前 reason 抄进 `meta.freeze_history[]` 数组,再清空 `freeze_reason`。当前业务量未必需要,先简单实现。
|
||||
|
||||
> [!question] 冻结期间能改账户的其他字段吗?
|
||||
> 看 Policy。当前 Policy 允许 update(改 payer 信息、备注等),但不允许 delete。如果有更严的"冻结期间什么都不能改"需求,在 `DepositAccountPolicy::update()` 加 `!isFrozen()` 检查。
|
||||
|
||||
## 异常分支
|
||||
|
||||
- 误冻结 → [[unfreeze-after-mediation]] 解开(reason 改 "误操作解除")
|
||||
- 长期冻结无解 → [[force-close-refund]] / [[force-close-forfeit]] / [[force-close-retain]]
|
||||
- 冻结期间业户失联 → 留 Frozen 到法律保留期满 → [[force-close-retain]]
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [[account-state-machine]]
|
||||
- [[unfreeze-after-mediation]]
|
||||
- [[force-close-refund]]
|
||||
- [[exception-deposit-on-frozen]]
|
||||
- [[forfeit-damage-public-area]]
|
||||
170
prop-acc/scenarios/deposit/unfreeze-after-mediation.md
Normal file
170
prop-acc/scenarios/deposit/unfreeze-after-mediation.md
Normal file
@@ -0,0 +1,170 @@
|
||||
---
|
||||
title: prop-acc · deposit · 场景 - 调解结束后解冻
|
||||
aliases:
|
||||
- 解冻押金账户
|
||||
- 调解完成解冻
|
||||
- unfreeze-after-mediation
|
||||
- 场景-押金账户解冻
|
||||
tags:
|
||||
- 场景
|
||||
- prop-acc
|
||||
- 保证金
|
||||
- 冻结
|
||||
audience:
|
||||
- 业户
|
||||
- 业务人员
|
||||
status: 已发布
|
||||
sub_feature: deposit
|
||||
last_review: 2026-05-25
|
||||
code_version: 2026-05-22
|
||||
---
|
||||
|
||||
# 场景:调解结束后解冻
|
||||
|
||||
[[freeze-during-dispute|冻结]] 期间纠纷调解出结果,业户与物业达成一致,账户解冻、回到 Active,然后按调解结果执行后续(退 / 扣 / 部分扣余下退)。
|
||||
|
||||
## 典型情境
|
||||
|
||||
> [!example] 真实情境
|
||||
> 陈先生家押金账户因墙面损坏归责纠纷被冻结 2 周。期间物业找施工方核实,发现损坏**确实是装修期间造成的**(有施工照片为证)。陈先生最终同意:扣 ¥500(原本物业要扣 ¥800,部分妥协),剩余 ¥4,500 退还。
|
||||
>
|
||||
> 物业财务先解冻,再按 [[refund-partial-after-forfeit]] 走"扣 500 + 退 4500"两步。
|
||||
|
||||
## 业户视角
|
||||
|
||||
### 您会感受到什么
|
||||
|
||||
- 收到通知:"您的押金账户已解冻"
|
||||
- 小程序"我的押金账户"显示 "✅ Active"
|
||||
- 后续按调解结果走 —— 收到红字扣罚收据 + 退款 + 退款收据
|
||||
|
||||
### 您要做什么
|
||||
|
||||
- 配合提供退款渠道(银行卡 / 微信)
|
||||
- 接收红字收据并妥善保管
|
||||
- 任何后续意见在系统外沟通(账户已按调解结果处置,不可逆)
|
||||
|
||||
## 业务人员视角(物业财务)
|
||||
|
||||
### 第 1 步:确认调解结果有书面凭证
|
||||
|
||||
- 调解协议书(双方签字)
|
||||
- 微信 / 录音确认(辅助)
|
||||
|
||||
> [!warning] 没有凭证不要解冻
|
||||
> 解冻后立即可做扣 / 退,如果调解没书面凭证、业户事后反悔,物业很被动。
|
||||
|
||||
### 第 2 步:打开账户
|
||||
|
||||
后台 → 保证金 → 账户列表 → 找到 Frozen 账户 → 进入 `ViewDepositAccount`。
|
||||
|
||||
> 状态显示 "🧊 Frozen",右上角只有 `UnfreezeAction` 和 `ForceCloseAction` 可点(其他都灰)。
|
||||
|
||||
### 第 3 步:点击 `UnfreezeAction`(标签"解冻")
|
||||
|
||||
Modal 表单:
|
||||
|
||||
| 字段 | 填什么 |
|
||||
|---|---|
|
||||
| **解冻事由(reason)** | 必填,如 "调解协议:扣 ¥500,退 ¥4,500" |
|
||||
|
||||
> [!warning] Policy 守护
|
||||
> `UnfreezeAction` 调 `canBeUnfreezed()`:仅 Frozen 状态可解。Active / Closed 状态下按钮灰化。
|
||||
|
||||
### 第 4 步:提交
|
||||
|
||||
系统调 `DepositAccount::unfreeze(reason)`,事务内:
|
||||
|
||||
1. 校验 `canBeUnfreezed()`(Frozen only)
|
||||
2. 更新 `status=Active`
|
||||
3. 在 `meta.unfreeze_reason` 记解冻事由
|
||||
4. 在 `meta.unfrozen_at` 记解冻时间
|
||||
5. (可选)在 `meta.freeze_history[]` 追加这次冻结-解冻的完整记录
|
||||
|
||||
**不产生 DepositTransaction**(同冻结,只是状态变更)。
|
||||
|
||||
### 第 5 步:按调解结果做后续
|
||||
|
||||
- 扣 ¥500 → `ForfeitureAction`(详见 [[forfeit-damage-public-area]])
|
||||
- 退 ¥4,500 → `RefundAction`(详见 [[refund-full-no-damage]])
|
||||
- 余额清零自动 Closed
|
||||
|
||||
两步顺序:**先扣再退**,因为扣罚事由要清楚,退款是处理剩余款。
|
||||
|
||||
### 第 6 步:通知业户
|
||||
|
||||
短信 / 微信告知调解结果已执行 + 退款已到账 + 收据已发。
|
||||
|
||||
## 系统流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant 业户
|
||||
participant 调解
|
||||
participant 财务
|
||||
participant Filament
|
||||
participant 数据库
|
||||
|
||||
Note over 业户,财务: 冻结 2 周后调解出结果:扣 500 + 退 4500
|
||||
|
||||
调解-->>财务: 提供书面调解协议
|
||||
财务->>Filament: ViewDepositAccount → UnfreezeAction(modal, reason)
|
||||
Filament->>数据库: 校验 canBeUnfreezed() (Frozen=true, ok)
|
||||
Filament->>数据库: 更新 status=Active + meta.unfreeze_reason
|
||||
数据库-->>财务: 已解冻
|
||||
|
||||
财务->>Filament: ForfeitureAction(500, "墙面损坏调解结果")
|
||||
Filament->>数据库: 建 CO(-500) + Transaction(forfeiture, 5000→4500) + Receipt
|
||||
|
||||
财务->>Filament: RefundAction(4500, 业户银行卡)
|
||||
Filament->>数据库: 建 CO(-4500) + Transaction(refund, 4500→0) + Receipt
|
||||
数据库-->>数据库: balance=0, 自动 status=Closed
|
||||
|
||||
财务-->>业户: 2 张红字收据 + 退款到账
|
||||
```
|
||||
|
||||
## 流水台账(本场景完整记录)
|
||||
|
||||
| 流水 | type | amount | balance_before | balance_after | 备注 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | deposit | 5000 | 0 | 5000 | 首次缴款 |
|
||||
| 2 | forfeiture | 500 | 5000 | 4500 | 调解结果扣罚(墙面损坏)|
|
||||
| 3 | refund | 4500 | 4500 | 0 | 调解结果退还 |
|
||||
|
||||
`status` 变化:`Active → Frozen → Active → Closed`(自动)。
|
||||
|
||||
## 常见问题
|
||||
|
||||
> [!question] 解冻后能立即做扣罚 / 退款吗?
|
||||
> 能。解冻是事务内同步操作,提交后立即生效。后续 `ForfeitureAction` / `RefundAction` 可立即触发。
|
||||
|
||||
> [!question] 解冻后忘了执行后续(扣 / 退)怎么办?
|
||||
> 账户停留 Active 状态,余额仍是冻结前的数。可任何时候继续操作。**业务上不应该**长期停留 Active 不处理 —— 应在解冻后立即完成调解结果。
|
||||
|
||||
> [!question] 解冻后业户反悔调解结果怎么办?
|
||||
> 解冻 + 已执行扣 / 退 → 已执行的不可逆。如果业户反悔:
|
||||
> - 走司法程序(系统不参与)
|
||||
> - 物业可选择补退一笔(走 deposit 反向冲)以化解纠纷
|
||||
>
|
||||
> **预防**:解冻前务必拿到书面调解协议。
|
||||
|
||||
> [!question] 解冻和重新冻结(同一账户多次冻结)可以吗?
|
||||
> 可以。账户可以在 `Active ↔ Frozen` 之间多次来回。如果业务上常发生,`meta.freeze_history[]` 数组就是用来记历次冻结的事由和时间。
|
||||
|
||||
> [!question] 调解时间过长,业户失联怎么办?
|
||||
> 走 [[force-close-retain]] —— 不解冻,直接从 Frozen 强制关账并保留余额,等业户出现再处理。
|
||||
|
||||
## 异常分支
|
||||
|
||||
- 调解失败长期僵持 → [[force-close-refund]] / [[force-close-forfeit]] / [[force-close-retain]]
|
||||
- 业户失联 → [[force-close-retain]]
|
||||
- 误冻结(其实没纠纷)→ UnfreezeAction(reason "误操作解除")
|
||||
- 解冻后业户立即提退 → 走 [[refund-full-no-damage]]
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [[freeze-during-dispute]]
|
||||
- [[account-state-machine]]
|
||||
- [[forfeit-damage-public-area]]
|
||||
- [[refund-partial-after-forfeit]]
|
||||
- [[force-close-retain]]
|
||||
Reference in New Issue
Block a user