--- title: prop-acc · billing · 场景 - 恢复挂起的账单 aliases: - 恢复账单 - ResumeBillAction - 解除挂起 - resume-bill - 场景-恢复挂起账单 tags: - 场景 - prop-acc - 账单 - 调整 audience: - 业务人员 - 财务 status: 已发布 sub_feature: billing last_review: 2026-05-26 code_version: 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",右上角只有 `ResumeBillAction` 和 `VoidBillAction` 可点。 ### 第 3 步:点击 `ResumeBillAction`(标签"恢复") > [!warning] 按钮可见性 > 守护:`bill.status === Suspended` + Policy `->authorize('resume')`。 Modal 表单: | 字段 | 填什么 | |---|---| | **恢复原因(reason)** | 必填,如 "业户出差回来,主动付清"| ### 第 4 步:提交 `ResumeBillAction` 业务层逻辑: ```php 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-single]] 或 [[collect-payment-batch]]。 ## 系统流程 ```mermaid 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` 判断。这样恢复后业户的"已付部分"还在账户上。 ## 多次"挂起-恢复"的历史记录 如果同一账单被多次挂起 / 恢复: ```json // 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] 恢复操作需要审批吗? > 当前**无审批流**(单签批操作)。业务上若需要审批(例如金额大),靠人员制度保障(高权限人员才操作)。 ## 异常分支 - 误恢复(应作废)→ [[void-paid-bill]] 走作废路径 - 恢复后改金额 → 复杂,走作废 + 重建([[create-single-bill-manual]]) - 长期挂起最终决定作废 → [[void-paid-bill]] ## 相关文档 - [[suspend-bill]] - [[bill-six-state-machine]] - [[void-paid-bill]] - [[collect-payment-single]] - [[exception-overdue-bills]] - [[audit-activitylog-trace]] - [[../deposit/unfreeze-after-mediation]](deposit 同类对比) - [[../prepaid/unfreeze-after-verification]](prepaid 同类对比)