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

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 · 场景 - 余额清零自动关闭
押金账户自动关账
余额 0 自动 Closed
close-after-zero-balance
场景-押金账户自动关闭
场景
prop-acc
保证金
结清
业户
业务人员
已发布 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,自动statusActive 翻到 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)。但业务上不正常(开了账户没缴款),应:

[!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 字段

异常分支

相关文档