vault backup: 2026-05-25 23:02:51
This commit is contained in:
198
prop-acc/concepts/prepaid/auto-deduction-design.md
Normal file
198
prop-acc/concepts/prepaid/auto-deduction-design.md
Normal file
@@ -0,0 +1,198 @@
|
||||
---
|
||||
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]]
|
||||
Reference in New Issue
Block a user