Files
2026-05-26 01:08:16 +08:00

8.1 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 · billing · 场景 - 恢复挂起的账单
恢复账单
ResumeBillAction
解除挂起
resume-bill
场景-恢复挂起账单
场景
prop-acc
账单
调整
业务人员
财务
已发布 billing 2026-05-26 2026-05-22

场景:恢复挂起的账单

suspend-bill 状态的账单,在纠纷解决 / 业户回来后恢复到 Unpaid / Partial,后续可正常收款。ResumeBillAction 对称于 SuspendBillAction。

典型情境

[!example] 真实情境(一):业户回来了 王先生(15-7-203)出国 3 个月后回来,到物业前台:"我去美国出差 3 个月,没顾上缴物业费,现在补"。

  • 王主管查看王先生账户 → 3 张挂起的物业费(Suspended)
  • ResumeBillAction 逐张恢复 → 状态 Suspended → Unpaid
  • 然后走 collect-payment-batch 一次性 ¥2,400 付清

[!example] 真实情境(二):纠纷解决 陈先生与物业 5 月物业费纠纷调解结果:物业有部分过错,协议金额 ¥600(而不是原 ¥800)。

  • 物业要做:
    • 走 ResumeBill(挂起 → Unpaid)
    • 改账单金额(Edit Bill,若 Policy 允许 update 字段)/ 或作废原账单 + 重建 ¥600 账单
    • 业户付 ¥600 → 走 collect-payment-single

业务人员视角

第 1 步:确认恢复场景

场景 后续
业户失联回来要付 恢复 → 收款
纠纷解决(物业胜) 恢复 → 收原金额
纠纷解决(妥协) 恢复 → 改金额(或作废+重建)
误挂起 恢复(reason = "误操作解除")

第 2 步:打开账单

后台 → 账单 → 过滤"状态=Suspended" → 找到目标账单 → 进 ViewBill

状态显示 "🧊 Suspended",右上角只有 ResumeBillActionVoidBillAction 可点。

第 3 步:点击 ResumeBillAction(标签"恢复")

[!warning] 按钮可见性 守护:bill.status === Suspended + Policy ->authorize('resume')

Modal 表单:

字段 填什么
恢复原因(reason) 必填,如 "业户出差回来,主动付清"

第 4 步:提交

ResumeBillAction 业务层逻辑:

class ResumeBillAction
{
    public function handle(Bill $bill, string $reason, User $user): void
    {
        if ($bill->status !== BillStatus::Suspended) {
            throw new RuntimeException("账单非 Suspended 状态,不可恢复");
        }

        // 智能恢复:有部分付 → Partial;无付款 → Unpaid
        $newStatus = $bill->paid_amount > 0
            ? BillStatus::Partial
            : BillStatus::Unpaid;

        $bill->update([
            'status' => $newStatus,
            'meta' => array_merge($bill->meta ?? [], [
                'resume_reason' => $reason,
                'resumed_at' => now(),
                'resumed_by' => $user->id,
                // 可选:把这次"挂起-恢复"完整记录追加到 suspend_history 数组
            ]),
        ]);

        activity()
            ->performedOn($bill)
            ->causedBy($user)
            ->withProperties([
                'reason' => $reason,
                'from_status' => BillStatus::Suspended->value,
                'to_status' => $newStatus->value,
                'bill_no' => $bill->bill_no,
            ])
            ->event('resumed')
            ->log('账单已恢复');
    }
}

第 5 步:通知业户(可选)

恢复后立即提示业户付款:

王先生,您的 3 张挂起账单已恢复(合计 ¥2,400),现在可以付清。

第 6 步:走收款

恢复后走 collect-payment-singlecollect-payment-batch

系统流程

