# cash_transaction — 现金流水账本(分)

> 模型 `app/models/wallet.py` · 仓库 `app/repositories/wallet.py` · 接口 [wallet-cash-transactions](../api/wallet/wallet-cash-transactions.md) · [← 索引](./README.md) · [总览](./OVERVIEW.md)

现金每变动一笔就记一行(单位:**分**,记变动后余额)。金币兑现金、提现、提现退款都落这里。**只增不改不删**。

> **只管「金币兑换现金」这本账**(`coin_account.cash_balance_cents`)。**邀请奖励金**是另一本物理隔离的账 → [`invite_cash_transaction`](./invite_cash_transaction.md)(同构表);`source=invite_cash` 的提现流水**不落本表**。

## 用在哪 / 增删改查
- **C(插入)**:四个来源,每次写一笔:

  | 动作 / endpoint | `biz_type` | `amount_cents` | `ref_id` 指向 |
  |---|---|---|---|
  | 金币兑现金 `POST /wallet/exchange` | `exchange_in` | + | null(配套 `coin_transaction.exchange_out`) |
  | 发起提现 `POST /wallet/withdraw`(`source=coin_cash`) | `withdraw` | −(扣现金) | `withdraw_order.out_bill_no` |
  | 提现失败/取消/审核拒绝退款 | `withdraw_refund` | +(退回) | `withdraw_order.out_bill_no` |
  | admin 手动调整 `POST /admin/api/users/{id}/cash`(`account=coin_cash`) | `admin_grant` / `admin_deduct` | + / − | null(原因记 `remark`=`admin:<reason>`) |

- **U / D**:无。账本只追加。
- **R**:`GET /wallet/cash-transactions`(现金明细,`id` 倒序游标);admin 跨用户现金流水。

## 字段
| 列 | 类型 | 约束 / 默认 | 说明(取值 / join) |
|---|---|---|---|
| `id` | Integer | PK, autoincrement | |
| `user_id` | Integer | FK→user.id, index, NOT NULL | 归属用户 |
| `amount_cents` | Integer | NOT NULL | 本笔变动现金(分);**正=入账(兑入/退款),负=出账(提现)** |
| `balance_after_cents` | Integer | NOT NULL | 本笔后现金余额(= 当时 `coin_account.cash_balance_cents`) |
| `biz_type` | String(32) | NOT NULL | 取值:`exchange_in`(兑入)/ `withdraw`(提现出账)/ `withdraw_refund`(提现退回) |
| `ref_id` | String(64) | nullable | `withdraw`/`withdraw_refund` 时 = **`withdraw_order.out_bill_no`**;`exchange_in` 为 null |
| `remark` | String(128) | nullable | 用户可见备注(如「提现到微信零钱(待审核)」「提现审核未通过,金额已退回」) |
| `created_at` | DateTime(tz) | server_default now(), index | 时间 |

## 关系 / Join Key
- `user_id` → `user.id`(多对一)。
- `ref_id` →(仅 withdraw/withdraw_refund)`withdraw_order.out_bill_no`(软关联,无 FK)。一笔提现正常对应两条流水:发起 `withdraw`(−),失败/拒绝时再 `withdraw_refund`(+)。

## 索引与约束
- PK `id`;index `user_id`、`created_at`。
- 部分唯一索引 `ux_cash_transaction_withdraw_refund_ref`(`ref_id`),条件 `biz_type = 'withdraw_refund' AND ref_id IS NOT NULL`:一笔提现单只能退款一次(按 `out_bill_no` 去重),挡重复退款。

## 注意
- 退款流水 `remark` 是用户可见文案(区分"未成功自动退"vs"审核未通过退");技术原因记在 `withdraw_order.fail_reason`,不外露。
