# 学时套餐购买接口文档

## 1. 模块说明

- 模块：企业端 / 移动端学时套餐购买
- 控制器：`/operationManagement/hourPackagePurchase`
- 适用端口：
    - 企业端：`port-company_admin`
    - 移动端：`port-driver`
- 钱包规则：
    - 企业端登录：默认使用企业钱包 `2`
    - 移动端登录：默认使用个人钱包 `1`
- 支付方式：当前仅支持钱包余额支付，固定为 `4=余额`

说明：

- 可购买套餐来自总部端已启用的学时套餐
- 购买成功后，后端会同时生成：
    - 学时套餐购买记录
    - 支付订单
    - 钱包流水
    - 课时购买明细
- 当前接口不需要前端传 `walletType`，后端会按登录端口自动判定

## 2. 字段说明

### 2.1 可购买套餐返回字段

接口 `GET /operationManagement/hourPackagePurchase/available/list` 返回字段与学时套餐列表保持一致：

| 字段名               | 类型           | 说明               |
|-------------------|--------------|------------------|
| `id`              | `Long`       | 套餐ID             |
| `packageCode`     | `String`     | 套餐编码             |
| `packageName`     | `String`     | 套餐名称             |
| `hourType`        | `Long`       | 学时类型，当前固定 `1`    |
| `hourCount`       | `Long`       | 套餐学时数            |
| `originPrice`     | `BigDecimal` | 原价格              |
| `packagePrice`    | `BigDecimal` | 活动价，可为空          |
| `unitPrice`       | `BigDecimal` | 学时单价             |
| `packageDesc`     | `String`     | 套餐说明             |
| `status`          | `Long`       | 状态：`1=启用`，`0=停用` |
| `sortNo`          | `Long`       | 排序号              |
| `applicableTypes` | `String`     | 适用类型             |

### 2.2 购买请求字段

| 字段名         | 类型       | 必填 | 说明            |
|-------------|----------|----|---------------|
| `packageId` | `Long`   | 是  | 购买的套餐ID       |
| `quantity`  | `Long`   | 是  | 购买数量，必须大于 `0` |
| `remark`    | `String` | 否  | 备注            |

### 2.3 购买成功返回字段

| 字段名                   | 类型           | 说明                     |
|-----------------------|--------------|------------------------|
| `purchaseId`          | `Long`       | 学时套餐购买记录ID             |
| `orderId`             | `Long`       | 支付订单ID                 |
| `orderNo`             | `String`     | 业务订单号                  |
| `payOrderId`          | `Long`       | 支付订单ID                 |
| `payOrderNo`          | `String`     | 平台支付订单号                |
| `walletType`          | `Long`       | 钱包类型：`1=个人钱包`，`2=企业钱包` |
| `walletId`            | `Long`       | 钱包ID                   |
| `walletTransactionId` | `Long`       | 钱包流水ID                 |
| `walletTransactionNo` | `String`     | 钱包流水号                  |
| `totalHours`          | `Long`       | 本次购买总课时数               |
| `totalAmount`         | `BigDecimal` | 本次支付总金额                |
| `walletBalance`       | `BigDecimal` | 扣款后的钱包余额               |

### 2.4 购买记录返回字段

接口 `GET /operationManagement/hourPackagePurchase/current/list` 主要返回以下字段：

| 字段名                   | 类型           | 说明                     |
|-----------------------|--------------|------------------------|
| `id`                  | `Long`       | 购买记录ID                 |
| `orderId`             | `Long`       | 支付订单ID                 |
| `orderNo`             | `String`     | 业务订单号                  |
| `packageId`           | `Long`       | 套餐ID                   |
| `packageName`         | `String`     | 套餐名称                   |
| `hourType`            | `Long`       | 学时类型                   |
| `hourCount`           | `Long`       | 单份套餐学时数                |
| `originPrice`         | `BigDecimal` | 原价格                    |
| `packagePrice`        | `BigDecimal` | 套餐价格                   |
| `unitPrice`           | `BigDecimal` | 学时单价                   |
| `quantity`            | `Long`       | 购买数量                   |
| `totalHours`          | `Long`       | 总课时数                   |
| `totalAmount`         | `BigDecimal` | 总金额                    |
| `payMethod`           | `Long`       | 支付方式，当前固定 `4=余额`       |
| `walletType`          | `Long`       | 钱包类型：`1=个人钱包`，`2=企业钱包` |
| `walletId`            | `Long`       | 钱包ID                   |
| `walletTransactionId` | `Long`       | 钱包流水ID                 |
| `walletTransactionNo` | `String`     | 钱包流水号                  |
| `payStatus`           | `Long`       | 支付状态：`2=已支付`           |
| `orderCreateTime`     | `Date`       | 下单时间                   |
| `payTime`             | `Date`       | 支付时间                   |
| `packageSnapshot`     | `String`     | 套餐快照JSON               |
| `remark`              | `String`     | 备注                     |

## 3. 接口1：分页查询当前登录用户可购买的学时套餐列表

- 地址：`GET /operationManagement/hourPackagePurchase/available/list`
- 说明：返回当前登录用户可购买的已启用学时套餐

