# 管理端产品详情 扩展 档位起步价 + Step5 模板展开字段

- **日期**: 2026-04-17
- **服务**: hl-product-service-v2（端口 8083）
- **PR**: https://git.1814.love:8443/wx/HL/pulls/763
- **Issue**: https://git.1814.love:8443/wx/HL/issues/760
- **类型**: feat（非破坏性扩展，纯字段新增）
- **影响**: 管理端产品编辑/查看页可直接拿到起步价与 Step5 补充模板展开内容，无需二次查模板接口

## 变更类型

**非破坏性扩展**：对已有接口 `GET /admin/product/item/{id}` 的响应体新增若干字段。
- 所有老字段不变、含义不变、类型不变
- 旧前端客户端忽略新字段即可继续运行
- 无需迁移

## 受影响接口

### `GET /admin/product/item/{id}`

管理端产品详情接口。**签名与请求参数不变**。响应 VO `ProductDetailRespVO` 新增以下字段。

## 新增字段清单

### Step4 价格/班期 区块（3 个字段）

| 字段 | 类型 | 含义 | 可能为 null 的情况 |
|------|------|------|-------------------|
| `startPrice` | BigDecimal | 起步价（未来可售最低成人售价） | 没有未来可售价格日历时为 `null` |
| `tierPrices` | `List<TierPriceInfo>` | 各档位起步价（管理端即使单档产品也会返回） | 全部档位价格已过期时为 `[]`（空数组，不是 null） |
| `priceCalendars` | `List<PriceCalendarRespVO>` | 价格日历全量回显（含已过期、含零价，不做过滤） | 从未配置价格日历时为 `[]` |

> **管理端 vs 小程序端**：小程序端通常会过滤掉过期与零价，管理端此处返回全量，方便后台编辑/校对。

### Step5 补充信息 区块（5 个字段，模板 ID 自动展开）

前端无需再发 GET 模板详情请求，后端已根据 `supplement.xxxTemplateId` / `supplement.xxxId` 展开内容：

| 字段 | 类型 | 含义 | 展开自 | 可能为 null 的情况 |
|------|------|------|--------|-------------------|
| `guaranteeItems` | Object/Array（透传 JSON） | 行程保障项 | `supplement.guaranteeTemplateId` 对应模板的 `items` JSON | `guaranteeTemplateId` 为 null 或模板不存在时为 `null` |
| `safetyItems` | Object/Array（透传 JSON） | 安全保障项 | `supplement.safetyTemplateId` 对应模板的 `items` JSON | `safetyTemplateId` 为 null 或模板不存在时为 `null` |
| `faqItems` | Object/Array（透传 JSON） | FAQ 问答 | `supplement.faqTemplateId` 对应模板的 `items` JSON | `faqTemplateId` 为 null 或模板不存在时为 `null` |
| `bookingTermsContent` | String | 预订条款内容（富文本） | `supplement.bookingTermsId` 对应的 `content` | `bookingTermsId` 为 null 或记录不存在时为 `null` |
| `warmTipsContent` | String | 温馨提示内容（富文本） | `supplement.warmTipsId` 对应的 `content` | `warmTipsId` 为 null 或记录不存在时为 `null` |

> **设计原则**：若对应 ID 为 null，后端不会查询无意义的数据，直接回空。

## 内部类结构：`TierPriceInfo`

`tierPrices` 数组的元素结构：

```json
{
  "tierSeq": 1,
  "tierName": "标准档",
  "tierDescription": "标间 / 四人桌",
  "startPrice": 2980.00
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| `tierSeq` | Integer | 档位序号（1、2、3…） |
| `tierName` | String | 档位名称 |
| `tierDescription` | String | 档位描述（房型/餐标等） |
| `startPrice` | BigDecimal | 该档位未来可售最低成人售价 |

## 响应示例（节选，只展示新增部分）

```json
{
  "code": 200,
  "data": {
    "productId": 1001,
    "name": "呼伦贝尔6日游",

    "startPrice": 2980.00,
    "tierPrices": [
      { "tierSeq": 1, "tierName": "标准档", "tierDescription": "标间",   "startPrice": 2980.00 },
      { "tierSeq": 2, "tierName": "豪华档", "tierDescription": "大床房", "startPrice": 3480.00 }
    ],
    "priceCalendars": [
      { "date": "2026-05-01", "tierSeq": 1, "adultPrice": 2980.00, "childPrice": 1980.00, "stock": 20 }
    ],

    "supplement": {
      "guaranteeTemplateId": 11,
      "safetyTemplateId": 12,
      "faqTemplateId": 13,
      "bookingTermsId": 21,
      "warmTipsId": 22
    },
    "guaranteeItems": [ { "title": "全程WIFI", "desc": "..." } ],
    "safetyItems":    [ { "title": "随车医药箱", "desc": "..." } ],
    "faqItems":       [ { "q": "可以加床吗？", "a": "可以，需付费" } ],
    "bookingTermsContent": "<p>下单后 24 小时内完成支付...</p>",
    "warmTipsContent":     "<p>请携带身份证原件...</p>"
  }
}
```

## 使用建议

| 场景 | 推荐字段 |
|------|---------|
| 列表页/卡片展示"起步价￥xxx" | `startPrice` |
| 编辑页档位 tab 切换展示各档起价 | `tierPrices` |
| 价格日历编辑/核对（含过期、含零价） | `priceCalendars` |
| Step5 预览页面渲染"行程保障/安全保障/FAQ" | `guaranteeItems` / `safetyItems` / `faqItems` |
| Step5 预览页面渲染"预订条款/温馨提示"富文本 | `bookingTermsContent` / `warmTipsContent` |

> 编辑模板 ID 选择时仍然使用 `supplement.xxxTemplateId` / `supplement.xxxId` 字段；本次新增的展开字段仅用于展示。

## 迁移建议

**无需迁移**。纯新增字段，老字段与老行为完全保持。

## 后端需重启的服务

- **hl-product-service-v2**（端口 8083）

拉最新 `dev` 分支后重启该服务即可生效，其它服务不受影响。
