Files
uniprop-manual/prop-acc/concepts/deposit/account-state-machine.md
2026-05-25 22:07:35 +08:00

4.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 · 押金账户状态机
押金账户状态机
DepositAccount 状态机
Active / Frozen / Closed
概念
prop-acc
保证金
状态机
业户
业务人员
已发布 deposit 2026-05-25 2026-05-22

押金账户状态机

押金账户三种状态:Active(在押) / Frozen(冻结) / Closed(已结清)

[!warning] 重要原则 一旦 Closed,永远 Closed。不允许重开。新业务一律开新账户。理由见本文末"为什么不允许 reopen"。

三状态速查

状态 中文 何时进入 能做什么
Active 在押 新账户首次缴款后 缴款 / 退款 / 扣罚 / 冻结 / 关账(余额 0)
Frozen 冻结 发生纠纷、内审等 看流水(只读)/ 解冻 / 强制关账
Closed 已结清 余额清零正常关账 OR ForceClose 看流水(只读),没有任何可写操作

状态机图

stateDiagram-v2
  [*] --> Active : 开户 + 首次缴款
  Active --> Active : 追加缴款 / 部分退款 / 扣罚
  Active --> Frozen : freeze() 纠纷/审计
  Frozen --> Active : unfreeze() 调解完成
  Active --> Closed : close() 余额=0
  Frozen --> Closed : forceClose() 强制结账
  Closed --> [*]

  note right of Frozen
    冻结期间禁止任何资金进出
    canDeposit = false
    canWithdraw = false
  end note

  note right of Closed
    永久终态
    canBeReopened = false
    新业务请开新账户
  end note

守护方法(代码层)

DepositAccount 模型上有一组 can*() 守护方法,所有写入 Action 必须先调用:

方法 返回 true 的状态 用途
canDeposit() Active only DepositAction 准入
canWithdraw() Active only RefundAction / ForfeitureAction 准入
canBeFreezed() Active FreezeAction 准入
canBeUnfreezed() Frozen UnfreezeAction 准入
canBeClosed() balance=0 且 ≠Closed CloseAction 准入
canBeReopened() 永远 false 占位,刻意禁止
canOperate() Active 复合判断:既不在 Frozen 也不在 Closed
hasBalance() balance>0 配合判定能否 Close / 是否需 ForceClose
isAvailable() Active UI 显示"可用"或灰化

[!info] 关键守护:Frozen 不允许任何资金动作 canDeposit()canWithdraw() 都只允许 Active,Frozen 一律拒绝。这条规则比直觉更严:

  • 原本曾允许 [Active, Frozen] 都能缴款(看着"反正多存钱不亏")
  • 但与"冻结 = 暂停所有交易"的语义矛盾
  • 真实风险:纠纷期间装修公司继续往受冻结账户灌钱 → 资金被困、责任更复杂

现在两个方向严格一致:Frozen = 完全冻结,只能解冻或 ForceClose。

业务人员视角

后台账户列表的"状态"列对应这三个值。

  • 看到 Active:绿色,可点开操作
  • 看到 Frozen:橙色,所有按钮变灰,只剩 Unfreeze / ForceClose
  • 看到 Closed:灰色,完全只读,只能看流水

业户视角

业户通常感受不到状态机,只感受到结果:

  • 余额能正常用 → Active
  • 申请退款被拒,前台告知"账户冻结中,等纠纷处理完才能动" → Frozen
  • 账户被关 → 收到一张红字收据 + 短信告知"您的押金账户已结清"

为什么不允许 Reopen

canBeReopened() 永远返回 false,是刻意的设计

假设允许 reopen 风险
Closed → Active 又开放 流水台账"已结清"的语义被破坏,审计难追责
业务方:"业户搬回来了就 reopen 老账户" 鼓励混账;两次业务关系应有清晰边界
系统级:"误操作 close 了能反悔" close 已经守护"余额 0",误操作不会丢钱;新业务开新账户即可

替代做法:业户搬回来续约,开新账户。旧账户保留作为历史台账,与"曾经的业务关系结束"语义一致。

异常路径:Frozen + 有余额 + 想关账?

矛盾:

  • Frozen 不允许 withdraw(包括退款 / 扣罚)
  • canBeClosed() 要求余额=0
  • 又不能 unfreeze 直接关

这种困境通过 ForceClose 解决,见 force-close-refund / force-close-forfeit / force-close-retain 三种 disposition。

ForceClose 是唯一能合法从 Frozen 直接到 Closed 的路径,通过专门的 DepositAccountPolicy::forceClose() 守护(update 权限 + isFrozen() && hasBalance())。

相关文档