# 小程序新增"最近可售日期"接口 + 全档位可售判定

**日期**: 2026-04-20
**服务**: hl-product-service-v2
**PR**: #991
**类型**: 新增接口 + 后端字段语义收紧

## 新增接口

### `GET /mp/product/{id}/earliest-available-date`
返回该产品从今天起的最近**全档位可售**日期（多档位产品要求该日所有档位都已配置成人价）。

**请求**：
- Path: `id` (Long) 产品ID
- 需要 mp token

**响应**：
```json
{
  "code": 200,
  "message": "成功",
  "data": {
    "date": "2026-04-20",
    "available": true,
    "daysFromToday": 0
  },
  "success": true
}
```

**字段说明**：
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `date` | `LocalDate` | 最近全档位可售日期（无可售时 null） |
| `available` | `Boolean` | `date != null` |
| `daysFromToday` | `Integer` | 距今天数（无可售时 null） |

**判定规则**：
```sql
SELECT date FROM product_price_calendar
WHERE product_id = ? AND date >= today AND adult_sell_price > 0
GROUP BY date
HAVING COUNT(DISTINCT tier_seq) >= tierCount(productId)
ORDER BY date ASC LIMIT 1
```
- `tierCount` = `product.tiers` JSON 数组长度
- 单档产品 `tierCount<=1` 退化为"任一未来日有成人价"

## 后端字段语义调整

### `MpProductDetailRespVO.isBooking` 判定收紧
**之前**: `isBooking = (status == PUBLISHED)`
**现在**: `isBooking = (status == PUBLISHED) && (最近全档位可售日期 != null)`

> 背景：isBooking 原意为"是否预约产品（订金可不填出行人）"，今天已复用为"是否可订"。若多档产品未配齐所有档位价格，则认为该产品不可订，按钮应置灰。

## 前端建议接入方式

1. 进入产品详情页时调 `/mp/product/{id}/earliest-available-date`，`available=true` 才点亮"预订"按钮。
2. 选日历时可将 `date` 作为最小可选日期。
3. 判断按钮状态**不要**用 `status` 字段（mp 详情响应体里没有）。

## 已部署
- 测试环境已部署，hl-product-service-v2 双实例（8083/8183）均生效。
- 验证产品 `2045345825172639746` 返 `isBooking:true, startPrice:4105`，全档位最近可售日 `2026-04-20`。
