# 提现管理 — 技术方案

> **依据：** 《提现管理功能需求.md》v1.0  
> **关联：** 平台《提现审核功能需求》v1.0、《提现审核技术方案》v1.0、《账务管理.md》v1.0；商家《资金概览功能需求》v1.0、《账户管理功能需求-草稿》；《关联需求分析.md》v1.6、《商家端店铺上下文接口说明》  
> **范围：** 商家端 **财务中心 · 提现管理** — **提交提现**、**提现记录列表与检索**、提交页 **可用余额** 辅助展示；**不含** 平台审核、账户 CRUD、资金概览页。  
> **原则：** `X-Shop-Id` 店铺隔离；提交 **同事务** 写提现单 + 钱包 + 流水；账户 **快照**；审核由 **平台模块** 回写状态。

---

## 1. 技术架构

| 项 | 选型 |
|----|------|
| 基础框架 | RuoYi **v3.9.2**（`springboot2` 分支） |
| 数据库 | **MySQL 5.7.39**；复用财务公共表（见 §2） |
| ORM / 权限 / 响应 | MyBatis；`@PreAuthorize`；`AjaxResult` / `TableDataInfo` |
| 店铺上下文 | `SellerShopContext` + 请求头 **`X-Shop-Id`** |
| 金额 | `BigDecimal`；两位小数；`ROUND_HALF_UP` |
| 提现编号 | **`WD` + yyyyMMdd + 6 位序号**（全平台唯一） |

### 1.1 与兄弟模块边界

| 维度 | **本模块 · 提现管理** | 账户管理 | 资金概览 | 平台 · 提现审核 |
|------|----------------------|----------|----------|-----------------|
| 路径 | `/agri/seller/finance/withdraw` | `/agri/seller/finance/payAccount` | `/agri/seller/finance/overview` | `/agri/finance/withdrawAudit` |
| 写操作 | **POST submit** | 账户增删改 | **无** | **PUT approve/reject** |
| 读操作 | 列表、余额 | 列表、下拉 options | 概览、流水 | 全平台列表 |
| 数据范围 | **当前 shop_id** | 当前 shop_id | 当前 shop_id | 全平台 |

### 1.2 关联协作链

```text
【订单 · 间接】confirmReceive → settleOrder
    → biz_shop_wallet.available += pay_amount（订单收支）
    → 本模块 submit 校验 available_balance

【账户管理】biz_shop_pay_account（有效账户）
    → GET /payAccount/options 供提交表单下拉
    → submit 时校验 accountId 归属本店且未删除

【本模块 · submit】
    → INSERT biz_shop_withdraw（status=1 待审核，账户快照）
    → UPDATE biz_shop_wallet（available−，frozen+）
    → INSERT biz_shop_fund_log（change_reason=2 余额提现）

【平台 · 提现审核】approve / reject
    → UPDATE biz_shop_withdraw.status / process_remark
    → 写流水 3提现驳回 / 4提现完成，调整 wallet
    → 本模块列表只读感知终态与处理说明

【资金概览】只读 biz_shop_wallet、biz_shop_fund_log；与本模块共用同一套余额口径
```

> 订单与提现 **无表级外键**；可提现余额来自 **订单确认收货结算**，详见平台《账务管理.md》。

### 1.3 模块落位

**文档目录：** `doc/店铺后台/财务中心/提现管理/`  
**菜单：** 财务中心 → 提现管理（`menu_id=6620`）  
**后端包：** `com.ruoyi.web.modules.finance`

```text
finance/
├── controller/SellerWithdrawController.java       # 本模块入口
├── service/IShopFundService.java                  # submit / list / balance
├── service/impl/ShopFundServiceImpl.java
├── domain/BizShopWithdraw.java
├── mapper/BizShopWithdrawMapper.java
├── dto/SellerWithdrawQuery.java
├── dto/SellerWithdrawSubmitDTO.java
├── vo/SellerWithdrawListRowVO.java
├── constant/FinanceConstants.java
├── support/FinanceSupport.java                    # 编号、脱敏、状态文案
resources/mapper/finance/BizShopWithdrawMapper.xml

# 协作依赖（非本模块 Controller，submit 内调用）
├── mapper/BizShopWalletMapper.java
├── mapper/BizShopFundLogMapper.java
├── mapper/BizShopPayAccountMapper.java
├── controller/SellerPayAccountController.java     # GET /options

sql/biz_shop_finance.sql                           # 公共 DDL
sql/biz_shop_finance_menu.sql                      # 6620 / 6621
```

**前端（建议）：**

| 项 | 值 |
|----|-----|
| 页面 | `ruoyi-ui/src/views/agri/seller/finance/withdraw/index.vue` |
| API | `ruoyi-ui/src/api/agri/seller/finance/withdraw.js` |
| 账户下拉 | 复用 `payAccount.js` → `GET /payAccount/options` |