### 3.1 请求参数

| 参数名            | 类型           | 必填 | 说明        |
|----------------|--------------|----|-----------|
| `pageNum`      | `Integer`    | 是  | 页码        |
| `pageSize`     | `Integer`    | 是  | 每页条数      |
| `packageName`  | `String`     | 否  | 套餐名称，模糊查询 |
| `hourCount`    | `Long`       | 否  | 学时数，精确查询  |
| `originPrice`  | `BigDecimal` | 否  | 原价格，精确查询  |
| `packagePrice` | `BigDecimal` | 否  | 活动价，精确查询  |
| `packageDesc`  | `String`     | 否  | 套餐说明，模糊查询 |

### 3.2 返回示例

```json
{
  "code": 200,
  "rows": [
    {
      "id": 1,
      "packageCode": "HP1931512345678901234",
      "packageName": "基础学时包",
      "hourType": 1,
      "hourCount": 40,
      "originPrice": 1000.00,
      "packagePrice": 800.00,
      "unitPrice": 20.00,
      "packageDesc": "总部通用学时套餐",
      "status": 1,
      "sortNo": 0,
      "applicableTypes": "1"
    }
  ],
  "total": 1,
  "msg": "查询成功"
}
```

## 4. 接口2：使用钱包余额购买学时套餐

- 地址：`POST /operationManagement/hourPackagePurchase/wallet/purchase`
- Content-Type：`application/json`
- 说明：按当前登录端口自动选择企业钱包或个人钱包扣款

### 4.1 请求示例

```json
{
  "packageId": 1,
  "quantity": 2,
  "remark": "购买2026年培训课时"
}
```

### 4.2 返回示例

```json
{
  "code": 200,
  "msg": "操作成功",
  "data": {
    "purchaseId": 10,
    "orderId": 23,
    "orderNo": "HPO1931512345678901234",
    "payOrderId": 23,
    "payOrderNo": "PO1931512345678905678",
    "walletType": 2,
    "walletId": 8,
    "walletTransactionId": 66,
    "walletTransactionNo": "WT1931512345678909999",
    "totalHours": 80,
    "totalAmount": 1600.00,
    "walletBalance": 3400.00
  }
}
```

### 4.3 失败示例

```json
{
  "code": 500,
  "msg": "钱包余额不足",
  "data": null
}
```

常见失败原因：

- 套餐不存在
- 套餐未启用
- 套餐学时配置异常
- 钱包不存在
- 钱包已冻结
- 钱包余额不足
- 当前登录端口不支持购买学时套餐

## 5. 接口3：分页查询当前登录用户购买记录

- 地址：`GET /operationManagement/hourPackagePurchase/current/list`
- 说明：按当前登录身份自动隔离购买记录

### 5.1 请求参数

| 参数名               | 类型        | 必填 | 说明                  |
|-------------------|-----------|----|---------------------|
| `pageNum`         | `Integer` | 是  | 页码                  |
| `pageSize`        | `Integer` | 是  | 每页条数                |
| `packageName`     | `String`  | 否  | 套餐名称，模糊查询           |
| `orderNo`         | `String`  | 否  | 订单号，精确查询            |
| `payStatus`       | `Long`    | 否  | 支付状态                |
| `walletType`      | `Long`    | 否  | 钱包类型；建议前端不传，由后端自动锁定 |
| `orderCreateTime` | `Date`    | 否  | 下单时间，精确查询           |

### 5.2 返回示例

```json
{
  "code": 200,
  "rows": [
    {
      "id": 10,
      "orderId": 23,
      "orderNo": "HPO1931512345678901234",
      "packageId": 1,
      "packageName": "基础学时包",
      "hourType": 1,
      "hourCount": 40,
      "originPrice": 1000.00,
      "packagePrice": 800.00,
      "unitPrice": 20.00,
      "quantity": 2,
      "totalHours": 80,
      "totalAmount": 1600.00,
      "payMethod": 4,
      "walletType": 2,
      "walletId": 8,
      "walletTransactionId": 66,
      "walletTransactionNo": "WT1931512345678909999",
      "payStatus": 2,
      "orderCreateTime": "2026-05-12 10:20:30",
      "payTime": "2026-05-12 10:20:30",
      "remark": "购买2026年培训课时"
    }
  ],
  "total": 1,
  "msg": "查询成功"
}
```

## 6. 业务规则

- 仅企业端与移动端可调用购买能力
- 企业端固定使用企业钱包，移动端固定使用个人钱包
- 钱包必须存在且未冻结
- 钱包余额必须大于等于购买金额
- 套餐必须是启用状态
- 购买数量必须大于 `0`
- 总金额计算规则：
    - 有活动价：`活动价 * 数量`
    - 无活动价：`原价格 * 数量`
- 总课时数计算规则：`单份学时 * 数量`
- 支付方式当前固定为 `4=余额`
- 购买成功后支付状态固定记为 `2=已支付`

## 7. 前端表格字段对照清单

### 7.1 可购买套餐列表页字段

