Files
uniprop-manual/prop-acc/scenarios/billing/bulk-delete-batch-mistake.md
2026-05-26 01:13:17 +08:00

8.3 KiB
Raw Blame History

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 · 场景 - 批量误建,智能 Modal 三档分类清理
批量删除账单
BulkDeleteBillsAction 实战
智能批删
bulk-delete-batch-mistake
场景-批量删除账单
场景
prop-acc
账单
删除
批量
业务人员
财务
已发布 billing 2026-05-26 2026-05-22

场景:批量误建,智能 Modal 三档分类清理

业务人员误触发批量生成(例如点了一次"生成 5 月物业费"后忘了又点了一次,导致每户有两张同样账单),需要清理。走 BulkDeleteBillsAction 的智能 Modal,自动按状态分三档处理。

典型情境

[!example] 真实情境 王主管 5 月 1 日上午点了"生成 5 月物业费"批量(已生成 300 张)。下午接电话被打断,忘了之前已经生成,又点了一次 → 系统按 SkipExisting 策略全部跳过(因为已存在)。

:王主管 Modal 没看清,选了 Replace 策略 → 系统 作废原 300 张 + 重新生成 300 张 → 同业户有重复账单(状态 Void 的旧账单 + Unpaid 的新账单)。

业务人员发现后需清理 300 张 Void 状态的旧账单。

或者:

[!example] 真实情境(更典型) 业务人员手工建了 100 张维修分摊账单,分摊金额算错,需要全部清理重建。

业务人员视角

第 1 步:筛选要清理的账单

后台 → 账单 → 列表 → 用过滤(按期次 / 状态 / 费用类型 / 生成时间)选出要清理的批。

例:5 月物业费 + Void 状态 + 创建时间 today → 300 张 Void 旧账单。

第 2 步:选中

Table 上勾选这 300 张(全选 / 多选)。

第 3 步:触发批量删除

顶部 "批量删除" 按钮(BulkDeleteBillsAction)。

[!warning] 权限守护 需要 bill.bulkDelete 独立高敏权限(Policy::deleteAny)。普通业务人员可能没此权限,需主管 / 财务总监操作。

详见 smart-bulk-delete-design"权限设计"段。

第 4 步:智能 Modal(关键)

Modal 自动预检查所选账单 + 分三档:

批量删除账单 (选中 300 张)

预检查统计:
  ✅ 可删:     200 张  (Unpaid + 无付款关联)
  ⚠️ 需作废:   50 张   (Void 已经 / Partial 已经 = 状态)
  ❌ 跳过:      50 张   (Paid 已付 / 其他异常)

请选择处理模式:
  ⚪ 仅删除可删的(200 张物理删,100 张跳过)
  ⚫ 删可删 + 作废需作废的(200 删 + 50 作废,50 跳过)

批量操作原因(必填,审计留痕):
[多行输入框:
  5 月 1 日批量生成时误选 Replace 策略,
  导致 300 张旧账单 Void。现批量清理。
]

⚠️ 注意:
  - 物理删除不可恢复(但 activitylog 保留 bill_no)
  - 作废不可撤销
  - 跳过的账单不动

[取消]  [确认执行]

第 5 步:选模式 + 提交

业务人员看到统计,做决定:

业务效果
仅删可删的 200 物理删,100 暂留(包括 50 已作废的 + 50 跳过的)
删可删 + 作废需作废 200 物理删,50 翻 Void(变作废),50 跳过

填原因 → 提交。

第 6 步:看执行结果

系统返回:

批量操作完成:
- 物理删除:200 张
- 作废:50 张(若选 DeleteAndVoid)
- 跳过:50 张

详细 bill_no 见 activitylog(本次操作)。

系统流程

sequenceDiagram
    participant 业务
    participant Filament
    participant Modal
    participant Action[BulkDeleteBillsAction 业务层]
    participant Activity
    participant DB

    业务->>Filament: BillsList → 选 300 张 → 批量删除
    Filament->>Modal: 预检查(分类三档)
    Modal->>Filament: 显示 ✅200 ⚠50 ❌50

    业务->>Modal: 选模式 + 填原因 + 提交
    Modal->>Action: handle(bills, mode, reason, user)

    Action->>Action: 再次分类(防 UI 缓存过期)

    Action->>DB: 开启事务

    loop 200 可删
      Action->>DB: bill.delete()
    end

    alt mode = DeleteAndVoid
      loop 50 可作废
        Action->>Action: VoidBillAction.handle(bill, reason, user)
        Action->>Activity: log voided(单条)
      end
    end

    Action->>Activity: log bulk_deleted(总体 + affected_bill_nos 数组)
    Action->>DB: 提交

    Action-->>Filament: 结果 + 通知
    Filament-->>业务: 显示"200 删 / 50 作废 / 50 跳过"

