--- 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)
+建红字流水
+全额退款 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]]