| 列表列名 | 对应字段           | 展示建议 | 说明           |
|------|----------------|------|--------------|
| 套餐名称 | `packageName`  | 文本   | 必显           |
| 学时数  | `hourCount`    | 数字   | 建议展示为 `40学时` |
| 原价格  | `originPrice`  | 金额   | 保留两位小数       |
| 活动价  | `packagePrice` | 金额   | 为空显示 `-`     |
| 学时单价 | `unitPrice`    | 金额   | 后端已算好        |
| 套餐说明 | `packageDesc`  | 文本   | 过长省略         |
| 操作   | `id`           | 按钮   | `立即购买`       |

### 7.2 可购买套餐列表页筛选项

| 筛选项  | 对应参数           | 组件建议  | 说明   |
|------|----------------|-------|------|
| 套餐名称 | `packageName`  | 输入框   | 模糊查询 |
| 学时数  | `hourCount`    | 数字输入框 | 精确查询 |
| 原价格  | `originPrice`  | 金额输入框 | 精确查询 |
| 活动价  | `packagePrice` | 金额输入框 | 精确查询 |
| 套餐说明 | `packageDesc`  | 输入框   | 模糊查询 |

### 7.3 购买记录列表页字段

| 列表列名  | 对应字段                  | 展示建议 | 说明                |
|-------|-----------------------|------|-------------------|
| 订单号   | `orderNo`             | 文本   | 可复制               |
| 套餐名称  | `packageName`         | 文本   | 必显                |
| 单份学时  | `hourCount`           | 数字   | 建议加后缀“学时”         |
| 购买数量  | `quantity`            | 数字   | 必显                |
| 总课时数  | `totalHours`          | 数字   | 必显                |
| 总金额   | `totalAmount`         | 金额   | 保留两位小数            |
| 支付方式  | `payMethod`           | 标签   | 当前固定显示“余额支付”      |
| 钱包类型  | `walletType`          | 标签   | `1=个人钱包`，`2=企业钱包` |
| 支付状态  | `payStatus`           | 标签   | 当前主要显示“已支付”       |
| 支付时间  | `payTime`             | 日期时间 | 建议格式化展示           |
| 钱包流水号 | `walletTransactionNo` | 文本   | 可复制               |
| 备注    | `remark`              | 文本   | 为空显示 `-`          |

### 7.4 购买记录列表页筛选项

| 筛选项  | 对应参数          | 组件建议 | 说明         |
|------|---------------|------|------------|
| 套餐名称 | `packageName` | 输入框  | 模糊查询       |
| 订单号  | `orderNo`     | 输入框  | 精确查询       |
| 支付状态 | `payStatus`   | 下拉框  | 当前可保留“已支付” |

### 7.5 列表页接口对接

- 可购买套餐列表：
    - 地址：`GET /operationManagement/hourPackagePurchase/available/list`
    - 数据源：响应中的 `rows`
    - 总数：响应中的 `total`
- 购买记录列表：
    - 地址：`GET /operationManagement/hourPackagePurchase/current/list`
    - 数据源：响应中的 `rows`
    - 总数：响应中的 `total`

## 8. 前端购买弹窗字段对照清单

### 8.1 购买弹窗字段

| 表单项  | 对应字段                       | 必填 | 组件建议    | 说明                |
|------|----------------------------|----|---------|-------------------|
| 套餐名称 | `packageName`              | 否  | 只读文本    | 从列表带入             |
| 单份学时 | `hourCount`                | 否  | 只读文本    | 从列表带入             |
| 套餐价格 | `packagePrice/originPrice` | 否  | 只读文本    | 有活动价优先展示活动价       |
| 购买数量 | `quantity`                 | 是  | 数字输入框   | 最小值 `1`           |
| 总金额  | 无                          | 否  | 只读文本    | 前端可实时计算，后端以实际计算为准 |
| 备注   | `remark`                   | 否  | 输入框/文本域 | 可选                |

### 8.2 弹窗提交字段

前端最终只需要提交：

```json
{
  "packageId": 1,
  "quantity": 1,
  "remark": "购买课时套餐"
}
```

### 8.3 弹窗中不需要前端传的字段

| 字段            | 原因           |
|---------------|--------------|
| `walletType`  | 后端按登录端口自动判断  |
| `walletId`    | 后端自动获取       |
| `totalAmount` | 后端按套餐价格和数量计算 |
| `totalHours`  | 后端按学时和数量计算   |
| `payMethod`   | 当前固定为余额支付    |

## 9. 前端实现建议

- 进入购买页时先调可购买套餐列表接口
- 点击“立即购买”后，弹窗内建议同时展示：
    - 套餐名称
    - 单份学时
    - 单份价格
    - 购买数量
    - 实时总金额
- 购买成功后建议：
    - 关闭弹窗
    - 刷新购买记录列表
    - 刷新钱包余额
- 购买记录页建议把 `payMethod` 映射为中文：
    - `4` -> `余额支付`
- 钱包类型建议映射为中文：
    - `1` -> `个人钱包`
    - `2` -> `企业钱包`
- 支付状态建议映射为中文：
    - `1` -> `待支付`
    - `2` -> `已支付`
    - `3` -> `已退款`
    - `4` -> `已取消`
    - `5` -> `已关闭`