---

## 2. 数据库设计

**权威 DDL：** [`sql/biz_shop_finance.sql`](../../../../sql/biz_shop_finance.sql)

本模块 **直接读写** 下表；钱包/流水/账户为 **财务公共表**，与平台审核、资金概览 **共用**。

### 2.1 提现单 `biz_shop_withdraw`（主表）

| 字段 | 类型 | 本模块 | 说明 |
|------|------|:------:|------|
| withdraw_id | bigint PK | R/W | 主键 |
| withdraw_no | varchar(32) UK | W | `WDyyyyMMdd######`；列表展示 |
| shop_id | bigint | W/R | **= X-Shop-Id**；列表过滤 |
| shop_name | varchar(100) | W | 提交时快照 |
| account_id | bigint | W | 源账户 ID（仅追溯） |
| account_type | char(1) | W | 快照：`1`银行卡 `2`支付宝 `3`微信 |
| account_real_name | varchar(64) | W/R | 快照 |
| account_no_mask | varchar(128) | W/R | 快照脱敏账号 |
| withdraw_amount | decimal(12,2) | W/R | 提现金额 |
| remark | varchar(200) | W/R | 商家备注 |
| withdraw_status | char(1) | W/R | `1`待审核 `2`审核不通过 `3`提现完成 |
| process_remark | varchar(200) | R | 平台审核回写；待审为 null |
| audit_by / audit_time | | R | 平台写入 |
| apply_time | datetime | W/R | 申请时间；默认排序 |
| create_by | varchar(64) | W | 提交人 |

**索引：** `uk_withdraw_no`；`idx_shop_status_time (shop_id, withdraw_status, apply_time)`。

**枚举（withdraw_status）：**

| 值 | 文案 | 本模块操作 |
|----|------|------------|
| `1` | 待审核 | 提交后默认；只读 |
| `2` | 审核不通过 | 平台驳回；只读 |
| `3` | 提现完成 | 平台通过；只读 |

### 2.2 店铺钱包 `biz_shop_wallet`（submit 时更新）

| 字段 | 类型 | submit 行为 |
|------|------|-------------|
| shop_id | bigint UK | 按店一行；无则 insert |
| available_balance | decimal(12,2) | **− withdraw_amount** |
| frozen_amount | decimal(12,2) | **+ withdraw_amount** |

> 读取 **`GET /balance`** 取 `available_balance`；须 **`SELECT … FOR UPDATE`** 后与 submit **同事务**。

### 2.3 资金流水 `biz_shop_fund_log`（submit 时写入）

| 字段 | submit 取值 |
|------|-------------|
| change_reason | `2`（余额提现） |
| biz_no | withdraw_no |
| balance_change | **−amount** |
| frozen_change | **+amount** |
| balance_after / frozen_after | 变动后快照 |
| ref_type / ref_id | `WITHDRAW` / withdraw_id |

> 审核产生的流水（`3`/`4`）由 **平台提现审核** 写入；本模块 **只读** 列表不查流水表。

### 2.4 收款账户 `biz_shop_pay_account`（只读引用）

| 用途 | 说明 |
|------|------|
| submit 校验 | `selectByIdAndShop(accountId, shopId)` 且 `del_flag=0` |
| 表单下拉 | **`GET /payAccount/options`**（账户管理模块） |
| 快照 | submit 时 copy type / real_name / mask(account_no) |

---

## 3. 接口设计

**基路径：** `/agri/seller/finance/withdraw`  
**上下文：** 请求头 **`X-Shop-Id`** 必填（`requireShopId()`）

### 3.1 接口一览

| 方法 | 路径 | 权限 | 说明 |
|------|------|------|------|
| GET | `/list` | `agri:seller:finance:withdraw:list` | 提现记录分页列表 |
| GET | `/balance` | `agri:seller:finance:withdraw:list` | 可用余额（提交页辅助） |
| POST | `/submit` | `agri:seller:finance:withdraw:submit` | 提交提现申请 |

### 3.2 列表 `GET /list`

**Query（SellerWithdrawQuery）：**

| 参数 | 类型 | 说明 |
|------|------|------|
| pageNum, pageSize | int | 分页 |
| beginApplyTime | string | 申请开始日期 `yyyy-MM-dd` |
| endApplyTime | string | 申请结束日期（含当天 23:59:59） |
| withdrawStatus | string | 空=全部；`1`/`2`/`3` |

**固定条件：** `shop_id = requireShopId()`；`ORDER BY apply_time DESC, withdraw_id DESC`。

**行 VO（SellerWithdrawListRowVO）：**

