# 小蒙马详情页字段补全 + 产品接口优化

**日期**: 2026-03-26
**服务**: hl-product-service
**环境**: 测试环境已部署，需重启: product-service

---

## 一、小蒙马(GROUP)详情页新增字段

产品详情接口 `GET /product/{productId}` 和小程序 `GET /internal/mp/product/{productId}` 新增以下字段：

### 1. 数据统计（自动计算，无需配置）

| 字段 | 类型 | 说明 |
|------|------|------|
| `batchCount` | Integer | 历史期数（非散团状态的批次数，仅GROUP产品） |
| `hotelSummary` | Array | 住宿安排汇总（连住合并，仅显示连住2晚及以上，仅GROUP产品） |

`hotelSummary` 每项结构（连续住同一酒店自动合并）：
```json
{
  "dayStart": 3,
  "dayEnd": 4,
  "hotelId": "3001000000000000005",
  "hotelName": "兰其果娃",
  "coverUrl": "https://xxx/hotel.jpg",
  "roomTypeName": "亲子房",
  "nights": 2,
  "label": "连住2晚"
}
```

**注意**：只住1晚的酒店不会出现在 `hotelSummary` 中（设计图只展示连住）。前端可直接用 `label` 字段显示标签，用 `dayStart`-`dayEnd` 显示日期范围（如 "Day3-4"）。

### 2. 年龄说明（后台可配）

| 字段 | 类型 | 说明 | 示例 |
|------|------|------|------|
| `ageMin` | Integer | 适合最小年龄 | `5` |
| `ageMax` | Integer | 适合最大年龄 | `12` |
| `ageDescription` | String | 年龄说明文案 | `"5-12岁儿童专属，课程按年龄分组"` |

### 3. 摄影跟拍（后台可配）

| 字段 | 类型 | 说明 |
|------|------|------|
| `photographyTags` | String[] | 摄影标签 如 `["200+精修照片", "1对1跟拍"]` |
| `photographyImages` | String[] | 摄影作品展示图URL列表 |

### 4. 餐饮亮点（后台可配）

| 字段 | 类型 | 说明 |
|------|------|------|
| `diningHighlights` | Array | 餐饮亮点列表 |

每项结构：
```json
{
  "name": "手把肉",
  "coverImage": "https://xxx/cover.jpg",
  "subtitle": "蒙古特色必吃",
  "detailImages": ["https://xxx/detail1.jpg", "https://xxx/detail2.jpg"]
}
```
- `coverImage`: 封面图（列表展示，name叠在图上）
- `detailImages`: 详情图列表（点击查看大图）

### 5. 安全保障（后台可配）

| 字段 | 类型 | 说明 |
|------|------|------|
| `safetyItems` | Array | 安全保障列表 |

每项结构：
```json
{ "icon": "insurance", "title": "保险", "description": "中国人保50万，含意外医疗" }
```

### 6. 价格档位表（自动计算，无需配置）

| 字段 | 类型 | 说明 |
|------|------|------|
| `priceTiers` | Array | 价格档位列表（从套餐combo自动取每种组合的最低售价，仅GROUP产品） |

每项结构：
```json
{ "label": "1大1小", "price": 12800, "adultCount": 1, "childCount": 1 }
```

**数据来源**：自动从产品的套餐（GroupBatchCombo）中按 comboCode 分组，取每组跨所有批次的最低 sellPrice。无需后台手动配置。

---

## 二、推荐餐厅封面图自动补全

行程节点中 `DINING_RECOMMEND` 类型的 `recommendedRestaurants` 列表，`coverUrl` 现在会自动从餐厅资源中补全。前端无需改动，原有字段结构不变。

---

## 三、上架校验优化

- 移除了上架时商户号（mchId）的强制校验，不再报"商户号不能为空"
- GROUP（小蒙马）产品上架新增校验：**至少需要1个可报名的团期**（状态为报名中/已确认，且出发日期在未来）。不满足会提示具体原因

---

## 四、产品人员配置增加备选人员

人员配置接口 `POST /product/{productId}/staff-config` 和 `PUT /product/staff-config/{id}` 新增字段：

| 字段 | 类型 | 说明 |
|------|------|------|
| `staffIds` | String[] | 备选人员ID列表（从人员资源中选取） |

- 数量（quantity）是出行时实际需要的人数，备选人员可以多于此数量
- 团期创建时从备选池中选人分配

### 领队介绍（自动填充，仅GROUP产品）

| 字段 | 类型 | 说明 |
|------|------|------|
| `leaderInfoList` | Array | 领队详情列表（从人员配置中LEADER类型的staffIds查询） |

每项结构：
```json
{
  "staffId": "123",
  "name": "巴特尔老师",
  "avatar": "https://xxx/avatar.jpg",
  "title": "首席研学导师 · 蒙古族",
  "experience": "10年儿童户外教育经验",
  "description": "...",
  "certificateTags": ["急救证书", "导游证", "教师资格证"]
}
```
前端可滚动展示多个领队。

### 人员资源新增字段

人员资源 `POST /staff` 和 `PUT /staff/{id}` 新增：

| 字段 | 类型 | 说明 |
|------|------|------|
| `certificateTags` | String[] | 证书标签，如 `["急救证书", "导游证"]` |

---

## 五、预订须知（所有产品类型通用）

小程序产品详情已返回以下字段（点击展开纯文字内容）：

| 字段 | 位置 | 说明 |
|------|------|------|
| `product.bookingTermsContent` | ProductVO | 预订条款全文 |
| `product.warmTipsContent` | ProductVO | 温馨提示全文 |
| `refundPolicies` | MpProductDetailVO | 取消政策（按支付方式分组） |

