Files
uniprop-manual/prop-acc/concepts/deposit/deposit-account-vs-transaction.md

126 lines
4.7 KiB
Markdown
Raw Normal View History

2026-05-25 22:07:35 +08:00
---
title: prop-acc · deposit · 押金账户与押金流水
aliases:
- 押金账户与押金流水
- DepositAccount 与 DepositTransaction
- 押金的双对象模式
tags:
- 概念
- prop-acc
- 保证金
- 核心概念
audience:
- 业户
- 业务人员
status: 已发布
sub_feature: deposit
last_review: 2026-05-25
code_version: 2026-05-22
---
# 押金账户与押金流水
保证金模块底层是**两个对象**配合的:**账户**(`DepositAccount`)只记**当前状态**,**流水**(`DepositTransaction`)记**每一笔变动**。
## 为什么要两个对象
> [!info] 类比:银行存折
> - **账户** = 存折封面那一行"当前余额 ¥5,000"
> - **流水** = 翻开存折每一页:几号存了多少、几号取了多少、每笔的"前余额→后余额"
如果只有账户没有流水,查到余额是 ¥3,000 你不知道**钱从哪里来的、被扣了什么**。审计、业户对账、纠纷复盘全都失据。
如果只有流水没有账户,每次想知道当前余额都要重新累加全部历史,慢且容易脏。
所以两个都要:**账户给你"现在长什么样"**,**流水给你"如何变成现在这样"**。
## 字段速查
### DepositAccount(账户)
| 字段 | 含义 |
|---|---|
| `id` | 账户 ID |
| `community_id` | 所属物业项目 |
| `fee_type_id` | 押金种类(装修保证金 / 入驻押金 / ...) |
| `payer_type` | 缴款人类型(详见 [[payer-types]]) |
| `payer_name` | 缴款人姓名(冗余存,免关联 user 表) |
| `payer_contact` | 缴款人联系方式 |
| `community_user_profile_id` | 业户档案 ID(若缴款人是平台业户) |
| `asset_id` | 关联房屋单元(若与具体房屋相关) |
| **`balance`** | **当前余额(单一事实来源)** |
| `status` | 账户状态(详见 [[account-state-machine]]) |
| `opened_at` | 开户时间 |
| `meta` | JSON 扩展字段(`force_closed_*` 等审计标记) |
### DepositTransaction(流水)
| 字段 | 含义 |
|---|---|
| `id` | 流水 ID |
| `deposit_account_id` | 归属账户 |
| `type` | 流水类型(详见 [[transaction-types]]) |
| `amount` | 本笔金额(正数;退款/扣罚也是正数,方向由 type 表达) |
| `balance_before` | 本笔之前账户余额 |
| `balance_after` | 本笔之后账户余额 |
| `related_collection_order_id` | 关联收款单(deposit / refund / forfeiture 都关联) |
| `memo` | 备注 |
| `operated_by` | 操作员 ID |
| 创建后**不可变** | 一旦生成就只读,任何"撤销"都建新流水反向冲 |
## 两者的契约
**账户.balance 必须等于流水按时间累加的净值**。
```php
$account->verifyBalance(); // bool,看是否一致
$account->getBalanceDifference(); // float,差额(0 才对)
$account->calculateBalanceFromTransactions(); // 现场重算
```
这是审计的核心校验。日常运行中两者必须一致;若出现不一致只可能是 bug 或人为绕过(已通过 [[account-state-machine]] 守护和 Policy 双重防御)。
## 与一次性收费的 CollectionOrder + Receipt 关系
保证金的每笔流水**也会建一张 CollectionOrder 和 Receipt**(详见 [[collection-order-and-receipt]]),退款 / 扣罚走红字 CollectionOrder(详见 [[red-receipt-design]])。
```mermaid
flowchart LR
A[业户缴款 5000] --> B[DepositTransaction<br/>type=deposit, amount=5000]
A --> C[CollectionOrder<br/>actual=5000]
C --> D[Receipt<br/>amount=5000]
B -.关联.-> C
E[退款 5000] --> F[DepositTransaction<br/>type=refund, amount=5000]
E --> G[CollectionOrder<br/>actual=-5000 红字]
G --> H[Receipt<br/>amount=-5000 红字]
F -.关联.-> G
```
为什么不只用 `DepositTransaction`?因为业户要拿到一张可下载、可打印的**收据凭证** —— 那是 `Receipt` 的职责。`DepositTransaction` 是内部台账,`Receipt` 是对外凭证。
## 业户视角(您看到什么)
业户**通常不直接接触账户/流水概念**。您看到的是:
- 物业前台告诉您"您还有 ¥5,000 装修保证金在账"
- 装修完了物业给您**一张红字收据**:"装修保证金退还 ¥-5,000"
- 微信小程序"我的账户"里能查到余额和历次变动
底下的两个对象都是后台的事。
## 业务人员视角
后台 → 保证金 → 账户列表,你看到的每行就是一个 `DepositAccount`。点开"查看"进入 `ViewDepositAccount`,右侧的"流水"标签就是该账户的 `DepositTransaction` 列表(只读、按时间倒序)。
所有写入操作(`DepositAction` / `RefundAction` / `ForfeitureAction` 等)都**同时写账户余额 + 流水**,事务内完成。任何只写一边的代码都是 bug。
## 相关文档
- [[account-state-machine]]
- [[transaction-types]]
- [[payer-types]]
- [[red-receipt-design]]
- [[collection-order-and-receipt]]
- [[deposit-vs-adhoc-vs-prepaid]]