6.3 KiB
title, aliases, tags, audience, status, sub_feature, last_review, code_version
| title | aliases | tags | audience | status | sub_feature | last_review | code_version | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| prop-acc · deposit · 场景 - 余额清零自动关闭 |
|
|
|
已发布 | deposit | 2026-05-25 | 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 模型上的相关方法:
public function canBeClosed(): bool
{
return $this->balance == 0 && $this->status !== DepositAccountStatus::Closed;
}
业务层(RefundFromDepositAccountAction / ForfeitFromDepositAccountAction)在事务最后会检查并自动调用 close():
// 伪代码
if ($account->canBeClosed()) {
$account->update(['status' => DepositAccountStatus::Closed]);
}
通知
后台日志记录"账户已自动关账",业户侧通过最后一张 Receipt 同时收到关账信息(Receipt 内容 + 短信)。
系统流程
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