# 算价/档位/定金系列变更

> **服务**: hl-product-service-v2 (端口 8083)
> **PR**: #718, #720, #722, #724
> **日期**: 2026-04-17
> **影响范围**: 小程序算价、管理端算价、管理端下单产品选择、小程序档位对比

---

## 一、变更总览

| # | 主题 | PR |
|---|------|----|
| 1 | 固定定金算价按总人头 × 单价 | #718 |
| 2 | 管理端下单产品选择列表补档位 + 新增档位下拉接口 | #720 |
| 3 | 算价接口补全4类人员单价/小计（成人/儿童/小童/幼童） | #722 |
| 4 | 小程序档位对比响应补充档位名称 tierName | #724 |

---

## 二、变更接口清单

| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|------|---------|------|
| 1 | 小程序算价 | POST | `/mp/product/quote` | 响应字段语义 + 新增字段 | 固定定金按总人头×单价；新增4类人员单价/小计 |
| 2 | 管理端算价 | POST | `/admin/product/item/quote` | 响应新增字段 | 新增4类人员单价/小计 |
| 3 | 商品选择列表 | GET | `/admin/product/item/simple-list` | 响应新增字段 | 新增 `tiers` 档位数组 |
| 4 | 产品档位下拉 | GET | `/admin/product/item/{productId}/tiers` | **新增接口** | 下单选档位用 |
| 5 | 小程序档位对比 | GET | `/mp/product/{id}/tier-compare` | 响应新增字段 | 每组新增 `tierName` |

---

## 三、接口详情

### 1. 小程序算价 `POST /mp/product/quote`

**响应完整字段**（新增 4 类人员细分）：

| 字段 | 类型 | 说明 |
|------|------|------|
| `adultUnitPrice` | BigDecimal | 成人单价 |
| `childUnitPrice` | BigDecimal | 儿童单价 |
| **`toddlerUnitPrice`** | BigDecimal | 🆕 小童单价 = max(儿童价 + 小童优惠额, 0) |
| **`infantUnitPrice`** | BigDecimal | 🆕 幼童单价（0=免费） |
| `totalAdultPrice` | BigDecimal | 成人小计 |
| `totalChildPrice` | BigDecimal | 儿童小计 |
| **`totalToddlerPrice`** | BigDecimal | 🆕 小童小计 |
| **`totalInfantPrice`** | BigDecimal | 🆕 幼童小计 |
| `grandTotal` | BigDecimal | 总价（4类小计之和，**旧版只含成人+儿童**） |
| `paymentType` | String | `FULL` / `DEPOSIT` |
| `depositRatio` | Integer | 订金比例(%)，仅比例定金 |
| `depositAmount` | BigDecimal | 订金金额（⚠️ 固定定金语义变更见下方） |
| `balanceAmount` | BigDecimal | 尾款金额 |

**⚠️ 固定定金算法变更（#718）**：

产品设置"订金固定额"时，之前返回的 `depositAmount` 是单人额度（不随人数变），导致前端显示与实际收款不符。

- **旧**：`depositAmount = product.depositAmount`
- **新**：`depositAmount = product.depositAmount × (adultCount + childCount + toddlerCount + infantCount)`
- 若乘出金额 > `grandTotal`，取 `grandTotal` 兜底（防止 balance 为负）
- 比例定金逻辑**不变**（仍按 `grandTotal × ratio%`）

**示例**（固定定金 500/人，2成人+1儿童+1幼童，grandTotal=13498）：

```json
{
  "code": 200,
  "data": {
    "adultUnitPrice": 4999.00,
    "childUnitPrice": 3500.00,
    "toddlerUnitPrice": 0,
    "infantUnitPrice": 0,
    "totalAdultPrice": 9998.00,
    "totalChildPrice": 3500.00,
    "totalToddlerPrice": 0,
    "totalInfantPrice": 0,
    "grandTotal": 13498.00,
    "paymentType": "DEPOSIT",
    "depositAmount": 2000.00,   // 500 × 4人
    "balanceAmount": 11498.00
  }
}
```

