166 lines
6.3 KiB
Markdown
166 lines
6.3 KiB
Markdown
|
|
---
|
||
|
|
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]]
|