270 lines
8.9 KiB
Markdown
270 lines
8.9 KiB
Markdown
---
|
|
title: prop-acc · billing · 场景 - 作废已付账单(走作废 + 退款)
|
|
aliases:
|
|
- 作废账单
|
|
- VoidBillAction
|
|
- void-paid-bill
|
|
- 作废加退款
|
|
- 场景-作废已付账单
|
|
tags:
|
|
- 场景
|
|
- prop-acc
|
|
- 账单
|
|
- 作废
|
|
audience:
|
|
- 业务人员
|
|
- 财务
|
|
status: 已发布
|
|
sub_feature: billing
|
|
last_review: 2026-05-26
|
|
code_version: 2026-05-22
|
|
---
|
|
|
|
# 场景:作废已付账单(走作废 + 退款)
|
|
|
|
业户**已付**的账单需要消除(误开账单业户付了 / 业户事后投诉成功 / 调解结果物业认错)。走**作废 + 退款**组合,留状态留审计 + 退还业户。
|
|
|
|
> [!warning] 当前实施状态
|
|
> `VoidBillAction` 本身**只翻状态 + 留 meta + 写 activitylog**(`canBeVoided=true` for **非 Paid**)。
|
|
>
|
|
> **Paid 状态 `canBeVoided=false`**,意味着 `VoidBillAction` 不直接处理 Paid 账单的作废。需要走**类似 meter 的修正流程**:
|
|
>
|
|
> - 当前手工 / tinker(运维操作)
|
|
> - 未来扩展 `VoidPaidBillAction` 自动化(包含退款 + 红字 CO 等)
|
|
>
|
|
> 详见 [[delete-vs-void-dual-track]]"`canBeVoided` 的微妙之处"段。
|
|
|
|
## 典型情境
|
|
|
|
### 情境 1:Partial 状态作废(系统支持)
|
|
|
|
> [!example] 真实情境
|
|
> 陈先生 5 月物业费 ¥800,部分付了 ¥300(状态 Partial)。后续物业承认服务有问题,**双方协议作废账单**,退 ¥300 给业户。
|
|
>
|
|
> 业务流程:
|
|
> 1. 走 `VoidBillAction`(Partial → Void)
|
|
> 2. **手工配套退款**(给陈先生退 ¥300 现金 / 微信)
|
|
> 3. (理想)系统自动建红字 CollectionOrder(待扩展)
|
|
|
|
### 情境 2:Paid 状态作废(当前需手工)
|
|
|
|
> [!example] 真实情境
|
|
> 张阿姨 5 月物业费 ¥800 已付清(Paid)。物业发现金额算错了(应该 ¥600),**多收 ¥200**。要全额作废 + 退还 ¥800 + 重新建一张 ¥600 账单。
|
|
>
|
|
> 当前流程(因 canBeVoided=false for Paid,VoidBillAction 不允许):
|
|
> 1. **运维 tinker 操作**:把 Bill 状态强制改为 Void + 记 meta
|
|
> 2. **手工建红字 CollectionOrder**(¥-800)+ Receipt(红字)
|
|
> 3. **物业线下退款** ¥800 给业户
|
|
> 4. **手工建新 Bill** ¥600
|
|
> 5. 业户付 ¥600
|
|
>
|
|
> 整个流程**复杂、易出错、无 UI**。issue.md Q6 未明确实施时间,标"待业务方明确"。
|
|
|
|
## 业务人员视角(Partial 作废)
|
|
|
|
### 第 1 步:确认作废
|
|
|
|
- 协议作废(业户与物业书面达成)
|
|
- 调解 / 司法判决物业方有责
|
|
- 业务上的特殊情况
|
|
|
|
### 第 2 步:打开账单
|
|
|
|
后台 → 账单 → 找到 Partial Bill → 进 `ViewBill`。
|
|
|
|
### 第 3 步:点击 `VoidBillAction`(标签"作废")
|
|
|
|
> [!warning] 按钮可见性
|
|
> 守护:`canBeVoided()` = 非 Paid 非 Void + `->authorize('void')`(`bill.void` 权限)。
|
|
|
|
Modal 表单:
|
|
|
|
| 字段 | 填什么 |
|
|
|---|---|
|
|
| **作废原因(reason)** | **必填**,如 "调解结果:物业服务质量有问题,账单作废 + 退还已付款" |
|
|
|
|
### 第 4 步:提交
|
|
|
|
`VoidBillAction` 业务逻辑(详见 [[delete-vs-void-dual-track]]):
|
|
|
|
```php
|
|
$bill->update([
|
|
'status' => BillStatus::Void,
|
|
'meta' => array_merge($bill->meta ?? [], [
|
|
'voided_reason' => $reason,
|
|
'voided_at' => now(),
|
|
'voided_by' => $user->id,
|
|
]),
|
|
]);
|
|
|
|
activity()->performedOn($bill)->causedBy($user)
|
|
->withProperties([
|
|
'reason' => $reason,
|
|
'from_status' => $bill->getOriginal('status'),
|
|
'to_status' => 'Void',
|
|
'bill_no' => $bill->bill_no,
|
|
'amount' => $bill->amount,
|
|
'paid_amount' => $bill->paid_amount, // 关键:有付款需要后续退
|
|
])
|
|
->event('voided')
|
|
->log('账单已作废');
|
|
```
|
|
|
|
### 第 5 步:手工退款(若 paid_amount > 0)
|
|
|
|
VoidBillAction **本身不退钱**。业务人员后续:
|
|
|
|
1. 看 activitylog 的 `paid_amount` 字段 = ¥300
|
|
2. 联系业户确认退款方式(微信 / 现金 / 银行转账)
|
|
3. 走线下退款(物业财务实际打钱)
|
|
4. **理想**:建红字 CollectionOrder(`actual_amount=-300`,`type=Bill`,关联到该 Bill)+ Receipt 红字(待扩展自动化)
|
|
|
|
> [!info] 当前简化做法
|
|
> Bill 作废后:
|
|
> - Bill 状态 = Void
|
|
> - CollectionOrderBill 关联**不动**(审计需要)
|
|
> - 业务人员**线下手工退款**给业户
|
|
> - 系统中没有红字 CO / 红字 Receipt(待 `VoidPaidBillAction` 自动化)
|
|
>
|
|
> 财务账面**临时不一致**(Bill 是 Void 但 CO 仍是 Completed),需事后人工对账修复。
|
|
>
|
|
> **业务方提需求时,优先级会上来。**
|
|
|
|
### 第 6 步:通知业户
|
|
|
|
> 陈先生您好,您的 5 月物业费账单已作废,已付的 ¥300 我们将退还您。请确认收款方式。
|
|
|
|
## 业务人员视角(Paid 作废,当前 tinker 流程)
|
|
|
|
详见上方"情境 2"。当前**没自动化**:
|
|
|
|
1. 评估业务必要性(确认无误后操作)
|
|
2. 联系运维 tinker:
|
|
```php
|
|
$bill->update([
|
|
'status' => BillStatus::Void,
|
|
'meta' => array_merge($bill->meta ?? [], [
|
|
'voided_reason' => $reason,
|
|
'voided_at' => now(),
|
|
'voided_by' => $user->id,
|
|
'manual_void' => true, // 标记是 tinker 手工作废
|
|
]),
|
|
]);
|
|
```
|
|
3. 走完整退款流程(线下退 + 系统记一笔红字 CO/Receipt 若可能)
|
|
4. 重建账单(走 [[create-single-bill-manual]])
|
|
5. 业户付新账单
|
|
|
|
## 系统流程
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant 业务
|
|
participant Filament
|
|
participant Action[VoidBillAction]
|
|
participant DB
|
|
participant Activity
|
|
|
|
业务->>Filament: ViewBill(Partial)→ VoidBillAction
|
|
Filament->>Action: handle(bill, reason, user)
|
|
Action->>Action: 校验 canBeVoided(非 Paid 非 Void)
|
|
Action->>DB: 1. bill.status = Void + meta
|
|
Action->>Activity: 2. log(event=voided, paid_amount=300)
|
|
Filament-->>业务: 成功
|
|
|
|
业务->>业务: 看 activitylog paid_amount=300
|
|
业务->>业户: 线下退款 + 通知
|
|
|
|
Note over Action: 注:不自动建红字 CO/Receipt
|
|
Note over Action: 待 VoidPaidBillAction 实现
|
|
```
|
|
|
|
## 业户视角
|
|
|
|
### 您会感受到什么
|
|
|
|
- 收到通知"您的账单 #XXX 已作废,理由 XXX"
|
|
- 已付的钱**未来几天**收到退款
|
|
- 收到收据备注(若系统支持)
|
|
- 小程序账单状态:Partial → Void
|
|
|
|
### 您要做什么
|
|
|
|
- 确认收款方式
|
|
- 接收退款(银行 / 微信 / 现金)
|
|
- 如有疑问联系物业
|
|
|
|
## 与 prop-acc 其他作废的对比
|
|
|
|
| 模块 | 作废 / void 机制 |
|
|
|---|---|
|
|
| **billing(本)** | `VoidBillAction`(非 Paid 非 Void)+ 手工退款配套(Paid 待扩展) |
|
|
| deposit | 无单独 void;用 [[../deposit/force-close-refund|ForceClose refund]] 等机制 |
|
|
| prepaid | 无 void;走 [[../prepaid/refund-partial-after-consume|refund]] |
|
|
| meter | 无 void(reading 不可改,见 [[../meter/exception-readings-locked-after-bill]])|
|
|
| adhoc | 走 [[../adhoc/cancel-amount-error-redo|VoidAction]](类似 billing 设计)|
|
|
|
|
bill 的 void 是**最完整的"账单作废"设计**,但 Paid 的作废**仍待补**(整个 prop-acc 通用问题:已收款的"反向"流程都不够成熟)。
|
|
|
|
## 已知限制(issue.md Q6 待补)
|
|
|
|
- **VoidBillAction 不处理 Paid 状态**:`canBeVoided=false`,需走专门流程(未实现)
|
|
- **作废后红字 CO + Receipt 自动生成**:未实现,需手工
|
|
- **退款金额自动算 / 自动建红字凭证**:未实现
|
|
- **BulkRefundBillsAction**(批量退款):未实现,issue.md 标优先级低
|
|
|
|
## 常见问题
|
|
|
|
> [!question] 作废后业户已付款怎么记账?
|
|
> 当前(简版):
|
|
> - Bill 状态 = Void
|
|
> - CollectionOrderBill 关联保留(`allocated_amount=300`)
|
|
> - 物业账面"已收 ¥300"(从 CO 角度)= 实际"应退给业户"
|
|
>
|
|
> 财务月度对账时需**人工识别这种情况**(账上有钱但 Bill 已 void = 待退款)。
|
|
>
|
|
> 未来自动化后:作废 + 同时建红字 CO(¥-300)抵消原 CO,账面归零。
|
|
|
|
> [!question] 作废能撤销吗?
|
|
> 不能(Void 是终态,详见 [[bill-six-state-machine]])。如需"恢复":新建一张同信息 Bill。
|
|
|
|
> [!question] 已付 + 已被预存款抵的 Bill 作废,怎么退到预存款?
|
|
> 自动化未实现。手工流程:
|
|
>
|
|
> 1. tinker 作废 Bill
|
|
> 2. 给业户预存款手工 deposit(走 [[../prepaid/deposit-additional-topup]] 把 ¥800 充回预存款)
|
|
> 3. 备注"账单作废退还"
|
|
>
|
|
> 系统层面:理想是自动反向(`PrepaidAccount::reverseConsume` 之类),未实现。
|
|
|
|
> [!question] activitylog 怎么查作废历史?
|
|
> ```sql
|
|
> SELECT * FROM activity_log
|
|
> WHERE event = 'voided'
|
|
> AND properties->>'$.bill_no' = ?
|
|
> ORDER BY created_at DESC;
|
|
> ```
|
|
|
|
> [!question] 业户对作废结果不满意?
|
|
> 已作废不可逆。业户:
|
|
> - 走司法 / 仲裁
|
|
> - 业务方重新协商(可能再建新账单)
|
|
|
|
## 异常分支
|
|
|
|
- Unpaid 无付款 → [[delete-bill-unpaid|物理删]] 更干净
|
|
- 批量作废 → [[bulk-delete-batch-mistake]] 选 DeleteAndVoid 模式
|
|
- 业户拒绝接受作废 → 协商 / 司法
|
|
- Paid 作废自动化 → 待业务方明确 + 实施 VoidPaidBillAction
|
|
|
|
## 相关文档
|
|
|
|
- [[delete-vs-void-dual-track]]
|
|
- [[smart-bulk-delete-design]]
|
|
- [[bill-six-state-machine]]
|
|
- [[delete-bill-unpaid]]
|
|
- [[bulk-delete-batch-mistake]]
|
|
- [[audit-activitylog-trace]]
|
|
- [[../meter/exception-readings-locked-after-bill]](类似 已落账修正)
|
|
- [[../adhoc/cancel-amount-error-redo]](adhoc 同类对比)
|