---

### 2. 管理端算价 `POST /admin/product/item/quote`

响应新增字段与小程序端一致（`toddlerUnitPrice` / `infantUnitPrice` / `totalToddlerPrice` / `totalInfantPrice`），`grandTotal` 同步包含 4 类。

---

### 3. 商品选择列表 `GET /admin/product/item/simple-list`

**请求参数**（无变化）：

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| productType | String | 否 | CORE / GROUP / CUSTOM |
| keyword | String | 否 | 名称/编号模糊 |
| pageNo / pageSize | Integer | 是 | 分页 |

**响应新增字段**：

| 字段 | 类型 | 说明 |
|------|------|------|
| **`tiers`** | Array | 🆕 档位列表（tierSeq / tierName / tierDescription） |

**响应示例**：

```json
{
  "code": 200,
  "data": {
    "records": [
      {
        "productId": 2043246204711550977,
        "productNo": "C260409001",
        "name": "额吉的故乡·亲子版",
        "tripDays": 6,
        "lineName": "草原环线",
        "tiers": [
          {"tierSeq": 1, "tierName": "标准档", "tierDescription": "三星酒店"},
          {"tierSeq": 2, "tierName": "豪华档", "tierDescription": "四星酒店升级"}
        ]
      }
    ],
    "total": 10,
    "page": 1,
    "pageSize": 50
  }
}
```

**使用场景**：下单搜索产品时，前端直接展示"有几档/具体档位"，避免再次请求。

---

### 4. 产品档位下拉 `GET /admin/product/item/{productId}/tiers` 🆕

**场景**：下单弹窗内选档位用。算价接口需要 `tierSeq` 参数，前端必须先让用户选档位。

**请求**：路径参数 `productId`

**响应**：

```json
{
  "code": 200,
  "data": [
    {"tierSeq": 1, "tierName": "标准档", "tierDescription": "三星酒店"},
    {"tierSeq": 2, "tierName": "豪华档", "tierDescription": "四星酒店升级"}
  ]
}
```

**异常**：产品不存在 → `code=404`，`message="产品不存在: xxx"`

---

### 5. 小程序档位对比 `GET /mp/product/{id}/tier-compare`

**响应变化**：`tiers[].tierName` 🆕 新增字段。

```json
{
  "code": 200,
  "data": {
    "tiers": [
      {
        "tierSeq": 1,
        "tierName": "标准档",    // 🆕
        "hotels": [...]
      },
      {
        "tierSeq": 2,
        "tierName": "豪华档",    // 🆕
        "hotels": [...]
      }
    ]
  }
}
```

---

## 四、前端适配指南

### 小程序端
1. **算价展示**：渲染 4 类人员的单价和小计；固定定金产品场景下 `depositAmount` 现在会随人数变化
2. **档位对比页**：用 `tiers[].tierName` 作为卡片标题（此前只能显示"档位1"之类）

### 管理端
1. **下单产品选择弹窗**：`simple-list` 响应里直接拿 `tiers` 展示档位数；选中产品后用 `GET /{productId}/tiers` 渲染档位下拉
2. **算价展示**：渲染 4 类人员单价/小计

---

## 五、重启提示

**需要重启的服务**：`hl-product-service-v2`（端口 8083）

不涉及 DB DDL，仅代码变更；字段注释有更新（`deposit_amount` COMMENT），但不影响运行时。

---

## 六、关联 Issue/PR

| Issue | PR | 主题 |
|-------|-----|------|
| #717 | #718 | 固定定金按人数计算 |
| #719 | #720 | simple-list 补档位 + 档位下拉接口 |
| #721 | #722 | 算价补全 4 类人员单价 |
| #723 | #724 | 档位对比补 tierName |