### 取消政策（退款政策）返回结构

`refundPolicies` 按支付方式分组，每个政策新增 `description` 纯文字字段：

```json
{
  "FULL": {
    "policyId": 1,
    "policyName": "标准退款政策",
    "description": "出发前30天以上取消，退全款；出发前15-30天取消，退80%；出发前15天内取消，不可退",
    "rules": [
      { "ruleId": 1, "minDays": 30, "refundRatio": 100 },
      { "ruleId": 2, "minDays": 15, "refundRatio": 80 },
      { "ruleId": 3, "minDays": 0, "refundRatio": 0 }
    ]
  },
  "DEPOSIT": { ... }
}
```

前端可直接用 `description` 展示文字，无需自己拼接规则。按产品的 `pricing.paymentType` 显示对应的退款政策：
- `FULL` → 显示 FULL 的 description
- `DEPOSIT` → 显示 DEPOSIT 和 BALANCE 的 description

---

## 前端对接要点

1. **batchCount / hotelSummary / priceTiers / leaderInfoList**: 自动计算字段，无需后台录入，GROUP产品详情自动返回
2. **年龄/摄影/餐饮/安全**: 需要在管理后台"编辑产品"时配置对应数据，否则返回 null
3. **所有新字段向后兼容**: 未配置时返回 null，不影响已有字段
4. **保存/更新产品接口同步支持**: `POST /product/save`、`PUT /product/{id}` 都可传入新字段

---

## 六、人员管理校验增强

**服务**: hl-resource-service

创建/更新人员时，如果填写了 `certificateTags`（证书标签），则 `certificateUrls`（证书文件）不能为空。否则报错：

> 添加了证书标签，必须同时上传对应的证书文件

前端在提交人员表单时，如果用户添加了证书标签但没上传证书文件，建议在前端也做提示拦截。

---

## 七、人员证书改为结构化列表

**服务**: hl-resource-service

### 新增字段 `certificates`（替代旧的 certificateTags + certificateUrls）

人员创建/更新接口新增 `certificates` 字段，标签和附件一一对应：

```json
[
  { "tag": "急救证书", "url": "https://xxx/cert1.jpg" },
  { "tag": "导游证", "url": "https://xxx/cert2.jpg" },
  { "tag": "教师资格证", "url": "https://xxx/cert3.jpg" }
]
```

**校验规则**：每项的 `tag` 和 `url` 必须同时存在，缺一报错（如"第1个证书'急救证书'缺少证书附件"）。

**兼容**：旧字段 `certificateUrls` / `certificateTags` 保留可用，但建议前端迁移到 `certificates`。

**领队介绍**：小程序详情 `leaderInfoList` 中的 `certificateTags` 优先从 `certificates` 提取，同时也返回完整的 `certificates` 列表。

---

## 八、安全保障改为模板机制

**服务**: hl-product-service

### 模板管理接口

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/product/safety-template` | 模板列表 |
| GET | `/product/safety-template/{id}` | 模板详情 |
| POST | `/product/safety-template` | 创建模板 |
| PUT | `/product/safety-template/{id}` | 更新模板 |
| DELETE | `/product/safety-template/{id}` | 删除模板（默认模板不可删） |

请求体：
```json
{
  "name": "标准安全保障",
  "items": [
    { "icon": "insurance", "title": "保险", "description": "中国人保50万，含意外医疗" },
    { "icon": "vehicle", "title": "车辆", "description": "营运资质、儿童安全座椅、司机驾龄10年" },
    { "icon": "emergency", "title": "应急", "description": "24h客服，医院30分钟可达" }
  ],
  "isDefault": true
}
```

### 产品关联

产品保存/更新时可传 `safetyTemplateId` 指定模板。**不传则自动使用默认模板**，定制师无需手动配置。

### 详情返回逻辑

`safetyItems` 字段填充优先级：产品指定模板 → 产品自身旧数据 → 默认模板。前端无需改动，`safetyItems` 返回格式不变。

系统已内置默认模板"标准安全保障"（保险/车辆/应急三项）。

---

## 九、价格档位改为最近一期可报名团期价格

**服务**: hl-product-service

`priceTiers` 不再取所有团期的最低价，改为只取**最近一期可报名团期**（ENROLLING/CONFIRMED 且出发日期在未来）的 combo 价格。

返回格式不变：
```json
[
  { "label": "1大1小", "price": 12800, "adultCount": 1, "childCount": 1 },
  { "label": "1大2小", "price": 15800, "adultCount": 1, "childCount": 2 }
]
```

如果没有可报名团期，`priceTiers` 返回 null。

---

## 十、人员证书上传说明

**上传流程**：使用通用文件上传接口，和上传其他图片一样。

**步骤**：
1. `POST /file/upload/token` → 获取预签名URL
```json
{ "fileName": "急救证书.jpg", "bizType": "staff" }
```
2. 用返回的预签名URL上传文件到OSS
3. `POST /file/upload/confirm` → 确认上传，拿到最终URL
4. 将URL填入 `certificates[].url`

**保存人员时传 certificates 字段**：
```json
{
  "certificates": [
    { "tag": "急救证书", "url": "https://xxx/cert1.jpg" },
    { "tag": "导游证", "url": "https://xxx/cert2.jpg" }
  ]
}
```

每项 `tag` 和 `url` 必须同时填，缺一报错。旧字段 `certificateTags`/`certificateUrls` 仍可用但建议迁移。

---

## 补充：行程酒店封面图自动补全

行程天的酒店（`itineraryDays[].hotels[].coverUrl`）如果为空，后端会自动从酒店资源的封面素材中补全。前端无需改动。
