vault backup: 2026-05-25 22:57:50

This commit is contained in:
Willie
2026-05-25 22:57:50 +08:00
parent af87159315
commit 754fcadaf4
2 changed files with 162 additions and 13 deletions

View File

@@ -13,12 +13,12 @@
"state": {
"type": "markdown",
"state": {
"file": "prop-acc/index.md",
"file": "prop-acc/scenarios/deposit/deposit-additional-topup.md",
"mode": "source",
"source": false
},
"icon": "lucide-file",
"title": "index"
"title": "deposit-additional-topup"
}
}
]
@@ -94,7 +94,7 @@
"state": {
"type": "backlink",
"state": {
"file": "prop-acc/index.md",
"file": "prop-acc/scenarios/deposit/deposit-additional-topup.md",
"collapseAll": false,
"extraContext": false,
"sortOrder": "alphabetical",
@@ -104,7 +104,7 @@
"unlinkedCollapsed": true
},
"icon": "links-coming-in",
"title": "Backlinks for index"
"title": "Backlinks for deposit-additional-topup"
}
},
{
@@ -113,12 +113,12 @@
"state": {
"type": "outgoing-link",
"state": {
"file": "prop-acc/index.md",
"file": "prop-acc/scenarios/deposit/deposit-additional-topup.md",
"linksCollapsed": false,
"unlinkedCollapsed": true
},
"icon": "links-going-out",
"title": "Outgoing links from index"
"title": "Outgoing links from deposit-additional-topup"
}
},
{
@@ -156,13 +156,13 @@
"state": {
"type": "outline",
"state": {
"file": "prop-acc/index.md",
"file": "prop-acc/scenarios/deposit/deposit-additional-topup.md",
"followCursor": false,
"showSearch": false,
"searchQuery": ""
},
"icon": "lucide-list",
"title": "Outline of index"
"title": "Outline of deposit-additional-topup"
}
},
{
@@ -180,7 +180,8 @@
}
],
"direction": "horizontal",
"width": 300
"width": 300,
"collapsed": true
},
"left-ribbon": {
"hiddenItems": {
@@ -196,9 +197,11 @@
},
"active": "b06ed69835363258",
"lastOpenFiles": [
"prop-acc/concepts/deposit/deposit-vs-adhoc-vs-prepaid.md",
"prop-acc/concepts/prepaid/prepaid-account-vs-transaction.md",
"prop-acc/concepts/prepaid",
"prop-acc/maps/deposit-knowledge-map.md",
"prop-acc/index.md",
"prop-acc/concepts/deposit/deposit-vs-adhoc-vs-prepaid.md",
"prop-acc/scenarios/deposit/audit-long-pending-accounts.md",
"prop-acc/scenarios/deposit/audit-monthly-deposit-balance.md",
"prop-acc/scenarios/deposit/exception-deposit-on-frozen.md",
@@ -222,7 +225,6 @@
"prop-acc/concepts/deposit/red-receipt-design.md",
"prop-acc/concepts/deposit/transaction-types.md",
"prop-acc/concepts/deposit/payer-types.md",
"prop-acc/concepts/deposit/account-state-machine.md",
"prop-acc/concepts/deposit",
"prop-acc/scenarios/adhoc",
"prop-acc/concepts/adhoc",
@@ -230,7 +232,6 @@
"resident-portal/reference",
"resident-portal/procedures",
"resident-portal/maps",
"resident-portal/glossary",
"resident-portal/features"
"resident-portal/glossary"
]
}

View File