| 字段 | 说明 |
|------|------|
| withdrawNo | 提现编号 |
| applyTime | 申请时间 |
| accountSummary | `{类型} {脱敏账号} {姓名}` |
| withdrawAmount | 金额 |
| remark | 备注 |
| withdrawStatus / withdrawStatusText | 状态码 + 文案 |
| processRemark | 平台处理说明；待审 null |

### 3.3 可用余额 `GET /balance`

**响应 data：**

| 字段 | 说明 |
|------|------|
| availableBalance | `biz_shop_wallet.available_balance`；无记录为 `0.00` |

### 3.4 提交 `POST /submit`

**Body（SellerWithdrawSubmitDTO）：**

| 字段 | 必填 | 说明 |
|------|------|------|
| accountId | 是 | 本店有效收款账户 ID |
| withdrawAmount | 是 | `> 0`；≤ availableBalance |
| remark | 否 | ≤200 字 |

**成功 data：**

| 字段 | 说明 |
|------|------|
| withdrawId | 提现单 ID |
| withdrawNo | 提现编号 |
| withdrawStatus | 固定 `1`（待审核） |

**事务内步骤：**

```text
1. 校验 accountId、amount、remark
2. wallet FOR UPDATE；校验 amount <= available_balance
3. 生成 withdraw_no；组装账户快照
4. INSERT biz_shop_withdraw
5. UPDATE biz_shop_wallet
6. INSERT biz_shop_fund_log（余额提现）
```

### 3.5 协作接口（非本 Controller）

| 方法 | 路径 | 用途 |
|------|------|------|
| GET | `/agri/seller/finance/payAccount/options` | 提交页账户下拉 |

---

## 4. 业务规则（实现）

| 编号 | 需求规则 | 实现要点 |
|------|----------|----------|
| WD-A1 | 唯一提交入口 | 仅 `POST /submit` 写提现单 |
| WD-A2 | 编号全平台唯一 | `FinanceSupport.nextWithdrawNo()` |
| WD-A4 | 余额提现流水 | submit 同事务写 log + wallet |
| WD-A5 | 金额 ≤ 可用余额 | `FOR UPDATE` 后比较 available_balance |
| WD-A6 | 账户须本店有效 | `payAccountMapper.selectByIdAndShop` |
| WD-A7 | 账户快照 | 写入 account_type / real_name / account_no_mask |
| WD-A8 | 处理说明只读 | 列表读 process_remark；本模块不写 |
| WD-A9 | 终态不可改 | 无 update/delete 接口 |
| WD-A11 | 仅当前店铺 | 全 SQL 带 `shop_id` |

**异常文案（FinanceConstants）：**

| 场景 | msg |
|------|-----|
| 未选账户 | 请选择提现账户 |
| 金额无效 | 请输入有效的提现金额 |
| 超余额 | 提现金额不能超过可用余额 |
| 账户无效 | 提现账户不存在或已删除 |
| 备注超长 | 备注不能超过200字 |

---

## 5. 权限与菜单

| 权限 | 说明 |
|------|------|
| `agri:seller:finance:withdraw:list` | 列表 + 余额 |
| `agri:seller:finance:withdraw:submit` | 提交提现 |

菜单：**财务中心 → 提现管理**（`parent_id=6600`，`menu_id=6620`，`path=withdraw`）。  
脚本：[`sql/biz_shop_finance_menu.sql`](../../../../sql/biz_shop_finance_menu.sql)。

---

## 6. 测试要点

| 编号 | 场景 | 预期 |
|------|------|------|
| T1 | 列表 shop 隔离 | 仅本店记录 |
| T2 | 按状态/日期筛选 | 条件生效 |
| T3 | submit 超余额 | ServiceException |
| T4 | submit 无效账户 | ServiceException |
| T5 | submit 成功 | withdraw+wallet+log 同事务；status=1 |
| T6 | 平台驳回后列表 | status=2，processRemark 可见，余额恢复 |
| T7 | VO 账户摘要 | accountSummary 含类型与脱敏 |

**建议测试类：** `ShopFundServiceImplTest`、`SellerWithdrawControllerTest`（MockMvc）。

---

## 7. 实现状态

| 项 | 状态 |
|----|------|
| DDL `sql/biz_shop_finance.sql` | **已有** |
| `SellerWithdrawController` / `ShopFundServiceImpl` | **已实现** |
| 菜单 SQL | **已有** |
| 单元测试 `ShopFundServiceImplTest` | **已有** |
| ruoyi-ui 提现管理页 | **待建** |
| 撤销待审核 / 手续费 | **非本期** |

---

## 8. 版本记录

| 版本 | 说明 |
|------|------|
| **v1.0** | 首版：对齐《提现管理功能需求》v1.0；关联平台提现审核、资金概览、账户管理、账务管理；简练 DB/接口设计 |

---

*文档版本：v1.0 · 依据《提现管理功能需求.md》v1.0*
