--- 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]]