sequenceDiagram
    participant 业户
    participant 业务
    participant Filament
    participant Action[ResumeBillAction]
    participant DB

    业户->>业务: 我要付挂起的账单
    业务->>Filament: ViewBill(Suspended)→ ResumeBillAction(modal, reason)
    Filament->>Action: handle(bill, reason, user)
    Action->>Action: 校验 status === Suspended
    Action->>Action: 判定恢复后状态(Unpaid 或 Partial)
    Action->>DB: 1. bill.status = Unpaid / Partial
    Action->>DB: 2. bill.meta.resume_reason / resumed_at / resumed_by
    Action->>Activity: 3. log(event=resumed)
    Filament-->>业务: 成功

    业务->>Filament: 继续走收款流程

智能恢复:Partial vs Unpaid

ResumeBillAction 判断恢复到哪个状态:

挂起前的状态 恢复后的状态
Unpaid(无付款)→ Suspended Unpaid
Partial(部分付)→ Suspended Partial

代码层用 paid_amount > 0 判断。这样恢复后业户的"已付部分"还在账户上。

多次"挂起-恢复"的历史记录

如果同一账单被多次挂起 / 恢复:

// Bill.meta 推荐结构(看实现是否如此)
{
  "suspend_reason": "最近一次挂起原因",
  "suspended_at": "最近一次挂起时间",
  "resume_reason": "最近一次恢复原因",
  "resumed_at": "最近一次恢复时间",
  "suspend_history": [
    {
      "suspended_at": "...",
      "suspended_by": "...",
      "suspend_reason": "...",
      "resumed_at": "...",
      "resumed_by": "...",
      "resume_reason": "..."
    },
    {  ...第二次... },
    ...
  ]
}

第一次"挂-恢复"完整存进 suspend_history 数组,新一次的"挂"覆盖 suspend_reason/suspended_at。完整审计可追溯。

[!info] 实施细节 当前 SuspendBillAction / ResumeBillAction 是否实现 suspend_history 数组看代码。简版实现可能只覆盖最近一次(无 history)。

业户视角

您会感受到什么

  • 收到通知"您的账单已恢复,请尽快付款"
  • 小程序"我的账单"看到状态:Suspended → Unpaid
  • 后续付款流程同正常

业户配合

业户应:

  • 立即付款(避免再次进入逾期)
  • 若有付款困难,提前告诉物业(可能再次挂起 + 协商)

与其他模块的对比

模块 类似 Suspend / Resume
billing(本) SuspendBillAction / ResumeBillAction
deposit freeze / unfreeze(账户级,详见 ../deposit/unfreeze-after-mediation)
prepaid FreezeAccountAction / ReactivateAccountAction(../prepaid/unfreeze-after-verification)
meter 无(meter 用 decommission,不可恢复)

billing / deposit / prepaid 都有"挂起 / 恢复"的对偶设计 —— 这是金融类业务的通用模式。

常见问题

[!question] 恢复后业户又找不到了怎么办? 走 exception-overdue-bills → 多次催不到 → 再 suspend-bill 一次(reason 改"再次失联")。多次"挂-恢复"循环说明业户有问题,需法律 / 走绕监管路径。

[!question] 恢复时账单期次已经过去很久(例如 5 月账单 11 月才恢复),还按原 due_at 算逾期吗? 看业务策略:

  • 严格:仍按原 due_at(5 月底 + 宽限期)→ 一恢复就是逾期(可能加滞纳金)
  • 宽松:挂起期间不算逾期 → 恢复时给新的宽限期(例如恢复后 + 7 天)

当前实施看 OverdueBillsListWidget 的判断逻辑。

[!question] 误挂起立即恢复,activitylog 显示两条(suspended + resumed)对吗? 对。每次状态变化各一条 log。可在 reason 备注"误操作 + 立即恢复"。

[!question] 恢复后能直接改金额吗? 看 Policy / EditBill。Unpaid 状态可能允许 Edit(改 amount)。Partial 状态(已付款部分)改金额复杂(原 paid_amount 怎么算)→ 不推荐改,改用"作废 + 重建"。

[!question] 恢复操作需要审批吗? 当前无审批流(单签批操作)。业务上若需要审批(例如金额大),靠人员制度保障(高权限人员才操作)。

异常分支

相关文档