activitylog 设计

详见 smart-bulk-delete-design"activitylog 设计"段。

单条 bulk_deleted log(批量操作只一条):

{
  "log_name": "default",
  "subject_type": null,
  "subject_id": null,
  "event": "bulk_deleted",
  "causer_id": 42,  // 王主管 ID
  "properties": {
    "mode": "DeleteAndVoid",
    "reason": "5 月 1 日批量生成时误选 Replace,300 张 Void 清理",
    "total_selected": 300,
    "deleted_count": 200,
    "voided_count": 50,
    "blocked_count": 50,
    "affected_bill_nos": [
      "B-202605-501-001 [DELETED]",
      "B-202605-502-001 [DELETED]",
      ...
      "B-202605-501-002 [VOIDED]",
      ...
      "B-202605-501-003 [SKIPPED]",
      ...
    ]
  }
}

affected_bill_nos 是数组,每条标记处理结果。事后可完整还原。

同时,每张被作废的 Bill 还各有一条 voided log(VoidBillAction 内部触发,subject=bill)。

业户视角

业户通常不感知:

  • 被物理删的 200 张:业户未付款,通常未通知 → 无感
  • 被作废的 50 张:业户可能收到通知(看物业策略),或无感
  • 跳过的 50 张:不动,不影响

理想:批量误建及时清理 → 业户全程无感 → 物业体面化解错误。

不理想:清理前业户已经收到推送 + 已付款 → 跳过(Paid 不可删 / 不可作废)→ 需要走单独的"已付作废 + 退款"流程(void-paid-bill)。

与单删 / 单作废的对比

| 维度 | delete-bill-unpaid | void-paid-bill | 智能批删(本) | |---|---|---|---| | 一次操作多少张 | 1 | 1 | 多张(N) | | 权限 | bill.delete | bill.void | bill.bulkDelete(独立高敏) | | 处理混合状态 | 无(只删 1 张)| 无 | 三档分类自动 | | activitylog | 1 条单条 | 1 条单条 | 1 条总体 + N 条单条 | | 业务人员效率 | 低(逐张)| 低 | 高(一次) | | 业务人员风险 | 低 | 低 | 中(影响面大) |

常见问题

[!question] 批删的"跳过"档具体包括哪些? canBeDeleted=false && canBeVoided=false:

  • Paid(已付清,无法走任何"消除"路径,需走专门退款流程)
  • Void(已经作废,无需重复)
  • Processing(罕见,处理中,等回调)

跳过的账单不动,业务人员需单独处理。

[!question] 实际执行时与 Modal 预检查不一致(其他人在中间改了)? Action 内部再次分类(防 UI 缓存过期),实际按最新状态分。可能与 Modal 显示统计略有差异(但极少)。

[!question] 批删后想撤销?

信息可从 activitylog 还原(bill_no / amount 等)。

[!question] 批删的影响面太大会有审批吗? 系统层面无审批流。靠权限控制(只主管 / 财务总监能批删)+ 必填原因(留审计)+ Modal 预检查统计(让操作者看到影响面后决策)。

业务上若需"超过 100 张需双签",需扩展加审批 workflow。

[!question] 批删失败怎么办? 事务内执行,某张失败 → 整批回滚。事务边界是全成功或全失败

例外:Action 实现可能优化为"个别失败不影响其他",但破坏原子性 → 通常不推荐。

[!question] activitylog 表太多 bulk_deleted 怎么管? 每次批删一条 log(总体 + affected_bill_nos)。即使 100 次批删 = 100 条 log。不会爆表

但 voided 的子 log(每张作废一条) = 大量。批删 50 张作废 = 50 条 voided log。归档策略详见 smart-bulk-delete-design

异常分支

相关文档