131 lines
5.4 KiB
Markdown
131 lines
5.4 KiB
Markdown
---
|
|
title: prop-acc · deposit · 红字凭证设计
|
|
aliases:
|
|
- 红字凭证设计
|
|
- 退款扣罚的金额正负
|
|
- 红字 CollectionOrder
|
|
tags:
|
|
- 概念
|
|
- prop-acc
|
|
- 保证金
|
|
- 架构决策
|
|
audience:
|
|
- 业务人员
|
|
- 架构师
|
|
status: 已发布
|
|
sub_feature: deposit
|
|
last_review: 2026-05-25
|
|
code_version: 2026-05-22
|
|
---
|
|
|
|
# 红字凭证设计
|
|
|
|
押金的退款和扣罚**也走 `CollectionOrder` + `Receipt` 链路**,通过**金额正负**表达方向 —— 这就是"红字凭证"。本文说明为什么这么设计,以及它对凭证文案、报表、未来扩展的影响。
|
|
|
|
## 核心结论(一句话)
|
|
|
|
> [!tip] 本系统采用「金额正负」而非「新增枚举 case」表达退款/扣罚方向。
|
|
> 缴款 `actual_amount = +5000`,退款 `actual_amount = -3000`,扣罚 `actual_amount = -2000`。
|
|
> `Receipt.amount` 同样可负。
|
|
|
|
## 设计决策表
|
|
|
|
| 维度 | 金额正负(已采用) | 加 `CollectionType` 枚举 case |
|
|
|---|---|---|
|
|
| **Schema 改动** | 零迁移 — `actual_amount` 是 `decimal` 无 CHECK 约束 | 改 enum + 改 listener match 分支 |
|
|
| **报表聚合** | `SUM(actual_amount)` 一行算净值(收入 - 退款) | 需 `GROUP BY type` 再相减 |
|
|
| **中国会计实践** | ✅「红字凭证」就是负数,会计师熟悉 | 不通用,需另解释 |
|
|
| **未来扩展** | 同模式可直接复用(账单退款 / 预存款退款) | 枚举持续膨胀,每加一个业务加一个 case |
|
|
| **类型分类细粒度** | `DepositTransaction.type` 已有 3 档 | 双重表达,信息冗余 |
|
|
|
|
## 资金流示意
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
subgraph "缴款(蓝字)"
|
|
A1[业户缴 5000] --> A2[DepositTransaction<br/>type=deposit, amount=5000]
|
|
A1 --> A3[CollectionOrder<br/>actual=+5000]
|
|
A3 --> A4[Receipt<br/>amount=+5000<br/>「装修保证金缴纳 ¥5,000」]
|
|
end
|
|
|
|
subgraph "退款(红字)"
|
|
B1[业户退 3000] --> B2[DepositTransaction<br/>type=refund, amount=3000]
|
|
B1 --> B3[CollectionOrder<br/>actual=-3000 红字]
|
|
B3 --> B4[Receipt<br/>amount=-3000 红字<br/>「装修保证金退还 ¥-3,000」]
|
|
end
|
|
|
|
subgraph "扣罚(红字)"
|
|
C1[物业扣 2000] --> C2[DepositTransaction<br/>type=forfeiture, amount=2000]
|
|
C1 --> C3[CollectionOrder<br/>actual=-2000 红字]
|
|
C3 --> C4[Receipt<br/>amount=-2000 红字<br/>「装修保证金扣罚 ¥-2,000」]
|
|
end
|
|
```
|
|
|
|
## 凭证文案如何区分退款/扣罚
|
|
|
|
`Receipt.amount` 都是负数,但**文案不同**,业户一眼能看明白方向。
|
|
|
|
| `DepositTransaction.type` | Receipt 文案 | 业户感受 |
|
|
|---|---|---|
|
|
| `refund` | 装修保证金退还 ¥-3,000 | 拿回自己的钱 |
|
|
| `forfeiture` | 装修保证金扣罚 ¥-2,000 | 钱被扣了 |
|
|
|
|
文案由 Listener `generateDepositReceiptItems` 按 `DepositTransaction.type` 选词,自动注入 Receipt 的 line items。
|
|
|
|
> [!info] 未来增强:PDF 红色字样
|
|
> Receipt PDF 模板对负数金额**当前是普通样式**,优先级排期低。可加红字效果(中国习惯)等业务方反馈再上。
|
|
|
|
## CollectionStatus 仍用 `Completed` 而不是 `Disbursed`
|
|
|
|
退款的 CollectionOrder 同样进入 `Completed` 状态,**不新增**"Disbursed(已支出)"枚举值。
|
|
|
|
理由:
|
|
|
|
- `Completed` 语义 = **"事务完成"**,不局限"收到钱"
|
|
- 不加新 case 减少枚举膨胀,符合"最简单实用"
|
|
- 报表里"已完成的 CO" `SUM(actual_amount)` 就是净值,无需关心方向
|
|
|
|
`refund_status` 字段也**保持 `None`**,因为退款的 CollectionOrder 是**独立的红字单**,不是"原单的退款标记"。这与传统电商"在原订单上挂退款"模式不同 —— 那种模式适合一次性商品交易,不适合押金的"账户 + 流水"长期账本。
|
|
|
|
## 为什么不在原 CollectionOrder 上挂退款标记?
|
|
|
|
替代方案:在缴款单上加 `refund_amount` 和 `refund_status`,退款时改这两个字段。
|
|
|
|
为什么我们没选:
|
|
|
|
| 问题 | 后果 |
|
|
|---|---|
|
|
| 原单状态会被频繁更新 | 缴款时建立的"已完成快照"被破坏,审计追溯困难 |
|
|
| 多次部分退款怎么办 | 原单要记一个数组?变成事实上的子流水,等于在错位置实现 |
|
|
| 跨期对账 | 4 月缴的 5000 在 6 月退 3000 → 4 月的报表数据会变,跨期数字不稳定 |
|
|
| 与新业务复用 | 账单 / 预存款的退款不能复用此模式,要各自重复实现 |
|
|
|
|
红字独立单解决以上全部问题:每张单是不可变快照,跨期数字不变,各业务模块通过相同的"红字 CO + Receipt"模式表达退款。
|
|
|
|
## 与一次性收费(adhoc)的对比
|
|
|
|
| 维度 | adhoc 一次性收费 | deposit 保证金 |
|
|
|---|---|---|
|
|
| 主对象 | `AdHocEvent` | `DepositAccount` |
|
|
| 流水/凭证 | `CollectionOrder` + `Receipt`(单笔) | `DepositTransaction` + 关联 `CollectionOrder` + `Receipt`(长期账本) |
|
|
| 退款方式 | 走 [[adhoc-void-after-payment]] 作废 + 退款 | **红字 CollectionOrder**(独立单)|
|
|
| 是否进入收入 | ✅ 一次性收入 | ❌ 负债科目(代管款) |
|
|
|
|
详见 [[deposit-vs-adhoc-vs-prepaid]]。
|
|
|
|
## 待补 / 已知限制
|
|
|
|
| 项 | 状态 |
|
|
|---|---|
|
|
| Receipt PDF 红色字体样式 | 待补,优先级低 |
|
|
| 小程序在线申请退款(走支付网关 webhook) | 待补,等业务有需求再加 |
|
|
| `CollectionOrders/Actions/RecordRefundAction`(从原单视角的退款入口) | **已知重叠**,未来要统一为单一退款流程 |
|
|
|
|
## 相关文档
|
|
|
|
- [[deposit-account-vs-transaction]]
|
|
- [[transaction-types]]
|
|
- [[collection-order-and-receipt]]
|
|
- [[refund-full-no-damage]]
|
|
- [[forfeit-damage-public-area]]
|