--- title: prop-acc · billing · 账单六状态机 aliases: - 账单六状态机 - Bill 状态机 - BillStatus - 6 个账单状态 tags: - 概念 - prop-acc - 账单 - 状态机 - 核心概念 audience: - 业务人员 - 财务 - 架构师 status: 已发布 sub_feature: billing last_review: 2026-05-26 code_version: 2026-05-22 --- # 账单六状态机 `Bill` 是 prop-acc 状态机**最复杂**的子模块,有 **6 个状态**:**Unpaid / Partial / Paid / Suspended / Processing / Void**。比 deposit / prepaid / meter 的 3-4 态都细,反映账单从生成到关闭的全生命周期 + 异常处置路径。 ## 6 状态速查 | 状态 | 中文 | 何时进入 | 能做什么 | |---|---|---|---| | `Unpaid` | 未付 | 账单刚创建 | 收款 / 拆单 / 挂起 / 删 / 作废 | | `Partial` | 部分付 | 收到一部分钱(小于账单金额) | 继续收款 / 作废(已收的需退) | | `Paid` | 已付 | 收齐全部金额 | 作废(走退款) | | `Suspended` | 挂起 | 业务人员主动暂停(纠纷 / 失联) | 恢复 / 作废 | | `Processing` | 处理中 | 收款流程中(罕见,如银行扣款排队)| 等回调 | | `Void` | 作废 | 主动 void | 只读,审计可查 | ## 状态机图 ```mermaid stateDiagram-v2 [*] --> Unpaid : 创建 Unpaid --> Partial : 部分收款 Unpaid --> Paid : 全额收款 Unpaid --> Suspended : suspend(纠纷) Unpaid --> Void : void Unpaid --> [*] : 物理删除(无付款关联) Partial --> Partial : 继续收款(仍未付清) Partial --> Paid : 收齐 Partial --> Suspended : suspend Partial --> Void : void(已收部分需退) Paid --> Void : void(已收全额需退) Suspended --> Unpaid : resume(纠纷解决) Suspended --> Partial : resume(回到部分付状态,若之前有付款) Suspended --> Void : void Processing --> Paid : 收款回调成功 Processing --> Unpaid : 收款回调失败 Void --> [*] ``` ## 守护方法(模型层) `Bill` 模型上的辅助方法: | 方法 | 返回 true 的状态 | 用途 | |---|---|---| | `canBePaid()` | Unpaid / Partial | CollectPaymentAction 准入 | | `canBeIssued()` | (用于"发出账单"流程,看实现) | — | | `isVoid()` | Void | UI 灰化判断 | | `isPaid()` | Paid | 报表 / 收款率统计 | | `hasAnyPayment()` | 有任何 CollectionOrderBill / prepaid_transaction 关联 | 决定能否物理删 | | `canBeDeleted()` | Unpaid + 无任何付款关联 | DeleteAction 守护 | | `canBeVoided()` | 非 Paid 非 Void | VoidBillAction 守护 | > [!warning] `canBeVoided` 的微妙之处 > Paid 状态**也可以走 void**(已付全额作废 → 退款),但 `canBeVoided()` 返回 false。原因: > - 已付账单的作废需要**走退款流程**,不是单纯改状态 > - 简单的 `VoidBillAction` 只翻状态 + 留 meta,**不处理钱** > - 已付的作废应该走专门的"作废 + 退款"组合流程(类似 [[../meter/exception-readings-locked-after-bill|计量账单修正]]) > > 即:`canBeVoided()` 检查的是"能否走简单作废",不是"能否所有方式作废"。 ## 状态转换详解 ### Unpaid → Partial(部分收款) 业户付了 ¥300 给 ¥800 的账单: ``` - Bill.status: Unpaid → Partial - 建 CollectionOrderBill(allocated_amount=300) - 关联 CollectionOrder(actual_amount=+300) - Bill.paid_amount(若有此字段) = 300 - 余 500 待付 ``` ### Partial → Paid(收齐) 业户再付 ¥500: ``` - Bill.status: Partial → Paid - 建第 2 条 CollectionOrderBill(allocated_amount=500) - Bill.paid_amount = 800(=账单金额) - 收齐 ``` ### Unpaid → Suspended(挂起) 业户与物业纠纷 / 业户失联: ``` - Bill.status: Unpaid → Suspended - 业务人员走 SuspendBillAction - 记 suspend_reason + suspended_at(meta) - 后续 CollectPaymentAction 守护拒绝(canBePaid=false) ``` ### Suspended → Unpaid(恢复) 纠纷解决: ``` - Bill.status: Suspended → Unpaid(或回到 Partial,若之前有付款) - 业务人员走 ResumeBillAction - 记 resume_reason + resumed_at - 后续 CollectPaymentAction 重新可用 ``` ### Unpaid → Void(作废) 误开的账单,业户没付: ``` - Bill.status: Unpaid → Void - VoidBillAction 守护通过(canBeVoided=true) - 记 voided_reason + voided_at + voided_by - 业户无影响(没付钱) ``` ### Paid → Void(已付作废) 走专门的"作废 + 退款"流程(未来扩展): ``` - 当前 VoidBillAction **不允许**(canBeVoided=false for Paid) - 需要走类似 meter 的修正流程 - 后续若实施 RefundBillAction:作废 + 退款一次性 ``` ### Unpaid → 物理删除(误建立刻删) ``` - 状态层面消失(从数据库删除) - 走 DeleteAction - 守护:canBeDeleted = Unpaid + 无任何付款关联 - 留 activitylog ``` 详见 [[delete-vs-void-dual-track]]。 ## 业务人员视角 后台账户列表的状态列对应这 6 个值: - 🟢 **Unpaid**:绿色,可操作(收款 / 拆 / 挂起 / 删 / 作废) - 🟡 **Partial**:黄色,部分付,可继续收款 - ✅ **Paid**:绿色对号,已完成,无操作 - 🧊 **Suspended**:灰色雪花,挂起,只能恢复 / 作废 - ⏳ **Processing**:旋转图标,处理中(等回调) - ❌ **Void**:红色叉,已作废,只读 ## 业户视角 业户感受到的: | 状态 | 业户感知 | |---|---| | Unpaid | 收到"账单 ¥800 待付"通知 | | Partial | 看到"已付 ¥300,还差 ¥500"| | Paid | 收到"已付清"通知 + 收据 | | Suspended | (通常通知)"账单挂起,事由 XXX,请联系物业" | | Processing | 几乎不感知(过渡态)| | Void | 收到"账单已作废,理由 XXX" | ## 与其他模块状态机的对比 | 模块 | 状态数 | 主要状态 | |---|---|---| | deposit | 3 | Active / Frozen / Closed | | prepaid | 3 | Active / Frozen / Closed | | meter(`is_active`)| 2 | active / inactive | | **bill** | **6** | **Unpaid / Partial / Paid / Suspended / Processing / Void** | bill 的复杂度反映**收款全流程**:从应收到已收,中间有挂起、部分付、回调中等多种异常路径。 ## 历史:Policy 修复 > [!info] issue.md Q6 第一轮发现的漏洞 > 原 `BillPolicy` 几乎是空壳(只有 `getPermissionPrefix`),所有授权全靠 `AbstractPolicy` 默认(只查权限,**不查状态**)。 > > 修复后补 7 个方法:`update` / `delete` / `deleteAny` / `void` / `collect` / `suspend` / `resume`。每个方法都加了**状态守护**(canBeDeleted / canBeVoided 等)。 > > 详见 [[delete-vs-void-dual-track]] 和 [[smart-bulk-delete-design]]。 ## 相关文档 - [[bill-types-and-sources]] - [[bill-vs-collection-order]] - [[delete-vs-void-dual-track]] - [[smart-bulk-delete-design]] - [[collect-payment-single]] - [[suspend-bill]] - [[void-paid-bill]] - [[../meter/decommission-and-locking]](类似的"状态机+守护"对比)