Files
uniprop-manual/prop-acc/scenarios/deposit/force-close-refund.md
2026-05-25 22:32:40 +08:00

7.6 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 · 场景 - 冻结状态强制全退并关账
强制全退关账
ForceClose refund
force-close-refund
场景-冻结强制退还关账
场景
prop-acc
保证金
强制关账
业务人员
已发布 deposit 2026-05-25 2026-05-22

场景:冻结状态强制全退并关账

账户处于 Frozen 状态、还有余额,但纠纷结果明确利向业户(损坏归责不在业户、调解结果全退、司法判决业户胜诉等)。物业用 ForceCloseActionrefund disposition,一步完成"解冻 + 全额退还 + 关账"

典型情境

[!example] 真实情境 陈先生家押金账户因墙面损坏归责争议被冻结。1 个月后第三方机构鉴定结论 —— 损坏与陈先生装修无关(是先前住户造成的)。物业认可结论,要把 ¥5,000 全额退给陈先生并关账户。

不能走普通 refund-full-no-damage,因为账户 Frozen 状态下 canWithdraw=false 守护会拦截。需要 ForceClose 走 refund disposition。

业户视角

您会感受到什么

  • 收到通知:"您的押金账户已结清,全额 ¥5,000 退还"
  • 收到红字收据:"装修保证金退还 ¥-5,000(强制关账退还,事由:鉴定结论无责)"
  • 银行 / 微信收到退款
  • 小程序"我的押金账户"显示 "🔒 已结清"

您要做什么

  • 配合提供退款渠道
  • 保管红字收据(税务凭证)

业务人员视角

第 1 步:确认全退判定

  • 第三方鉴定 / 司法判决 / 调解协议(书面)
  • 物业内部决定"按全退处理"

[!warning] 这是不可逆操作 ForceClose 提交后账户立即 Closed,无法撤销。务必在书面凭证就位后再操作。

第 2 步:打开 Frozen 账户

后台 → 保证金 → 账户列表 → 找 Frozen 账户(状态显示 🧊)→ 进 ViewDepositAccount

右上角状态管理组只有 UnfreezeActionForceCloseAction 可点。

第 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=refundmeta.force_closed_memo=...meta.force_closed_at=... 记审计字段
  7. 触发 CollectionOrderCompleted → Listener 建红字 Receipt

第 5 步:走线下退款 + 给收据

银行 / 微信退 ¥5,000;红字收据交业户。

系统流程

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 + 红字收据

状态转换

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)能改吗? 不能。DepositTransactionCollectionOrder 都不可变。如果选错:

  • 已实际退款给业户 → 找业户协商再缴回,系统记一笔 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

异常分支

相关文档