@@ -0,0 +1,148 @@
---
title: prop-acc · prepaid · 预存款账户与流水
aliases:
- 预存款账户与流水
- PrepaidAccount 与 PrepaidTransaction
- 预存款的双对象模式
tags:
- 概念
- prop-acc
- 预存款
- 核心概念
audience:
- 业户
- 业务人员
status: 已发布
sub_feature: prepaid
last_review: 2026-05-25
code_version: 2026-05-22
---
# 预存款账户与流水
预存款模块底层是**账户**(`PrepaidAccount`)+ **流水**(`PrepaidTransaction`)的双对象模式 —— 与 [[deposit-account-vs-transaction|押金的账户+流水]]同构。但预存款多了两条**独有的约束**:
1. **一户一账**(每社区每业户**最多一个**预存款账户)
2. **零余额不自动关账**(账户随时可继续充值,不像押金清零就 Closed)
详细差异见 [[deposit-vs-adhoc-vs-prepaid]]。
## 为什么也用双对象
> [!info] 类比:超市充值卡
> - **账户** = 卡面上的"当前余额 ¥1,500"
> - **流水** = 每次充值 + 每次消费的明细列表
业户充一笔(deposit)、消费抵账单(consume)、退余额(refund)—— 每一笔都需要可追溯。账户只记**当前余额**和**状态**,流水记**每一笔变动**。
## 字段速查
### PrepaidAccount(账户)
| 字段 | 含义 |
|---|---|
| `id` | 账户 ID |
| `community_id` | 所属物业项目 |
| `community_user_profile_id` | **业户档案 ID(必填,与 community_id 组合唯一)** |
| **`balance`** | **当前余额** |
| `status` | 账户状态(详见 [[account-state-machine]]) |
| `opened_at` | 开户时间 |
| `meta` | JSON 扩展字段 |
> [!warning] 一户一账约束
> `(community_id, community_user_profile_id)` **数据库唯一约束**,保证同业户在同社区不会有两个预存款账户。开户时违反这条会抛 unique violation。详见 [[one-account-per-resident]]。
### PrepaidTransaction(流水)
| 字段 | 含义 |
|---|---|
| `id` | 流水 ID |
| `prepaid_account_id` | 归属账户 |
| `type` | 流水类型(详见 [[transaction-types]]) |
| `amount` | 本笔金额(正数;refund 也是正数,方向由 type 表达) |
| `balance_before` | 本笔之前余额 |
| `balance_after` | 本笔之后余额 |
| `related_collection_order_id` | 关联收款单(deposit / consume / refund 都关联) |
| **`related_bill_id`** | **(consume 独有)关联被抵扣的账单** |
| `memo` | 备注 |
| `operated_by` | 操作员 ID |
| 创建后**不可变** | 一旦生成只读 |
## 与 deposit 的关键差异
| 维度 | DepositAccount | PrepaidAccount |
|---|---|---|
| 缴款人 | 多种(`payer_type`:业主/租户/装修公司/...) | **只能是业户本人** |
| 每业户账户数 | 多个(按 fee_type、按 asset) | **1 个**(每社区每业户) |
| 关联对象 | `fee_type_id``asset_id` 可选 | 只关联 `community_user_profile_id` |
| 核心写入操作 | deposit / refund / forfeiture(3 种) | **deposit / consume / refund / adjustment(4 种,consume 是高频)** |
| Bill 关联 | ❌ | ✅(consume 时通过 `related_bill_id`) |
| 零余额行为 | 自动关账(`canBeClosed()` 触发) | **不关**,可继续充值 |
| ForceClose | ✅(Frozen + 有余额 → 关账) | ❌(一户一账纠纷罕见,不需要) |
## 两者的契约
与 deposit 完全一致:**账户.balance 必须等于流水按时间累加的净值**。
```php
$account->verifyBalance(); // bool
$account->getBalanceDifference(); // float
$account->calculateBalanceFromTransactions();
```
`canOperate()` 守护所有写入(deposit / consume / refund 都调):
```php
public function canOperate(): bool
{
return $this->status === PrepaidAccountStatus::Active;
}
```
Frozen / Closed 都不允许操作。这条由模型层兜底,即使 Action 类、Filament UI 全部绕过,模型方法 `deposit()` / `consume()` / `refund()` 内置 `canOperate()` 检查,任何调用方都跑不掉(详见 [[exception-refund-on-frozen]])。
## 资金流概览
```mermaid
flowchart LR
A[业户充值 5000] -->|deposit| B[PrepaidAccount<br/>balance=5000]
B -->|consume 抵账单 800| C[balance=4200<br/>Bill 状态翻 Paid]
C -->|consume 抵账单 1200| D[balance=3000]
D -->|refund 退余 3000| E[balance=0<br/>Active 仍可继续充值]
```
注意最后一步:**余额清零账户保持 Active**,不像 deposit 那样自动 Closed。理由见 [[account-state-machine]] "零余额不自动关账" 段。
## 业户视角
业户在小程序"我的预存款"看到:
```
账户余额:¥3,000.00 [+ 充值]
最近流水:
2026-05-15 -800.00 抵扣 物业费(5月)
2026-05-15 -1200.00 抵扣 水电费(5月)
2026-05-01 +5000.00 预存款充值
```
充值随时可做,余额可看,消费明细可追。
## 业务人员视角
后台 → 预存款 → 账户列表 → 找到业户的账户 → `ViewPrepaidAccount`。右侧"流水"标签是 `PrepaidTransaction` 列表(只读、按时间倒序)。
所有写入 Action(`DepositAction` / `ConsumeAction` / `RefundAction`):
- 同时写账户余额 + 流水 + CollectionOrder + Receipt(事务内)
- 任何只写一边的代码都是 bug
- 状态守护(`canOperate()`)在 Filament Action、Policy、模型方法三层都有
## 相关文档
- [[account-state-machine]]
- [[one-account-per-resident]]
- [[transaction-types]]
- [[consume-via-bill-collection-type]]
- [[deposit-vs-adhoc-vs-prepaid]]
- [[deposit-account-vs-transaction]]