7.0 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 · 场景 - 物理删除未付账单(误建立刻删) |
|
|
|
已发布 | billing | 2026-05-26 | 2026-05-22 |
场景:物理删除未付账单(误建立刻删)
业务人员误建了账单(选错业户 / 写错金额 / 误触发批量生成),立即发现且业户还没付任何钱 → 走物理删除。canBeDeleted() 守护:Unpaid + 无付款关联。
典型情境
[!example] 真实情境 王主管月初手工建账单"5 月物业费 ¥800",填错业户 ID(应该是张阿姨,选成了陈先生)。提交后立刻发现错误。
- 账单状态:Unpaid
- 业户没付过任何钱
- 可走物理删除(走 delete-vs-void-dual-track 的 delete 路径)
- 再重新建一张正确的(选张阿姨)
业务人员视角
第 1 步:发现错误
| 发现时机 | 处置 |
|---|---|
| 立即(几秒钟内) | 物理删(本场景) |
| 几小时(可能业户已收到推送) | 仍可删(若 Unpaid + 无付款) |
| 几天(可能业户已付) | 不可删,走 [[void-paid-bill |
第 2 步:打开账单
后台 → 账单 → 找到误建账单 → 进 EditBill(或 ViewBill)。
第 3 步:点击 DeleteAction(标签"删除")
[!warning] 按钮可见性 守护:
canBeDeleted()= Unpaid + 无付款 +->authorize('delete')(bill.delete权限)。任何其他状态(Partial / Paid / Suspended / Void)或有任何付款关联 → 按钮灰化。
第 4 步:Modal 确认
确认删除账单 #B-202605-501-001?
⚠️ 此账单状态为 Unpaid 且无付款关联,可物理删除。
删除后无法恢复(但 activitylog 会保留记录)。
[取消] [确认删除]
第 5 步:提交
系统:
- 再次校验
canBeDeleted()(防 UI 缓存过期) - 物理删 Bill(从数据库消失)
- 写 activitylog(event=deleted,记 bill_no / amount / 操作员 / 时间)
[!info] activitylog 留什么 即使 Bill 物理删了,activitylog 表还有完整记录:
SELECT * FROM activity_log WHERE event = 'deleted' AND properties->>'$.bill_no' = 'B-202605-501-001';可看到"谁在什么时候删了什么账单"。详见 smart-bulk-delete-design"activitylog 设计"。
第 6 步:重新建正确账单(若需要)
走 create-single-bill-manual 流程,选正确业户。
系统流程
sequenceDiagram
participant 业务
participant Filament
participant Action[DeleteAction]
participant Bill
participant Activity
participant DB
业务->>Filament: EditBill → DeleteAction
Filament->>Action: handle(bill)
Action->>Bill: canBeDeleted()? Unpaid + 无付款 = true
Action->>Activity: log(event=deleted, properties={bill_no, amount, ...})
Action->>DB: 物理删 Bill
Action-->>Filament: 跳回 ListBills
业户视角
业户无感:
- 误建的账单(尤其几秒内删的)业户根本不知道它存在过
- 即使业户已经收到推送,删除后再次刷新看不到了
- 物业可选择给业户发"前述账单作废,系统误建"(可选,看业务策略)
最理想场景:业务人员发现 → 立即删 → 业户从未感知。
canBeDeleted 详解
Bill::canBeDeleted():
public function canBeDeleted(): bool
{
return $this->status === BillStatus::Unpaid
&& ! $this->hasAnyPayment();
}
hasAnyPayment():
public function hasAnyPayment(): bool
{
return $this->collectionOrderBills()->exists()
|| $this->prepaidTransactionsRelatedToBill()->exists();
}
两条都为真(Unpaid + 无付款关联) → 可删。
[!info] 为什么 Partial 不能删? Partial 意味着业户已经付了一部分 → 有 CollectionOrderBill 关联 →
hasAnyPayment=true→ 拒绝物理删。业户付的钱不能"凭空消失"。Partial 状态要么继续收款 → Paid;要么走 void-paid-bill 流程。
与作废的对比
| 维度 | 删除(本场景) | void-paid-bill | |---|---|---| | 适用 | Unpaid + 无付款 | 非 Paid 非 Void(基本是 Partial / Suspended) | | 数据库 | 从数据库消失 | 留状态 = Void | | activitylog | 留(event=deleted)| 留(event=voided)| | 业户感知 | 通常无感(未付过)| 有(看到状态 Void) | | 可恢复 | ❌ 不可(数据真没了)| ❌ 不可(Void 终态)| | 适用场景 | 误建立刻删 | 任何已付 / 有付款 / 业户已知的 |
详见 delete-vs-void-dual-track 双轨制设计哲学。
批量删除(误建一批)
如果不是单张误建,而是误触发批量生成(例如点了一次"生成 5 月物业费"后忘了又点了一次):
→ 走 bulk-delete-batch-mistake,走 BulkDeleteBillsAction 的预检查 + 三档分类。
常见问题
[!question] 删了后悔了能恢复吗? 不能(数据真的没了)。只能重新建(走 create-single-bill-manual 或 create-periodic-property-fee)。
但所有信息可从 activitylog 还原(原 bill_no / amount / 业户 等),重建相对简单。
[!question] 业务人员误删大量账单怎么办? 单删一次一张,影响有限。批删才容易误删大量(详见 smart-bulk-delete-design 的预检查防护)。
单张误删的预防:
- Modal 确认(默认有)
- 思考"是否真要删"再点
[!question] 删除有审批吗? 当前无审批流(单签批操作)。但权限层有控制:
bill.delete是普通业务人员权限;bill.bulkDelete是高敏独立权限(详见 smart-bulk-delete-design)。
[!question] activitylog 表会爆掉吗? 长期看会(每次单删 + 批删 + 作废 + 收款都加几条)。需归档策略(详见 smart-bulk-delete-design"常见问题"段)。
[!question] 删除影响 reading.bill_id 吗(若是计量账单)? 看 cascade 设计:
- 若 reading 的
bill_id是 FK + cascade SET NULL → 自动 nullify- 若没 cascade → reading.bill_id 仍指向已删 Bill(孤儿 FK,需修复)
当前实施看代码。预防:计量账单不轻易物理删,走作废更稳妥。
[!question] 已删账单的 activitylog 怎么用?
-- 某员工某天的全部 delete SELECT id, causer_id, properties->>'$.bill_no' AS bill_no, properties->>'$.amount' AS amount, created_at FROM activity_log WHERE event = 'deleted' AND causer_id = ? AND created_at BETWEEN ? AND ?;
异常分支
- 误删后想重建 → create-single-bill-manual
- 误删一批 → 走批删的反向(无,只能 1 张张重建)
- 不能删(有付款)→ void-paid-bill
- 批量误建 → bulk-delete-batch-mistake