--- 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
type=deposit, amount=5000] A1 --> A3[CollectionOrder
actual=+5000] A3 --> A4[Receipt
amount=+5000
「装修保证金缴纳 ¥5,000」] end subgraph "退款(红字)" B1[业户退 3000] --> B2[DepositTransaction
type=refund, amount=3000] B1 --> B3[CollectionOrder
actual=-3000 红字] B3 --> B4[Receipt
amount=-3000 红字
「装修保证金退还 ¥-3,000」] end subgraph "扣罚(红字)" C1[物业扣 2000] --> C2[DepositTransaction
type=forfeiture, amount=2000] C1 --> C3[CollectionOrder
actual=-2000 红字] C3 --> C4[Receipt
amount=-2000 红字
「装修保证金扣罚 ¥-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]]