# 接口文档：团期日历数据

> **日期**: 2026-03-27
> **类型**: 📖 接口文档补充（非新增接口，已有接口的完整使用说明）
> **涉及服务**: hl-product-service、hl-mp-service
> **接口状态**: ✅ 已上线可用

---

## 一、功能说明

此接口为 **GROUP（小蒙马拼团）产品** 提供团期日历数据，供小程序端日历选择器展示。

**核心能力**：
- 返回指定产品所有**可报名**团期（状态为 ENROLLING 或 CONFIRMED）
- 只返回**今天及以后**的出发日期（历史团期自动过滤）
- 每个团期包含：出发日期、起步价、定金金额、剩余名额、剩余房间数、是否已成团
- 起步价来源于**价格日历**（product_price_calendar 表的成人售价）
- 定金计算规则：固定金额优先，否则用 `起步价 × 定金比例%`（向上取整）

---

## 二、接口清单

| # | 接口 | 方法 | 路径 | 说明 |
|---|------|------|------|------|
| 1 | 团期日历（内部） | GET | `/internal/product/{productId}/batches/calendar` | 服务间调用 |
| 2 | 团期日历（C端） | GET | `/mp/product/{productId}/batches/calendar` | 小程序直接调用（经网关） |

> 两个接口返回数据完全相同，区别仅在路由层：
> - **内部接口**：hl-mp-service 通过 Feign 调用 hl-product-service
> - **C端接口**：小程序通过网关 `/mp/product/...` 直接请求

---

## 三、接口详细定义

### GET `/mp/product/{productId}/batches/calendar`

> **前端应调用此路径**（经网关转发），不要直接调用 internal 路径

#### 使用场景
小程序 GROUP 产品详情页，展示**团期日历选择器**，用户选择出发日期后进入报价流程。

#### 请求参数

| 参数 | 位置 | 类型 | 必填 | 说明 |
|------|------|------|------|------|
| productId | path | Long | ✅ | 产品ID（必须是 GROUP 类型产品） |

#### 请求示例

```
GET /mp/product/1903770276454400001/batches/calendar
```

#### 响应示例

```json
{
  "code": 200,
  "message": "操作成功",
  "data": [
    {
      "departureDate": "2026-05-01",
      "batchId": "1903770276454400101",
      "batchLabel": "五一团",
      "batchName": "2026年五一黄金周第一批",
      "batchStatus": "ENROLLING",
      "startingPrice": 12800.00,
      "enrolledCount": 15,
      "remainingSlots": 15,
      "maxRooms": 15,
      "bookedRooms": 5,
      "remainingRooms": 10,
      "isConfirmed": false,
      "depositAmount": 3999.00
    },
    {
      "departureDate": "2026-05-08",
      "batchId": "1903770276454400102",
      "batchLabel": "五一加开团",
      "batchName": "2026年五一黄金周第二批",
      "batchStatus": "CONFIRMED",
      "startingPrice": 11800.00,
      "enrolledCount": 28,
      "remainingSlots": 2,
      "maxRooms": 15,
      "bookedRooms": 13,
      "remainingRooms": 2,
      "isConfirmed": true,
      "depositAmount": 3540.00
    }
  ]
}
```

---

## 四、响应字段说明

| 字段 | 类型 | 说明 |
|------|------|------|
| `departureDate` | String (yyyy-MM-dd) | 出发日期 |
| `batchId` | String | 批次ID（雪花ID字符串） |
| `batchLabel` | String | 批次标签（短名，如"五一团"） |
| `batchName` | String | 批次全称 |
| `batchStatus` | String | 批次状态，见下方枚举 |
| `startingPrice` | BigDecimal | 起步价(元)，来自价格日历的成人售价。**可能为 null**（价格日历未配置时） |
| `enrolledCount` | Integer | 已报名人数 |
| `remainingSlots` | Integer | 剩余名额 = maxParticipants - enrolledCount |
| `maxRooms` | Integer | 总房间数 |
| `bookedRooms` | Integer | 已预订房间数 |
| `remainingRooms` | Integer | 剩余房间数 = maxRooms - bookedRooms |
| `isConfirmed` | Boolean | 是否已成团（batchStatus == "CONFIRMED" 时为 true） |
| `depositAmount` | BigDecimal | 定金金额(元/人)。**可能为 null**（未配置定金且无起步价时） |

