Files
uniprop-manual/prop-acc/concepts/prepaid/auto-deduction-design.md

199 lines
6.8 KiB
Markdown
Raw Permalink Normal View History

2026-05-25 23:02:51 +08:00
---
title: prop-acc · prepaid · 月初批量自动抵扣设计
aliases:
- 自动抵扣设计
- 月初批量抵账单
- auto-deduction-design
- 预存款自动消费 job
tags:
- 概念
- prop-acc
- 预存款
- 架构设计
- 待补
audience:
- 业务人员
- 架构师
status: 草稿
sub_feature: prepaid
last_review: 2026-05-25
code_version: 2026-05-22
---
# 月初批量自动抵扣设计(待补)
> [!warning] 本功能**代码未实现**
> 当前所有 consume 操作都是**业务人员手动触发**(在后台 `ConsumeAction`)。月初批量自动抵扣**是预存款产品的核心卖点之一**,但代码层暂未实现。本文档描述设计意图,等业务方明确需求后落地。
>
> issue.md Q4 "待补" 段记录:
> > 月初批量自动抵扣 scheduled job:扫所有 status=Active AND balance>0 的预存款账户,找各自社区/业户下未付账单,自动调用 ConsumeFromPrepaidAccountAction 抵扣(优先抵 due_at 最早的账单)。
## 为什么这个 job 重要
预存款的**产品价值主张**是:
> "业户预存一次,以后账单自动扣,不用月月手动操作"。
如果没有自动抵扣 job:
- 业户预存了 ¥5000,以为以后账单会自动扣
- 月底账单出来,**不会自动扣**,需要业务人员手动到每个账户上点 ConsumeAction
- 业户次月发现还欠物业费,余额还有 → 投诉
- 业务人员手动抵扣 100+ 户 → 工作量大,容易遗漏
**没有 job = 产品价值缺失 50%**。
## 设计意图(待实现)
### 1. 触发时机
- **月初**(每月 1 日 00:30,避开 0 点高峰)
- 或**账单生成后立即触发**(若账单生成本身也在月初,可串行)
### 2. 扫描范围
```sql
-- 候选预存款账户
SELECT id, community_id, community_user_profile_id, balance
FROM acc_prepaid_accounts
WHERE status = 'active'
AND balance > 0;
```
### 3. 对每个候选账户,找未付账单
```sql
-- 该业户未付账单(按到期日升序,先抵最早的)
SELECT id, amount, due_at, bill_type
FROM acc_bills
WHERE community_id = ? -- 与账户同社区(跨社区不抵)
AND resident_id = ? -- 业户档案
AND status = 'unpaid'
ORDER BY due_at ASC;
```
### 4. 按账户余额贪心抵扣
```python
# 伪代码
balance = account.balance
for bill in unpaid_bills:
if balance <= 0:
break
if balance >= bill.amount:
consume(account, bill, bill.amount) # 全额抵
balance -= bill.amount
else:
consume(account, bill, balance) # 部分抵(若 Bill 支持)
balance = 0
```
> [!info] 是否支持部分抵扣?
> 当前 `ConsumeFromPrepaidAccountAction` 看实现是按完整账单金额走的。**部分抵扣**(余额不够全付时抵一部分)需要 `Bill.recordPayment()` 支持部分支付才能实现。若不支持,余额不够时**跳过该账单**,等下月或业户补充其他方式付。
### 5. 复用既有 Action
```php
// 伪代码 — Job 内调用既有 Action 类
app(ConsumeFromPrepaidAccountAction::class)->handle(
account: $account,
bill: $bill,
amount: $consumeAmount,
);
```
**关键**:Job 不重新实现 consume 逻辑,直接调 `ConsumeFromPrepaidAccountAction` —— 与 Filament 手动触发**走同一份代码**。守护、事务、Receipt 生成全都自动复用。
### 6. 失败容忍
- 单笔抵扣失败(余额不够 / 账户 Frozen / 数据异常) → 跳过,继续下一个
- 整个 Job 失败 → 记录到 `failed_jobs` 表,可重跑
- 不允许"全部成功才提交" —— 部分账户的抵扣成功不应因其他账户失败回滚
### 7. 通知策略(设计待定)
| 业户场景 | 通知 |
|---|---|
| 余额充足,账单全抵 | 推送"5 月账单已自动抵扣,余额还有 ¥X" |
| 余额不够,部分抵 | 推送"已抵 ¥X,还差 ¥Y 请补缴" |
| 账户冻结无法抵 | 不主动通知(等业户自己看到欠费) |
## 与手动 ConsumeAction 的关系
| 维度 | 手动 ConsumeAction(已实现)| 自动 Job(待实现)|
|---|---|---|
| 触发 | 业务人员后台点击 | Scheduled job(crontab / Laravel Scheduler)|
| 单次范围 | 单个账户 + 单张账单 | 全社区所有账户 + 所有未付账单 |
| 业务上 | 个别情况(业户主动来抵)| 月度默认行为(产品价值核心)|
| 代码 | `Filament/Resources/.../Actions/ConsumeAction.php` + `Actions/Prepaids/ConsumeFromPrepaidAccountAction` | 待添加 `Console/Commands/PrepaidAutoDeductionCommand.php`(或类似) |
| 复用业务 Action | ✅ 直接调 ConsumeFromPrepaidAccountAction | ✅ 同上 |
二者**共用业务层 Action**,只是触发方式不同。这是为什么 issue.md Q4 强调"未来批量自动抵扣 job 可直接复用此 Action"。
## 数据流(自动抵扣场景)
```mermaid
sequenceDiagram
participant Scheduler
participant Job
participant Account[PrepaidAccount]
participant Bill[Bill]
participant Consume[ConsumeFromPrepaidAccountAction]
participant 数据库
Note over Scheduler: 每月 1 日 00:30
Scheduler->>Job: dispatch PrepaidAutoDeductionJob
Job->>数据库: SELECT 全部 Active 余额>0 的预存款账户
数据库-->>Job: [account1, account2, ..., accountN]
loop 对每个 account
Job->>数据库: SELECT 该业户未付账单 ORDER BY due_at
数据库-->>Job: [bill1, bill2, ...]
loop 对每张账单
alt 余额够付
Job->>Consume: handle(account, bill, full_amount)
Consume->>数据库: 建 CO + PrepaidTransaction + 更新 Bill
else 余额不够
Job->>Job: 跳过(或部分抵,看 Bill 支持)
end
end
end
Job->>Scheduler: 完成 + 报告(抵扣总额、失败数)
```
## 待讨论 / 决策
业务方拍板前,以下问题需明确:
| 问题 | 选项 |
|---|---|
| **触发频率** | 每月 1 次 / 每周 / 每天扫(更及时但更频繁)|
| **触发时点** | 月初固定时间 / 账单生成事件触发 / 业户手动充值后立即触发本户 |
| **优先级排序** | due_at 升序(最早的先) / amount 升序(小额先抵清) / 账单类型(物业费先 → 水电费后)|
| **部分抵扣** | 支持 / 不支持 / 取决于账单类型 |
| **失败通知** | 业户立即通知 / 月底汇总通知 / 仅后台告警 |
| **跨社区策略** | 跨社区一律不抵(已确认) / 跨社区可抵(需重新设计) |
| **运维监控** | 抵扣金额 / 失败率 / 跳过原因分布 |
| **回滚机制** | 抵错怎么办?(理论上事务保证,但业务上若抵了不该抵的账单需手工补正) |
## 关联场景
待 job 实现后,场景文档 [[consume-batch-auto-monthly]] 会描述:
- Job 执行的完整时序
- 业户/业务人员在何处感知结果
- 失败排查
- 业务人员的运维介入入口
## 相关文档
- [[transaction-types]]
- [[consume-via-bill-collection-type]]
- [[consume-monthly-property-bill]]
- [[consume-multiple-bills-priority]]
- [[consume-batch-auto-monthly]]
- [[one-account-per-resident]]