---

## 五、枚举/字典值

### batchStatus 批次状态（本接口只返回前两种）

| 值 | 中文 | 说明 |
|----|------|------|
| `ENROLLING` | 报名中 | ✅ 本接口会返回，可正常报名 |
| `CONFIRMED` | 已成团 | ✅ 本接口会返回，已达成团人数，仍可继续报名 |
| `PENDING` | 待开放 | ❌ 不会返回 |
| `FULL` | 已满员 | ❌ 不会返回 |
| `DEPARTED` | 已出发 | ❌ 不会返回 |
| `COMPLETED` | 已完成 | ❌ 不会返回 |
| `CANCELLED` | 已取消 | ❌ 不会返回 |

---

## 六、业务规则

1. **过滤条件**：只返回 `batchStatus IN ('ENROLLING', 'CONFIRMED')` 且 `departureDate >= 今天` 的团期
2. **排序**：按出发日期升序 → 排序号升序
3. **起步价来源**：从 `product_price_calendar` 表查询对应日期的 `adult_sell_price`，状态必须为 AVAILABLE
4. **定金计算**：
   - 如果产品配置了固定定金金额（`deposit_amount > 0`）→ 直接使用
   - 否则用 `起步价 × deposit_ratio / 100`，向上取整（RoundingMode.UP）
   - 两者都没有配置时 → depositAmount 为 null
5. **非 GROUP 产品调用此接口**：返回空数组 `[]`（不会报错）

---

## 七、调用位置（后端内部使用情况）

此接口在后端有 **3 处调用**，前端了解即可：

| 调用位置 | 场景 | 说明 |
|----------|------|------|
| `MpProductController.java:97` | 产品列表接口 | GROUP 产品列表自动追加 `batches` 字段 |
| `ProductAggregationService.java:174` | 产品详情聚合 | GROUP 产品详情页异步加载团期 |
| `MpProductFeignClient.java:57` | Feign 声明 | hl-mp-service → hl-product-service 的 Feign 调用声明 |

### 前端调用建议

**场景1：产品列表页**
- GROUP 产品列表接口 `GET /mp/product/list` 已自动包含 `batches` 字段（数组），无需单独调用

**场景2：产品详情页**
- 产品详情聚合接口 `GET /mp/product/{id}/detail` 已自动包含 `batches` 字段，无需单独调用

**场景3：需要单独刷新团期**
- 用户在详情页停留较久需要刷新团期时，可单独调用 `GET /mp/product/{productId}/batches/calendar`

---

## 八、前端实现建议

```
┌─────────────────────────────────────────────┐
│  团期日历选择器                                │
│                                              │
│  ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐  │
│  │ 4/28│ │ 4/29│ │ 4/30│ │ 5/01│ │ 5/02│  │
│  │     │ │     │ │     │ │五一团│ │     │  │
│  │     │ │     │ │     │ │¥12800│ │     │  │
│  │  —  │ │  —  │ │  —  │ │剩15位│ │  —  │  │
│  └─────┘ └─────┘ └─────┘ └─────┘ └─────┘  │
│                                              │
│  选中团期信息：                                │
│  ┌──────────────────────────────────────┐    │
│  │ 五一团 · 2026-05-01出发               │    │
│  │ 起步价 ¥12,800/人  定金 ¥3,999/人    │    │
│  │ 剩余 15 名额 · 剩余 10 间房           │    │
│  │ 🔴 报名中（未成团）                    │    │
│  └──────────────────────────────────────┘    │
│                                              │
│  [ 立即报名 ]                                 │
└─────────────────────────────────────────────┘
```

**展示建议**：
- 日历格子：有团期的日期高亮显示，无团期的日期置灰
- `isConfirmed = true` → 显示 "已成团" 绿色标签
- `isConfirmed = false` → 显示 "报名中" 橙色标签
- `remainingSlots <= 3` → 显示 "仅剩X位" 红色提示
- `remainingRooms <= 2` → 显示 "房间紧张" 提示
- `startingPrice` 为 null → 显示 "价格待定"
- `depositAmount` 不为 null → 显示 "定金 ¥xxx/人"
- `batchLabel` 作为日历格子内的短标签显示

---

*📖 此文档为已有接口的详细说明，无代码变更*
